#include "protocol.h"
#include "donkey.h"
#include "sPacket.h"
#include "misc.h"
#include "cygwin.h"
#include "sTag.h"
#include "opcodes.h"

// Tag handling
unsigned sTag::count = 0;
sTag::sTag () {{{
	count++;
	loc  	  = NULL;
	name	  = NULL;
	type	  = NULL;
	codec	  = NULL;
	desc 	  = NULL;
	artist	  = NULL;
	title	  = NULL;
	album	  = NULL;
	length	  = NULL;
	bitrate	  = NULL;
	temp	  = NULL;
	corrupt_dat = NULL;
	corrupt_len = 0;
	transfered = 0;
	size  	  = 0;
	client_udp= 4672;
	version   = 0;
	users     = 0;
	files     = 0;
	port      = 0;
	gap_start = 0;
	gap_end   = 0;
	pref	  = 0;
	prio	  = 0;
	compress  = 0;
	ip.s_addr = 0;
	ul_prio   = 0;
	dl_prio   = 0;
	status    = 0;
	users_max = 0;
	fail	  = 0;
	last_ping = 0;
	}}}
sTag::~sTag() {{{
	count--;
	if (loc     != NULL) Free2 (loc  	); 
	if (name    != NULL) Free2 (name 	); 
	if (type    != NULL) Free2 (type 	);
	if (codec   != NULL) Free2 (codec	);
	if (desc    != NULL) Free2 (desc 	);
	if (artist  != NULL) Free2 (artist 	);
	if (title   != NULL) Free2 (title  	);
	if (album   != NULL) Free2 (album  	);
	if (length  != NULL) Free2 (length 	);
	if (bitrate != NULL) Free2 (bitrate	);
	if (temp    != NULL) Free2 (temp	);
	if (corrupt_dat != NULL) free (corrupt_dat);
	gap_map.clear();
	}}}

static void replace_str (char **dst, char **src) {{{
	if ((*dst) != NULL) free (*dst);
	*dst = *src;
	*src = NULL;
	}}}
sTag *parse_tag (unsigned char *data, unsigned *len) {{{
	ASSERT (NULL != data);
	ASSERT (NULL != len );
	if (data == NULL) {{{
		*len = 0;
		return NULL;
		}}}
	if (*len == 0) return NULL;
	unsigned char *DATA = data;
	int   LEN  = *len;
	unsigned char *start = data;
	unsigned count = GET_U4(&data, len);
	if (count == 0) return NULL;
	class sTag *tag = new sTag();
	char *key = NULL;
	char     *val_str = NULL;
	do {
		unsigned  type = GET_U1 (&data, len);
		key  = GET_S2_ (&data, len);
		uint32_t  val_int = 0;
		tHash	  hash2;
		if (*len == 0) break;
		switch (type) {
			case STRING  : 	val_str = GET_S2_  (&data, len); 	break;
			case INTEGER : 	val_int = GET_U4   (&data, len); 	break;
			case HASH    : 	GET_HASH (&data, len, hash2);		break;
			case tagFLOAT: 	GET_U4   (&data, len); 			break;
			case tagBLOB :  val_int = GET_U4   (&data, len);
					val_str = reinterpret_cast<char*>(MALLOC (val_int));
					memcpy(val_str, data, val_int);
					data += val_int;
					*len -= val_int;
					break;
			default	     : 	printf("Tag Type ? 0x%02x\n", type);
					dump (DATA, LEN);
					delete tag;
					unsigned char *ende = data;
					*len = (ende - start);
					if (key     != NULL) Free2 (key);     key     = NULL;
					if (val_str != NULL) Free2 (val_str); val_str = NULL;
					return NULL;
			}
		if (strlen(key) == 1) {{{
			unsigned char short_tag = key[0];
			switch(short_tag) {
				case 0x01: replace_str (&tag->name , &val_str);		break; // tName
				case 0x03: replace_str (&tag->type , &val_str); 	break; // tType
				case 0x04: replace_str (&tag->codec, &val_str); 	break; // tCodec
				case 0x0B: replace_str (&tag->desc , &val_str); 	break; // tDescription
				case 0x12: replace_str (&tag->temp , &val_str);		break; // tTempfile
				case 0x02: tag->size	   = val_int;		 	break; // tSize
				case 0x08: tag->transfered = val_int;  			break; // tTransfered
				case 0x09: tag->gap_start  = val_int;			break; // tGap_start
				case 0x0A: tag->gap_end    = val_int;			break; // tGap_end
				case 0x0D: tag->fail       = val_int; 			break; // tFail
				case 0x0E: tag->pref	   = val_int;			break; // tPref
				case 0x0F: tag->port	   = (val_int);			break; // tPort
				case 0x11: tag->version    = val_int;			break; // tVersion
				case 0x13: tag->prio	   = val_int;			break; // tPrio
				case 0x14: tag->status     = val_int;			break; // tStatus
				case 0x17: tag->ul_prio    = val_int; 			break;
				case 0x18: tag->dl_prio    = val_int;			break; // tPrio_dl
				case 0x20: tag->compress   = val_int;			break; // tCompress
				case 0x21: tag->client_udp =(val_int);			break; // tUDP_client_port
				case 0x05:    						break; // tCollection (deprecated)
				case 0x06:    						break; // tPart_path
				case 0x07:    						break; // tPart_hash
				case 0x0C:    						break; // tPing
				case 0x10:    						break; // tIP
				case 0x15:    						break; // tSources
				case 0x16:    						break; // tQTime
				case 0x22:    						break; // UDP Version
				case 0x23:    						break; // source exchange
				case 0x24:    						break; // comment file
				case 0x25:    						break; // extended request
				case 0x26:						break; // cDonkey signature
				case 0x50:						break;
				case 0x51:						break;
				case 0x52:						break;
				case 0x53:						break; // only 0 ?
				case 0x55:						break; // eMule mod info ?
				case 0x83:						break; // eMule mod info
				case 0x82:						break; // eMule mod info
				case 0x94:						break; // eMule mod info
				case 0x66:						break; // eMule ??
				case 0x67: /* FluX (R6) */				break;
				case 0x99:						break; // eMule ??
				case 'v' :    					        break;	// eMule ?
				case 'w' :    						break;	// eMule ?
				case 'x' :    						break;	// eMule ?
				case 'y' :    						break;	// eMule ?
				case 0x80:						break; // ??
				case 0x86: tag->last_ping = val_int;			break; // server.met
				case 0x87: tag->users_max = val_int;			break; // Server.met
				case 0x88: /* 78 */					break;
						   
				default  : printf("KEY 0x%02X (%u) ", short_tag, type);
					   if (type == 2) printf ("Val %s\n", val_str);
					   if (type == 3) printf ("Val %i\n", val_int);
					   break;
				}
			}}}
		else {{{
			if (0 == strcmp (key, FT_LONG_ARTIST	)) { replace_str (&tag->artist , &val_str); 	} else
			if (0 == strcmp (key, FT_LONG_TITLE	)) { replace_str (&tag->title  , &val_str); 	} else
			if (0 == strcmp (key, FT_LONG_ALBUM	)) { replace_str (&tag->album  , &val_str); 	} else
			if (0 == strcmp (key, FT_LONG_LENGTH	)) { replace_str (&tag->length , &val_str); 	} else
			if (0 == strcmp (key, FT_LONG_BITRATE	)) { replace_str (&tag->bitrate, &val_str); 	} else
			if (0 == strcmp (key, FT_LONG_CODEC	)) { replace_str (&tag->codec  , &val_str); 	} else
			if (0 == strcmp (key, CT_LONG_LOC	)) { replace_str (&tag->loc    , &val_str);	} else
			if (0 == strcmp (key, CT_LONG_IP	)) { tag->ip.s_addr = val_int;			} else
			if (0 == strcmp (key, CT_LONG_PORT	)) { tag->port      = (val_int);		} else
			if (0 == strcmp (key, ST_LONG_USERS  	)) { tag->users	    = val_int; 			} else	
			if (0 == strcmp (key, ST_LONG_FILES 	)) { tag->files	    = val_int; 		  	} else
			if (0 == strcmp (key, "corrupt"		)) { 
				tag->corrupt_dat = reinterpret_cast<uint8_t*>(val_str);
				tag->corrupt_len = val_int;
				val_str = NULL;
				} else
			if (key[0] == FT_GAPSTART) {{{
				unsigned idx = atoi (key + 1);
				tGap_map::iterator i = tag->gap_map.find(idx);
				if (i == tag->gap_map.end()) {
					struct sGap gap;
					gap.von = val_int;
					gap.bis = 0;
					gap.val = 0;
					tag->gap_map[idx] = gap;
					}
				else	i->second.von = val_int;
				}}} else
			if (key[0] == FT_GAPEND  ) {{{
				unsigned idx = atoi (key + 1);
				tGap_map::iterator i = tag->gap_map.find(idx);
				if (i == tag->gap_map.end()) {
					struct sGap gap;
					gap.von = 0;
					gap.bis = val_int;
					gap.val = 0;
					tag->gap_map[idx] = gap;
					}
				else	i->second.bis = val_int;
				}}} else
				{
				printf("KEY %s (%u) ", key, type);
				if (type == 2) printf ("Val %s\n", val_str);
				if (type == 3) printf ("Val %i\n", val_int);
				}
			}}}
		if (key     != NULL) Free2 (key);     key     = NULL;
		if (val_str != NULL) Free2 (val_str); val_str = NULL;
		} while (--count > 0 && *len > 0);
	if (key     != NULL) Free2 (key);     key     = NULL;
	if (val_str != NULL) Free2 (val_str); val_str = NULL;
	unsigned char *ende = data;
	*len = ende - start;
	return tag;
	}}}

void dump (unsigned char *data, size_t len) {{{
	char digit[17]="0123456789ABCDEF";
	char out[80];
	memset(out,' ',80);
	out[48]=' ';
	out[65]='\n';
	out[66]=0;
	for (int i = 0; len-->0; i++) {
		unsigned ii = i & 15;
		unsigned char tmp = *(data++);
		out[3*ii+0] = digit[15 & (tmp>>4)];
		out[3*ii+1] = digit[15 & (tmp   )];
		if (tmp>='A' && tmp<='Z') out[49+ii]=tmp; else
		if (tmp>='a' && tmp<='z') out[49+ii]=tmp; else
		if (tmp>='0' && tmp<='9') out[49+ii]=tmp; else
					  out[49+ii]=' ';
		if (15 == (i % 16)) {
			printf("%s",out);
			memset(out,' ',80);
			out[48]=' ';
			out[65]='\n';
			out[66]=0;
			}
		}
	printf("%s",out);
	}}}

bool add_intTag   (unsigned char **buf, size_t *len, const char *key, size_t  val) {{{
	ASSERT(buf != NULL);
	ASSERT(*buf != NULL);
	ASSERT(key != NULL);
	ASSERT(key[0] != 0);
	ASSERT(1 + 2 + strlen(key) + 4 <= *len);
	ADD_U1   (buf, len, 3);
	ADD_S2   (buf, len, key);
	ADD_U4   (buf, len, val);
	return true;
	}}}
bool add_strTag   (unsigned char **buf, size_t *len, const char *key, const char* val) {{{
	ASSERT(1 + 2 + strlen(key) + 2 + strlen(val) <= *len);
	ADD_U1   (buf, len, 2);
	ADD_S2   (buf, len, key);
	ADD_S2   (buf, len, val);
	return true;
	}}}
bool add_intTag   (unsigned char **buf, size_t *len, uint8_t     key, size_t  val) {{{
	ASSERT(8 <= *len);
	ADD_U1   (buf, len, 3);
	ADD_U2   (buf, len, 1);
	ADD_U1   (buf, len, key);
	ADD_U4   (buf, len, val);
	return true;
	}}}
bool add_strTag   (unsigned char **buf, size_t *len, uint8_t     key, const char* val) {{{
	assert(val != NULL);
	ASSERT(4 + 2 + strlen(val) <= *len);
	ADD_U1   (buf, len, 2);
	ADD_U2   (buf, len, 1);
	ADD_U1   (buf, len, key);
	ADD_S2   (buf, len, val);
	return true;
	}}}
bool add_bobTag	  (unsigned char **buf, size_t *len, const char *key, const char* val, size_t count) {{{
	assert(val != NULL);
	ASSERT(1+2+strlen(key)+4+count <= *len);
	ADD_U1   (buf, len, tagBLOB);
	ADD_S2   (buf, len, key);
	ADD_U4   (buf, len, count);
	memcpy(*buf, val, count);
	*buf += count;
	*len -= count;
	return true;
	}}}
bool add_strCmp   (unsigned char **buf, size_t *len, const char *key) {{{
	ASSERT(1 + 2 + strlen(key) <= *len);
	ADD_U1   (buf, len, 1);
	ADD_S2   (buf, len, key);
	return true;
	}}}
// {{ Raw Packet Handling
uint32_t GET_U4   (unsigned char **buf, size_t *len) {{{
	if (*len < 4) { *len = 0; return 0; }
	uint32_t ret = ctol32(*(reinterpret_cast<uint32_t*>(*buf)));
	(*len) -= 4;
	(*buf) += 4;
	return ret;
	}}}
uint16_t GET_U2   (unsigned char **buf, size_t *len) {{{
	if (*len < 2) { *len = 0; return 0; }
	uint16_t ret = ctol16(*(reinterpret_cast<uint16_t*>(*buf)));
	(*len) -= 2;
	(*buf) += 2;
	return ret;
	}}}
uint8_t  GET_U1   (unsigned char **buf, size_t *len) {{{
	if (*len < 1) { *len = 0; return 0; }
	uint8_t ret = (**buf);
	(*len) -= 1;
	(*buf) += 1;
	return ret;
	}}}
char *   GET_S2_  (unsigned char **buf, size_t *len) {{{
	if (*len < 2  ) { *len = 0; return NULL; }
	unsigned LEN = GET_U2 (buf, len);
	if (*len < LEN) { *len = 0; return NULL; }
	char *ret = reinterpret_cast<char*>(MALLOC (LEN+1));
	ASSERT(ret != NULL);
	memcpy(ret, *buf, LEN);
	ret[LEN] = 0;
	(*len) -= LEN;
	(*buf) += LEN;
	return ret;
	}}}
bool     GET_HASH (unsigned char **buf, size_t *len, uint8_t *dst) {{{
	if (*len < HASH_LEN) {
		bzero (dst, HASH_LEN);
		return false;
		}
	memcpy (dst, *buf, HASH_LEN);
	*len -= HASH_LEN;
	*buf += HASH_LEN;
	return true;
	}}}
bool     ADD_F4   (unsigned char **buf, size_t *len, double val) {{{
	ASSERT(*len >= 4);
	if (*len < 4) { *len = 0; return false; }
	*(reinterpret_cast<float*>(*buf)) = val;
	(*len) -= 4;
	(*buf) += 4;
	return true;
	}}}
bool     ADD_U4   (unsigned char **buf, size_t *len, size_t   val) {{{
	ASSERT(*len >= 4);
	if (*len < 4) { *len = 0; return false; }
	*(reinterpret_cast<uint32_t*>(*buf)) = ctol32(val);
	(*len) -= 4;
	(*buf) += 4;
	return true;
	}}}
bool     ADD_U2   (unsigned char **buf, size_t *len, size_t   val) {{{
	ASSERT(val < 65536);
	ASSERT(*len >= 2);
	if (*len < 2) { *len = 0; return false; }
	*(reinterpret_cast<uint16_t*>(*buf)) = ctol16(val);
	(*len) -= 2;
	(*buf) += 2;
	return true;
	}}}
bool     ADD_U1   (unsigned char **buf, size_t *len, uint8_t  val) {{{
	ASSERT(buf != NULL);
	ASSERT(*buf != NULL);
	ASSERT(*len >= 1);
	if (*len < 1) { *len = 0; return false; }
	*(reinterpret_cast<uint8_t *>(*buf)) = val;
	(*len) -= 1;
	(*buf) += 1;
	return true;
	}}}
bool     ADD_S2   (unsigned char **buf, size_t *len, const char *val) {{{
	size_t cnt = strlen(val);
	ASSERT (*len >= 2 + cnt);
	ASSERT (cnt < 0x10000);
	ADD_U2 (buf, len, cnt);
	memcpy (*buf, val , cnt);
	(*buf) += cnt;
	(*len) -= cnt;
	return true;
	}}}
bool     ADD_HASH (unsigned char **buf, size_t *len, const uint8_t *hash ) {{{
	ASSERT(*len >= HASH_LEN);
	if (*len < HASH_LEN) { *len = 0; return false; }
	memcpy(*buf, hash, HASH_LEN);
	(*len) -= HASH_LEN;
	(*buf) += HASH_LEN;
	return true;
	}}}
// }}}

