/*  -*- C++ -*-
 *  Copyright (C) 2003 Thiago Macieira <thiago@kde.org>
 *
 *
 *  Permission is hereby granted, free of charge, to any person obtaining
 *  a copy of this software and associated documentation files (the
 *  "Software"), to deal in the Software without restriction, including
 *  without limitation the rights to use, copy, modify, merge, publish,
 *  distribute, sublicense, and/or sell copies of the Software, and to
 *  permit persons to whom the Software is furnished to do so, subject to
 *  the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "k3socketbuffer_p.h"

#include <config-network.h>

#include <assert.h>
#include <string.h>

#include <QMutableListIterator>

#include "k3socketbase.h"

using namespace KNetwork;
using namespace KNetwork::Internal;

KSocketBuffer::KSocketBuffer(qint64 size)
    : m_mutex(QMutex::Recursive), m_offset(0), m_size(size), m_length(0)
{
}

KSocketBuffer::KSocketBuffer(const KSocketBuffer &other)
    : m_mutex(QMutex::Recursive)
{
    *this = other;
}

KSocketBuffer::~KSocketBuffer()
{
    // QValueList takes care of deallocating memory
}

KSocketBuffer &KSocketBuffer::operator=(const KSocketBuffer &other)
{
    QMutexLocker locker1(&m_mutex);
    QMutexLocker locker2(&other.m_mutex);

    m_list = other.m_list;    // copy-on-write
    m_offset = other.m_offset;
    m_size = other.m_size;
    m_length = other.m_length;

    return *this;
}

bool KSocketBuffer::canReadLine() const
{
    QMutexLocker locker(&m_mutex);

    qint64 offset = m_offset;

    // walk the buffer
    for (int i = 0; i < m_list.size(); ++i) {
        if (m_list.at(i).indexOf('\n', offset) != -1) {
            return true;
        }
        if (m_list.at(i).indexOf('\r', offset) != -1) {
            return true;
        }
        offset = 0;
    }

    return false;         // not found
}

qint64 KSocketBuffer::readLine(char *data, qint64 maxSize)
{
    if (!canReadLine()) {
        return qint64(-1);    // empty
    }

    QMutexLocker locker(&m_mutex);

    // find the offset of the newline in the buffer
    qint64 newline = 0;
    qint64 offset = m_offset;

    // walk the buffer
    for (int i = 0; i < m_list.size(); ++i) {
        int posnl = m_list.at(i).indexOf('\n', offset);
        if (posnl == -1) {
            // not found in this one
            newline += m_list.at(i).size();
            offset = 0;
            continue;
        }

        // we found it
        newline += posnl;
        break;
    }

    qint64 bytesToRead = newline + 1 - m_offset;
    if (bytesToRead > maxSize) {
        bytesToRead = maxSize;    // don't read more than maxSize
    }

    return consumeBuffer(data, bytesToRead);
}

qint64 KSocketBuffer::length() const
{
    return m_length;
}

qint64 KSocketBuffer::size() const
{
    return m_size;
}

bool KSocketBuffer::setSize(qint64 size)
{
    m_size = size;
    if (size == -1 || m_length < m_size) {
        return true;
    }

    // size is now smaller than length
    QMutexLocker locker(&m_mutex);

    // repeat the test
    if (m_length < m_size) {
        return true;
    }

    // discard from the beginning
    return (m_length - m_size) == consumeBuffer(0L, m_length - m_size, true);
}

qint64 KSocketBuffer::feedBuffer(const char *data, qint64 len)
{
    if (data == 0L || len == 0) {
        return 0;    // nothing to write
    }
    if (isFull()) {
        return -1;    // can't write
    }

    QMutexLocker locker(&m_mutex);

    // verify if we can add len bytes
    if (m_size != -1 && (m_size - m_length) < len) {
        len = m_size - m_length;
    }

    QByteArray a(data, len);
    m_list.append(a);

    m_length += len;
    return len;
}

qint64 KSocketBuffer::consumeBuffer(char *destbuffer, qint64 maxlen, bool discard)
{
    if (maxlen == 0 || isEmpty()) {
        return 0;
    }

    QMutableListIterator<QByteArray> it(m_list);
    qint64 offset = m_offset;
    qint64 copied = 0;

    // walk the buffer
    while (it.hasNext() && maxlen) {
        QByteArray &item = it.next();
        // calculate how much we'll copy
        qint64 to_copy = item.size() - offset;
        if (to_copy > maxlen) {
            to_copy = maxlen;
        }

        // do the copying
        if (destbuffer) {
            memcpy(destbuffer + copied, item.data() + offset, to_copy);
        }
        maxlen -= to_copy;
        copied += to_copy;

        if (item.size() - offset > to_copy) {
            // we did not copy everything
            offset += to_copy;
            break;
        } else {
            // we copied everything
            // discard this element;
            offset = 0;
            if (discard) {
                it.remove();
            }
        }
    }

    if (discard) {
        m_offset = offset;
        m_length -= copied;
        assert(m_length >= 0);
    }

    return copied;
}

void KSocketBuffer::clear()
{
    QMutexLocker locker(&m_mutex);
    m_list.clear();
    m_offset = 0;
    m_length = 0;
}

qint64 KSocketBuffer::sendTo(KActiveSocketBase *dev, qint64 len)
{
    if (len == 0 || isEmpty()) {
        return 0;
    }

    QMutexLocker locker(&m_mutex);

    QMutableListIterator<QByteArray> it(m_list);
    qint64 offset = m_offset;
    qint64 written = 0;

    // walk the buffer
    while (it.hasNext() && (len || len == -1)) {
        // we have to write each element up to len bytes
        // but since we can have several very small buffers, we can make things
        // better by concatenating a few of them into a big buffer
        // question is: how big should that buffer be? 2 kB should be enough

        uint bufsize = 1460;
        if (len != -1 && len < bufsize) {
            bufsize = len;
        }
        QByteArray buf(bufsize, '\0');
        qint64 count = 0;

        while (it.hasNext() && count + (it.peekNext().size() - offset) <= bufsize) {
            QByteArray &item = it.next();
            memcpy(buf.data() + count, item.data() + offset, item.size() - offset);
            count += item.size() - offset;
            offset = 0;
        }

        // see if we can still fit more
        if (count < bufsize && it.hasNext()) {
            // getting here means this buffer (peekNext) is larger than
            // (bufsize - count) (even for count == 0).
            QByteArray &item = it.next();
            memcpy(buf.data() + count, item.data() + offset, bufsize - count);
            offset += bufsize - count;
            count = bufsize;
        }

        // now try to write those bytes
        qint64 wrote = dev->write(buf, count);

        if (wrote == -1)
            // error?
        {
            break;
        }

        written += wrote;
        if (wrote != count)
            // can't fit more?
        {
            break;
        }
    }

    // discard data that has been written
    // this updates m_length too
    if (written) {
        consumeBuffer(0L, written);
    }

    return written;
}

qint64 KSocketBuffer::receiveFrom(KActiveSocketBase *dev, qint64 len)
{
    if (len == 0 || isFull()) {
        return 0;
    }

    QMutexLocker locker(&m_mutex);

    if (len == -1) {
        len = dev->bytesAvailable();
    }
    if (len <= 0)
        // error or closing socket
    {
        return len;
    }

    // see if we can read that much
    if (m_size != -1 && len > (m_size - m_length)) {
        len = m_size - m_length;
    }

    // here, len contains just as many bytes as we're supposed to read

    // now do the reading
    QByteArray a(len, '\0');
    len = dev->read(a.data(), len);

    if (len == -1)
        // error?
    {
        return -1;
    }

    // success
    // resize the buffer and add it
    a.truncate(len);
    m_list.append(a);
    m_length += len;
    return len;
}
