/*
This Work is under GPL provided with this work.

This work is based on the java implementation of the Kademlia protocol.
Kademlia: Peer-to-peer routing based on the XOR metric
Copyright (C) 2002  Petar Maymounkov [petar@post.harvard.edu]
http://kademlia.scs.cs.nyu.edu
and
This work is bassed on translation from Barry Dunne to C++
Copyright (C)2003 Barry Dunne (http://www.emule-project.net)
*/

#include "cygwin.h"
#include "opcodes.h"
#include "kademlia.h"
#include "cHash.h"
#include "cPeer.h"
#include "cBucket.h"
#include "cSearch.h"
#include "cZone.h"
#include "sTag.h"

class TagList;
class CTag;

extern class cZone	 overnet;
extern class cKademlia  *kademlia;

cSearch:: cSearch() {{{
	m_created = time(NULL);
	m_searchTerms = NULL;
	}}}
cSearch::~cSearch() {{{
	if (m_searchTerms != NULL) free(m_searchTerms);
	cPeerList::const_iterator it;
	for (it = m_delete.begin(); it != m_delete.end(); it++) delete *it;
	m_responded.clear();
	m_possible.clear();
	}}}
void cSearch::go(void) {{{ // Start with a lot of possible contacts, this is a fallback in case search stalls due to dead contacts
	if (m_possible.empty()) overnet.getClosestTo(m_target, 50, &m_possible);
	size_t count = 3;	// Take top 3 possible
	if (m_possible.size() < count) count = m_possible.size();
	cPeerMap::iterator it;
	for (size_t i=0; i<count; i++) {
		it = m_possible.begin();
		cPeer *c = it->second;
		m_tried[it->first] = c;			// Move to tried
		m_possible.erase(it);			// and remove it from possible
		sendFindValue(m_target, c->ip, c->udp);	// Send request
		}
	}}}
void cSearch::jumpStart(void) {{{
	if (m_possible.empty()) return;
	if (!m_responded.empty()) {			// Remove any obsolete possible contacts
		cHash best = m_responded.begin()->first;
		cPeerMap::iterator it = m_possible.begin();
		while (it != m_possible.end()) {
			cPeerMap::iterator next = it;
			next++;
			if (it->first < best) m_possible.erase(it);
			it = next;
			}
		}
	if (m_possible.empty()) return;
	class cPeer *c = m_possible.begin()->second;
	m_tried[m_possible.begin()->first] = c;		// add to tried
	m_possible.erase(m_possible.begin());		// delete from possible
	sendFindValue(m_target, c->ip, c->udp);		// Send request
	}}}
void cSearch::sendFindValue       (const uint8_t *target, uint32_t ip, uint16_t port) {{{
	struct in_addr IP;
	IP.s_addr = ip;
	size_t LEN, len = LEN = 19;
	unsigned char BUF[19], *buf = BUF;
	ADD_U1   (&buf, &len, OP_EDONKEYHEADER);
	ADD_U1   (&buf, &len, OVERNET_REQUEST);
	ADD_U1   (&buf, &len, ((m_type == NODE)?OVERNET_FIND_NODE:OVERNET_FIND_VALUE));
	ADD_HASH (&buf, &len, target);
	if (kademlia != NULL) kademlia->send_Raw(IP, port, BUF, LEN);
	}}}
void cSearch::processResponse     (const uint8_t *target, uint32_t fromIP, uint16_t fromPort, cPeerList *results) {{{
	cPeerList::iterator response; // Remember the contacts to be deleted when finished
	for (response = results->begin(); response != results->end(); response++)
		if (m_type == NODE) {{{ // Not interested in responses for FIND_NODE, will be added to contacts by udp listener
			return;
			}}}
	cPeerMap::const_iterator tried;
	bool returnedCloser = false;;
	for (tried = m_tried.begin(); tried != m_tried.end(); tried++) {    // Find the person who sent this
		cHash fromDistance = tried->first;
		cPeer *from = tried->second;
		m_responded[fromDistance] = from;   // Add to list of people who responded
		if (from->ip  != fromIP  ) continue;
		if (from->udp != fromPort) continue;
		returnedCloser = false;
		for (response = results->begin(); response != results->end(); response++) { // Loop through their responses
			cPeer *c = *response;
			if (c->type != 0) continue;    // Only interested in type 0
			cHash distance = *(c->get_Hash());
			xor_128(distance.hash, target);
			if (m_tried.count(distance) > 0)    continue;   // Ignore if already tried
			if (distance >= fromDistance) break;    // If response is closer than 'from'
			returnedCloser = true;
			bool top = false;   // If in top 3 responses
			if (m_best.size() < 3) top = true;
			else {{{
				cPeerMap::iterator it = m_best.end();
				it--;
				if (distance < it->first) {
					m_best.erase(it);
					m_best[distance] = c;
					top = true;
					}
				}}} 
			if (top) {
				m_tried[distance] = c;  // Add to tried
				sendFindValue(m_target, c->ip, c->udp);    // Send request
				}
			else m_possible[distance] = c;  // Add to possible
			break;
			}
		if (!returnedCloser)    // Ask 'from' for the file if closest
			if(kademlia!=NULL) {
				struct in_addr ip;
				ip.s_addr = from->ip;
				kademlia->send_Raw(ip, from->udp, m_searchTerms, m_lenSearchTerms);
				class sFile *file = forHash(m_target);
				if (file != NULL) {
					kademlia->send_Publish(file->hash, file->size, file->Name());
					}
				}
		}
	}}}

