#include "cSocket.h"
#include "misc.h"
#include "sTag.h"
#include "sGui.h"
#include "sFile.h"
#include "donkey.h"
#include "protocol.h"
#include "cygwin.h"
#include "sServer.h"
#include "sSource.h"
#include "cServer_udp.h"
#include "opcodes.h"

int  cServer_udp::req_file_ping     (in_addr ip, uint16_t port, uint8_t *file_hash) {{{ // (0x90)
	peer_ip.s_addr = ip.s_addr;
	peer_port =port;
	if (logSend('>', this, OP_REASKFILEPING, true)) printf("%32s\n", hash_bin2hex(file_hash));
	unsigned len, LEN = len = 18;
	unsigned char *buf, *BUF = buf = reinterpret_cast<unsigned char*>(alloca(LEN));
	ADD_U1   (&buf, &len, OP_EMULEPROT);
	ADD_U1   (&buf, &len, OP_REASKFILEPING);
	ADD_HASH (&buf, &len, file_hash);
	ASSERT(len == 0);
	Write(BUF, LEN);
	return 1;
	}}}
bool cServer_udp::UDP_next_ping     (                                   ) {{{ // (0x96)
	uint32_t quest;
	if (!nextPing (&peer_ip, &peer_port, &quest)) return false;
	peer_port += 4;
	if (logSend('>', this, OP_GLOBSERVSTATREQ, false));
	unsigned len, LEN = len = 6;
	unsigned char *buf, *BUF = buf = reinterpret_cast<unsigned char*>(alloca(LEN));
	ADD_U1 (&buf, &len, OP_EDONKEYHEADER);
	ADD_U1 (&buf, &len, OP_GLOBSERVSTATREQ);
	ADD_U4 (&buf, &len, quest);
	ASSERT(len == 0);
	ASSERT(fd < 10000);
	ASSERT (16 != (mask&16));
	Write(BUF, LEN);
	return true;
	}}}
bool cServer_udp::UDP_search4gui    (const sGui *gui                    ) {{{ // (0x98)
	struct in_addr ip;
	if (!nextServer (&ip, &peer_port)) return false;
        peer_ip    = ip;
        peer_port += 4;
	ASSERT(peer_port != 0);
	printf("Extending search to %s %u\n", inet_ntoa(ip), peer_port);
	ASSERT (gui!=NULL);
	if (logSend('>', this, OP_GLOBSEARCHREQ, false));
	unsigned len, LEN = len = 2 + gui->searchLen();
	unsigned char *buf, *BUF = buf = reinterpret_cast<unsigned char*>(alloca(LEN));
	ADD_U1 (&buf, &len, OP_EDONKEYHEADER);
	ADD_U1 (&buf, &len, OP_GLOBSEARCHREQ);
	memcpy ( buf, gui->searchTree(), len);
	Write(BUF, LEN);
	return true;
	}}}
bool cServer_udp::UDP_req_sources   (in_addr ip,uint16_t port, const uint8_t *Hash) {{{ // (0x9A)
	ASSERT(Hash != NULL);
	if (0 == (ip.s_addr & 0xFF000000)) return false;
	if (0 == (ip.s_addr & 0x000000FF)) return false;
	class sFile *file = forHash(Hash);
	if (file == NULL) return false;
	ASSERT(file->status != ALL_DONE && !file->Pause());
	peer_port = port + 4;
        peer_ip   = ip;
	if (port == 0) return false;
	if (!IP_is_OK (ip)) return false;
	if (logSend('>', this, OP_GLOBGETSOURCES, true)) printf("%32s\n", hash_bin2hex(Hash));
	actionServer    (ip, port);
	unsigned len, LEN = len = 18;
	unsigned char *buf, *BUF = buf = reinterpret_cast<unsigned char*>(alloca(LEN));
	ADD_U1   (&buf, &len, OP_EDONKEYHEADER);
	ADD_U1   (&buf, &len, OP_GLOBGETSOURCES);
	ADD_HASH (&buf, &len, Hash);
	ASSERT(len == 0);
	Write(BUF, LEN);
	return true;
	}}}
bool cServer_udp::UDP_req_callback  (uint32_t idx) {{{ // (0x9C)
	if (logSend('>', this, OP_GLOBCALLBACKREQ, true)) printf("%u\n", idx);	
	unsigned len, LEN = len = 12;
	unsigned char *buf, *BUF = buf = reinterpret_cast<unsigned char*>(alloca(LEN));
	ADD_U1 (&buf, &len, OP_EDONKEYHEADER	);
	ADD_U1 (&buf, &len, OP_GLOBCALLBACKREQ	);
	ADD_U4 (&buf, &len, cSocket::getLocalExternalIP().s_addr); // should check peer_ip here
	ADD_U2 (&buf, &len, pref.ports.client	);
	ADD_U4 (&buf, &len, idx			);
	ASSERT (len == 0);
	Write(BUF, LEN);
	return true;
	}}}
bool cServer_udp::UDP_next_info     (in_addr ip, uint16_t port          ) {{{ // (0xA2)
	peer_ip    = ip;
	peer_port  = port + 4;
	if (logSend('>', this, OP_SERVER_DESC_REQ, false));
	unsigned len, LEN = len = 2;
	unsigned char *buf, *BUF = buf = reinterpret_cast<unsigned char*>(alloca(LEN));
	ADD_U1   (&buf, &len, OP_EDONKEYHEADER);
	ADD_U1   (&buf, &len, OP_SERVER_DESC_REQ);
	ASSERT(len == 0);
	Write(BUF, LEN);
	return true;
	}}}
bool cServer_udp::req_server_list   (struct in_addr ip, uint16_t port   ) {{{ // (0xA4)
	peer_port = port + 4;
	peer_ip   = ip;
	if (logSend('>', this, OP_SERVER_LIST_REQ, false));
	unsigned len, LEN = len = 2;
	unsigned char *buf, *BUF = buf = reinterpret_cast<unsigned char*>(alloca(LEN));
	ADD_U1   (&buf, &len, OP_EDONKEYHEADER);
	ADD_U1   (&buf, &len, OP_SERVER_LIST_REQ);
	ASSERT(len == 0);
	Write(BUF, LEN);
	return true;
	}}}
bool cServer_udp::res_ping          (unsigned query, unsigned user, unsigned files) {{{
	if (logSend('>', this, OP_GLOBSERVSTATRES, true)) printf("%5u - %5u\n", user, files);
	size_t len, LEN = len = 14;
	unsigned char *buf, *BUF = buf = reinterpret_cast<unsigned char*>(alloca(LEN));
	ADD_U1(&buf, &len, OP_EDONKEYHEADER);
	ADD_U1(&buf, &len, OP_GLOBSERVSTATRES);
	ADD_U4(&buf, &len, query);
	ADD_U4(&buf, &len, user);
	ADD_U4(&buf, &len, files);
	ASSERT(len == 0);
	Write(BUF, LEN);
	return true;
	}}}

// Client-Server UDP handling (0x97) ... (0x9B)
int cServer_udp::RES_rang       (unsigned char *data, size_t len) {{{ // (0x91)
	struct eSources *info = Source_ip(peer_ip);
	if (info == NULL) return 0;
	setSources_version (peer_ip, info->version | 0x01000000);
	unsigned rank = GET_U2(&data, &len);
	if (rank > 0) setSource_timeout(peer_ip, currentTime +  5 * 60); 
	else	{
		setSource_timeout(peer_ip, currentTime - 10);
		setSources_version (peer_ip, info->version & 0xFEFFFFFF);
		}
	if (logSend('<', this, 0x91, true)) printf("%u\n", rank);
	return 1;
	}}}
int cServer_udp::RES_not_found  (unsigned char *    , size_t    ) {{{ // (0x92)
	struct eSources *info = Source_ip(peer_ip);
	if (logSend('<', this, 0x92, false));
	if (info == NULL) return 0;
	setSources_version (peer_ip, info->version | 0x01000000);
	setSource_timeout(peer_ip, currentTime - 10);
	if (info == NULL) return 0;
	FILE_LIST {
		if (File_info_get(info->hash,akt->hash,akt->size, NULL)) {
			File_info_del   (info->hash,akt->hash);
			return 1;
			}
		}
	return 1;
	}}}
int cServer_udp::RES_queue_full (unsigned char *    , size_t    ) {{{ // (0x93)
	if (logSend('<', this, 0x93, false));
	struct eSources *info = Source_ip(peer_ip);
	if (info == NULL) return 0;
	setSources_version (peer_ip, info->version | 0x01000000);
	setSource_timeout(peer_ip, currentTime + 5 * 60);
	return 1;
	}}}
int cServer_udp::REQ_ping       (unsigned char *data, size_t len) {{{ // (0x96)
	if (logSend('<', this, 0x96, false));
	int cnt = sServer::count;
	uint32_t query = GET_U4(&data, &len);
	res_ping (query,cnt, cnt*7);
	return 1;
	}}}
int cServer_udp::RES_ping       (unsigned char *data, size_t len) {{{ // (0x97)
	if (logSend('<', this, 0x97, false));
	if (len != 12) return 0;
			 GET_U4(&data, &len);
	uint32_t user  = GET_U4(&data, &len);
	uint32_t files = GET_U4(&data, &len);
	saveServer(peer_ip, peer_port - 4, files, user, false);
	if (!pongServer(peer_ip)) return 1;
	struct eServers *s = Server_ip (peer_ip);
	if (s == NULL) return 1;
	if (s->name[0] == 0 && s->desc[0] == 0) {
		UDP_next_info   (s->ip, s->port);
		req_server_list (s->ip, s->port);
		}
	return 1;
	}}}
int cServer_udp::RES_sources    (unsigned char *data, size_t len) {{{ // (0x9B)
	if (logSend('<', this, 0x9B, false));
	if (len <= 17) return 0;
	tHash hash;
	GET_HASH(&data, &len, hash);
	unsigned cnt = GET_U1(&data, &len);
	log (1,"Response for (%3u) Sources for %32s from (%s:%i)\n", cnt, hash_bin2hex(hash),inet_ntoa(peer_ip), peer_port);
	while (cnt-- && len>=6) {
		struct in_addr	c_ip;
		uint16_t 	c_port;
		c_ip.s_addr = GET_U4(&data, &len);
		c_port      = GET_U2(&data, &len);
		if (htonl(c_ip.s_addr) < LOW_ID || c_port == 0) {
			UDP_req_callback  (c_ip.s_addr);
			}
		else {
			insertFiles (hash, c_ip, 0, NULL);
			addSource (c_ip, c_port, 0x00000000,true);
			}
		}
	return 1;
	}}}
int cServer_udp::REQ_callback   (unsigned char *DATA, size_t len) {{{ // (0x9C)
	if (logSend('<', this, 0x9C, false));
	if (len != 10) return 0;
	unsigned char *data = DATA;
	in_addr  ip;
	 ip.s_addr    = GET_U4 (&data, &len);
	uint16_t port = GET_U2 (&data, &len);
	uint32_t idx  = GET_U4 (&data, &len);
	SERVER_LIST {
		if (akt->my_id != idx) continue;
		akt->CMD_callback (ip, port);
		return 1;
		}
	return 0;
	}}}
int cServer_udp::RES_search     (unsigned char *DATA, size_t LEN) {{{
	unsigned char *data = DATA;
	size_t len  = LEN;
	printf("UDP Search Results\n");
	log (1,"New extended results received from %s\n", inet_ntoa(peer_ip));
	size_t glen, GLEN = glen = len + 6;
	unsigned char *gbuf, *GBUF = gbuf = reinterpret_cast<unsigned char*>(alloca(GLEN));
	ADD_U1 (&gbuf, &glen, OP_EDONKEYHEADER);
	ADD_U4 (&gbuf, &glen, GLEN - 5);
	ADD_U1 (&gbuf, &glen, CO_ED_SEARCH_RESULT);
	ASSERT(glen == len);
	memcpy (gbuf, data, len);
	GUI_LIST akt->Write(GBUF, GLEN);

	data = DATA;
	len  = LEN;
	if (LEN > 0) {
		tHash hash;     
		GET_HASH(&data, &len, hash);
		GET_U4  (&data, &len);
		GET_U2  (&data, &len);
		class sTag *tag = parse_tag       (data, &len);
		if (tag == NULL) return 0;
		printf("Found file with %i info len %s\n", len, tag->name);
		insertFiles (hash, peer_ip, len, data);
		LEN  -= len;
		data += len;
		len   = LEN;
		delete tag;
		// The Rest is List of IP:Port where the file can be found *cool* TODO ad it to the file list
		}
	return 1;
	}}}
int cServer_udp::RES_server_lst (unsigned char *DATA, size_t LEN) {{{ // (0xA1)
	if (logSend('<', this, 0xA1, false));
	unsigned char *data = DATA;
	size_t len = LEN;
	unsigned cnt = GET_U1 (&data, &len);
	if (cnt == 0) return 0;
	do {
		in_addr ip;
		ip.s_addr     = GET_U4 (&data, &len);
		uint16_t port = GET_U2 (&data, &len);
		if (port == 4665) port = 4661;	// very posible that this is the udp port
		if (htonl(ip.s_addr) >  0x00FFFFFF) saveServer (ip, port, 0, 0, false);	
		} while (--cnt > 0);
	return 1;
	}}}
int cServer_udp::RES_info       (unsigned char *DATA, size_t LEN) {{{ // (0xA3)
	if (logSend('<', this, 0xA3, false));
	if (!pongServer(peer_ip)) return 1;
	unsigned char *data = DATA;
	size_t len  = LEN;
	char *name = GET_S2_ (&data, &len);
	char *desc = GET_S2_ (&data, &len);
	log (1,"UDP Info ");
	if (name != NULL) {
		setServer_name (peer_ip, 0, name);
		log (1,"NAME: %32s ", name);
		Free2 (name);
		}
	if (desc != NULL) {
		setServer_desc (peer_ip, 0, desc);
		log (1,"DESC: %32s ", desc);
		Free2 (desc);
		}
	log (1,"\n");
	return 1;
	}}}
int cServer_udp::REQ_file_ping  (unsigned char *data, size_t in_len) {{{ // (0x90) TODO
	if (logSend('<', this, 0x9A, false));
	if (in_len != 16) return 0;
	tHash hash;
	GET_HASH(&data, &in_len, hash);
	sFile *file = forHash(hash);
	if (file == NULL) {
		unsigned len, LEN = len = 2;
		unsigned char *buf, *BUF = buf = reinterpret_cast<unsigned char*>(alloca(LEN));
		ADD_U1 (&buf, &len, OP_EDONKEYHEADER);
		ADD_U1 (&buf, &len, OP_FILENOTFOUND);
		ASSERT(len == 0);
		Write (BUF, LEN);
		return 1;
		}
	else {
		unsigned len, LEN = len = 4;
		unsigned char *buf, *BUF = buf = reinterpret_cast<unsigned char*>(alloca(LEN));
		ADD_U1 (&buf, &len, OP_EDONKEYHEADER);
		ADD_U1 (&buf, &len, OP_REASKACK);
		ADD_U2 (&buf, &len , 50);
		ASSERT(len == 0);
		Write(BUF, LEN);
		}
	return 1;
	}}}

int  cServer_udp::doRead_high(void) {{{
	unsigned char *data = reinterpret_cast<unsigned char *>(read_buf);
	size_t len = read_len;
	if (len <= 0) return 0;
	dl_left -= len;			// i know it maybe miss bites
	unsigned proto = GET_U1((&data), &len);
	unsigned code  = GET_U1((&data), &len);
	if (proto != OP_EDONKEYHEADER && proto!=0xC5) {
		printf ("UDP proto: 0x%02X  <= %15s:%5u\n", proto, inet_ntoa(peer_ip), peer_port);
		dump(read_buf, read_len);
		Read(read_buf, read_len);
		return 0;
		}
//	printf("cServer_udp::doWork(0x%02X) = %u\n", code, len);
	switch (code) {
	case 0x90 : REQ_file_ping	(data, len); break;
	case 0x91 : RES_rang		(data, len); break;
	case 0x92 : RES_not_found	(data, len); break;
	case 0x93 : RES_queue_full	(data, len); break;
	case 0x96 : REQ_ping   		(data, len); break;
	case 0x97 : RES_ping   		(data, len); break;
//	case 0x98 : printf("UDP 0x97\n"); 	// REQ_search TODO
	case 0x99 : RES_search     	(data, len); break;
	case 0x9A : break;			// REQ_source TODO	// OP_GLOBGETSOURCE
	case 0x9B : RES_sources		(data, len); break;
	case 0x9C : REQ_callback	(data, len); break;
	case 0x9E : break;			// No such client (wrong Client ID)
	case 0xA0 : break; // TODO
	case 0xA1 : RES_server_lst	(data, len); break;
	case 0xA2 : break; // TODO
	case 0xA3 : RES_info      	(data, len); break;
	default   : printf ("UDP [%15s] <= 0x%02X : %02X\n", inet_ntoa(peer_ip), code, proto);
		    dump (data, len);
		    printf("\n");
		    if (code != 0xA0) break;
//		    ASSERT(0);
		    break;
		}
	Read(read_buf, read_len);
	return 0;
	}}}

