///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany     //
// written by Christian Daniel                                                       //
// Copyright (C) 2014 John Greb <hexameron@spam.no>                                  //
//                                                                                   //
// 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 as version 3 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 V3 for more details.                                   //
//                                                                                   //
// You should have received a copy of the GNU General Public License                 //
// along with this program. If not, see <http://www.gnu.org/licenses/>.              //
///////////////////////////////////////////////////////////////////////////////////////
#include <stdint.h>
#include "util/simpleserializer.h"

#if __WORDSIZE == 64
#define PRINTF_FORMAT_S32 "%d"
#define PRINTF_FORMAT_U32 "%u"
#define PRINTF_FORMAT_S64 "%d"
#define PRINTF_FORMAT_U64 "%u"
#else
#define PRINTF_FORMAT_S32 "%d"
#define PRINTF_FORMAT_U32 "%u"
#define PRINTF_FORMAT_S64 "%lld"
#define PRINTF_FORMAT_U64 "%llu"
#endif

template <bool> struct Assert { };
typedef Assert<(bool(sizeof(float) == 4))> float_must_be_32_bits[bool(sizeof(float) == 4) ? 1 : -1];
typedef Assert<(bool(sizeof(double) == 8))> double_must_be_64_bits[bool(sizeof(double) == 8) ? 1 : -1];

SimpleSerializer::SimpleSerializer(uint version) :
	m_data(),
	m_finalized(false)
{
	m_data.reserve(100);

	// write version information
	int length;
	if(version >= (1 << 24))
		length = 4;
	else if(version >= (1 << 16))
		length = 3;
	else if(version >= (1 << 8))
		length = 2;
	else if(version > 0)
		length = 1;
	else length = 0;
	if(!writeTag(TVersion, 0, length))
		return;
	length--;
	for(int i = length; i >= 0; i--)
		m_data.push_back((char)((version >> (i * 8)) & 0xff));
}

void SimpleSerializer::writeS32(quint32 id, qint32 value)
{
	int length;

	if(id == 0) {
		qCritical("SimpleSerializer: ID 0 is not allowed");
		return;
	}

	if((value < -(1 << 23)) || (value >= (1 << 23)))
		length = 4;
	else if((value < -(1 << 15)) || (value >= (1 << 15)))
		length = 3;
	else if((value < -(1 << 7)) || (value >= (1 << 7)))
		length = 2;
	else if(value != 0)
		length = 1;
	else length = 0;

	if(!writeTag(TSigned32, id, length))
		return;

	length--;
	for(int i = length; i >= 0; i--)
		m_data.push_back((char)((value >> (i * 8)) & 0xff));
}

void SimpleSerializer::writeU32(quint32 id, quint32 value)
{
	int length;

	if(id == 0) {
		qCritical("SimpleSerializer: ID 0 is not allowed");
		return;
	}

	if(value >= (1 << 24))
		length = 4;
	else if(value >= (1 << 16))
		length = 3;
	else if(value >= (1 << 8))
		length = 2;
	else if(value > 0)
		length = 1;
	else length = 0;

	if(!writeTag(TUnsigned32, id, length))
		return;

	length--;
	for(int i = length; i >= 0; i--)
		m_data.push_back((char)((value >> (i * 8)) & 0xff));
}

void SimpleSerializer::writeS64(quint32 id, qint64 value)
{
	int length;

	if(id == 0) {
		qCritical("SimpleSerializer: ID 0 is not allowed");
		return;
	}

	if((value < -(1ll << 55)) || (value >= (1ll << 55)))
		length = 8;
	else if((value < -(1ll << 47)) || (value >= (1ll << 47)))
		length = 7;
	else if((value < -(1ll << 39)) || (value >= (1ll << 39)))
		length = 6;
	else if((value < -(1ll << 31)) || (value >= (1ll << 31)))
		length = 5;
	else if((value < -(1ll << 23)) || (value >= (1ll << 23)))
		length = 4;
	else if((value < -(1ll << 15)) || (value >= (1ll << 15)))
		length = 3;
	else if((value < -(1ll << 7)) || (value >= (1ll << 7)))
		length = 2;
	else if(value != 0)
		length = 1;
	else length = 0;

	if(!writeTag(TSigned64, id, length))
		return;

	length--;
	for(int i = length; i >= 0; i--)
		m_data.push_back((char)((value >> (i * 8)) & 0xff));
}

void SimpleSerializer::writeU64(quint32 id, quint64 value)
{
	int length;

	if(id == 0) {
		qCritical("SimpleSerializer: ID 0 is not allowed");
		return;
	}

	if(value >= (1ll << 56))
		length = 8;
	else if(value >= (1ll << 48))
		length = 7;
	else if(value >= (1ll << 40))
		length = 6;
	else if(value >= (1ll << 32))
		length = 5;
	else if(value >= (1ll << 24))
		length = 4;
	else if(value >= (1ll << 16))
		length = 3;
	else if(value >= (1ll << 8))
		length = 2;
	else if(value > 0)
		length = 1;
	else length = 0;

	if(!writeTag(TUnsigned64, id, length))
		return;

	length--;
	for(int i = length; i >= 0; i--)
		m_data.push_back((char)((value >> (i * 8)) & 0xff));
}

union floatasint {
	quint32 u;
	float f;
};

void SimpleSerializer::writeFloat(quint32 id, float value)
{
	union floatasint tmp;
	if(id == 0) {
		qCritical("SimpleSerializer: ID 0 is not allowed");
		return;
	}

	if(!writeTag(TFloat, id, 4))
		return;

	tmp.f = value;
	m_data.push_back((char)((tmp.u >> 24) & 0xff));
	m_data.push_back((char)((tmp.u >> 16) & 0xff));
	m_data.push_back((char)((tmp.u >> 8) & 0xff));
	m_data.push_back((char)(tmp.u & 0xff));
}

union doubleasint {
	quint64 u;
	double d;
};

void SimpleSerializer::writeDouble(quint32 id, double value)
{
	union doubleasint tmp;
	if(id == 0) {
		qCritical("SimpleSerializer: ID 0 is not allowed");
		return;
	}

	if(!writeTag(TDouble, id, 8))
		return;

	tmp.d = value;
	m_data.push_back((char)((tmp.u >> 56) & 0xff));
	m_data.push_back((char)((tmp.u >> 48) & 0xff));
	m_data.push_back((char)((tmp.u >> 40) & 0xff));
	m_data.push_back((char)((tmp.u >> 32) & 0xff));
	m_data.push_back((char)((tmp.u >> 24) & 0xff));
	m_data.push_back((char)((tmp.u >> 16) & 0xff));
	m_data.push_back((char)((tmp.u >> 8) & 0xff));
	m_data.push_back((char)(tmp.u & 0xff));
}

void SimpleSerializer::writeBool(quint32 id, bool value)
{
	if(id == 0) {
		qCritical("SimpleSerializer: ID 0 is not allowed");
		return;
	}

	if(!writeTag(TBool, id, 1))
		return;
	if(value)
		m_data.push_back((char)0x01);
	else m_data.push_back((char)0x00);
}

void SimpleSerializer::writeString(quint32 id, const QString& value)
{
	if(id == 0) {
		qCritical("SimpleSerializer: ID 0 is not allowed");
		return;
	}

	QByteArray utf8 = value.toUtf8();
	if(!writeTag(TString, id, utf8.size()))
		return;
	m_data.append(utf8);
}

void SimpleSerializer::writeBlob(quint32 id, const QByteArray& value)
{
	if(id == 0) {
		qCritical("SimpleSerializer: ID 0 is not allowed");
		return;
	}

	if(!writeTag(TBlob, id, value.size()))
		return;
	m_data.append(value);
}

const QByteArray& SimpleSerializer::final()
{
	m_finalized = true;
	return m_data;
}

bool SimpleSerializer::writeTag(Type type, quint32 id, quint32 length)
{
	if(m_finalized) {
		qCritical("SimpleSerializer: config has already been finalized (id %u)", id);
		return false;
	}

	int idLen;
	int lengthLen;

	if(id < (1 << 8))
		idLen = 0;
	else if(id < (1 << 16))
		idLen = 1;
	else if(id < (1 << 24))
		idLen = 2;
	else idLen = 3;

	if(length < (1 << 8))
		lengthLen = 0;
	else if(length < (1 << 16))
		lengthLen = 1;
	else if(length < (1 << 24))
		lengthLen = 2;
	else lengthLen = 3;

	m_data.push_back((char)((type << 4) | (idLen << 2) | lengthLen));
	for(int i = idLen; i >= 0; i--)
		m_data.push_back((char)((id >> (i * 8)) & 0xff));
	for(int i = lengthLen; i >= 0; i--)
		m_data.push_back((char)((length >> (i * 8)) & 0xff));
	return true;
}

SimpleDeserializer::SimpleDeserializer(const QByteArray& data) :
	m_data(data)
{
	m_valid = parseAll();

	// read version information
	uint readOfs;
	Elements::const_iterator it = m_elements.constFind(0);
	if(it == m_elements.constEnd())
		goto setInvalid;
	if(it->type != TVersion)
		goto setInvalid;
	if(it->length > 4)
		goto setInvalid;

	readOfs = it->ofs;
	m_version = 0;
	for(uint i = 0; i < it->length; i++)
		m_version = (m_version << 8) | readByte(&readOfs);
	return;

setInvalid:
	m_valid = false;
}

bool SimpleDeserializer::readS32(quint32 id, qint32* result, qint32 def) const
{
	uint readOfs;
	qint32 tmp;
	Elements::const_iterator it = m_elements.constFind(id);
	if(it == m_elements.constEnd())
		goto returnDefault;
	if(it->type != TSigned32)
		goto returnDefault;
	if(it->length > 4)
		goto returnDefault;

	readOfs = it->ofs;
	tmp = 0;
	for(uint i = 0; i < it->length; i++) {
		quint8 byte = readByte(&readOfs);
		if((i == 0) && (byte & 0x80))
			tmp = -1;
		tmp = (tmp << 8) | byte;
	}
	*result = tmp;
	return true;

returnDefault:
	*result = def;
	return false;
}

bool SimpleDeserializer::readU32(quint32 id, quint32* result, quint32 def) const
{
	uint readOfs;
	quint32 tmp;
	Elements::const_iterator it = m_elements.constFind(id);
	if(it == m_elements.constEnd())
		goto returnDefault;
	if(it->type != TUnsigned32)
		goto returnDefault;
	if(it->length > 4)
		goto returnDefault;

	readOfs = it->ofs;
	tmp = 0;
	for(uint i = 0; i < it->length; i++)
		tmp = (tmp << 8) | readByte(&readOfs);
	*result = tmp;
	return true;

returnDefault:
	*result = def;
	return false;
}

bool SimpleDeserializer::readS64(quint32 id, qint64* result, qint64 def) const
{
	uint readOfs;
	qint64 tmp;
	Elements::const_iterator it = m_elements.constFind(id);
	if(it == m_elements.constEnd())
		goto returnDefault;
	if(it->type != TSigned64)
		goto returnDefault;
	if(it->length > 8)
		goto returnDefault;

	readOfs = it->ofs;
	tmp = 0;
	for(uint i = 0; i < it->length; i++) {
		quint8 byte = readByte(&readOfs);
		if((i == 0) && (byte & 0x80))
			tmp = -1;
		tmp = (tmp << 8) | byte;
	}
	*result = tmp;
	return true;

returnDefault:
	*result = def;
	return false;
}

bool SimpleDeserializer::readU64(quint32 id, quint64* result, quint64 def) const
{
	uint readOfs;
	quint64 tmp;
	Elements::const_iterator it = m_elements.constFind(id);
	if(it == m_elements.constEnd())
		goto returnDefault;
	if(it->type != TUnsigned64)
		goto returnDefault;
	if(it->length > 8)
		goto returnDefault;

	readOfs = it->ofs;
	tmp = 0;
	for(uint i = 0; i < it->length; i++)
		tmp = (tmp << 8) | readByte(&readOfs);
	*result = tmp;
	return true;

returnDefault:
	*result = def;
	return false;
}

bool SimpleDeserializer::readFloat(quint32 id, float* result, float def) const
{
	uint readOfs;
	union floatasint tmp;
	Elements::const_iterator it = m_elements.constFind(id);
	if(it == m_elements.constEnd())
		goto returnDefault;
	if(it->type != TFloat)
		goto returnDefault;
	if(it->length != 4)
		goto returnDefault;

	readOfs = it->ofs;
	tmp.u = 0;
	for(int i = 0; i < 4; i++)
		tmp.u = (tmp.u << 8) | readByte(&readOfs);
	*result = tmp.f;
	return true;

returnDefault:
	*result = def;
	return false;
}

bool SimpleDeserializer::readDouble(quint32 id, double* result, double def) const
{
	uint readOfs;
	union doubleasint tmp;
	Elements::const_iterator it = m_elements.constFind(id);
	if(it == m_elements.constEnd())
		goto returnDefault;
	if(it->type != TDouble)
		goto returnDefault;
	if(it->length != 8)
		goto returnDefault;

	readOfs = it->ofs;
	tmp.u = 0;
	for(int i = 0; i < 8; i++)
		tmp.u = (tmp.u << 8) | readByte(&readOfs);
	*result = tmp.d;
	return true;

returnDefault:
	*result = def;
	return false;
}

union real4asint {
	quint32 u;
	Real r;
};

union real8asint {
	quint64 u;
	Real r;
};

bool SimpleDeserializer::readReal(quint32 id, Real* result, Real def) const
{
	if(sizeof(Real) == 4) {
		uint readOfs;
		union real4asint tmp;
		Elements::const_iterator it = m_elements.constFind(id);
		if(it == m_elements.constEnd())
			goto returnDefault32;
		if(it->type != TFloat)
			goto returnDefault32;
		if(it->length != 4)
			goto returnDefault32;

		readOfs = it->ofs;
		tmp.u = 0;
		for(int i = 0; i < 4; i++)
			tmp.u = (tmp.u << 8) | readByte(&readOfs);
		*result = tmp.r;
		return true;

	returnDefault32:
		*result = def;
		return false;
	} else {
		uint readOfs;
		union real8asint tmp;
		Elements::const_iterator it = m_elements.constFind(id);
		if(it == m_elements.constEnd())
			goto returnDefault64;
		if(it->type != TDouble)
			goto returnDefault64;
		if(it->length != 8)
			goto returnDefault64;

		readOfs = it->ofs;
		tmp.u = 0;
		for(int i = 0; i < 8; i++)
			tmp.u = (tmp.u << 8) | readByte(&readOfs);
		*result = tmp.r;
		return true;

	returnDefault64:
		*result = def;
		return false;
	}
}

bool SimpleDeserializer::readBool(quint32 id, bool* result, bool def) const
{
	uint readOfs;
	quint8 tmp;
	Elements::const_iterator it = m_elements.constFind(id);
	if(it == m_elements.constEnd())
		goto returnDefault;
	if(it->type != TBool)
		goto returnDefault;
	if(it->length != 1)
		goto returnDefault;

	readOfs = it->ofs;
	tmp = readByte(&readOfs);
	if(tmp == 0x00)
		*result = false;
	else *result = true;
	return true;

returnDefault:
	*result = def;
	return false;
}

bool SimpleDeserializer::readString(quint32 id, QString* result, const QString& def) const
{
	Elements::const_iterator it = m_elements.constFind(id);
	if(it == m_elements.constEnd())
		goto returnDefault;
	if(it->type != TString)
		goto returnDefault;

	*result = QString::fromUtf8(m_data.data() + it->ofs, it->length);
	return true;

returnDefault:
	*result = def;
	return false;
}

bool SimpleDeserializer::readBlob(quint32 id, QByteArray* result, const QByteArray& def) const
{
	Elements::const_iterator it = m_elements.constFind(id);
	if(it == m_elements.constEnd())
		goto returnDefault;
	if(it->type != TBlob)
		goto returnDefault;

	*result = QByteArray(m_data.data() + it->ofs, it->length);
	return true;

returnDefault:
	*result = def;
	return false;
}

void SimpleDeserializer::dump() const
{
	if(!m_valid) {
		qDebug("SimpleDeserializer dump: data invalid");
		return;
	} else {
		qDebug("SimpleDeserializer dump: version %u", m_version);
	}

	for(Elements::const_iterator it = m_elements.constBegin(); it != m_elements.constEnd(); ++it) {
		switch(it->type) {
			case TSigned32: {
				qint32 tmp;
				readS32(it.key(), &tmp);
				qDebug("id %d, S32, len %d: " PRINTF_FORMAT_S32, it.key(), it->length, tmp);
				break;
			}
			case TUnsigned32: {
				quint32 tmp;
				readU32(it.key(), &tmp);
				qDebug("id %d, U32, len %d: " PRINTF_FORMAT_U32, it.key(), it->length, tmp);
				break;
			}
			case TSigned64: {
				qint64 tmp;
				readS64(it.key(), &tmp);
//				qDebug("id %d, S64, len %d: " PRINTF_FORMAT_S64, it.key(), it->length, (int)tmp);
				break;
			}
			case TUnsigned64: {
				quint64 tmp;
				readU64(it.key(), &tmp);
//				qDebug("id %d, U64, len %d: " PRINTF_FORMAT_U64, it.key(), it->length, (uint)tmp);
				break;
			}
			case TFloat: {
				float tmp;
				readFloat(it.key(), &tmp);
				qDebug("id %d, FLOAT, len %d: %f", it.key(), it->length, tmp);
				break;
			}
			case TDouble: {
				double tmp;
				readDouble(it.key(), &tmp);
				qDebug("id %d, DOUBLE, len %d: %f", it.key(), it->length, tmp);
				break;
			}
			case TBool: {
				bool tmp;
				readBool(it.key(), &tmp);
				qDebug("id %d, BOOL, len %d: %s", it.key(), it->length, tmp ? "true" : "false");
				break;
			}
			case TString: {
				QString tmp;
				readString(it.key(), &tmp);
				qDebug("id %d, STRING, len %d: \"%s\"", it.key(), it->length, qPrintable(tmp));
				break;
			}
			case TBlob: {
				QByteArray tmp;
				readBlob(it.key(), &tmp);
				qDebug("id %d, BLOB, len %d", it.key(), it->length);
				break;
			}
			case TVersion: {
				qDebug("id %d, VERSION, len %d", it.key(), it->length);
				break;
			}
			default: {
				qDebug("id %d, UNKNOWN TYPE 0x%02x, len %d", it.key(), it->type, it->length);
				break;
			}
		}
	}
}

bool SimpleDeserializer::parseAll()
{
	uint readOfs = 0;
	Type type;
	quint32 id;
	quint32 length;

	/*
	QString hex;
	for(int i = 0; i < m_data.size(); i++)
		hex.append(QString("%1 ").arg((quint8)m_data[i], 2, 16, QChar('0')));
	qDebug("%s", qPrintable(hex));
	qDebug("==");
	*/

	while(readOfs < (uint)m_data.size()) {
		if(!readTag(&readOfs, m_data.size(), &type, &id, &length))
			return false;

		//qDebug("-- id %d, TYPE 0x%02x, len %d", id, type, length);

		if(m_elements.contains(id)) {
			qDebug("SimpleDeserializer: same ID found twice (id %u)", id);
			return false;
		}

		m_elements.insert(id, Element(type, readOfs, length));

		readOfs += length;

		if(readOfs == (uint)m_data.size())
			return true;
	}
	return false;
}

bool SimpleDeserializer::readTag(uint* readOfs, uint readEnd, Type* type, quint32* id, quint32* length) const
{
	quint8 tag = readByte(readOfs);

	*type = (Type)(tag >> 4);
	int idLen = ((tag >> 2) & 0x03) + 1;
	int lengthLen = (tag & 0x03) + 1;

	// make sure we have enough header bytes left
	if(((*readOfs) + idLen + lengthLen) > readEnd)
		return false;

	quint32 tmp = 0;
	for(int i = 0; i < idLen; i++)
		tmp = (tmp << 8) | readByte(readOfs);
	*id = tmp;
	tmp = 0;
	for(int i = 0; i < lengthLen; i++)
		tmp = (tmp << 8) | readByte(readOfs);
	*length = tmp;

	// check if payload fits the buffer
	if(((*readOfs) + (*length)) > readEnd)
		return false;
	else return true;
}

