/***************************************************************************
                          buffer.cpp  -  description
                             -------------------
    begin                : Sun Mar 10 2002
    copyright            : (C) 2002 by Vladimir Shutoff
    email                : vovan@shutoff.ru
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <stdio.h>

#ifdef WIN32
#include <winsock.h>
#else
#include <netinet/in.h>
#endif

#include <vector>

#include "buffer.h"
#include "event.h"
#include "log.h"

using namespace std;
using namespace SIM;

#ifdef WORDS_BIGENDIAN
# define SWAP_S(s)  s = ((s&0xFF)<<8) + ((s&0xFF00)>>8);  
# define SWAP_L(s)  s = ((s&0xFF)<<24) + ((s&0xFF00)<<8) + ((s&0xFF0000)>>8) + ((s&0xFF000000)>>24); 
#else
# define SWAP_S(s)
# define SWAP_L(s)
#endif

// Buffer
Buffer::Buffer(unsigned size)
        : QByteArray(size)
{
    init(size);
}

Buffer::Buffer(const QByteArray &ba)
    : QByteArray(ba)
{
    init(ba.size());
    m_posWrite = ba.size();
}

Buffer::Buffer(const QCString &cstr)
    : QByteArray(cstr.copy())
{
    uint len = cstr.length();
    init(len);
    m_posWrite = len;
}

Buffer::~Buffer()
{
}

void Buffer::init(unsigned size)
{
    m_posRead = 0;
    m_posWrite = 0;
    m_packetStartPos = 0;
    m_startSection = 0;
    resize(size);
}

void Buffer::incReadPos(int n)
{
    m_posRead += n;
    if (m_posRead > m_posWrite) m_posRead = m_posWrite;
}

bool Buffer::add(uint addSize)
{
    return(resize(size()+addSize));
}

bool Buffer::resize(uint size)
{
    bool bRet = QByteArray::resize(size);
    if (m_posWrite > size)
        m_posWrite = size;
    if (m_posRead > size)
        m_posRead = size;
    return bRet;
}

void Buffer::setWritePos(unsigned n)
{
    m_posWrite = n;
    if (m_posRead > m_posWrite) m_posRead = m_posWrite;
    if (m_posWrite > size())
        resize(m_posWrite);
}

void Buffer::setReadPos(unsigned n)
{
    if (n > m_posWrite)
        n = m_posWrite;
    m_posRead = n;
}

void Buffer::pack(const char *d, unsigned s)
{
    if(s == 0)
        return;
    if(m_posWrite+s > size())
        resize(m_posWrite+s);
    if(d) {
        memcpy(data() + m_posWrite, d, s);
    } else {
        memcpy(data() + m_posWrite, "", 1);
    }
    m_posWrite += s;
}

unsigned Buffer::unpack(char *d, unsigned s)
{
    unsigned readn = size() - m_posRead;
    if (s < readn)
        readn = s;
    memcpy(d, data() + m_posRead, readn);
    m_posRead += readn;
    return readn;
}


Buffer &Buffer::operator << (char c)
{
    pack(&c, 1);
    return *this;
}

Buffer &Buffer::operator << (const char *str)
{
    if(!str)
        return *this;
    pack(str, strlen(str));
    return *this;
}

Buffer &Buffer::operator << (unsigned short c)
{
    c = htons(c);
    pack((char*)&c, 2);
    return *this;
}

Buffer &Buffer::operator << (long c)
{
    /* XXX:
       FIXME WARNING! BUG HERE. sizeof(long) is not 4 on 64bit platform */
    c = htonl(c);
    pack((char*)&c, 4);
    return *this;
}

Buffer &Buffer::operator >> (char &c)
{
    if (unpack(&c, 1) != 1)
        c = 0;
    return *this;
}

Buffer &Buffer::operator >> (unsigned short &c)
{
    if (unpack((char*)&c, 2) != 2)
        c = 0;
    c = ntohs(c);
    return *this;
}

Buffer &Buffer::operator >> (long &c)
{
    /* XXX:
       FIXME WARNING! BUG HERE. sizeof(long) is not 4 on 64bit platform */
    if (unpack((char*)&c, 4) != 4)
        c = 0;
    c = ntohl(c);
    return *this;
}

bool Buffer::scan(const char *substr, QCString &res)
{
    char c = *substr;
    for (unsigned pos = readPos(); pos < writePos(); pos++){
        if (*data(pos) != c)
            continue;
        const char *sp = substr;
        for (unsigned pos1 = pos; *sp; pos1++, sp++){
            if (pos1 >= writePos())
                break;
            if (*data(pos1) != *sp)
                break;
        }
        if (*sp == 0){
            res = "";
            if (pos - readPos()){
                unpack(res, pos - readPos());
            }
            incReadPos(pos + strlen(substr) - readPos());
            return true;
        }
    }
    return false;
}

void Buffer::packetStart()
{
    m_packetStartPos = writePos();
}

static int findStartSection(const Buffer *pBuf, unsigned start)
{
    int idx = start == ~0U ? 0 : start;

    for ( ; ; ) {
        if(idx >= (int)pBuf->size())
            return -1;
        idx = pBuf->find( '[', idx);
        if(idx == -1)
            return -1;
        if( idx == 0 || pBuf->at( idx - 1 ) == '\n' )
            return idx;
        idx++;
    }
}

static int findEndSection(const Buffer *pBuf, unsigned start)
{
    int idx = start == ~0U ? 0 : start;

	for ( ; ; ) {
        if(idx >= (int)pBuf->size())
            return -1;
        idx = pBuf->find( ']', idx);
        if(idx == -1)
            return -1;
        if( idx == (int)pBuf->size() - 1 || pBuf->at( idx + 1 ) == '\n' )
            return idx;
        idx++;
    }
}

QCString Buffer::getSection(bool bSkip)
{
    QCString str;
    unsigned start = m_posRead;
    unsigned end = m_posRead;

    if( bSkip )
        start = findStartSection(this, m_posRead + 1);
    if( start == ~0U )
        return str;
    start = findStartSection( this, start );
    end   = findEndSection( this, start );
    if( start == ~0U || end == ~0U )
        return str;
    m_startSection = m_posRead = start;

    str = QCString( data() + start + 1, end - start );

    m_posRead = end + 1;
    if ( m_posRead < size() )
        if ( at(m_posRead) == '\n' )
            m_posRead++;
    if ( m_posRead >= size() )
        m_posRead = size() - 1;
    m_posWrite = findStartSection( this, end );
    if( m_posWrite == ~0U )
        m_posWrite = size();

    return str;
}

QCString Buffer::getLine()
{
    if (readPos() >= writePos())
        return "";
    unsigned start = m_posRead;
    int end = find('\n', start);
    if(end == -1)
        end = size();
    QCString res = QCString(data() + start, end - start + 1);
    m_posRead = end + 1; 
    if ( m_posRead < size() )
        if ( at(m_posRead) == '\n' )
            m_posRead++;

    return res;
}

// for Buffer::scan()
unsigned Buffer::unpack(QCString &d, unsigned s)
{
    unsigned readn = size() - m_posRead;
    if (s < readn)
        readn = s;
    d = QCString(data() + m_posRead, readn + 1);
    m_posRead += readn;
    return readn;
}

Buffer Buffer::fromBase64(QCString &from)
{
    unsigned n = 0;
    unsigned tmp2 = 0;
    Buffer to;
    int len = from.length();

    for (int i = 0; i < len; i++) {
        char c = from[i];
        char res[3];
        char tmp = 0;
        if ((c >= 'A') && (c <= 'Z')) {
            tmp = (char)(c - 'A');
        } else if ((c >= 'a') && (c <= 'z')) {
            tmp = (char)(26 + (c - 'a'));
        } else if ((c >= '0') && (c <= 57)) {
            tmp = (char)(52 + (c - '0'));
        } else if (c == '+') {
            tmp = 62;
        } else if (c == '/') {
            tmp = 63;
        } else if ((c == '\r') || (c == '\n')) {
            continue;
        } else if (c == '=') {
            if (n == 3) {
                res[0] = (char)((tmp2 >> 10) & 0xff);
                res[1] = (char)((tmp2 >> 2) & 0xff);
                to.pack(res, 2);
            } else if (n == 2) {
                res[0] = (char)((tmp2 >> 4) & 0xff);
                to.pack(res, 1);
            }
            break;
        }
        tmp2 = ((tmp2 << 6) | (tmp & 0xff));
        n++;
        if (n == 4) {
            res[0] = (char)((tmp2 >> 16) & 0xff);
            res[1] = (char)((tmp2 >> 8) & 0xff);
            res[2] = (char)(tmp2 & 0xff);
            to.pack(res, 3);
            tmp2 = 0;
            n = 0;
        }
    }
    return to;
}

static const char alphabet[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    "0123456789+/";

QCString Buffer::toBase64(Buffer &from)
{
    unsigned char b[3];
    char res[5];
    QCString to;

    res[4] = '\0';
    while (from.readPos() + 3 <= from.size()){
        from.unpack((char*)b, 3);
        unsigned tmp = (b[0] << 16) | (b[1] << 8) | b[2];
        res[0] = alphabet[(tmp >> 18) & 0x3F];
        res[1] = alphabet[(tmp >> 12) & 0x3F];
        res[2] = alphabet[(tmp >> 6) & 0x3F];
        res[3] = alphabet[tmp & 0x3F];
        to += res;
    }

    unsigned rest = from.size() - from.readPos();
    if(rest == 2 || rest == 1) {
        from.unpack((char*)b, rest);
        unsigned tmp = (b[0] << 16);
        if(rest == 2)
            tmp |= (b[1] << 8);
        res[0] = alphabet[(tmp >> 18) & 0x3F];
        res[1] = alphabet[(tmp >> 12) & 0x3F];
        res[2] = (rest == 2 ? alphabet[(tmp >> 6) & 0x3F] : '=');
        res[3] = '=';
        to += res;
    }
    return to;
}

// for msn
unsigned Buffer::unpack(QString &d, unsigned s)
{
    unsigned readn = size() - m_posRead;
    if (s < readn)
        readn = s;
    d = QString::fromUtf8(data() + m_posRead, readn);
    m_posRead += readn;
    return readn;
}
