//
// This file is part of the imule Project.
//
// Copyright (c) 2004-2011 Angel Vidal ( kry@amule.org )
// Copyright (c) 2004-2011 aMule Team ( admin@amule.org / http://www.amule.org )
// Copyright (c) 2003-2011 Barry Dunne (http://www.emule-project.net)
// Copyright (c) 2004-2011 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net )
//
// Any parts of this program derived from the xMule, lMule or eMule project,
// or contributed by third-party developers are copyrighted by their
// respective authors.
//
// 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; either version 2 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 for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
//

// Note To Mods //
/*
Please do not change anything here and release it..
There is going to be a new forum created just for the Kademlia side of the client..
If you feel there is an error or a way to improve something, please
post it in the forum first and let us look at it.. If it is a real improvement,
it will be added to the offical client.. Changing something without knowing
what all it does can cause great harm to the network if released in mass form..
Any mod that changes anything within the Kademlia side will not be allowed to advertise
there client on the eMule forum..
*/

#include "Search.h"

#include <protocol/Protocols.h>
#include <protocol/kad/Client2Client/UDP.h>
#include <protocol/kad/Constants.h>
#include <protocol/kad2/Client2Client/UDP.h>
#include <tags/FileTags.h>

#include "Defines.h"
#include "../routing/RoutingZone.h"
#include "../routing/Contact.h"
#include "../net/KademliaUDPListener.h"
#include "../utils/KadClientSearcher.h"
#include "../../amule.h"
#include "../../SharedFileList.h"
#include "../../DownloadQueue.h"
#include "../../PartFile.h"
#include "../../SearchList.h"
#include "../../MemFile.h"
#include "../../ClientList.h"
#include "../../updownclient.h"
#include "../../Logger.h"
#include "../../Preferences.h"
#include "../../GuiEvents.h"
#include "../../Packet.h"

////////////////////////////////////////
using namespace Kademlia;
////////////////////////////////////////

CSearch::CSearch()
{
        m_created = time(NULL);
        m_type = (uint32_t)-1;
        m_completedPeers = 0;
        m_searchResults = 0;
        m_searchID = (uint32_t)-1;
        m_stopping = false;
        m_totalLoad = 0;
        m_totalLoadResponses = 0;
        m_nodeSpecialSearchRequester = NULL;
        m_lastResponse = time ( NULL );
        m_closestDistantFound = 0;
        m_requestedMoreNodesContact = CContact();
}

CSearch::~CSearch()
{
        AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i:  deleted search" ) ) % m_searchID ) ;

        // remember the closest node we found and tried to contact (if any) during this search
        // for statistical caluclations, but only if its a certain type
        switch (m_type) {
        case NODECOMPLETE:
        case FILE:
        case KEYWORD:
        case NOTES:
        case STOREFILE:
        case STOREKEYWORD:
        case STORENOTES:
                if (m_closestDistantFound != 0) {
                        CKademlia::StatsAddClosestDistance(m_closestDistantFound);
                }
                break;
        default: // NODE, NODESPECIAL, NODEFWCHECKUDP, FINDBUDDY
                break;
        }
        if (m_nodeSpecialSearchRequester != NULL) {
                // inform requester that our search failed
                m_nodeSpecialSearchRequester->KadSearchIPByNodeIDResult(KCSR_NOTFOUND, CI2PAddress::null);
        }

        // Check if a source search is currently being done.
        CPartFile* temp = theApp->downloadqueue->GetFileByKadFileSearchID(GetSearchID());

        // Reset the searchID if a source search is currently being done.
        if (temp) {
                temp->SetKadFileSearchID(0);
        }

        // Check if this search was containing an overload node and adjust time of next time we use that node.
        if ( CKademlia::IsRunning() && GetNodeLoad() > 20 ) {
                switch(GetSearchTypes()) {
                case CSearch::STOREKEYWORD:
                        Kademlia::CKademlia::GetIndexed()->AddLoad(GetTarget(), ((uint32_t)(DAY2S(7)*((double)GetNodeLoad()/100.0))+(uint32_t)time(NULL)));
                        break;
                }
        }

        // add keywords if a file has been published
        // and updates the item in the sharedfileslist : new source counts can have been received
        if ( GetSearchTypes() == CSearch::STOREFILE
                        && GetCompletedPeers() > 0 ) {
                CKnownFile* file = NULL ;
                uint8_t fileid[16];
                m_target.ToByteArray ( fileid );
                file = theApp->sharedfiles->GetFileByID ( CMD4Hash ( fileid ) );
                if (file ) {
                        theApp->sharedfiles->AddKeywords( file ) ;
                        Notify_SharedFilesUpdateItem(file);
                }
        }

        switch (m_type) {
        case KEYWORD:
                Notify_KadSearchEnd(m_searchID);
                break;
        }
}

void CSearch::Go()
{
        wxString debugStr;
#ifdef __DEBUG__
#define DEBSTR(X) debugStr << (X) << wxT(" ")
#else
#define DEBSTR(X)
#endif
        // Send some requests to myself
        // Useful for very small networks
        switch (m_type) {
        case FILE: case KEYWORD: case NOTES: case STOREFILE: case STOREKEYWORD: case STORENOTES:
                wxASSERT( m_ask_for_data.empty() );
                m_ask_for_data[ CKademlia::GetPrefs()->GetKadID() ^ m_target ] = CContact::Self();
                SendRequestToSelectedPeers();
        }

        DEBSTR ( 0 );
        // Start with a lot of possible contacts, this is a fallback in case search stalls due to dead contacts

        wxASSERT( m_ask_for_contacts.empty() && m_ask_for_data.empty() );

        DEBSTR ( 1 );
        CUInt128 distance ( CKademlia::GetPrefs()->GetKadID() );
        distance.XOR ( m_target );
        CKademlia::GetRoutingZone()->GetClosestTo ( 3, m_target, distance, 50, m_ask_for_contacts, true );
        // fall back to type 4 contacts if still empty
        if (m_ask_for_contacts.empty()) {
                DEBSTR ( 1.1 );
                CKademlia::GetRoutingZone()->GetClosestTo ( 4, m_target, distance, 50, m_ask_for_contacts, true );
        }

        DEBSTR ( 2 );


        if (!m_ask_for_contacts.empty()) {
                // Take top ALPHA_QUERY to start search with.
                int count = m_type == NODE ? 1 : min(ALPHA_QUERY, (int)m_ask_for_contacts.size());

                // Send initial packets to start the search.
                ContactMap::iterator it = m_ask_for_contacts.begin();
                for (int i = 0; i < count; i++) {
                        CContact c = it->second;
                        // Add to ALPHA_QUERY best list
                        AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i (%s): GO ! %s added to m_best list searching %s (debug steps=%s)" ) )
                                         % m_searchID  % typestr() % c.GetUDPDest().humanReadable() % m_target.ToHexString() % debugStr ) ;
                        m_best [it->first] = c;
                        // Move to tried
                        m_asked_for_contacts[it->first] = m_waiting_contacts[it->first] = c;
                        // Send the KadID so other side can check if I think it has the right KadID.
                        // Send request
                        SendFindPeersForTarget ( c );
                        ++it;
                }
                m_ask_for_contacts.clear();
        }

        AddDebugLogLineN(logKadSearch,
                         CFormat ( wxT ( "#%i: Go. CONTACTS askable: %d, asked: %d, awaited: %d, DATA askable: %d, asked: %d, awaited: %d, completed: %d, searchAnswers: %d (maxAnswers: %d)" ) )
                         % m_searchID
                         % m_ask_for_contacts.size() % m_asked_for_contacts.size() % m_waiting_contacts.size()
                         % m_ask_for_data.size() % m_asked_for_data.size() % m_waiting_data.size()
                         % m_completedPeers % GetSearchAnswers() % GetSearchMaxAnswers());
}

//If we allow about a 15 sec delay before deleting, we won't miss a lot of delayed returning packets.
// stop looking for peers, but gives time for waiting for responses

void CSearch::StopLookingForPeers() throw ()
{
        if ( m_stopping ) {
                return;
        }
        m_created = time ( NULL ) ;
        m_stopping = true;
}

/**
 * JumpStart: collect responses and act accordingly
 * return: false if the search is to be killed
 */

bool CSearch::JumpStart()
{
        // If we had a response within the last 3 seconds, no need to jumpstart the search.
        if ((time_t)(m_lastResponse + SEC(3)) > time(NULL)) {
                return true;
        }
        // m_lastResponse is first set in CSearch::go
        // then at every received response in CSearch::processResponse

        AddDebugLogLineN(logKadSearch,
                         CFormat ( wxT ( "#%i: JumpStart. CONTACTS askable: %d, asked: %d, awaited: %d, DATA askable: %d, asked: %d, awaited: %d, completed: %d, searchAnswers: %d (maxAnswers: %d)" ) )
                         % m_searchID
                         % m_ask_for_contacts.size() % m_asked_for_contacts.size() % m_waiting_contacts.size()
                         % m_ask_for_data.size() % m_asked_for_data.size() % m_waiting_data.size()
                         % m_completedPeers % GetSearchAnswers() % GetSearchMaxAnswers());

        // kill this search if max answers have been received
        // or if every possible peers have already answered with results
        if (GetSearchAnswers()>=GetSearchMaxAnswers() || (m_ask_for_contacts.empty() && m_waiting_contacts.empty() && m_ask_for_data.empty() &&
                        (m_waiting_data.empty() || (m_completedPeers==m_waiting_data.size() && (m_type & (STOREFILE | STOREKEYWORD | STORENOTES)))))) {
                if ( m_type == NODECOMPLETE && GetSearchAnswers() != 0 ) CKademlia::GetPrefs()->SetPublish ( true );
                return false ;
        }

        // if timeout is reached,
        // first time : stop looking for peers (side effect : m_created <- now)
        //    but still waits for answers if needed
        // second time : kill this search
        if ( m_created + GetSearchLifetime() < time(NULL) ) {
                if (m_type==NODECOMPLETE && GetSearchAnswers()!=0) CKademlia::GetPrefs()->SetPublish ( true );
                if (!m_stopping && NumberOfRequestedPeers()>GetCompletedPeers() )
                        StopLookingForPeers() ;
                else
                        return false ;
        }


        //AddDebugLogLineN(logKadSearch, CFormat(wxT("#%i:  CSearch::jumpStart. %d possible, %d remaining_responses")) % m_searchID  % m_possible.size() % m_ask_for_data.size()) ;

        // Is this a find lookup and are the best two (=KADEMLIA_FIND_VALUE) nodes dead/unreachable?
        // In this case try to discover more close nodes before using our other results
        // The reason for this is that we may not have found the closest node alive due to results being limited to 2 contacts,
        // which could very well have been the duplicates of our dead closest nodes
        bool lookupCloserNodes = false;
        if (m_requestedMoreNodesContact.IsInvalid() && GetRequestContactCount() == KADEMLIA_FIND_VALUE && m_asked_for_contacts.size() >= 3 * KADEMLIA_FIND_VALUE) {
                ContactMap::const_iterator it = m_asked_for_contacts.begin();
                lookupCloserNodes = true;
                for (unsigned i = 0; i < KADEMLIA_FIND_VALUE; i++) {
                        if (m_ask_for_data.count(it->first) > 0) {
                                lookupCloserNodes = false;
                                break;
                        }
                        ++it;
                }
                if (lookupCloserNodes) {
                        while (it != m_asked_for_contacts.end()) {
                                if (m_ask_for_data.count(it->first) > 0) {
                                        AddDebugLogLineN(logKadSearch, CFormat(wxT("Best %d nodes for lookup (id=%x) were unreachable or dead, reasking closest for more")) % KADEMLIA_FIND_VALUE % GetSearchID());
                                        SendFindPeersForTarget(it->second, true);
                                        return true;
                                }
                                ++it;
                        }
                }
        }

        while ( !m_ask_for_data.empty() ) {
                // send request to contact that have responded
                SendRequestToSelectedPeers();
        }
        while ( !m_ask_for_contacts.empty() ) {
                CContact c = m_ask_for_contacts.begin()->second;
                // Move to tried
                m_asked_for_contacts[m_ask_for_contacts.begin()->first] = m_waiting_contacts[m_ask_for_contacts.begin()->first] = c;
                m_ask_for_contacts.erase(m_ask_for_contacts.begin());
                // Send request
                SendFindPeersForTarget ( c );
                // and quit, waiting for the new requesting process to time out
        }

        return true ;
}

/**
 * Processes a contact list received for this search
 * @param fromDest
 * @param results
 */
void CSearch::ProcessResponse ( const CI2PAddress & fromIP, const ContactList & results )
{
        AddDebugLogLineN(logKadSearch, wxT("Processing search response from ") + fromIP.humanReadable());

        m_lastResponse = time(NULL);

        // Find contact that is responding.
        CUInt128 fromDistance(0u);
        CContact fromContact;
        for (ContactMap::iterator it = m_waiting_contacts.begin(); it != m_waiting_contacts.end(); ++it) {
                CContact tmpContact = it->second;
                if ((tmpContact.GetIPAddress() == fromIP)) {
                        fromDistance = it->first;
                        fromContact = tmpContact;
                        m_waiting_contacts.erase(it);
                        break;
                }
                if (it==m_waiting_contacts.end())
                        AddDebugLogLineC(logKadSearch, wxT("Node ") + fromIP.humanReadable() + wxT(" reply with contacts was not awaited"));
        }

        // Make sure the node is not sending more results than we requested, which is not only a protocol violation
        // but most likely a malicious answer
        if (results.size() > GetRequestContactCount() && !(m_requestedMoreNodesContact.IsValid() && fromContact.IsValid() && m_requestedMoreNodesContact == fromContact && results.size() <= KADEMLIA_FIND_VALUE_MORE)) {
                AddDebugLogLineN(logKadSearch, wxT("Node ") + fromIP.humanReadable() + wxT(" sent more contacts than requested on a routing query, ignoring response"));
                return;
        }

        // Not interested in responses for FIND_NODE, will be added to contacts by udp listener
        if (m_type == NODE) {
                AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: Node type search result, discarding." ) ) % m_searchID );
                m_searchResults++;
                m_completedPeers ++ ;
                m_ask_for_contacts.clear();
                return;
        }

        if (fromContact.IsValid()) {
//unused                 bool providedCloserContacts = false;
                std::map<uint32_t, unsigned> receivedIPs;
                // A node is not allowed to answer with contacts to itself
                receivedIPs[fromIP] = 1;

                AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: %s added to the list of people who responded, searching %s" ) )
                                 % m_searchID % fromIP.humanReadable() % m_target.ToHexString() );

                // Loop through their responses
                for ( ContactList::const_iterator it = results.begin(); it != results.end(); ++it ) {
                        // Get next result
                        CContact c = *it;
                        // calc distance this result is to the target
                        CUInt128 distance(c.GetClientID() ^ m_target);

                        if (distance < fromDistance) {
// unused                                providedCloserContacts = true;
                        }

                        // Ignore this contact if already known or tried it.
                        if (m_ask_for_contacts.count(distance) > 0) {
                                AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: %s already in list of possible clients searching %s" ) )
                                                 % m_searchID % c.GetUDPDest().humanReadable() % m_target.ToHexString() );
                                continue;
                        }
                        if (m_asked_for_contacts.count(distance) > 0) {
                                AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: %s already in list of asked    clients searching %s" ) )
                                                 % m_searchID % c.GetUDPDest().humanReadable() % m_target.ToHexString() );
                                continue;
                        }

                        // We only accept unique IPs in the answer, having multiple IDs pointing to one IP in the routing tables
                        // is no longer allowed since eMule0.49a, aMule-2.2.1 anyway
                        if (receivedIPs.count(c.GetIPAddress()) > 0) {
                                AddDebugLogLineN(logKadSearch, wxT("Multiple KadIDs pointing to same IP (") + c.GetIPAddress().humanReadable() + wxT(") in Kad2Res answer - ignored, sent by ") + fromContact.GetIPAddress().humanReadable());
                                continue;
                        } else {
                                receivedIPs[c.GetIPAddress()] = 1;
                        }

                        // Add to possible
                        AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: %s added to the list of possible clients searching %s" ) )
                                         % m_searchID % c.GetUDPDest().humanReadable() % m_target.ToHexString() );
                        m_ask_for_contacts[distance] = c;

                        // Try it now if in m_best list
                        // Verify if the result is closer to the target than the one we just checked.
                        if (distance < fromDistance) {
                                // The top ALPHA_QUERY of results are used to determine if we send a request.
                                bool top = false;
                                if (m_best.size() < ALPHA_QUERY) {
                                        AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: %s added to m_best list for searching %s" ) )
                                                         % m_searchID % c.GetUDPDest().humanReadable() % m_target.ToHexString() ) ;
                                        top = true;
                                        m_best[distance] = c;
                                } else {
                                        //AddDebugLogLineN(logKadSearch, wxT("m_best.size() >= ALPHA_QUERY"));
                                        ContactMap::iterator worst = m_best.end();
                                        --worst;
                                        if (distance < worst->first) {
                                                AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: %s added to m_best list searching %s, replacing %x" ) )
                                                                 % m_searchID % c.GetUDPDest().humanReadable() % m_target.ToHexString() % c.GetUDPDest().hashCode() ) ;
                                                // Prevent having more than ALPHA_QUERY within the Best list.
                                                m_best.erase(worst);
                                                m_best[distance] = c;
                                                top = true;
                                        }
                                }

                                if (top) {
                                        // We determined this contact is a candidate for a request.
                                        // Add to tried
                                        m_asked_for_contacts[distance] = m_waiting_contacts[distance] = c;
                                        m_ask_for_contacts.erase ( distance );
                                        // Send the KadID so other side can check if I think it has the right KadID.
                                        // Send request
                                        SendFindPeersForTarget ( c );
                                }
                        } else {
                                //AddDebugLogLineN(logKadSearch, CFormat(wxT("%x not added to m_best list for search %s"))
                                //                                      % c->GetUDPDest().hashCode() % m_target.ToHexString()) ;
                        }
                }

                // Complete node search, just increment the counter.
                if ( m_type == NODECOMPLETE || m_type == NODESPECIAL ) {
                        AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: Search result type: Node complete" ) ) % m_searchID );
                        m_searchResults++;
                        m_completedPeers++;
                } else {
                        // Add to list of people to ask for data
                        if (m_asked_for_data.count(fromDistance)==0) {
                                m_ask_for_data[fromDistance] = fromContact;
                                AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: Adding %s to m_ask_for_data" ) ) % m_searchID % fromContact.GetUDPDest().humanReadable());
                        } else {
                                AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: Ignore %s already in m_ask_for_data" ) ) % m_searchID % fromContact.GetUDPDest().humanReadable());
                        }
                }
        }
}

// sends the effective request to peers that have responded to peer request
// called CSearch::StorePacket() in amule
void CSearch::SendRequestToSelectedPeers()
{
        wxASSERT ( !m_ask_for_data.empty() );

        ContactMap::const_iterator  remaining_responses       = m_ask_for_data.begin();
        CUInt128                    fromDistance    = remaining_responses->first;
        const CContact *            from            = &(remaining_responses->second);
        bool requestSent = false ;
        if (fromDistance < m_closestDistantFound || m_closestDistantFound == 0) {
                m_closestDistantFound = fromDistance;
        }

        // What kind of search are we doing?
        switch (m_type) {
        case FILE: {
                AddDebugLogLineN(logKadSearch, wxT("Search request type: File"));
                CMemFile searchTerms;
                searchTerms.WriteUInt128(m_target);
                if (from->GetVersion() >= 3) {
                        // Find file we are storing info about.
                        uint8_t fileid[16];
                        m_target.ToByteArray(fileid);
                        CKnownFile *file = theApp->downloadqueue->GetFileByID(CMD4Hash(fileid));
                        if (file) {
                                // Start position range (0x0 to 0x7FFF)
                                searchTerms.WriteUInt16(0);
                                searchTerms.WriteUInt64(file->GetFileSize());
                                DebugSend(Kad2SearchSourceReq, from->GetUDPDest());
                                if (from->GetVersion() >= 6) {
                                        CUInt128 clientID = from->GetClientID();
                                        CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA2_SEARCH_SOURCE_REQ, from->GetUDPDest(), from->GetUDPKey(), &clientID);
                                } else {
                                        CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA2_SEARCH_SOURCE_REQ, from->GetUDPDest(), 0, NULL);
                                        wxASSERT(from->GetUDPKey() == CKadUDPKey(0));
                                }
                        } else {
                                StopLookingForPeers();
                                break;
                        }
                } else {
                        searchTerms.WriteUInt8(1);
                        DebugSendF(wxT("KadSearchReq(File)"), from->GetUDPDest());
                        CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA_SEARCH_REQ, from->GetUDPDest(), 0, NULL);
                }
                requestSent = true;
                break;
        }
        case KEYWORD: {
                AddDebugLogLineN(logKadSearch, wxT("Search request type: Keyword"));
                CMemFile searchTerms;
                searchTerms.WriteUInt128(m_target);
                if (from->GetVersion() >= 3) {
                        if (m_searchTerms.GetLength() == 0) {
                                // Start position range (0x0 to 0x7FFF)
                                searchTerms.WriteUInt16(0);
                        } else {
                                // Start position range (0x8000 to 0xFFFF)
                                searchTerms.WriteUInt16(0x8000);
                                searchTerms.Write(m_searchTerms.GetRawBuffer(), m_searchTerms.GetLength());
                        }
                        DebugSend(Kad2SearchKeyReq, from->GetUDPDest());
                } else {
                        if (m_searchTerms.GetLength() == 0) {
                                searchTerms.WriteUInt8(0); // iMule v1 seems to need this
                        } else {
                                searchTerms.WriteUInt8(1); // iMule v1 seems to need this
                                searchTerms.Write(m_searchTerms.GetRawBuffer(), m_searchTerms.GetLength());
                        }
                        DebugSendF(wxT("KadSearchReq(Keyword)"), from->GetUDPDest());
                }
                if (from->GetVersion() >= 6) {
                        CUInt128 clientID = from->GetClientID();
                        CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA2_SEARCH_KEY_REQ, from->GetUDPDest(), from->GetUDPKey(), &clientID);
                } else if (from->GetVersion() >= 3) {
                        CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA2_SEARCH_KEY_REQ, from->GetUDPDest(), 0, NULL);
                        wxASSERT(from->GetUDPKey() == CKadUDPKey(0));
                } else {
                        CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA_SEARCH_REQ, from->GetUDPDest(), 0, NULL);
                }
                requestSent = true;
                break;
        }
        case NOTES: {
                AddDebugLogLineN(logKadSearch, wxT("Search request type: Notes"));
                // Write complete packet.
                CMemFile searchTerms;
                searchTerms.WriteUInt128(m_target);
                if (from->GetVersion() >= 3) {
                        // Find file we are storing info about.
                        uint8_t fileid[16];
                        m_target.ToByteArray(fileid);
                        CKnownFile *file = theApp->sharedfiles->GetFileByID(CMD4Hash(fileid));
                        if (file) {
                                // Start position range (0x0 to 0x7FFF)
                                searchTerms.WriteUInt64(file->GetFileSize());
                                DebugSend(Kad2SearchNotesReq, from->GetUDPDest());
                                if (from->GetVersion() >= 6) {
                                        CUInt128 clientID = from->GetClientID();
                                        CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA2_SEARCH_NOTES_REQ, from->GetUDPDest(),
                                                                                from->GetUDPKey(), &clientID);
                                } else {
                                        CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA2_SEARCH_NOTES_REQ, from->GetUDPDest(), 0, NULL);
                                        wxASSERT(from->GetUDPKey() == CKadUDPKey(0));
                                }
                        } else {
                                StopLookingForPeers();
                                break;
                        }
                } else {
                        searchTerms.WriteUInt128(CKademlia::GetPrefs()->GetKadID());
                        DebugSend(KadSearchNotesReq, from->GetUDPDest());
                        CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA_SEARCH_NOTES_REQ, from->GetUDPDest(), 0, NULL);
                }
                requestSent = true;
                break;
        }
        case STOREFILE: {
                AddDebugLogLineN(logKadSearch, wxT("Search request type: StoreFile"));
                // Try to store myself as a source to a Node.

                // Find the file I'm trying to store myself as a source to.
                uint8_t fileid[16];
                m_target.ToByteArray(fileid);
                CKnownFile* file = theApp->sharedfiles->GetFileByID(CMD4Hash(fileid));
                if (file) {
                        // We store this mostly for GUI reasons.
                        m_fileName = file->GetFileName().GetPrintable();

                        // Get our clientID for the packet.
                        CUInt128 id(CKademlia::GetPrefs()->GetClientHash());
                        TagList taglist;

                        // We're not firewalled..
                        taglist.push_back ( CTag ( TAG_SOURCETYPE, 1 ) );
                        taglist.push_back ( CTag ( TAG_SOURCEDEST, theApp->GetTcpDest() ) );
                        taglist.push_back ( CTag ( TAG_FLAGS, file->IsPartFile() ? 0 : TAG_FLAGS_COMPLETEFILE) );
                        if (from->GetVersion() >= 2) {
                                taglist.push_back(CTag(TAG_FILESIZE, file->GetFileSize()));
                        }
                        taglist.push_back(CTag(TAG_ENCRYPTION, (uint64_t) CPrefs::GetMyConnectOptions(true, true)));

                        // Send packet
                        CKademlia::GetUDPListener()->SendPublishSourcePacket(*from, m_target, id, taglist);
                        requestSent = true;
                } else {
                        StopLookingForPeers();
                }
                break;
        }
        case STOREKEYWORD: {
                AddDebugLogLineN(logKadSearch, wxT("Search request type: StoreKeyword"));
                // Try to store keywords to a Node.

                uint16_t count = m_fileIDs.size();
                if (count == 0) {
                        StopLookingForPeers();
                        break;
                } else if (count > 150) {
                        count = 150;
                }

                UIntList::const_iterator itLastSentFileID, itListFileID = m_fileIDs.begin();
                uint8_t fileid[16];
                uint64_t posNresults ;
                bool initPacket = true ;
                uint16_t packetCount;
                CMemFile packetdata(2 * CPacket::UDPPacketMaxDataSize()); // Allocate a good amount of space.
                size_t posLastResult = 0 ;

                while (count && (itListFileID != m_fileIDs.end())) {
                        // packet initialization
                        if (initPacket) {
                                packetdata.Reset();
                                packetdata.WriteUInt128(m_target);
                                posNresults = packetdata.GetPosition() ;
                                packetdata.WriteUInt16 ( 0 );           // number of results. Will be overriden by "count"
                                packetCount = 0;
                                initPacket = false ;                    // unset initPacket flag
                                posLastResult = (uint32_t)packetdata.GetPosition() ; // packet length before writing the entry
                        }
                        CUInt128 id(*itListFileID);
                        id.ToByteArray(fileid);
                        CKnownFile *pFile = theApp->sharedfiles->GetFileByID(CMD4Hash(fileid));
                        if (pFile) {
                                packetdata.WriteUInt128(id);
                                PreparePacketForTags(&packetdata, pFile);
                                // test : if packet size is too big, revert last write
                                //        and reset initPacket flag
                                if (packetdata.GetPosition()>CPacket::UDPPacketMaxDataSize()) {
                                        if ( count != 0 ) {
                                                initPacket = true ;
                                        } else { // forget this entry if it cannot fit alone in a packet
                                                count-- ;
                                                itListFileID++ ;
                                        }
                                } else {
                                        packetCount++;
                                        posLastResult = (uint32_t)packetdata.GetPosition() ; // packet length after writing the entry
                                        count--;
                                        itListFileID++;
                                }

                        }
                        // test : send packet if it is full or if we reached its max size
                        if ( initPacket or (itListFileID==m_fileIDs.end()) ) {
                                // Correct file count.
                                packetdata.Seek ( posNresults );
                                packetdata.WriteUInt16(packetCount);
                                packetdata.Reset();
                                packetdata.SetLength( posLastResult );

                                // Send packet
                                if (from->GetVersion() >= 6) {
                                        DebugSend(Kad2PublishKeyReq, from->GetUDPDest());
                                        CUInt128 clientID = from->GetClientID();
                                        CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_PUBLISH_KEY_REQ, from->GetUDPDest(), from->GetUDPKey(), &clientID);
                                } else if (from->GetVersion() >= 2) {
                                        DebugSend(Kad2PublishKeyReq, from->GetUDPDest());
                                        CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_PUBLISH_KEY_REQ, from->GetUDPDest(), 0, NULL);
                                        wxASSERT(from->GetUDPKey() == CKadUDPKey(0));
                                } else {
                                        DebugSend(KadPublishReq, from->GetUDPDest());
                                        CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA_PUBLISH_REQ, from->GetUDPDest(), 0, NULL);
                                }
                        }
                }
                requestSent = true;
                break;
        }
        case STORENOTES: {
                AddDebugLogLineN(logKadSearch, wxT("Search request type: StoreNotes"));
                // Find file we are storing info about.
                uint8_t fileid[16];
                m_target.ToByteArray(fileid);
                CKnownFile* file = theApp->sharedfiles->GetFileByID(CMD4Hash(fileid));

                if (file) {
                        CMemFile packetdata(1024*2);
                        // Send the hash of the file we're storing info about.
                        packetdata.WriteUInt128(m_target);
                        // Send our ID with the info.
                        packetdata.WriteUInt128(CKademlia::GetPrefs()->GetKadID());

                        // Create our taglist.
                        TagList taglist;
                        taglist.push_back(CTag(TAG_FILENAME, file->GetFileName().GetPrintable()));
                        if (file->GetFileRating() != 0) {
                                taglist.push_back(CTag(TAG_FILERATING, (uint64_t) file->GetFileRating()));
                        }
                        if (!file->GetFileComment().IsEmpty()) {
                                taglist.push_back(CTag(TAG_DESCRIPTION, file->GetFileComment()));
                        }
                        if (from->GetVersion() >= 2) {
                                taglist.push_back(CTag(TAG_FILESIZE, file->GetFileSize()));
                        }
                        taglist.WriteTo(&packetdata);

                        // Send packet
                        if (from->GetVersion() >= 6) {
                                DebugSend(Kad2PublishNotesReq, from->GetUDPDest());
                                CUInt128 clientID = from->GetClientID();
                                CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_PUBLISH_NOTES_REQ, from->GetUDPDest(), from->GetUDPKey(), &clientID);
                        } else if (from->GetVersion() >= 2) {
                                DebugSend(Kad2PublishNotesReq, from->GetUDPDest());
                                CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_PUBLISH_NOTES_REQ, from->GetUDPDest(), 0, NULL);
                                wxASSERT(from->GetUDPKey() == CKadUDPKey(0));
                        } else {
                                DebugSend(KadPublishNotesReq, from->GetUDPDest());
                                CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA_PUBLISH_NOTES_REQ_DEPRECATED, from->GetUDPDest(), 0, NULL);
                        }
                        requestSent = true;
                } else {
                        StopLookingForPeers();
                }
                break;
        }
        case NODESPECIAL: {
                // we are looking for the IP of a given NodeID, so we just check if we 0 distance and if so, report the
                // tip to the requester
                if (fromDistance == 0) {
                        m_nodeSpecialSearchRequester-> KadSearchIPByNodeIDResult(KCSR_SUCCEEDED, from->GetTCPDest());
                        m_nodeSpecialSearchRequester = NULL;
                        StopLookingForPeers();
                }
                break;
        }
        case NODECOMPLETE:
                AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: Search result type : NodeComplete" ) ) % m_searchID );
                break;
        case NODE:
                AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: Search result type : Node" ) ) % m_searchID );
                break;
        default:
                AddDebugLogLineN(logKadSearch, wxString::Format ( wxT ( "#%i:Search result type: Unknown (%i)" ), m_searchID, m_type ) );
                break;
        }

        if (requestSent) m_asked_for_data[fromDistance] = m_waiting_data[fromDistance] = *from;
        m_ask_for_data.erase(m_ask_for_data.begin());
}

void CSearch::ProcessSearchResultList ( const CMemFile & bio )
{
        // How many results.. Not supported yet..
        uint16_t count = bio.ReadUInt16();

        AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: processSearchResultList (%d results)" ) )
                         % m_searchID % count );

        while ( count > 0 ) {
                // What is the answer
                CUInt128 answer = bio.ReadUInt128();

                // Get info about answer

                try {
                        AddDebugLogLineN(logClientKadUDP,
                                         CFormat ( wxT ( "processSearchResultList : %d response(s) remaining (rem. filesize=%llu)" ) )
                                         % count %  bio.GetAvailable() );
                        TagList tags ( bio, true );
                        ProcessSearchResult (answer, tags);
                        count--;
                } catch ( ... ) {
                        AddDebugLogLineN(logKadSearch, wxT ( "CSearch::processSearchResultList : error while trying to add a result" ) );
                        throw;
                }
        }

        m_completedPeers ++ ;
}


void CSearch::ProcessSearchResult ( const CUInt128 &answer, TagList & info )
{
        wxString type = wxT("Unknown");
        switch (m_type) {
        case FILE:
                type = wxT("File");
                ProcessResultFile(answer, info);
                break;
        case KEYWORD:
                type = wxT("Keyword");
                ProcessResultKeyword(answer, info);
                break;
        case NOTES:
                type = wxT("Notes");
                ProcessResultNotes(answer, info);
                break;
        }
        AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: type=%s processSearchResult : answer = %s" ) ) % m_searchID % type  % answer.ToHexString() );
}




void CSearch::ProcessResultFile ( const CUInt128 &answer, TagList & info )
{
        uint8_t type = 0;
        CI2PAddress tcpdest;
        CI2PAddress udpdest;
        CI2PAddress serverdest;
        uint8	    byCryptOptions = 0; // 0 = not supported

        for ( TagList::const_iterator it = info.begin(); it != info.end(); ++it ) {
                const CTag & tag = *it;

                if ( tag . GetID() == TAG_SOURCETYPE ) {
                        type = (uint8_t) tag . GetInt();
                } else if ( tag . GetID() == TAG_SOURCEDEST ) {
                        tcpdest = tag . GetAddr();
                } else if ( tag . GetID() == TAG_SOURCEUDEST ) {
                        udpdest = tag . GetAddr();
                } else if ( tag . GetID() == TAG_SERVERDEST ) {
                        serverdest = tag . GetAddr();
                } else if ( tag . GetID() == TAG_ENCRYPTION ) {
                        byCryptOptions = tag . GetInt();
                }
        }

        AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: source type : %d" ) ) % m_searchID % type );

        // Process source based on its type. Currently only one method is needed to process all types.
        switch( type ) {
        case 1:
        case 3: {
                theApp->downloadqueue->KademliaSearchFile ( m_searchID, &answer, type, tcpdest, udpdest, serverdest, byCryptOptions ) ;
                break;
        }

        case 2:
                //Don't use this type, some clients will process it wrong..
        default:
                break;
        }
}

void CSearch::ProcessResultNotes ( const CUInt128 &answer, TagList & info )
{
        // Process a received Note to a file.
        // Create a Note and set the IDs.
        CEntry entry;
        entry.m_uKeyID.SetValue ( m_target );
        entry.m_uSourceID.SetValue ( answer );

        bool bFilterComment = false;

        // Loop through tags and pull wanted into. Currently we only keep Filename, Rating, Comment.
        for ( TagList::const_iterator it = info.begin(); it != info.end(); it++ ) {
                const CTag & tag = *it;

                switch ( tag.GetID() ) {
                case TAG_SOURCEDEST :
                        entry.m_tcpdest = tag . GetAddr();
                        break ;
                case TAG_FILENAME :
                        entry.SetFileName (tag . GetStr());
                        break ;
                case TAG_DESCRIPTION :
                        //wxString strComment ( tag . GetStr() );
                        bFilterComment = thePrefs::IsMessageFiltered ( tag . GetStr() );
                        entry.GetTagList().push_front ( tag );
                        break ;
                case TAG_FILERATING :
                        entry.GetTagList().push_front ( tag );
                        break ;
                default :
                        break ;
                }
        }

        if (bFilterComment) {
                return;
        }

        uint8_t fileid[16];
        m_target.ToByteArray(fileid);
        const CMD4Hash fileHash(fileid);

        //Check if this hash is in our shared files..
        CKnownFile* file = theApp->sharedfiles->GetFileByID(fileHash);

        if (!file) {
                // If we didn't find anything check if it's in our download queue.
                file = (CKnownFile*)theApp->downloadqueue->GetFileByID(fileHash);
        }

        // If we found a file try to add the note to the file.
        if (file) {
                file->AddNote ( entry ) ;
                m_searchResults++;
        } else {
                AddDebugLogLineN(logKadSearch, wxT("Comment received for unknown file"));
        }
}

void CSearch::ProcessResultKeyword ( const CUInt128 &answer, TagList & info )
{
        // Process a keyword that we received.
        // Set of data we can use for a keyword result.
        wxString name;
        uint64_t size = 0;
        wxString type;
        uint32_t publishInfo = 0;
        // Flag that is set if we want this keyword
        bool bFileName = false;
        bool bFileSize = false;

        TagList taglist;

        for ( TagList::const_iterator it = info.begin(); it != info.end(); ++it ) {
                const CTag& tag = *it;

                switch ( tag.GetID() ) {
                case TAG_FILENAME :
                        name = tag . GetStr();
                        bFileName = !name.IsEmpty();
                        break ;

                case TAG_FILESIZE :
                        size = tag . GetInt();
                        bFileSize = true;
                        break ;

                case  TAG_FILETYPE :
                        type = tag . GetStr();
                        break ;

                case TAG_PUBLISHINFO : {
                        // we don't keep this as tag, but as a member property of the searchfile, as we only need its informations
                        // in the search list and don't want to carry the tag over when downloading the file (and maybe even wrongly publishing it)
                        publishInfo = tag.GetInt();
#ifdef __DEBUG__
                        uint32_t differentNames = (publishInfo & 0xFF000000) >> 24;
                        uint32_t publishersKnown = (publishInfo & 0x00FF0000) >> 16;
                        uint32_t trustValue = publishInfo & 0x0000FFFF;
                        AddDebugLogLineN(logKadSearch, CFormat(wxT("Received PublishInfo Tag: %u different names, %u publishers, %.2f trustvalue")) % differentNames % publishersKnown % ((double)trustValue/ 100.0));
#endif
                }
                break;
                default :
                        taglist. push_back ( tag );
                }
        }

        // If we don't have a valid filename and filesize, drop this keyword.
        if (!bFileName || !bFileSize) {
                AddDebugLogLineN(logKadSearch, wxString(wxT("No ")) + (!bFileName ? wxT("filename") : wxT("filesize")) + wxT(" on search result, ignoring"));
                return;
        }

        // the file name of the current search response is stored in "name"
        // the list of words the user entered is stored in "m_words"
        // so the file name now gets parsed for all the words entered by the user (even repetitive ones):

        // Step 1: Get the words of the response file name
        WordList listFileNameWords;
        CSearchManager::GetWords(name, &listFileNameWords, true);

        // Step 2: Look for each entered search word in those present in the filename
        bool bFileNameMatchesSearch = true;  // this will be set to "false", if not all search words are found in the file name

        for (WordList::const_iterator itSearchWords = m_words.begin(); itSearchWords != m_words.end(); ++itSearchWords) {
                bool bSearchWordPresent = false;
                for (WordList::iterator itFileNameWords = listFileNameWords.begin(); itFileNameWords != listFileNameWords.end(); ++itFileNameWords) {
                        if (!itFileNameWords->CmpNoCase(*itSearchWords)) {
                                listFileNameWords.erase(itFileNameWords);  // remove not to find same word twice
                                bSearchWordPresent = true;
                                break;  // found word, go on using the next searched word
                        }
                }
                if (!bSearchWordPresent) {
                        bFileNameMatchesSearch = false;  // not all search words were found in the file name
                        break;
                }
        }

        // Step 3: Accept result only if all(!) words are found
        if (bFileNameMatchesSearch) {
                m_searchResults++;
                theApp->searchlist->KademliaSearchKeyword ( m_searchID, &answer, name, (uint64_t) size, type, publishInfo, taglist ) ;
        }
}

//void CSearch::SendFindValue(CContact *contact, bool reaskMore)
void CSearch::SendFindPeersForTarget ( CContact contact, bool reaskMore )
{
        // Found a node that we think has contacts closer to our target.
        try {
                if (m_stopping) {
                        return;
                }

                CMemFile packetdata( 1/*number of contacts requested*/ + 16 /*m_target*/ + 16 /*dest ID (check)*/ + 16 /*my ID (proposed for booking)*/);
                // The number of returned contacts is based on the type of search.
                uint8_t contactCount = GetRequestContactCount();

                if (reaskMore) {
                        if (m_requestedMoreNodesContact.IsInvalid()) {
                                m_requestedMoreNodesContact = contact;
                                wxASSERT(contactCount == KADEMLIA_FIND_VALUE);
                                contactCount = KADEMLIA_FIND_VALUE_MORE;
                        } else {
                                wxFAIL;
                        }
                }

                if (contactCount > 0) {
                        packetdata.WriteUInt8(contactCount);
                } else {
                        return;
                }
                // Put the target we want into the packet.
                packetdata.WriteUInt128 ( m_target );
                // Add the ID of the contact we're contacting for sanity checks on the other end.
                packetdata.WriteUInt128(contact.GetClientID());
                // Add my ID so that the contact can ask for my details if he is interested.
                packetdata.WriteUInt128(CContact::Self().GetClientID());

                // send the request, but first signal the routing zone that the contact will be sent something
                // so that the type of contact will increase if it does not respond
                //         contact.CheckingType() ;

                if (contact.GetVersion() >= 2) {
                        if (contact.GetVersion() >= 6) {
                                CUInt128 clientID = contact.GetClientID();
                                CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_REQ, contact.GetUDPDest(), contact.GetUDPKey(), &clientID);
                        } else {
                                CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_REQ, contact.GetUDPDest(), 0, NULL);
                                wxASSERT(contact.GetUDPKey() == CKadUDPKey(0));
                        }
#ifdef __DEBUG__
                        switch (m_type) {
                        case NODE:
                                DebugSendF(wxT("Kad2Req(Node)"), contact.GetUDPDest());
                                break;
                        case NODECOMPLETE:
                                DebugSendF(wxT("Kad2Req(NodeComplete)"), contact.GetUDPDest());
                                break;
                        case NODESPECIAL:
                                DebugSendF(wxT("Kad2Req(NodeSpecial)"), contact.GetUDPDest());
                                break;
                        case FILE:
                                DebugSendF(wxT("Kad2Req(File)"), contact.GetUDPDest());
                                break;
                        case KEYWORD:
                                DebugSendF(wxT("Kad2Req(Keyword)"), contact.GetUDPDest());
                                break;
                        case STOREFILE:
                                DebugSendF(wxT("Kad2Req(StoreFile)"), contact.GetUDPDest());
                                break;
                        case STOREKEYWORD:
                                DebugSendF(wxT("Kad2Req(StoreKeyword)"), contact.GetUDPDest());
                                break;
                        case STORENOTES:
                                DebugSendF(wxT("Kad2Req(StoreNotes)"), contact.GetUDPDest());
                                break;
                        case NOTES:
                                DebugSendF(wxT("Kad2Req(Notes)"), contact.GetUDPDest());
                                break;
                        default:
                                DebugSend(Kad2Req, contact.GetUDPDest());
                                break;
                        }
#endif
                } else {
                        CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA_REQ_DEPRECATED, contact.GetUDPDest(), 0, NULL);
#ifdef __DEBUG__
                        switch (m_type) {
                        case NODE:
                                DebugSendF(wxT("KadReq(Node)"), contact.GetUDPDest());
                                break;
                        case NODECOMPLETE:
                                DebugSendF(wxT("KadReq(NodeComplete)"), contact.GetUDPDest());
                                break;
                        case NODESPECIAL:
                                DebugSendF(wxT("KadReq(NodeSpecial)"), contact.GetUDPDest());
                                break;
                        case FILE:
                                DebugSendF(wxT("KadReq(File)"), contact.GetUDPDest());
                                break;
                        case KEYWORD:
                                DebugSendF(wxT("KadReq(Keyword)"), contact.GetUDPDest());
                                break;
                        case STOREFILE:
                                DebugSendF(wxT("KadReq(StoreFile)"), contact.GetUDPDest());
                                break;
                        case STOREKEYWORD:
                                DebugSendF(wxT("KadReq(StoreKeyword)"), contact.GetUDPDest());
                                break;
                        case STORENOTES:
                                DebugSendF(wxT("KadReq(StoreNotes)"), contact.GetUDPDest());
                                break;
                        case NOTES:
                                DebugSendF(wxT("KadReq(Notes)"), contact.GetUDPDest());
                                break;
                        default:
                                DebugSend(KadReq, contact.GetUDPDest());
                                break;
                        }
#endif
                }
        } catch (const CEOFException& err) {
                AddDebugLogLineC(logKadSearch, wxT("CEOFException in CSearch::SendFindPeersForTarget: ") + err.what());
        } catch (const CInvalidPacket& err) {
                AddDebugLogLineC(logKadSearch, wxT("CInvalidPacket Exception in CSearch::SendFindPeersForTarget: ") + err.what());
        } catch (const wxString& e) {
                AddDebugLogLineC(logKadSearch, wxT("Exception in CSearch::SendFindPeersForTarget: ") + e);
        } catch ( const CSafeIOException & ioe ) {
                AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "Exception in CSearch::SendFindPeersForTarget (IO error(%s))" ) ) % ioe.what() );
        }
}

void CSearch::ProcessPublishResult ( const CMemFile & bio )
{
        AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "#%i: processPublishResult" ) )
                         % m_searchID
                        );
        // Verify packet is expected size
        uint8_t minExpectedSize = 0 ;

        if ( m_type == STOREFILE ) minExpectedSize = 8 ;

        if ( bio.GetAvailable() < minExpectedSize ) {
                throw ( wxString ) ( CFormat ( wxT ( "***NOTE: Received wrong size (%llu, at least %u expected) packet in %s" ) ) %
                                     bio.GetAvailable() % minExpectedSize % wxString::FromAscii ( __func__ ) );
        }


        if ( m_type == STOREFILE ) {
                // update source counts in file entry
                uint32_t total = bio.ReadUInt32();
                uint32_t complete = bio.ReadUInt32();

                uint8_t fileid[16];
                m_target.ToByteArray ( fileid );
                CKnownFile * file =	theApp->sharedfiles->GetFileByID ( CMD4Hash ( fileid ) ) ;

                if ( file != NULL ) {
                        file->AddTagUnique ( CTag ( TAG_COMPLETE_SOURCES, uint64_t
                                                    (std::max ( ( uint32_t ) file->GetIntTagValue ( TAG_COMPLETE_SOURCES ), complete )) ) );
                        file->AddTagUnique ( CTag ( TAG_SOURCES, uint64_t
                                                    (std::max ( ( uint32_t ) file->GetIntTagValue ( TAG_SOURCES ), total )) ) );
                }
        }
        if ( bio.GetLength() > bio.GetPosition() ) {
                uint8_t load = bio.ReadUInt8();
                UpdateNodeLoad ( load );
        }

        m_searchResults  ++ ;
        m_completedPeers ++ ;
}

void CSearch::PreparePacketForTags(CMemFile *bio, CKnownFile *file)
/*! Prepares a CSearch for keyword publishing.
 * Puts infos (taglist) in a packet :
 * - file name
 * - file size
 * - number of complete sources
 * - total number of sources
 * - OPT: file type
 * - OPT: file format
 * - OPT: TAG_MEDIA_ARTIST
 * - OPT: TAG_MEDIA_ALBUM
 * - OPT: TAG_MEDIA_TITLE
 * - OPT: TAG_MEDIA_LENGTH
 * - OPT: TAG_MEDIA_BITRATE
 * - OPT: TAG_MEDIA_CODEC
 */
{
        // We're going to publish a keyword, set up the tag list
        TagList taglist;

        try {
                if (file && bio) {
                        // Name, Size
                        taglist.push_back ( CTag ( TAG_FILENAME, file->GetFileName().GetPrintable() ) );
                        taglist.push_back ( CTag ( TAG_FILESIZE, file->GetFileSize() ) );
                        taglist.push_back ( file->GetTag ( TAG_COMPLETE_SOURCES ) );
                        taglist.push_back ( file->GetTag ( TAG_SOURCES ) );

                        AddDebugLogLineN(logKadSearch, CFormat ( wxT ( "PreparePacketForTags. Filename : %s, TAG_SOURCES : %d" ) )
                                         % file->GetFileName().GetRaw() % file->m_nCompleteSourcesCount );

                        // eD2K file type (Audio, Video, ...)
                        // NOTE: Archives and CD-Images are published with file type "Pro"
                        wxString strED2KFileType(GetED2KFileTypeSearchTerm(GetED2KFileTypeID(file->GetFileName())));
                        if (!strED2KFileType.IsEmpty()) {
                                taglist.push_back ( CTag ( TAG_FILETYPE, strED2KFileType ) );
                        }


                        // additional meta data (Artist, Album, Codec, Length, ...)
                        // only send verified meta data to nodes
                        if (file->GetMetaDataVer() > 0) {
                                static const struct {
                                        uint8_t	nName;
                                        uint8_t	nType;
                                } _aMetaTags[] = {
                                        { TAG_MEDIA_ARTIST,  2 },
                                        { TAG_MEDIA_ALBUM,   2 },
                                        { TAG_MEDIA_TITLE,   2 },
                                        { TAG_MEDIA_LENGTH,  3 },
                                        { TAG_MEDIA_BITRATE, 3 },
                                        { TAG_MEDIA_CODEC,   2 }
                                };
                                for ( size_t i = 0; i < itemsof ( _aMetaTags ); i++ ) {
                                        const ::CTag& pTag = file->GetTag ( _aMetaTags[i].nName, _aMetaTags[i].nType );

                                        if ( pTag.isValid() ) {
                                                // skip string tags with empty string values
                                                if ( pTag . IsStr() && pTag . GetStr().IsEmpty() ) {
                                                        continue;
                                                }
                                                // skip integer tags with '0' values
                                                if ( pTag . IsInt() && pTag . GetInt() == 0 ) {
                                                        continue;
                                                }
                                                taglist.push_back ( pTag ) ;
                                        }
                                }
                        }
                        taglist.WriteTo ( bio );

                        taglist.deleteTags();
                } else if ( bio ) {
                        // empty tag list
                        taglist.WriteTo ( bio );
                } else {
                        //If we get here.. Bad things happen.. Will fix this later if it is a real issue.
                        wxASSERT ( 0 );
                }
        } catch (const CEOFException& err) {
                AddDebugLogLineC(logKadSearch, wxT("CEOFException in CSearch::PreparePacketForTags: ") + err.what());
        } catch (const CInvalidPacket& err) {
                AddDebugLogLineC(logKadSearch, wxT("CInvalidPacket Exception in CSearch::PreparePacketForTags: ") + err.what());
        } catch (const wxString& e) {
                AddDebugLogLineC(logKadSearch, wxT("Exception in CSearch::PreparePacketForTags: ") + e);
        }
}


void CSearch::SetSearchTermData( CMemFile * searchTermsData)
{
        m_searchTerms.Reset();
        m_searchTerms.Write( searchTermsData->GetRawBuffer(), searchTermsData->GetLength() ) ;
}


time_t CSearch::GetSearchLifetime() const throw()

{
        switch ( GetSearchTypes() ) {
        case FILE               : return SEARCHFILE_LIFETIME ;

        case KEYWORD            : return SEARCHKEYWORD_LIFETIME ;

        case NOTES              : return SEARCHNOTES_LIFETIME ;

        case NODE               : return SEARCHNODE_LIFETIME ;

        case NODECOMPLETE       : return SEARCHNODECOMP_LIFETIME ;

        case STOREFILE          : return SEARCHSTOREFILE_LIFETIME ;

        case STOREKEYWORD       : return SEARCHSTOREKEYWORD_LIFETIME ;

        case STORENOTES	        : return SEARCHSTORENOTES_LIFETIME ;

        default                 : return SEARCH_LIFETIME ;
        }
}

uint32_t CSearch::GetSearchMaxAnswers() const throw()
{
        switch ( GetSearchTypes() ) {
        case FILE                           : return SEARCHFILE_TOTAL ;

        case KEYWORD                        : return SEARCHKEYWORD_TOTAL ;

        case NOTES                          : return SEARCHNOTES_TOTAL ;

        case NODE                           : return 1 ;

        case NODECOMPLETE                   : return SEARCHNODECOMP_TOTAL ;

        case STOREFILE                      : return SEARCHSTOREFILE_TOTAL ;

        case STOREKEYWORD                   : return SEARCHSTOREKEYWORD_TOTAL ;

        case STORENOTES	                    : return SEARCHSTORENOTES_TOTAL ;

        default                             : return 1 ;
        }
}

uint8_t CSearch::GetRequestContactCount() const throw()
{
        // Returns the amount of contacts we request on routing queries based on the search type
        switch (m_type) {
        case NODE:
        case NODECOMPLETE:
        case NODESPECIAL:
                return KADEMLIA_FIND_NODE;
        case FILE:
        case KEYWORD:
        case NOTES:
                return KADEMLIA_FIND_VALUE;
        case STOREFILE:
        case STOREKEYWORD:
        case STORENOTES:
                return KADEMLIA_STORE;
        default:
                AddDebugLogLineN(logKadSearch, wxT("Invalid search type. (CSearch::GetRequestContactCount())"));
                wxFAIL;
                return 0;
        }
}

wxString CSearch::typestr()
{
        switch(m_type) {
        case NODE: return wxString(wxT("NODE"));
        case NODECOMPLETE: return wxString(wxT("NODECOMPLETE"));
        case NODESPECIAL: return wxString(wxT("NODESPECIAL"));
        case FILE: return wxString(wxT("FILE"));
        case KEYWORD: return wxString(wxT("KEYWORD"));
        case NOTES: return wxString(wxT("NOTES"));
        case STOREFILE: return wxString(wxT("STOREFILE"));
        case STOREKEYWORD: return wxString(wxT("STOREKEYWORD"));
        case STORENOTES: return wxString(wxT("STORENOTES"));
        default: return wxString(wxT("syntax error"));
        }
}