/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Qt Software Information (qt-info@nokia.com)
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
** $QT_END_LICENSE$
**
****************************************************************************/

/**
 * Copyright (C) 2023-2026 Linaro Limited (or its affiliates). All rights reserved.
 * Copyright (C) 2011-2023 Arm Limited (or its affiliates).
 * All rights reserved.
 */

#include "qringbuffer_p.h"
#include "qfindbytearray.h"
#include <stdio.h>
#include <limits.h>

DDTRingBuffer::DDTRingBuffer(int growth) : basicBlockSize(growth) {
    buffers << QByteArray();
    bufferSize = 0;
    clear();
}

int DDTRingBuffer::nextDataBlockSize() const {
    return (tailBuffer == 0 ? tail : buffers.first().size()) - head;
}

const char *DDTRingBuffer::readPointer() const {
    return buffers.isEmpty() ? 0 : (buffers.first().constData() + head);
}

// access the bytes at a specified position
// the out-variable length will contain the amount of bytes readable
// from there, i.e. the amount still the same QByteArray
const char *DDTRingBuffer::readPointerAtPosition(qint64 pos, qint64 &length) const {
    if (buffers.isEmpty()) {
        length = 0;
        return 0;
    }

    if (pos >= bufferSize) {
        length = 0;
        return 0;
    }

    // special case: it is in the first buffer
    int nextDataBlockSizeValue = nextDataBlockSize();
    if (pos < nextDataBlockSizeValue) {
        length = nextDataBlockSizeValue - pos;
        return buffers.at(0).constData() + head + pos;
    }

    // special case: we only had one buffer and tried to read over it
    if (buffers.length() == 1) {
        length = 0;
        return 0;
    }

    // skip the first
    pos -= nextDataBlockSizeValue;

    // normal case: it is somewhere in the second to the-one-before-the-tailBuffer
    for (int i = 1; i < tailBuffer; ++i) {
        if (pos >= buffers[i].size()) {
            pos -= buffers[i].size();
            continue;
        }

        length = buffers[i].length() - pos;
        return buffers[i].constData() + pos;
    }

    // it is in the tail buffer
    length = tail - pos;
    return buffers[tailBuffer].constData() + pos;
}

char DDTRingBuffer::at(qint64 pos) const {
    qint64 length;
    return *readPointerAtPosition(pos, length);
}

void DDTRingBuffer::free(int bytes) {
    updateCursors(0, bytes);

    bufferSize -= bytes;
    if (bufferSize < 0)
        bufferSize = 0;

    for (;;) {
        int nextBlockSize = nextDataBlockSize();
        if (bytes < nextBlockSize) {
            head += bytes;
            if (head == tail && tailBuffer == 0)
                head = tail = 0;
            break;
        }

        bytes -= nextBlockSize;
        if (buffers.count() == 1) {
            if (buffers.at(0).size() != basicBlockSize)
                buffers[0].resize(basicBlockSize);
            head = tail = 0;
            tailBuffer = 0;
            break;
        }

        buffers.removeAt(0);
        --tailBuffer;
        head = 0;
    }

    if (isEmpty())
        clear(); // try to minify/squeeze us
}

char *DDTRingBuffer::reserve(int bytes) {
    // if this is a fresh empty DDTRingBuffer
    if (bufferSize == 0) {
        buffers[0].resize(qMax(basicBlockSize, bytes));
        bufferSize += bytes;
        tail = bytes;
        return buffers[tailBuffer].data();
    }

    bufferSize += bytes;

    // if there is already enough space, simply return.
    if (tail + bytes <= buffers.at(tailBuffer).size()) {
        char *writePtr = buffers[tailBuffer].data() + tail;
        tail += bytes;
        return writePtr;
    }

    // if our buffer isn't half full yet, simply resize it.
    if (tail < buffers.at(tailBuffer).size() / 2) {
        buffers[tailBuffer].resize(tail + bytes);
        char *writePtr = buffers[tailBuffer].data() + tail;
        tail += bytes;
        return writePtr;
    }

    // shrink this buffer to its current size
    buffers[tailBuffer].resize(tail);

    // create a new QByteArray with the right size
    buffers << QByteArray();
    ++tailBuffer;
    buffers[tailBuffer].resize(qMax(basicBlockSize, bytes));
    tail = bytes;
    return buffers[tailBuffer].data();
}

void DDTRingBuffer::truncate(int pos) {
    if (pos < size())
        chop(size() - pos);
}

void DDTRingBuffer::chop(int bytes) {
    updateCursors(bufferSize - bytes, bytes);

    bufferSize -= bytes;
    if (bufferSize < 0)
        bufferSize = 0;

    for (;;) {
        // special case: head and tail are in the same buffer
        if (tailBuffer == 0) {
            tail -= bytes;
            if (tail <= head)
                tail = head = 0;
            return;
        }

        if (bytes <= tail) {
            tail -= bytes;
            return;
        }

        bytes -= tail;
        buffers.removeAt(tailBuffer);

        --tailBuffer;
        tail = buffers.at(tailBuffer).size();
    }

    if (isEmpty())
        clear(); // try to minify/squeeze us
}

bool DDTRingBuffer::isEmpty() const {
    return tailBuffer == 0 && tail == 0;
}

int DDTRingBuffer::getChar() {
    if (isEmpty())
        return -1;
    char c = *readPointer();
    free(1);
    return int(uchar(c));
}

void DDTRingBuffer::putChar(char c) {
    char *ptr = reserve(1);
    *ptr = c;
}

inline void DDTRingBuffer::ungetChar(char c) {
    --head;
    if (head < 0) {
        buffers.prepend(QByteArray());
        buffers[0].resize(basicBlockSize);
        head = basicBlockSize - 1;
        ++tailBuffer;
    }
    buffers[0][head] = c;
    ++bufferSize;
}

int DDTRingBuffer::size() const {
    return bufferSize;
}

void DDTRingBuffer::clear() {
    updateCursors(0, bufferSize);

    buffers.erase(buffers.begin() + 1, buffers.end());
    buffers[0].resize(0);
    buffers[0].squeeze();

    head = tail = 0;
    tailBuffer = 0;
    bufferSize = 0;
}

int DDTRingBuffer::indexOf(char c, int from) const {
    int index = 0;
    for (int i = 0; i < buffers.size(); ++i) {
        int start = 0;
        int end = buffers.at(i).size();

        if (i == 0)
            start = head;
        if (i == tailBuffer)
            end = tail;
        int skip = qMin(end - start, from);
        from -= skip;
        if (from == 0) {
            index += skip;
            const char *ptr = buffers.at(i).data() + start + skip;
            for (int j = start + skip; j < end; ++j) {
                if (*ptr++ == c)
                    return index;
                ++index;
            }
        } else {
            index += end - start;
        }
    }
    return -1;
}

int DDTRingBuffer::indexOf(const QByteArray& s, int from) const {
    int index = 0;
    int sl_minus_1 = s.size() - 1;
    for (int i = 0; i < buffers.size(); ++i) {
        int start = 0;
        int end = buffers.at(i).size();
        if (i == 0)
            start = head;
        if (i == tailBuffer)
            end = tail;
        int skip = qMin(end - start, from);
        from -= skip;
        if (from == 0) {
            int found = ddtFindByteArray(buffers.at(i).constData(), end, start + skip, s, s.size());
            if (found != -1)
                return index + found - start;
            if (i != tailBuffer) {
                // Search for input in the overlap of this i-th buffer and subsequent buffers. The
                // overlap spans sl_minus_1 characters from either side of the boundary between this
                // buffer and the next.
                int overlapStart = qMax(end - sl_minus_1, start + skip);
                int bytesFromThisBuffer = end - overlapStart;
                int bytesFromNextBuffers = sl_minus_1;
                int overlapBufferSize = bytesFromThisBuffer + bytesFromNextBuffers;
                if (overlapBufferSize > overlap.size())
                    overlap.resize(overlapBufferSize);
                memcpy(overlap.data(), buffers.at(i).constData() + overlapStart, end - overlapStart);
                char *p = overlap.data() + end - overlapStart;
                int todo = bytesFromNextBuffers;
                for (int j = i + 1; todo > 0 && j < buffers.size(); j++) {
                    int len;
                    if (j == tailBuffer)
                        len = qMin(tail, todo);
                    else
                        len = qMin(buffers.at(j).size(), todo);
                    memcpy(p, buffers.at(j).constData(), len);
                    p += len;
                    todo -= len;
                }
                found = ddtFindByteArray(overlap.constData(), p - overlap.data(), 0, s, s.size());
                if (found != -1)
                    return index + overlapStart - start + found;
            }
        }
        index += end - start;
    }
    return -1;
}

int DDTRingBuffer::read(char *data, int maxLength) {
    int bytesToRead = qMin(size(), maxLength);
    int readSoFar = 0;
    while (readSoFar < bytesToRead) {
        const char *ptr = readPointer();
        int bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar, nextDataBlockSize());
        if (data)
            memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock);
        readSoFar += bytesToReadFromThisBlock;
        free(bytesToReadFromThisBlock);
    }
    return readSoFar;
}

QByteArray DDTRingBuffer::read(int maxLength) {
    QByteArray tmp;
    tmp.resize(qMin(maxLength, size()));
    read(tmp.data(), tmp.size());
    return tmp;
}

QByteArray DDTRingBuffer::readAll() {
    return read(size());
}

// read an unspecified amount (will read the first buffer)
QByteArray DDTRingBuffer::read() {
    if (bufferSize == 0)
        return QByteArray();

    // multiple buffers, just take the first one
    if (head == 0 && tailBuffer != 0) {
        QByteArray qba = buffers.takeFirst();
        --tailBuffer;
        updateCursors(0, qba.length());
        bufferSize -= qba.length();
        return qba;
    }

    // one buffer with good value for head. Just take it.
    if (head == 0 && tailBuffer == 0) {
        QByteArray qba = buffers.takeFirst();
        qba.resize(tail);
        buffers << QByteArray();
        updateCursors(0, bufferSize);
        bufferSize = 0;
        tail = 0;
        return qba;
    }

    // Bad case: We have to memcpy.
    // We can avoid by initializing the DDTRingBuffer with basicBlockSize of 0
    // and only using this read() function.
    QByteArray qba(readPointer(), nextDataBlockSize());
    buffers.removeFirst();
    head = 0;
    if (tailBuffer == 0) {
        buffers << QByteArray();
        tail = 0;
    } else {
        --tailBuffer;
    }
    updateCursors(0, qba.length());
    bufferSize -= qba.length();
    return qba;
}

QByteArray DDTRingBuffer::mid(int pos, int maxLength) const {
    QByteArray tmp;
    if (pos < 0 || bufferSize == 0)
        return tmp;
    if (maxLength == -1)
        maxLength = INT_MAX;
    // Optimise for a single buffer.
    if (tailBuffer == 0) {
        Q_ASSERT(!buffers.isEmpty());
        tmp = buffers.first();
        if (head + pos != 0)
            return tmp.mid(head + pos, qMin(maxLength, bufferSize - pos));
        else
            return tmp.left(qMin(maxLength, bufferSize));
    }
    int bytesToRead = qMin(bufferSize - pos, maxLength);
    if (bytesToRead <= 0)
        return tmp;
    tmp.resize(bytesToRead);
    int readSoFar = 0;
    char *data = tmp.data();
    while (readSoFar < bytesToRead) {
        qint64 blockSize = 0;
        const char *ptr = readPointerAtPosition(pos + readSoFar, blockSize);
        int bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar, static_cast<int>(blockSize));
        if (data)
            memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock);
        readSoFar += bytesToReadFromThisBlock;
    }
    return tmp;
}

// append a new buffer to the end
void DDTRingBuffer::append(const QByteArray &qba) {
    if (bufferSize == 0 && !buffers.isEmpty()) {
        buffers[0] = qba;
    } else {
        buffers[tailBuffer].resize(tail);
        buffers << qba;
        ++tailBuffer;
    }
    tail = qba.length();
    bufferSize += qba.length();
}

QByteArray DDTRingBuffer::peek(int maxLength) const {
    int bytesToRead = qMin(size(), maxLength);
    if(maxLength <= 0)
        return QByteArray();
    QByteArray ret;
    ret.resize(bytesToRead);
    int readSoFar = 0;
    for (int i = 0; readSoFar < bytesToRead && i < buffers.size(); ++i) {
        int start = 0;
        int end = buffers.at(i).size();
        if (i == 0)
            start = head;
        if (i == tailBuffer)
            end = tail;
        const int len = qMin(ret.size()-readSoFar, end-start);
        memcpy(ret.data()+readSoFar, buffers.at(i).constData()+start, len);
        readSoFar += len;
    }
    Q_ASSERT(readSoFar == ret.size());
    return ret;
}

void DDTRingBuffer::skip(int length) {
    return free(length);
}

int DDTRingBuffer::readLine(char *data, int maxLength) {
    int index = indexOf('\n');
    if (index == -1)
        return read(data, maxLength);
    if (maxLength <= 0)
        return -1;

    int readSoFar = 0;
    while (readSoFar < index + 1 && readSoFar < maxLength - 1) {
        int bytesToRead = qMin((index + 1) - readSoFar, nextDataBlockSize());
        bytesToRead = qMin(bytesToRead, (maxLength - 1) - readSoFar);
        memcpy(data + readSoFar, readPointer(), bytesToRead);
        readSoFar += bytesToRead;
        free(bytesToRead);
    }

    // Terminate it.
    data[readSoFar] = '\0';
    return readSoFar;
}

bool DDTRingBuffer::canReadLine() const {
    return indexOf('\n') != -1;
}

void DDTRingBuffer::remove(int from, int length)
{
    if (from < 0) {
        length += from;
        from = 0;
    }
    if (from >= bufferSize)
        return;
    if (from + length > bufferSize)
        length = bufferSize - from;

    updateCursors(from, length);

    bufferSize -= length;

    int index = 0;
    for (int i = 0; length && i < buffers.size(); ++i) {
        int start = 0;
        int end = buffers.at(i).size();
        if (i == 0)
            start = head;
        if (i == tailBuffer)
            end = tail;
        int skip = qMin(end - start, from);
        from -= skip;
        if (from == 0) {
            int remove = qMin(end - start - skip, length);
            length -= remove;
            if (i == 0 && start + skip == head) {
                head += remove;
                if (tailBuffer == 0 && head == tail)
                    head = tail = 0;
            } else if (i == tailBuffer && start + skip + remove == tail) {
                tail -= remove;
                if (tail == 0 && tailBuffer != 0) {
                    buffers.removeAt(tailBuffer);
                    --tailBuffer;
                    tail = buffers.at(tailBuffer).size();
                }
            } else {
                buffers[i].remove(start + skip, remove);
                if (i == tailBuffer)
                    tail -= remove;
            }
            Q_ASSERT(tailBuffer != 0 || tail >= head);
        }
        index += end - start;
    }
}

RingBufferCursor::RingBufferCursor() :
    mBuffer(0), mPos(0)
{
}

RingBufferCursor::RingBufferCursor(DDTRingBuffer *buffer) :
    mBuffer(buffer), mPos(0)
{
    mBuffer->addCursor(this);
}

RingBufferCursor::~RingBufferCursor()
{
    if (mBuffer)
        mBuffer->removeCursor(this);
}

RingBufferCursor& RingBufferCursor::operator=(const RingBufferCursor &other)
{
    if (mBuffer) {
        Q_ASSERT(mBuffer == other.mBuffer);
    } else {
        mBuffer = other.mBuffer;
        mBuffer->addCursor(this);
    }
    mPos = other.mPos;
    return *this;
}

RingBufferCursor& RingBufferCursor::operator=(int n)
{
    mPos = n;
    return *this;
}

RingBufferCursor::operator int() const
{
    return mPos;
}

RingBufferCursor& RingBufferCursor::operator+=(int delta)
{
    mPos += delta;
    return *this;
}

RingBufferCursor& RingBufferCursor::operator-=(int delta)
{
    mPos -= delta;
    return *this;
}

RingBufferCursor& RingBufferCursor::operator++()
{
    ++mPos;
    return *this;
}

RingBufferCursor RingBufferCursor::operator++(int)
{
    RingBufferCursor ret(*this);
    ++(*this);
    return ret;
}

RingBufferCursor& RingBufferCursor::operator--()
{
    --mPos;
    return *this;
}

RingBufferCursor RingBufferCursor::operator--(int)
{
    RingBufferCursor ret(*this);
    --(*this);
    return ret;
}

void DDTRingBuffer::addCursor(RingBufferCursor *cursor)
{
    cursors.append(cursor);
}

void DDTRingBuffer::removeCursor(RingBufferCursor *cursor)
{
    cursors.removeOne(cursor);
}

void DDTRingBuffer::updateCursors(int offset, int removed)
{
    Q_ASSERT(offset >= 0);
    Q_ASSERT(removed >= 0);
    foreach (RingBufferCursor *cursor, cursors) {
        Q_ASSERT(*cursor >= 0);
        if (*cursor > offset)
            *cursor -= qMin(*cursor - offset, removed);
        Q_ASSERT(*cursor >= 0);
    }
}

void DDTRingBuffer::dump() const
{
    printf("bufferSize: %d\n", bufferSize);
    printf("head: %d\n", head);
    printf("tail: %d\n", tail);
    printf("tailBuffer: %d\n", tailBuffer);
    for (int i=0;i<buffers.size();i++)
        printf("buffers[%d] = \"%s%s\"\n", i, buffers[i].left(50).constData(), buffers.size() > 50 ? "..." : "");
}

qint64 DDTRingBuffer::readData(char * data, qint64 maxlen)
{
    if (maxlen == 1 && !isEmpty()) {
        int c = getChar();
        if (c == -1)
            return -1;
        *data = (char) c;
        return 1;
    }

    qint64 bytesToRead = qint64(qMin(size(), (int)maxlen));
    qint64 readSoFar = 0;
    while (readSoFar < bytesToRead) {
        const char *ptr = readPointer();
        int bytesToReadFromThisBlock = qMin<qint64>(bytesToRead - readSoFar,
                                                    nextDataBlockSize());
        memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock);
        readSoFar += bytesToReadFromThisBlock;
        free(bytesToReadFromThisBlock);
    }

    return readSoFar;
}
