/* -*- c++ -*-
 *
 * donkeymessage.cpp
 *
 * Copyright (C) 2003 Petter Stokke <ummo@hellokitty.com>
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>

#include <qdatetime.h>
#include <kdebug.h>
#include <qtextcodec.h>

#include "donkeymessage.h"

QTextCodec* DonkeyMessage::codec = 0;

void DonkeyMessage::initCodec()
{
    if (!codec) {
        codec = QTextCodec::codecForName("ISO-8859-1");
        if (!codec) codec = QTextCodec::codecForLocale();
    }
}

void DonkeyMessage::setStringCodec(QTextCodec* newCodec)
{
    codec = newCodec;
}

DonkeyMessage::DonkeyMessage( int opcode ) : QMemArray<int8>()
{
    initCodec();
    op = opcode;
    pos = 0;
}

DonkeyMessage::DonkeyMessage( const char* data, int len ) : QMemArray<int8>()
{
    initCodec();
    resize(len - 2);
    op = data[0] | (data[1] << 8);
    char *p = (char*)data + 2;
    int i;
    for (i=0; i<(len-2); i++)
        (*this)[i] = p[i];
    pos = 0;
}

DonkeyMessage::DonkeyMessage(int opcode, int len) : QMemArray<int8>(len)
{
    initCodec();
    op = opcode;
    pos = 0;
}

int DonkeyMessage::opcode() const
{
    return op;
}

void DonkeyMessage::setOpcode(int opcode)
{
    op = opcode;
}

inline void DonkeyMessage::writeInt(int64 v, int sz)
{
    int j;
    pos = size();
    resize(pos + sz);
    for(j = 0; j < sz; j++)
        (*this)[pos + j] = (v & (-1)) >> (8 * j);
    pos += sz;
}

void DonkeyMessage::writeInt8(int8 v)
{
    writeInt((int64)v, 1);
}

void DonkeyMessage::writeInt16(int16 v)
{
    writeInt((int64)v, 2);
}

void DonkeyMessage::writeInt32(int32 v)
{
    writeInt((int64)v, 4);
}

void DonkeyMessage::writeInt64(int64 v)
{
    writeInt((int64)v, 8);
}

void DonkeyMessage::writeBool(bool v)
{
    writeInt8(v ? 1 : 0);
}

void DonkeyMessage::writeString(const QString& v)
{
    QCString s = codec->fromUnicode(v);
    if (s.isNull()) {
        kdDebug() << "Unable to convert string into charset " << codec->name() << "." << endl;
        writeString("");
    } else
        writeString((const char*)s);
}

void DonkeyMessage::writeString(const char* v)
{
    int i,l = strlen(v);
    pos = size();
    if (l >= 0xffff) {
        writeInt16(0xffff);
        writeInt32(l);
    } else writeInt16(l);
    resize(pos + l);
    for (i=0; i<l; i++)
        (*this)[pos++] = v[i];
}

void DonkeyMessage::writeByteArray(const QByteArray& v)
{
    int i,l = (int)v.size();
    if (l >= 0xffff) {
        writeInt16(0xffff);
        writeInt32(l);
    } else writeInt16(l);
    resize(pos + l);
    for (i=0; i<l; i++)
        (*this)[pos++] = v[i];
}

void DonkeyMessage::writeFloat(double v)
{
    QString foo = QString();
    foo.sprintf("%.4f", v);
    writeString(foo);
}

void DonkeyMessage::feedBuffer(const char* buf, int sz)
{
    memcpy(data() + pos, buf, sz);
    pos += sz;
}

void DonkeyMessage::resetPosition()
{
    pos = 0;
}

inline int64 DonkeyMessage::readInt(int sz)
{
    if ((uint)(pos + sz) > count()) {
        QString foo(kdBacktrace());
        QString msg(dumpArray());
        kdDebug() << "Position " << pos + sz << " exceeds buffer size " << count() << "\nMessage: " << msg << "\nBT: " << foo << endl;
        kdFatal() << "Invalid index access.";
    }
    int i;
    int64 res = 0;
    for(i = 0; i < sz; i++) {
        int64 quux = (*this)[pos + i] & 0xff;
        int64 flarp = quux << (8 * i);
        res |= flarp;
    }
    pos += sz;
    return res;
}

int8 DonkeyMessage::readInt8()
{
    return (int8)readInt(1);
}

int16 DonkeyMessage::readInt16()
{
    return (int16)readInt(2);
}

int32 DonkeyMessage::readInt32()
{
    return (int32)readInt(4);
}

int64 DonkeyMessage::readInt64()
{
    return (int64)readInt(8);
}

bool DonkeyMessage::readBool()
{
    return (bool)readInt(1);
}

QString DonkeyMessage::readString(bool* ok)
{
    return codec->toUnicode( readByteArray(ok) );
}

QByteArray DonkeyMessage::readByteArray(bool* ok)
{
    int sz = (int)readInt16();
    if (sz == 0xffff) sz = (int)readInt32();
    if ((uint)pos + sz > count()) {
        QString foo(kdBacktrace());
        QString msg(dumpArray());
        kdDebug() << "Position " << pos + sz << " exceeds buffer size " << count() << "\nMessage: " << msg << "\nBT: " << foo << endl;
        if(ok) {
            *ok = false;
            return QByteArray();
        }
        kdFatal() << "Invalid index access.";
    }
    QByteArray buf(sz);
    memcpy(buf.data(), *this + pos, sz);
    pos += sz;
    return buf;
}

int DonkeyMessage::readDate()
{
    return QDateTime::currentDateTime().toTime_t() - readInt32();
}

double DonkeyMessage::readFloat()
{
    return readString().toDouble();
}

QString DonkeyMessage::readIPAddress()
{
/*
    QString address = QString::number(readInt8()) + "."
        + QString::number(readInt8()) + "."
        + QString::number(readInt8()) + "."
        + QString::number(readInt8());
*/
    struct in_addr in;
    in.s_addr = readInt32();
    return QString(inet_ntoa(in));
}

QString DonkeyMessage::readAddress()
{
    if (readInt8())
        return readString();
    else
        return readIPAddress();
}

bool DonkeyMessage::readTag(QMap<QString,QVariant>& dict)
{
    QString name = readString();
    QVariant value;
    switch (readInt8()) {
    case 0:
    case 1:
        value = QVariant(readInt32());
        break;
    case 2: {
        bool is_ok = true;
        value = QVariant(readString(&is_ok));
        if(! is_ok) return false;
    } break;
    case 3:
        value = QVariant(readIPAddress());
        break;
    case 4:
        value = QVariant(readInt16());
        break;
    case 5:
        value = QVariant(readInt8());
        break;
    default:
        kdWarning() << "DonkeyMessage::readTag() returned unknown value!" << endl;
        return false;
    }
    dict.replace(name, value);
    return true;
}

QString DonkeyMessage::dumpArray() const
{
    QString out = "Opcode " + QString::number(op) + ", size " + QString::number(size()) + "\n";

    int i;
    QString hex = "", asc = "", tmp;
    for (i=0; i<(int)size(); i++) {
        if (at(i) >= 32 && at(i) <= 127)
            asc += QChar(at(i));
        else
            asc += ".";
        tmp.sprintf("%02x", at(i));
        hex += tmp + " ";
        if (i % 16 == 15) {
            tmp.sprintf("%8d: ", i - 15);
            out += tmp + hex + "  " + asc + "\n";
            hex = "";
            asc = "";
        }
    }
    tmp.sprintf("%8d: ", i - (i % 16));
    for (i %= 16; i < 16; i++)
        hex += "   ";
    out += tmp + hex + "  " + asc + "\n";
    return out;
}

