// ==============================================================
//	This file is part of MegaGlest (www.megaglest.org)
//
//	Copyright (C) 2012 Mark Vejvoda
//
//	You can redistribute this code 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 "network_protocol.h"
#include <stdarg.h>
#include <cstring>
#include <ctype.h>
#include <stdio.h>
#include "data_types.h"
#include "util.h"
#include "leak_dumper.h"

using namespace Shared::Platform;
using namespace Shared::Util;

namespace Glest{ namespace Game{

#pragma pack(push, 1)

// macros for packing floats and doubles:
#define pack754_16(f) (pack754((f), 16, 5))
#define pack754_32(f) (pack754((f), 32, 8))
#define pack754_64(f) (pack754((f), 64, 11))
#define unpack754_16(i) (unpack754((i), 16, 5))
#define unpack754_32(i) (unpack754((i), 32, 8))
#define unpack754_64(i) (unpack754((i), 64, 11))

/*
** pack754() -- pack a floating point number into IEEE-754 format
*/
unsigned long long int pack754(long double f, unsigned bits, unsigned expbits)
{
	long double fnorm;
	int shift;
	long long sign, exp, significand;
	unsigned significandbits = bits - expbits - 1; // -1 for sign bit

	if (f == 0.0) return 0; // get this special case out of the way

	// check sign and begin normalization
	if (f < 0) { sign = 1; fnorm = -f; }
	else { sign = 0; fnorm = f; }

	// get the normalized form of f and track the exponent
	shift = 0;
	while(fnorm >= 2.0) { fnorm /= 2.0; shift++; }
	while(fnorm < 1.0) { fnorm *= 2.0; shift--; }
	fnorm = fnorm - 1.0;

	// calculate the binary form (non-float) of the significand data
	significand = fnorm * ((1LL<<significandbits) + 0.5f);

	// get the biased exponent
	exp = (long long)shift + ((1<<(expbits-1)) - 1); // shift + bias

	// return the final answer
	return (sign<<(bits-1)) | (exp<<(bits-expbits-1)) | significand;
}

/*
** unpack754() -- unpack a floating point number from IEEE-754 format
*/
long double unpack754(unsigned long long int i, unsigned bits, unsigned expbits)
{
	long double result;
	long long shift;
	unsigned bias;
	unsigned significandbits = bits - expbits - 1; // -1 for sign bit

	if (i == 0) return 0.0;

	// pull the significand
	result = (i&((1LL<<significandbits)-1)); // mask
	result /= (1LL<<significandbits); // convert back to float
	result += 1.0f; // add the one back on

	// deal with the exponent
	bias = (1<<(expbits-1)) - 1;
	shift = ((i>>significandbits)&((1LL<<expbits)-1)) - bias;
	while(shift > 0) { result *= 2.0; shift--; }
	while(shift < 0) { result /= 2.0; shift++; }

	// sign it
	result *= (i>>(bits-1))&1? -1.0: 1.0;

	return result;
}

/*
** packi16() -- store a 16-bit int into a char buffer (like htons())
*/
void packi16(unsigned char *buf, uint16 i)
{
	*buf++ = i>>8; *buf++ = i;
}

/*
** packi32() -- store a 32-bit int into a char buffer (like htonl())
*/
void packi32(unsigned char *buf, uint32 i)
{
	*buf++ = i>>24; *buf++ = i>>16;
	*buf++ = i>>8;  *buf++ = i;
}

/*
** packi64() -- store a 64-bit int into a char buffer (like htonl())
*/
void packi64(unsigned char *buf, uint64 i)
{
	*buf++ = i>>56; *buf++ = i>>48;
	*buf++ = i>>40; *buf++ = i>>32;
	*buf++ = i>>24; *buf++ = i>>16;
	*buf++ = i>>8;  *buf++ = i;
}

/*
** unpacki16() -- unpack a 16-bit int from a char buffer (like ntohs())
*/
int16 unpacki16(unsigned char *buf)
{
	uint16 i2 = ((uint16)buf[0]<<8) | buf[1];
	int16 i;

	// change unsigned numbers to signed
	if (i2 <= 0x7fffu) {
		i = i2;
	}
	else {
		i = -1 - (uint16)(0xffffu - i2);
		if(SystemFlags::VERBOSE_MODE_ENABLED) printf("IN [%s] [%d] [%d] [%d] [%u]\n",__FUNCTION__,buf[0],buf[1],i,i2);
	}

	return i;
}

/*
** unpacku16() -- unpack a 16-bit unsigned from a char buffer (like ntohs())
*/
uint16 unpacku16(unsigned char *buf)
{
	return ((uint16)buf[0]<<8) | buf[1];
}

/*
** unpacki32() -- unpack a 32-bit int from a char buffer (like ntohl())
*/
int32 unpacki32(unsigned char *buf)
{
	uint32 i2 = ((uint32)buf[0]<<24) |
	            ((uint32)buf[1]<<16) |
	            ((uint32)buf[2]<<8)  |
	                     buf[3];
	int32 i;

	// change unsigned numbers to signed
	if (i2 <= 0x7fffffffu) {
		i = i2;
	}
	else {
		i = -1 - (int32)(0xffffffffu - i2);
		if(SystemFlags::VERBOSE_MODE_ENABLED) printf("IN [%s] [%d] [%d] [%d] [%d] [%d] [%u]\n",__FUNCTION__,buf[0],buf[1],buf[2],buf[3],i,i2);
	}

	return i;
}

/*
** unpacku32() -- unpack a 32-bit unsigned from a char buffer (like ntohl())
*/
uint32 unpacku32(unsigned char *buf)
{
	return ((uint32)buf[0]<<24) |
	       ((uint32)buf[1]<<16) |
	       ((uint32)buf[2]<<8)  |
	       buf[3];
}

/*
** unpacki64() -- unpack a 64-bit int from a char buffer (like ntohl())
*/
int64 unpacki64(unsigned char *buf)
{
	uint64 i2 = ((uint64)buf[0]<<56) |
	              ((uint64)buf[1]<<48) |
	              ((uint64)buf[2]<<40) |
	              ((uint64)buf[3]<<32) |
	              ((uint64)buf[4]<<24) |
	              ((uint64)buf[5]<<16) |
	              ((uint64)buf[6]<<8)  |
	                       buf[7];
	int64 i;

	// change unsigned numbers to signed
	if (i2 <= 0x7fffffffffffffffull) {
		i = i2;
	}
	else {
		i = -1 -(int64)(0xffffffffffffffffull - i2);
		if(SystemFlags::VERBOSE_MODE_ENABLED) printf("IN [%s] [%d] [%d] [%d] [%d] [%d] [%d] [%d] [%d] [" MG_I64_SPECIFIER "] [" MG_I64U_SPECIFIER "]\n",__FUNCTION__,buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],i,i2);
	}

	return i;
}

/*
** unpacku64() -- unpack a 64-bit unsigned from a char buffer (like ntohl())
*/
uint64 unpacku64(unsigned char *buf)
{
	return ((uint64)buf[0]<<56) |
	       ((uint64)buf[1]<<48) |
	       ((uint64)buf[2]<<40) |
	       ((uint64)buf[3]<<32) |
	       ((uint64)buf[4]<<24) |
	       ((uint64)buf[5]<<16) |
	       ((uint64)buf[6]<<8)  |
	       buf[7];
}

/*
** pack() -- store data dictated by the format string in the buffer
**
**   bits |signed   unsigned   float   string
**   -----+----------------------------------
**      8 |   c        C
**     16 |   h        H         f
**     32 |   l        L         d
**     64 |   q        Q         g
**      - |                               s
**
**  (16-bit unsigned length is automatically prepended to strings)
*/

unsigned int pack(unsigned char *buf, const char *format, ...) {
	va_list ap;

	int8 c;              // 8-bit
	uint8 C;

	int16 h;                      // 16-bit
	uint16 H;

	int32 l;                 // 32-bit
	uint32 L;

	int64 q;            // 64-bit
	uint64 Q;

	float f;                    // floats
	double d;
	long double g;
	unsigned long long int fhold;

	char *s;                    // strings
	uint16 len;

	unsigned int size = 0;

	uint16 maxstrlen=0;

	unsigned char *bufStart = buf;

	va_start(ap, format);

	for(; *format != '\0'; format++) {
		switch(*format) {
		case 'c': // 8-bit
			size += 1;
			c = (int8)va_arg(ap, int); // promoted
			*buf++ = (unsigned char)c;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("pack int8 = %d [%X] c = %d [%X] buf pos = %lu\n",*(buf-1),*(buf-1),c,c,(unsigned long)(buf - bufStart));
			break;

		case 'C': // 8-bit unsigned
			size += 1;
			C = (uint8)va_arg(ap, unsigned int); // promoted
			*buf++ = (unsigned char)C;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("pack uint8 = %u [%X] C = %u [%X] buf pos = %lu\n",*(buf-1),*(buf-1),C,C,(unsigned long)(buf - bufStart));
			break;

		case 'h': // 16-bit
			size += 2;
			h = (int16)va_arg(ap, int);
			packi16(buf, h);
			buf += 2;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("pack int16 = %d [%X] h = %d [%X] buf pos = %lu\n",*(buf-2),*(buf-2),h,h,(unsigned long)(buf - bufStart));
			break;

		case 'H': // 16-bit unsigned
			size += 2;
			H = (uint16)va_arg(ap, unsigned int);
			packi16(buf, H);
			buf += 2;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("pack uint16 = %u [%X] H = %u [%X] buf pos = %lu\n",*(buf-2),*(buf-2),H,H,(unsigned long)(buf - bufStart));
			break;

		case 'l': // 32-bit
			size += 4;
			l = va_arg(ap, int32);
			packi32(buf, l);
			buf += 4;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("pack int32 = %d [%X] l = %d [%X] buf pos = %lu\n",*(buf-4),*(buf-4),l,l,(unsigned long)(buf - bufStart));
			break;

		case 'L': // 32-bit unsigned
			size += 4;
			L = va_arg(ap, uint32);
			packi32(buf, L);
			buf += 4;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("pack uint32 = %u [%X] L = %u [%X] buf pos = %lu\n",*(buf-4),*(buf-4),L,L,(unsigned long)(buf - bufStart));
			break;

		case 'q': // 64-bit
			size += 8;
			q = va_arg(ap, int64);
			packi64(buf, q);
			buf += 8;

			//if(SystemFlags::VERBOSE_MODE_ENABLED) printf("pack int64 = " MG_I64_SPECIFIER " [%X] q = " MG_I64_SPECIFIER " [%lX] buf pos = %lu\n",(int64)*(buf-8),*(buf-8),q,q,(unsigned long)(buf - bufStart));
			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("pack int64 = " MG_I64_SPECIFIER " [%X] q = " MG_I64_SPECIFIER " buf pos = %lu\n",(int64)*(buf-8),*(buf-8),q,(unsigned long)(buf - bufStart));
			break;

		case 'Q': // 64-bit unsigned
			size += 8;
			Q = va_arg(ap, uint64);
			packi64(buf, Q);
			buf += 8;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("pack uint64 = " MG_I64U_SPECIFIER " [%X] Q = " MG_I64U_SPECIFIER " buf pos = %lu\n",(uint64)*(buf-8),*(buf-8),Q,(unsigned long)(buf - bufStart));
			break;

		case 'f': // float-16
			size += 2;
			f = (float)va_arg(ap, double); // promoted
			fhold = pack754_16(f); // convert to IEEE 754
			packi16(buf, fhold);
			buf += 2;
			break;

		case 'd': // float-32
			size += 4;
			d = va_arg(ap, double);
			fhold = pack754_32(d); // convert to IEEE 754
			packi32(buf, fhold);
			buf += 4;
			break;

		case 'g': // float-64
			size += 8;
			g = va_arg(ap, long double);
			fhold = pack754_64(g); // convert to IEEE 754
			packi64(buf, fhold);
			buf += 8;
			break;

		case 's': // string
			s = va_arg(ap, char*);
			len = (uint16)strlen(s);
			if (maxstrlen > 0 && len < maxstrlen)
				len = maxstrlen - 1;

			size += len + 2;
			packi16(buf, len);
			buf += 2;

			memcpy(buf, s, len);
			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("pack string size = %d [%X] len = %d str [%s] buf pos = %lu\n",*(buf-2),*(buf-2),len,s,(unsigned long)(buf - bufStart));

			buf += len;
			break;

		default:
			if (isdigit(*format)) { // track max str len
				maxstrlen = maxstrlen * 10 + (*format-'0');
			}
			break;
		}

		if (!isdigit(*format)) maxstrlen = 0;

	}

	va_end(ap);

	return size;
}

/*
** unpack() -- unpack data dictated by the format string into the buffer
**
**   bits |signed   unsigned   float   string
**   -----+----------------------------------
**      8 |   c        C
**     16 |   h        H         f
**     32 |   l        L         d
**     64 |   q        Q         g
**      - |                               s
**
**  (string is extracted based on its stored length, but 's' can be
**  prepended with a max length)
*/
unsigned int unpack(unsigned char *buf, const char *format, ...) {
	va_list ap;

	int8 *c;              // 8-bit
	uint8 *C;

	int16 *h;                      // 16-bit
	uint16 *H;

	int32 *l;                 // 32-bit
	uint32 *L;

	int64 *q;            // 64-bit
	uint64 *Q;

	unsigned long long int fhold;

	char *s;
	uint16 len, maxstrlen=0, count;

	unsigned int size = 0;

	unsigned char *bufStart = buf;

	va_start(ap, format);

	for(; *format != '\0'; format++) {
		switch(*format) {
		case 'c': // 8-bit
			c = va_arg(ap, int8*);
//			if (*buf <= 0x7f) {
//				*c = *buf++;
//				size += 1;
//			} // re-sign
//			else {
//				*c = -1 - (unsigned char)(0xffu - *buf);
//			}
			*c = (int8)*buf++;
			size += 1;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("unpack int8 = %d [%X] c = %d [%X] buf pos = %lu\n",*(buf-1),*(buf-1),*c,*c,(unsigned long)(buf - bufStart));
			break;

		case 'C': // 8-bit unsigned
			C = va_arg(ap, uint8*);
			*C = (uint8)*buf++;
			size += 1;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("unpack uint8 = %u [%X] C = %u [%X] buf pos = %lu\n",*(buf-1),*(buf-1),*C,*C,(unsigned long)(buf - bufStart));
			break;

		case 'h': // 16-bit
			h = va_arg(ap, int16*);
			*h = unpacki16(buf);
			buf += 2;
			size += 2;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("unpack int16 = %d [%X] h = %d [%X] buf pos = %lu\n",*(buf-2),*(buf-2),*h,*h,(unsigned long)(buf - bufStart));
			break;

		case 'H': // 16-bit unsigned
			H = va_arg(ap, uint16*);
			*H = unpacku16(buf);
			buf += 2;
			size += 2;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("unpack uint16 = %u [%X] H = %u [%X] buf pos = %lu\n",*(buf-2),*(buf-2),*H,*H,(unsigned long)(buf - bufStart));
			break;

		case 'l': // 32-bit
			l = va_arg(ap, int32*);
			*l = unpacki32(buf);
			buf += 4;
			size += 4;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("unpack int32 = %d [%X] l = %d [%X] buf pos = %lu\n",*(buf-4),*(buf-4),*l,*l,(unsigned long)(buf - bufStart));
			break;

		case 'L': // 32-bit unsigned
			L = va_arg(ap, uint32*);
			*L = unpacku32(buf);
			buf += 4;
			size += 4;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("unpack uint32 = %u [%X] L = %u [%X] buf pos = %lu\n",*(buf-4),*(buf-4),*L,*L,(unsigned long)(buf - bufStart));
			break;

		case 'q': // 64-bit
			q = va_arg(ap, int64*);
			*q = unpacki64(buf);
			buf += 8;
			size += 8;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("unpack int64 = " MG_I64_SPECIFIER " [%X] q = " MG_I64_SPECIFIER " buf pos = %lu\n",(int64)*(buf-8),*(buf-8),*q,(unsigned long)(buf - bufStart));
			break;

		case 'Q': // 64-bit unsigned
			Q = va_arg(ap, uint64*);
			*Q = unpacku64(buf);
			buf += 8;
			size += 8;

			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("unpack uint64 = " MG_I64U_SPECIFIER " [%X] Q = " MG_I64U_SPECIFIER " buf pos = %lu\n",(uint64)*(buf-8),*(buf-8),*Q,(unsigned long)(buf - bufStart));
			break;

		case 'f': // float
			{
			float *f = va_arg(ap, float*);
			fhold = unpacku16(buf);
			*f = unpack754_16(fhold);
			buf += 2;
			size += 2;
			}
			break;

		case 'd': // float-32
			{
			double *d = va_arg(ap, double*);
			fhold = unpacku32(buf);
			*d = unpack754_32(fhold);
			buf += 4;
			size += 4;
			}
			break;

		case 'g': // float-64
			{
			long double *g = va_arg(ap, long double*);
			fhold = unpacku64(buf);
			*g = unpack754_64(fhold);
			buf += 8;
			size += 8;
			}
			break;

		case 's': // string
			s = va_arg(ap, char*);
			len = unpacku16(buf);
			buf += 2;
			if (maxstrlen > 0 && len > maxstrlen)
				count = maxstrlen - 1;
			else
				count = len;

			memcpy(s, buf, count);
			s[count] = '\0';
			if(SystemFlags::VERBOSE_MODE_ENABLED) printf("unpack string size = %d [%X] count = %d len = %d str [%s] buf pos = %lu\n",*(buf-2),*(buf-2),count,len,s,(unsigned long)(buf - bufStart));

			buf += len;
			size += len;
			break;

		default:
			if (isdigit(*format)) { // track max str len
				maxstrlen = maxstrlen * 10 + (*format-'0');
			}
			break;
		}

		if (!isdigit(*format)) maxstrlen = 0;
	}

	va_end(ap);

	return size;
}

#pragma pack(pop)

}}
