//
// This file is part of the imule Project.
//
// Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
// Copyright (c) 2002-2011 Merkur ( devs@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
//

#include "ClientList.h"		// Interface declarations.

#include <protocol/Protocols.h>
#include <protocol/ed2k/Constants.h>
#include <protocol/kad/Client2Client/UDP.h>
#include <protocol/kad/Constants.h>
#include <protocol/kad2/Client2Client/TCP.h>

#include "amule.h"		// Needed for theApp
#include "ClientTCPSocket.h"	// Needed for CClientTCPSocket
#include "DownloadQueue.h"	// Needed for CDownloadQueue
#include "UploadQueue.h"	// Needed for CUploadQueue
#include "IPFilter.h"		// Needed for CIPFIlter
#include "updownclient.h"	// Needed for CUpDownClient
#include "Preferences.h"	// Needed for thePrefs
#include "Statistics.h"		// Needed for theStats
#include "Logger.h"
#include "GuiEvents.h"		// Needed for Notify_*
#include "Packet.h"

#include <common/Format.h>

#include "kademlia/kademlia/Search.h"
#include "kademlia/kademlia/SearchManager.h"
// #include "kademlia/kademlia/UDPFirewallTester.h" // not for i2p
#include "kademlia/net/KademliaUDPListener.h"
#include "kademlia/routing/Contact.h"

#include "i2p/CI2PAddress.h"

/**
 * CDeletedClient Class
 *
 * This class / list is a bit overkill, but currently needed to avoid any
 * exploit possibility. It will keep track of certain clients attributes
 * for 2 hours, while the CUpDownClient object might be deleted already.
 * Currently saves: IP, Port, UserHash.
 */
class CDeletedClient
{
public:
        CDeletedClient(CUpDownClient* pClient) {
                m_dwInserted = ::GetTickCount();
                PortAndHash porthash = { 0, pClient->GetCreditsHash()};
                m_ItemsList.push_back(porthash);
        }

        struct PortAndHash {
                uint16 nPort;
                void* pHash;
        };

        typedef std::list<PortAndHash> PaHList;
        PaHList	m_ItemsList;
        uint32	m_dwInserted;
};



CClientList::CClientList()
        : m_deadSources( true )
{
        m_dwLastBannCleanUp = 0;
        m_dwLastTrackedCleanUp = 0;
        m_dwLastClientCleanUp = 0;
}


CClientList::~CClientList()
{
        DeleteContents(m_trackedClientsList);

        wxASSERT(m_clientList.empty());
}


void CClientList::AddClient( CUpDownClient* toadd )
{
        // Ensure that only new clients can be added to the list
        if ( toadd->GetClientState() == CS_NEW ) {
                // Update the client-state
                toadd->m_clientState = CS_LISTED;

                //Notify_ClientCtrlAddClient( toadd );

                // We always add the ID/ptr pair, regardles of the actual ID value
                m_clientList.insert( IDMapPair( toadd->GetUserIDHybrid(), CCLIENTREF(toadd, wxT("CClientList::AddClient m_clientList.insert"))) );

                // We only add the IP if it is valid
                if ( toadd->GetTCPDest() ) {
                        m_ipList.insert( IDMapPair( toadd->GetTCPDest(), CCLIENTREF(toadd, wxT("CClientList::AddClient m_ipList.insert")) ) );
                }
                // We only add the hash if it is valid
                if ( toadd->HasValidHash() ) {
                        m_hashList.insert( HashMapPair( toadd->GetUserHash(), CCLIENTREF(toadd, wxT("CClientList::AddClient m_hashList.insert")) ) );
                }

                toadd->UpdateStats();
        }
}


void CClientList::RemoveClient(CUpDownClient* client)
{
        //I2P : KadList is only for firewall -- RemoveFromKadList( client );
        //I2P : no buddy -- RemoveDirectCallback( client );

        if ( RemoveIDFromList( client ) ) {
                // Also remove the ip and hash entries
                RemoveIPFromList( client );
                RemoveHashFromList( client );
        }
}


void CClientList::UpdateClientID( CUpDownClient* client, uint32 newID )
{
        // Sanity check
        if ( ( client->GetClientState() != CS_LISTED ) || ( client->GetUserIDHybrid() == newID ) )
                return;

        // First remove the ID entry
        RemoveIDFromList( client );

        // Add the new entry
        m_clientList.insert( IDMapPair( newID, CCLIENTREF(client, wxT("CClientList::UpdateClientID")) ) );
}


void CClientList::UpdateClientIP( CUpDownClient* client, uint32 newIP )
{
        // Sanity check
        if ( ( client->GetClientState() != CS_LISTED ) || ( client->GetIP() == newIP ) )
                return;

        // Remove the old IP entry
        RemoveIPFromList( client );

        if ( newIP ) {
                m_ipList.insert( IDMapPair( newIP, CCLIENTREF(client, wxT("CClientList::UpdateClientIP")) ) );
        }
}


void CClientList::UpdateClientHash( CUpDownClient* client, const CMD4Hash& newHash )
{
        // Sanity check
        if ( ( client->GetClientState() != CS_LISTED ) || ( client->GetUserHash() == newHash ) )
                return;


        // Remove the old entry
        RemoveHashFromList( client );

        // And add the new one if valid
        if ( !newHash.IsEmpty() ) {
                m_hashList.insert( HashMapPair( newHash, CCLIENTREF(client, wxT("CClientList::UpdateClientHash")) ) );
        }
}


bool CClientList::RemoveIDFromList( CUpDownClient* client )
{
        bool result = false;

        // First remove the ID entry
        std::pair<IDMap::iterator, IDMap::iterator> range = m_clientList.equal_range( client->GetUserIDHybrid() );

        for ( ; range.first != range.second; ++range.first ) {
                if ( client == range.first->second.GetClient() ) {
                        /* erase() will invalidate the iterator, but we're not using it anymore
                            anyway (notice the break;) */
                        m_clientList.erase( range.first );
                        result = true;

                        break;
                }
        }

        return result;
}


void CClientList::RemoveIPFromList( CUpDownClient* client )
{
        // Check if we need to look for the IP entry
        if ( !client->GetTCPDest() ) {
                return;
        }

        // Remove the IP entry
        std::pair<IDMap::iterator, IDMap::iterator> range = m_ipList.equal_range( client->GetIP() );

        for ( ; range.first != range.second; ++range.first ) {
                if ( client == range.first->second.GetClient() ) {
                        /* erase() will invalidate the iterator, but we're not using it anymore
                            anyway (notice the break;) */
                        m_ipList.erase( range.first );
                        break;
                }
        }
}
void CClientList::RemoveHashFromList( CUpDownClient* client )
{
        // Nothing to remove
        if ( !client->HasValidHash() ) {
                return;
        }

        // Find all items with the specified hash
        std::pair<HashMap::iterator, HashMap::iterator> range = m_hashList.equal_range( client->GetUserHash() );

        for ( ; range.first != range.second; ++range.first ) {
                if ( client == range.first->second.GetClient() ) {
                        /* erase() will invalidate the iterator, but we're not using it anymore
                            anyway (notice the break;) */
                        m_hashList.erase( range.first );
                        break;
                }
        }
}


CUpDownClient* CClientList::FindMatchingClient( CUpDownClient* client )
{
        typedef std::pair<IDMap::const_iterator, IDMap::const_iterator> IDMapIteratorPair;
        wxCHECK(client, NULL);

        const uint32 userDest = client->GetTCPDest();
        const uint32 userID = client->GetUserIDHybrid();
        const uint32 userKadDest = client->GetUDPDest();


        if (userDest || userKadDest) {
                // Check by dest, then ID
                struct { const IDMap& map; uint32 value; } toCheck[] = {
                        { m_ipList, userDest }, { m_clientList, userDest }
                };

                for (size_t i = 0; i < itemsof(toCheck); ++i) {
                        if (toCheck[i].value == 0) {
                                // We may not have both (or any) of these values.
                                continue;
                        }

                        IDMapIteratorPair range = toCheck[i].map.equal_range(toCheck[i].value);

                        if (userDest) {
                                IDMap::const_iterator it = range.first;
                                for (; it != range.second; ++it) {
                                        if (userDest == it->second.GetClient()->GetTCPDest()) {
                                                return it->second.GetClient();
                                        }
                                }
                        }

                        if (userKadDest) {
                                IDMap::const_iterator it = range.first;
                                for (; it != range.second; ++it) {
                                        if (userKadDest == it->second.GetClient()->GetUDPDest()) {
                                                return it->second.GetClient();
                                        }
                                }
                        }
                }
        }


        // If anything else fails, then we look at hashes
        if ( client->HasValidHash() ) {
                // Find all items with the specified hash
                std::pair<HashMap::iterator, HashMap::iterator> range = m_hashList.equal_range( client->GetUserHash() );

                // Just return the first item if any
                if ( range.first != range.second ) {
                        return range.first->second.GetClient();
                }
        }

        // Nothing found, must be a new client
        return NULL;
}


uint32 CClientList::GetClientCount() const
{
        return m_clientList.size();
}


void CClientList::DeleteAll()

{
        m_ipList.clear();
        m_hashList.clear();

        while ( !m_clientList.empty() ) {
                IDMap::iterator it = m_clientList.begin();

                // Will call the removal of the item on this same class
                it->second.GetClient()->Disconnected(wxT("Removed while deleting all from ClientList."));
                it->second.GetClient()->Safe_Delete();
        }
}


bool CClientList::AttachToAlreadyKnown(CUpDownClient** client, CClientTCPSocket* sender)
{
        CUpDownClient* tocheck = (*client);

        CUpDownClient* found_client = FindMatchingClient( tocheck );

        if ( tocheck == found_client ) {
                // We found the same client instance (client may have sent more than one OP_HELLO). do not delete that client!
                return true;
        }

        if ( found_client != NULL ) {
                if ( sender ) {
                        if ( found_client->GetSocket() ) {
                                if ( found_client->IsConnected()
                                                && ( found_client->GetTCPDest() != tocheck->GetTCPDest() ) ) {
                                        // if found_client is connected and has the IS_IDENTIFIED, it's safe to say that the other one is a bad guy
                                        if (found_client->IsIdentified()) {
                                                AddDebugLogLineN(logClient, wxT("Client: ") + tocheck->GetUserName() + wxT("(") + tocheck->GetTCPDest().humanReadable() +  wxT("), Banreason: Userhash invalid"));
                                                tocheck->Ban();
                                                return false;
                                        }

                                        AddDebugLogLineN(logClient, wxT("WARNING! Found matching client, to a currently connected client: ")
                                                         + tocheck->GetUserName() + wxT("(") +  tocheck->GetTCPDest().humanReadable()
                                                         + wxT(") and ") + found_client->GetUserName() + wxT("(") +  found_client->GetTCPDest().humanReadable() + wxT(")"));
                                        return false;
                                }
                                found_client->GetSocket()->Safe_Delete();
                        }
                        found_client->SetSocket( sender );
                        tocheck->SetSocket( NULL );
                }
                *client = 0;
                tocheck->Safe_Delete();
                *client = found_client;
                return true;
        }

        return false;
}


CUpDownClient* CClientList::FindClientByIP( uint32 clientip, uint16 port)
{
        // Find all items with the specified ip
        std::pair<IDMap::iterator, IDMap::iterator> range = m_ipList.equal_range( clientip );

        for ( ; range.first != range.second; ++range.first ) {
                CUpDownClient* cur_client = range.first->second.GetClient();
                // Check if it's actually the client we want
                if ( cur_client->GetUserPort() == port ) {
                        return cur_client;
                }
        }

        return NULL;
}


CUpDownClient* CClientList::FindClientByIP( uint32 clientip )
{
        // Find all items with the specified ip
        std::pair<IDMap::iterator, IDMap::iterator> range = m_ipList.equal_range( clientip );

        return (range.first != range.second) ? range.first->second.GetClient() : NULL;
}


CUpDownClient* CClientList::FindClientByECID(uint32 ecid) const
{
        for (IDMap::const_iterator it =	m_clientList.begin(); it != m_clientList.end(); it++) {
                if (it->second.ECID() == ecid) {
                        return it->second.GetClient();
                }
        }

        return NULL;
}


bool CClientList::IsIPAlreadyKnown(uint32_t ip)
{
        // Find all items with the specified ip
        std::pair<IDMap::iterator, IDMap::iterator> range = m_ipList.equal_range(ip);
        return range.first != range.second;
}


bool CClientList::ComparePriorUserhash(uint32 dwIP, uint16 nPort, void* pNewHash)
{
        std::map<uint32, CDeletedClient*>::iterator it = m_trackedClientsList.find( dwIP );

        if ( it != m_trackedClientsList.end() ) {
                CDeletedClient* pResult = it->second;

                CDeletedClient::PaHList::iterator it2 = pResult->m_ItemsList.begin();
                for ( ; it2 != pResult->m_ItemsList.end(); ++it2 ) {
                        if ( it2->nPort == nPort ) {
                                if ( it2->pHash != pNewHash) {
                                        return false;
                                } else {
                                        break;
                                }
                        }
                }
        }
        return true;
}


void CClientList::AddTrackClient(CUpDownClient* toadd)
{
        std::map<uint32, CDeletedClient*>::iterator it = m_trackedClientsList.find( toadd->GetTCPDest() );

        if ( it != m_trackedClientsList.end() ) {
                CDeletedClient* pResult = it->second;

                pResult->m_dwInserted = ::GetTickCount();

                CDeletedClient::PaHList::iterator it2 = pResult->m_ItemsList.begin();
                for ( ; it2 != pResult->m_ItemsList.end(); ++it2 ) {
                        if ( it2->nPort == toadd->GetUserPort() ) {
                                // already tracked, update
                                it2->pHash = toadd->GetCreditsHash();
                                return;
                        }
                }

                // New client for that IP, add an entry
                CDeletedClient::PortAndHash porthash = { toadd->GetUserPort(), toadd->GetCreditsHash()};
                pResult->m_ItemsList.push_back(porthash);
        } else {
                m_trackedClientsList[ toadd->GetTCPDest() ] = new CDeletedClient(toadd);
        }
}


uint16 CClientList::GetClientsFromIP(uint32 dwIP)
{
        std::map<uint32, CDeletedClient*>::iterator it = m_trackedClientsList.find( dwIP );

        if ( it != m_trackedClientsList.end() ) {
                return it->second->m_ItemsList.size();
        } else {
                return 0;
        }
}


void CClientList::Process()
{
        const uint32 cur_tick = ::GetTickCount();

        if (m_dwLastBannCleanUp + BAN_CLEANUP_TIME < cur_tick) {
                m_dwLastBannCleanUp = cur_tick;

                ClientMap::iterator it = m_bannedList.begin();
                while ( it != m_bannedList.end() ) {
                        if ( it->second + CLIENTBANTIME < cur_tick ) {
                                ClientMap::iterator tmp = it++;

                                m_bannedList.erase( tmp );
                                theStats::RemoveBannedClient();
                        } else {
                                ++it;
                        }
                }
        }


        if ( m_dwLastTrackedCleanUp + TRACKED_CLEANUP_TIME < cur_tick ) {
                m_dwLastTrackedCleanUp = cur_tick;

                std::map<uint32, CDeletedClient*>::iterator it = m_trackedClientsList.begin();
                while ( it != m_trackedClientsList.end() ) {
                        std::map<uint32, CDeletedClient*>::iterator cur_src = it++;

                        if ( cur_src->second->m_dwInserted + KEEPTRACK_TIME < cur_tick ) {
                                delete cur_src->second;
                                m_trackedClientsList.erase( cur_src );
                        }
                }
        }
#ifdef this_seems_to_be_related_to_firewalls_and_is_not_useful_in_i2p
        //We need to try to connect to the clients in m_KadList
        //If connected, remove them from the list and send a message back to Kad so we can send a ACK.
        //If we don't connect, we need to remove the client..
        //The sockets timeout should delete this object.

        // buddy is just a flag that is used to make sure we are still connected or connecting to a buddy.
        buddyState buddy = Disconnected;

        CClientRefSet::iterator current_it = m_KadSources.begin();
        while (current_it != m_KadSources.end()) {
                CUpDownClient* cur_client = current_it->GetClient();
                ++current_it; // Won't be used anymore till while loop
                if( !Kademlia::CKademlia::IsRunning() ) {
                        //Clear out this list if we stop running Kad.
                        //Setting the Kad state to KS_NONE causes it to be removed in the switch below.
                        cur_client->SetKadState(KS_NONE);
                }
                switch (cur_client->GetKadState()) {
                case KS_QUEUED_FWCHECK:
                case KS_QUEUED_FWCHECK_UDP:
                        //Another client asked us to try to connect to them to check their firewalled status.
                        cur_client->TryToConnect(true);
                        break;

                case KS_CONNECTING_FWCHECK:
                        //Ignore this state as we are just waiting for results.
                        break;

                case KS_FWCHECK_UDP:
                case KS_CONNECTING_FWCHECK_UDP:
                        // We want a UDP firewallcheck from this client and are just waiting to get connected to send the request
                        break;

                case KS_CONNECTED_FWCHECK:
                        //We successfully connected to the client.
                        //We now send a ack to let them know.
                        if (cur_client->GetKadVersion() >= 7) {
                                // The result is now sent per TCP instead of UDP, because this will fail if our intern port is unreachable.
                                // But we want the TCP testresult regardless if UDP is firewalled, the new UDP state and test takes care of the rest
                                wxASSERT(cur_client->IsConnected());
                                AddDebugLogLineN(logLocalClient, wxT("Local Client: OP_KAD_FWTCPCHECK_ACK to ") + Uint32toStringIP(cur_client->GetIP()));
                                CPacket *packet = new CPacket(OP_KAD_FWTCPCHECK_ACK, 0, OP_EMULEPROT);
                                cur_client->SafeSendPacket(packet);
                        } else {
                                AddDebugLogLineN(logClientKadUDP, wxT("KadFirewalledAckRes to ") + Uint32_16toStringIP_Port(cur_client->GetIP(), cur_client->GetKadPort()));
                                Kademlia::CKademlia::GetUDPListener()->SendNullPacket(KADEMLIA_FIREWALLED_ACK_RES, wxUINT32_SWAP_ALWAYS(cur_client->GetIP()), cur_client->GetKadPort(), 0, NULL);
                        }
                        //We are done with this client. Set Kad status to KS_NONE and it will be removed in the next cycle.
                        cur_client->SetKadState(KS_NONE);
                        break;

                case KS_INCOMING_BUDDY:
                        //A firewalled client wants us to be his buddy.
                        //If we already have a buddy, we set Kad state to KS_NONE and it's removed in the next cycle.
                        //If not, this client will change to KS_CONNECTED_BUDDY when it connects.
                        if( m_nBuddyStatus == Connected ) {
                                cur_client->SetKadState(KS_NONE);
                        }
                        break;

                case KS_QUEUED_BUDDY:
                        //We are firewalled and want to request this client to be a buddy.
                        //But first we check to make sure we are not already trying another client.
                        //If we are not already trying. We try to connect to this client.
                        //If we are already connected to a buddy, we set this client to KS_NONE and it's removed next cycle.
                        //If we are trying to connect to a buddy, we just ignore as the one we are trying may fail and we can then try this one.
                        if( m_nBuddyStatus == Disconnected ) {
                                buddy = Connecting;
                                m_nBuddyStatus = Connecting;
                                cur_client->SetKadState(KS_CONNECTING_BUDDY);
                                cur_client->TryToConnect(true);
                                Notify_ServerUpdateED2KInfo();
                        } else {
                                if( m_nBuddyStatus == Connected ) {
                                        cur_client->SetKadState(KS_NONE);
                                }
                        }
                        break;

                case KS_CONNECTING_BUDDY:
                        //We are trying to connect to this client.
                        //Although it should NOT happen, we make sure we are not already connected to a buddy.
                        //If we are we set to KS_NONE and it's removed next cycle.
                        //But if we are not already connected, make sure we set the flag to connecting so we know
                        //things are working correctly.
                        if( m_nBuddyStatus == Connected ) {
                                cur_client->SetKadState(KS_NONE);
                        } else {
                                wxASSERT( m_nBuddyStatus == Connecting );
                                buddy = Connecting;
                        }
                        break;

                case KS_CONNECTED_BUDDY:
                        //A potential connected buddy client wanting to me in the Kad network
                        //We set our flag to connected to make sure things are still working correctly.
                        buddy = Connected;

                        //If m_nBuddyStatus is not connected already, we set this client as our buddy!
                        if( m_nBuddyStatus != Connected ) {
                                m_pBuddy.Link(cur_client CLIENT_DEBUGSTRING("CClientList::Process KS_CONNECTED_BUDDY m_pBuddy.Link"));
                                m_nBuddyStatus = Connected;
                                Notify_ServerUpdateED2KInfo();
                        }
                        if( m_pBuddy.GetClient() == cur_client && theApp->IsFirewalled() && cur_client->SendBuddyPingPong() ) {
                                cur_client->SendBuddyPing();
                        }
                        break;

                default:
                        RemoveFromKadList(cur_client);
                }
        }

        //We either never had a buddy, or lost our buddy..
        if( buddy == Disconnected ) {
                if( m_nBuddyStatus != Disconnected || m_pBuddy.IsLinked() ) {
                        if( Kademlia::CKademlia::IsRunning() && theApp->IsFirewalled() && Kademlia::CUDPFirewallTester::IsFirewalledUDP(true) ) {
                                //We are a lowID client and we just lost our buddy.
                                //Go ahead and instantly try to find a new buddy.
                                Kademlia::CKademlia::GetPrefs()->SetFindBuddy();
                        }
                        m_pBuddy.Unlink();
                        m_nBuddyStatus = Disconnected;
                        Notify_ServerUpdateED2KInfo();
                }
        }

        if ( Kademlia::CKademlia::IsConnected() ) {
                // we only need a buddy if direct callback is not available
                if(Kademlia::CKademlia::IsFirewalled() && Kademlia::CUDPFirewallTester::IsFirewalledUDP(true)) {
                        // TODO: Kad buddies won't work with RequireCrypt, so it is disabled for now, but should (and will)
                        // be fixed in later version
                        // Update: buddy connections themselves support obfuscation properly since eMule 0.49a and aMule SVN 2008-05-09
                        // (this makes it work fine if our buddy uses require crypt), however callback requests don't support it yet so we
                        // wouldn't be able to answer callback requests with RequireCrypt, protocolchange intended for eMule 0.49b
                        if(m_nBuddyStatus == Disconnected && Kademlia::CKademlia::GetPrefs()->GetFindBuddy() && !thePrefs::IsClientCryptLayerRequired()) {
                                AddDebugLogLineN(logKadMain, wxT("Starting BuddySearch"));
                                //We are a firewalled client with no buddy. We have also waited a set time
                                //to try to avoid a false firewalled status.. So lets look for a buddy..
                                if (!Kademlia::CSearchManager::PrepareLookup(Kademlia::CSearch::FINDBUDDY, true, Kademlia::CUInt128(true).XOR(Kademlia::CKademlia::GetPrefs()->GetKadID()))) {
                                        //This search ID was already going. Most likely reason is that
                                        //we found and lost our buddy very quickly and the last search hadn't
                                        //had time to be removed yet. Go ahead and set this to happen again
                                        //next time around.
                                        Kademlia::CKademlia::GetPrefs()->SetFindBuddy();
                                }
                        }
                } else {
                        if (m_pBuddy.IsLinked()) {
                                //Lets make sure that if we have a buddy, they are firewalled!
                                //If they are also not firewalled, then someone must have fixed their firewall or stopped saturating their line..
                                //We just set the state of this buddy to KS_NONE and things will be cleared up with the next cycle.
                                if( !m_pBuddy.HasLowID() ) {
                                        m_pBuddy.GetClient()->SetKadState(KS_NONE);
                                }
                        }
                }
        } else {
                if (m_pBuddy.IsLinked()) {
                        //We are not connected anymore. Just set this buddy to KS_NONE and things will be cleared out on next cycle.
                        m_pBuddy.GetClient()->SetKadState(KS_NONE);
                }
        }
#endif
        CleanUpClientList();
        //I2P : no woman no cry, no firewall no callback -- ProcessDirectCallbackList();
}


void CClientList::AddBannedClient(uint32 dwIP)
{
        m_bannedList[dwIP] = ::GetTickCount();
        theStats::AddBannedClient();
}


bool CClientList::IsBannedClient(uint32 dwIP)
{
        ClientMap::iterator it = m_bannedList.find( dwIP );

        if ( it != m_bannedList.end() ) {
                if ( it->second + CLIENTBANTIME > ::GetTickCount() ) {
                        return true;
                } else {
                        RemoveBannedClient(dwIP);
                }
        }
        return false;
}


void CClientList::RemoveBannedClient(uint32 dwIP)
{
        m_bannedList.erase(dwIP);
        theStats::RemoveBannedClient();
}


void CClientList::FilterQueues()
{
        // Filter client list
        for ( IDMap::iterator it = m_ipList.begin(); it != m_ipList.end(); ) {
                IDMap::iterator tmp = it++; // Don't change this to a ++it!
                CUpDownClient* client = tmp->second.GetClient();
                if ( theApp->ipfilter->IsFiltered(client->GetTCPDest())) {
                        client->Disconnected(wxT("Filtered by IPFilter"));
                        client->Safe_Delete();
                }
        }
}


CClientList::SourceList	CClientList::GetClientsByHash( const CMD4Hash& hash )
{
        SourceList results;

        // Find all items with the specified hash
        std::pair<HashMap::iterator, HashMap::iterator> range = m_hashList.equal_range( hash );

        for ( ; range.first != range.second; ++range.first)  {
                results.push_back( range.first->second );
        }

        return results;
}


CClientList::SourceList	CClientList::GetClientsByIP( unsigned long ip )
{
        SourceList results;

        // Find all items with the specified hash
        std::pair<IDMap::iterator, IDMap::iterator> range = m_ipList.equal_range( ip );

        for ( ; range.first != range.second; range.first++ )  {
                results.push_back( range.first->second );
        }

        return results;
}


const CClientList::IDMap& CClientList::GetClientList()
{
        return m_clientList;
}


void CClientList::AddDeadSource(const CUpDownClient* client)
{
        m_deadSources.AddDeadSource( client );
}


bool CClientList::IsDeadSource(const CUpDownClient* client)
{
        return m_deadSources.IsDeadSource( client );
}

bool CClientList::SendChatMessage(const CI2PAddress & client_id, const wxString& message)
{
        CUpDownClient* client = FindClientByIP(client_id);
        AddDebugLogLineN( logClient, wxT("Trying to Send Message.") );
        if (client) {
                AddDebugLogLineN( logClient, wxT("Sending.") );
        } else {
                AddDebugLogLineC( logClient,
                                  CFormat( wxT("No client (GUI_ID %s) found in CClientList::SendChatMessage(). Creating") )
                                  % client_id.humanReadable()
                                );
                client = new CUpDownClient(client_id, CI2PAddress::null, NULL, true, true);
                AddClient(client);
        }
        return client->SendChatMessage(message);
}

void CClientList::SetChatState(uint64 client_id, uint8 state)
{
        CUpDownClient* client = FindClientByIP(client_id);
        if (client) {
                client->SetChatState(state);
        }
}

/* Kad stuff */
#ifdef kad_stuff_seems_to_mean_firewall_stuff_and_no_firewall_with_i2p
bool CClientList::RequestTCP(Kademlia::CContact* contact, uint8_t connectOptions)
{
        uint32_t nContactIP = wxUINT32_SWAP_ALWAYS(contact->GetIPAddress());
        // don't connect ourself
        if (theApp->GetPublicIP() == nContactIP && thePrefs::GetPort() == contact->GetTCPPort()) {
                return false;
        }

        CUpDownClient* pNewClient = FindClientByIP(nContactIP, contact->GetTCPPort());

        if (!pNewClient) {
                //#warning Do we actually have to check friendstate here?
                pNewClient = new CUpDownClient(contact->GetTCPPort(), contact->GetIPAddress(), 0, 0, NULL, false, true);
        } else if (pNewClient->GetKadState() != KS_NONE) {
                return false; // already busy with this client in some way (probably buddy stuff), don't mess with it
        }

        //Add client to the lists to be processed.
        pNewClient->SetKadPort(contact->GetUDPPort());
        pNewClient->SetKadState(KS_QUEUED_FWCHECK);
        if (contact->GetClientID() != 0) {
                uint8_t ID[16];
                contact->GetClientID().ToByteArray(ID);
                pNewClient->SetUserHash(CMD4Hash(ID));
                pNewClient->SetConnectOptions(connectOptions, true, false);
        }
        AddToKadList(pNewClient); // This was a direct adding, but I like to check duplicates
        //This method checks if this is a dup already.
        AddClient(pNewClient);
        return true;
}

void CClientList::RequestBuddy(Kademlia::CContact* contact, uint8_t connectOptions)
{
        uint32_t nContactIP = wxUINT32_SWAP_ALWAYS(contact->GetIPAddress());
        // Don't connect to ourself
        if (theApp->GetPublicIP() == nContactIP && thePrefs::GetPort() == contact->GetTCPPort()) {
                return;
        }

        CUpDownClient* pNewClient = FindClientByIP(nContactIP, contact->GetTCPPort());
        if (!pNewClient) {
                pNewClient = new CUpDownClient(contact->GetTCPPort(), contact->GetIPAddress(), 0, 0, NULL, false, true );
        } else if (pNewClient->GetKadState() != KS_NONE) {
                return; // already busy with this client in some way (probably fw stuff), don't mess with it
        } else if (IsKadFirewallCheckIP(nContactIP)) { // doing a kad firewall check with this IP, abort
                AddDebugLogLineN(logKadMain, wxT("Kad TCP firewallcheck / Buddy request collision for IP ") + Uint32toStringIP(nContactIP));
                return;
        }

        //Add client to the lists to be processed.
        pNewClient->SetKadPort(contact->GetUDPPort());
        pNewClient->SetKadState(KS_QUEUED_BUDDY);
        uint8_t ID[16];
        contact->GetClientID().ToByteArray(ID);
        pNewClient->SetUserHash(CMD4Hash(ID));
        pNewClient->SetConnectOptions(connectOptions, true, false);
        AddToKadList(pNewClient);
        //This method checks if this is a dup already.
        AddClient(pNewClient);
}

bool CClientList::IncomingBuddy(Kademlia::CContact* contact, Kademlia::CUInt128* buddyID)
{
        uint32_t nContactIP = wxUINT32_SWAP_ALWAYS(contact->GetIPAddress());
        //If aMule already knows this client, abort this.. It could cause conflicts.
        //Although the odds of this happening is very small, it could still happen.
        if (FindClientByIP(nContactIP, contact->GetTCPPort())) {
                return false;
        } else if (IsKadFirewallCheckIP(nContactIP)) { // doing a kad firewall check with this IP, abort
                AddDebugLogLineN(logKadMain, wxT("Kad TCP firewallcheck / Buddy request collision for IP ") + Uint32toStringIP(nContactIP));
                return false;
        }

        if (theApp->GetPublicIP() == nContactIP && thePrefs::GetPort() == contact->GetTCPPort()) {
                return false; // don't connect ourself
        }

        //Add client to the lists to be processed.
        CUpDownClient* pNewClient = new CUpDownClient(contact->GetTCPPort(), contact->GetIPAddress(), 0, 0, NULL, false, true );
        pNewClient->SetKadPort(contact->GetUDPPort());
        pNewClient->SetKadState(KS_INCOMING_BUDDY);
        byte ID[16];
        contact->GetClientID().ToByteArray(ID);
        pNewClient->SetUserHash(CMD4Hash(ID));
        buddyID->ToByteArray(ID);
        pNewClient->SetBuddyID(ID);
        AddToKadList(pNewClient);
        AddClient(pNewClient);
        return true;
}

void CClientList::RemoveFromKadList(CUpDownClient* torem)
{
        wxCHECK_RET(torem, wxT("NULL pointer in RemoveFromKadList"));

        if (m_KadSources.erase(CCLIENTREF(torem, wxEmptyString))) {
                if (torem == m_pBuddy.GetClient()) {
                        m_pBuddy.Unlink();
                        m_nBuddyStatus = Disconnected;
                        Notify_ServerUpdateED2KInfo();
                }
        }
}

void CClientList::AddToKadList(CUpDownClient* toadd)
{
        wxCHECK_RET(toadd, wxT("NULL pointer in AddToKadList"));

        m_KadSources.insert(CCLIENTREF(toadd, wxT("CClientList::AddToKadList"))); // This will take care of duplicates.
}

bool CClientList::DoRequestFirewallCheckUDP(const Kademlia::CContact& contact)
{
        // first make sure we don't know this IP already from somewhere
        if (IsIPAlreadyKnown(wxUINT32_SWAP_ALWAYS(contact.GetIPAddress()))) {
                return false;
        }
        // fine, just create the client object, set the state and wait
        // TODO: We don't know the client's userhash, this means we cannot build an obfuscated connection, which
        // again mean that the whole check won't work on "Require Obfuscation" setting, which is not a huge problem,
        // but certainly not nice. Only somewhat acceptable way to solve this is to use the KadID instead.
        CUpDownClient* pNewClient = new CUpDownClient(contact.GetTCPPort(), contact.GetIPAddress(), 0, 0, NULL, false, true);
        pNewClient->SetKadState(KS_QUEUED_FWCHECK_UDP);
        AddDebugLogLineN(logClient, wxT("Selected client for UDP Firewallcheck: ") + KadIPToString(contact.GetIPAddress()));
        AddToKadList(pNewClient);
        AddClient(pNewClient);
        wxASSERT(!pNewClient->SupportsDirectUDPCallback());
        return true;
}
#endif
void CClientList::CleanUpClientList()
{
        // We remove clients which are not needed any more by time
        // this check is also done on CUpDownClient::Disconnected, however it will not catch all
        // cases (if a client changes the state without beeing connected
        //
        // Adding this check directly to every point where any state changes would be more effective,
        // is however not compatible with the current code, because there are points where a client has
        // no state for some code lines and the code is also not prepared that a client object gets
        // invalid while working with it (aka setting a new state)
        // so this way is just the easy and safe one to go (as long as amule is basically single threaded)
        const uint32 cur_tick = ::GetTickCount();
        if (m_dwLastClientCleanUp + CLIENTLIST_CLEANUP_TIME < cur_tick ) {
                m_dwLastClientCleanUp = cur_tick;
                DEBUG_ONLY( uint32 cDeleted = 0; )
                IDMap::iterator current_it = m_clientList.begin();
                while (current_it != m_clientList.end()) {
                        CUpDownClient* pCurClient = current_it->second.GetClient();
                        ++current_it; // Won't be used till while loop again
                        // Don't delete sources coming from source seeds for 10 mins,
                        // to give them a chance to connect and become a useful source.
                        if (pCurClient->GetSourceFrom() == SF_SOURCE_SEEDS && cur_tick - (uint32)theStats::GetStartTime() < MIN2MS(10)) continue;
                        if ((pCurClient->GetUploadState() == US_NONE || (pCurClient->GetUploadState() == US_BANNED && !pCurClient->IsBanned()))
                                        && pCurClient->GetDownloadState() == DS_NONE
                                        && pCurClient->GetChatState() == MS_NONE
                                        && true // I2P -- pCurClient->GetKadState() == KS_NONE
                                        && pCurClient->GetSocket() == NULL) {
                                DEBUG_ONLY( cDeleted++; )
                                pCurClient->Disconnected(wxT("Removed during ClientList cleanup."));
                                pCurClient->Safe_Delete();
#ifdef __DEBUG__
                        } else {
                                if (!(pCurClient->GetUploadState() == US_NONE || (pCurClient->GetUploadState() == US_BANNED && !pCurClient->IsBanned()))) {
                                        AddDebugLogLineN(logProxy,
                                                         CFormat(wxT("Debug: Not deleted client %x with up state: %i "))
                                                         % (long int)pCurClient % pCurClient->GetUploadState());
                                }
                                if (!(pCurClient->GetDownloadState() == DS_NONE)) {
                                        AddDebugLogLineN(logProxy,
                                                         CFormat(wxT("Debug: Not deleted client %x with down state: %i "))
                                                         % (long int)pCurClient % pCurClient->GetDownloadState());
                                }
                                if (!(pCurClient->GetChatState() == MS_NONE)) {
                                        AddDebugLogLineN(logProxy,
                                                         CFormat(wxT("Debug: Not deleted client %x with chat state: %i "))
                                                         % (long int)pCurClient % pCurClient->GetChatState());
                                }
                                /* I2P
                                if (!(pCurClient->GetKadState() == KS_NONE)) {
                                        AddDebugLogLineN(logProxy,
                                                         CFormat(wxT("Debug: Not deleted client %x with kad state: %i ip: %s"))
                                                         % (long int)pCurClient % (int)pCurClient->GetKadState() % pCurClient->GetFullIP());
                                }
                                */
                                if (!(pCurClient->GetSocket() == NULL)) {
                                        AddDebugLogLineN(logProxy,
                                                         CFormat(wxT("Debug: Not deleted client %x: has socket")) % (long int)pCurClient);
                                }
                                AddDebugLogLineN(logProxy,
                                                 CFormat(wxT("Debug: Not deleted client %x with kad version: %i"))
                                                 % (long int)pCurClient % pCurClient->GetKadVersion());
#endif
                        }
                }
                AddDebugLogLineN(logClient, CFormat(wxT("Cleaned ClientList, removed %i not used known clients")) % cDeleted);
        }
}
#ifdef no_firewall_with_i2p
void CClientList::AddKadFirewallRequest(uint32 ip)
{
        uint32 ticks = ::GetTickCount();
        IpAndTicks add = { ip, ticks };
        m_firewallCheckRequests.push_front(add);
        while (!m_firewallCheckRequests.empty()) {
                if (ticks - m_firewallCheckRequests.back().inserted > SEC2MS(180)) {
                        m_firewallCheckRequests.pop_back();
                } else {
                        break;
                }
        }
}

bool CClientList::IsKadFirewallCheckIP(uint32 ip) const
{
        uint32 ticks = ::GetTickCount();
        for (IpAndTicksList::const_iterator it = m_firewallCheckRequests.begin(); it != m_firewallCheckRequests.end(); ++it) {
                if (it->ip == ip && ticks - it->inserted < SEC2MS(180)) {
                        return true;
                }
        }
        return false;
}

void CClientList::AddDirectCallbackClient(CUpDownClient* toAdd)
{
        wxASSERT(toAdd->GetDirectCallbackTimeout() != 0);
        if (toAdd->HasBeenDeleted()) {
                return;
        }
        for (DirectCallbackList::const_iterator it = m_currentDirectCallbacks.begin(); it != m_currentDirectCallbacks.end(); ++it) {
                if (it->GetClient() == toAdd) {
                        wxFAIL; // might happen very rarely on multiple connection tries, could be fixed in the client class, till then it's not much of a problem though
                        return;
                }
        }
        m_currentDirectCallbacks.push_back(CCLIENTREF(toAdd, wxT("CClientList::AddDirectCallbackClient")));
}

void CClientList::ProcessDirectCallbackList()
{
        // we do check if any direct callbacks have timed out by now
        const uint32_t cur_tick = ::GetTickCount();
        for (DirectCallbackList::iterator it = m_currentDirectCallbacks.begin(); it != m_currentDirectCallbacks.end();) {
                DirectCallbackList::iterator it2 = it++;
                CUpDownClient* curClient = it2->GetClient();
                if (curClient->GetDirectCallbackTimeout() < cur_tick) {
                        wxASSERT(curClient->GetDirectCallbackTimeout() != 0);
                        // TODO LOGREMOVE
                        //DebugLog(_T("DirectCallback timed out (%s)"), pCurClient->DbgGetClientInfo());
                        m_currentDirectCallbacks.erase(it2);
                        if (curClient->Disconnected(wxT("Direct Callback Timeout"))) {
                                curClient->Safe_Delete();
                        }
                }
        }
}

void CClientList::AddTrackCallbackRequests(uint32_t ip)
{
        uint32_t now = ::GetTickCount();
        IpAndTicks add = { ip, now };
        m_directCallbackRequests.push_front(add);
        while (!m_directCallbackRequests.empty()) {
                if (now - m_directCallbackRequests.back().inserted > MIN2MS(3)) {
                        m_directCallbackRequests.pop_back();
                } else {
                        break;
                }
        }
}

bool CClientList::AllowCallbackRequest(uint32_t ip) const
{
        uint32_t now = ::GetTickCount();
        for (IpAndTicksList::const_iterator it = m_directCallbackRequests.begin(); it != m_directCallbackRequests.end(); ++it) {
                if (it->ip == ip && now - it->inserted < MIN2MS(3)) {
                        return false;
                }
        }
        return true;
}

uint32 CClientList::GetBuddyIP()
{
        return GetBuddy()->GetIP();
}

uint16 CClientList::GetBuddyPort()
{
        return GetBuddy()->GetUDPPort();
}

#endif
// File_checked_for_headers