// This file is a part of the xMule Project.
//
// Copyright (c) 2004 Theodore R. Smith (hopeseekr@xmule.ws / http://www.xmule.ws/)
// DSA-1024 Fingerprint: 10A0 6372 9092 85A2 BB7F 907B CB8B 654B E33B F1ED
//
// Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net )
//
// 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., 675 Mass Ave, Cambridge, MA 02139, USA.

#ifdef PRECOMP
    #include "xmule-headers.h"
#endif

#include "ServerSocket.h"                   // Needed for this Interface's Prototype

#include "PartFile.h"                       // Needed for CPartFile::GetStatus
#include "ClientList.h"                     // Needed for CClientList
#include "DownloadQueue.h"                  // Needed for CDownloadQueue
#include "ListenSocket.h"                   // Needed for CListenSocket
#include "otherstructs.h"                   // Needed for LoginAnswer_Struct
#include "packets.h"                        // Needed for Packet
#include "SafeFile.h"                       // Needed for CSafeFile
#include "SearchDlg.h"                      // Needed for CSearchDlg
#include "server.h"                         // Needed for CServer
#include "ServerList.h"                     // Needed for CServerList
#include "ServerListCtrl.h"                 // Needed for CServerListCtrl
#include "ServerWnd.h"                      // Needed for CServerWnd
#include "sockets.h"                        // Needed for CServerConnect
#include "updownclient.h"                   // Needed for CUpDownClient
#include "xmule.h"                          // Needed for theApp
#include "xmuleDlg.h"                       // Needed for theApp.xmuledlg

#include <DynPrefs/DynPrefs.h>              // Needed for DynamicPreferences

#include <arpa/inet.h>                      // Needed for sockaddr_in

class ServerSocketEvt: public wxEvtHandler
{
private:
	void HandleSocket(wxSocketEvent& event);
	DECLARE_EVENT_TABLE();
};

BEGIN_EVENT_TABLE(ServerSocketEvt, wxEvtHandler)
	EVT_SOCKET(-1, ServerSocketEvt::HandleSocket)
END_EVENT_TABLE()

void ServerSocketEvt::HandleSocket(wxSocketEvent& event)
{
    CServerSocket* soc = dynamic_cast<CServerSocket *>(event.GetSocket());

    int id = event.GetSocketEvent();

    if (id == wxSOCKET_CONNECTION)
    {
        soc->OnConnect(0);
    }
    else if (id == wxSOCKET_LOST)
    {
        soc->OnLost(0);
    }
    else if (id == wxSOCKET_INPUT)
    {
        if (theApp.dynprefs->Get<bool>("enable-serverless") == false)
        {
            soc->OnInput(0);
        }
    }
    else if (id == wxSOCKET_OUTPUT)
    {   
        soc->OnSend(0);
    }

}

IMPLEMENT_DYNAMIC_CLASS(CServerSocket, CEMSocket)

CServerSocket::CServerSocket(CServerConnect* in_serverconnect)
{
    //printf("Serversocket: size %d\n",sizeof(CServerSocket));
    serverconnect = in_serverconnect;
    connectionstate = 0;
    cur_server = 0;
    info = "";
    m_bIsDeleting = false;
    m_event_handler = new ServerSocketEvt;
    SetEventHandler(*m_event_handler, -1);
    SetNotify(wxSOCKET_CONNECTION_FLAG |wxSOCKET_INPUT_FLAG |wxSOCKET_OUTPUT_FLAG |wxSOCKET_LOST_FLAG);
    Notify(TRUE);
}

CServerSocket::~CServerSocket()
{
    // remove event handler...
    SetNotify(0);
    Notify(FALSE);
    delete m_event_handler;

    if (cur_server)
    {
        delete cur_server;
    }

    cur_server = NULL;
}

#include <errno.h>

void CServerSocket::OnConnect(int nErrorCode)
{
#if defined(__DEBUG__)
    //printf("CServerSocket::OnConnect errno=%ld nErrorCode=%ld\n", errno, nErrorCode);
#endif
    switch (errno)
    {
    case EINPROGRESS:
        {
            while (!WaitOnConnect(1, 0));
            break;
        }
    case 0:
        {
            if (cur_server->HasDynIP())
            {
                struct sockaddr_in sockAddr;
                memset( &sockAddr, 0, sizeof(sockAddr));
                uint32_t nSockAddrLen = sizeof(sockAddr);
                wxIPV4address tmpaddr;
                GetPeer(tmpaddr);
                sockAddr.sin_addr.s_addr = GAddress_INET_GetHostAddress(tmpaddr.GetAddress());
                cur_server->SetID(sockAddr.sin_addr.s_addr);
                theApp.serverlist->GetServerByAddress(cur_server->GetAddress(), cur_server->GetPort())->SetID(sockAddr.sin_addr.s_addr);
            }
            SetConnectionState(CS_WAITFORLOGIN);
            break;
        }
    case E2BIG:
        {
            // hmm?? argument list too big
            m_bIsDeleting = true;
            SetConnectionState(CS_SERVERDEAD);
            serverconnect->DestroySocket(this);
            break;
        }
    default:
        {
            m_bIsDeleting = true;
            SetConnectionState(CS_FATALERROR);
            serverconnect->DestroySocket(this);
            break;
        }
    }
    return;
}

void CServerSocket::OnInput(int nErrorCode)
{
#if defined(__DEBUG__)
    //printf("CServerSocket::OnInput errno=%ld nErrorCode=%ld\n", errno, nErrorCode);
#endif
    if (connectionstate != CS_CONNECTED && !this->serverconnect->IsConnecting())
    {
        serverconnect->DestroySocket(this);
    }
    else
    {
        CEMSocket::OnReceive(nErrorCode);
    }
}

bool CServerSocket::ProcessPacket(char *packet, int32_t size, int8_t opcode)
{
//printf("====> CServerSocket::ProcessPacket opcode=0x%02x size=%u\n",opcode,size);
    CServer *update;
    switch (opcode)
    {
    case OP_SERVERMESSAGE:
        {
#if defined(__DEBUG__)
            //printf("CServerSocket::ProcessPacket OP_SERVERMESSAGE %02x size=%ld\n", opcode, size);
#endif
            if (size > 1)
            {
                char *buffer = new char[size - 1];
                memcpy(buffer, &packet[2], size - 2);
                buffer[size - 2] = 0;
                wxString message(buffer);
                if (message.Find("[emDynIP: ") != ( - 1) &&message.Find("]") != ( - 1) &&message.Find("[emDynIP: ") < message.Find("]"))
                {
                    wxString dynip = message.Mid(message.Find("[emDynIP: ") + 10, message.Find("]") - (message.Find("[emDynIP: ") + 10));
                    dynip.Trim(" ");
                    if (dynip.empty() == false && dynip.length() < 51)
                    {
                        CServer* eserver = theApp.serverlist->GetServerByAddress(cur_server->GetAddress(), cur_server->GetPort());
                        if (eserver)
                        {
                            eserver->SetDynIP((char *)dynip.c_str());
                            cur_server->SetDynIP((char *)dynip.c_str());
                            theApp.xmuledlg->serverwnd->serverlistctrl->RefreshServer(eserver);
                        }
                    }
                }
                theApp.xmuledlg->AddServerMessageLine("%s", buffer);
                delete[] buffer;
            }
            break;
        }
    case OP_IDCHANGE:
        {
#if defined(__DEBUG__)
            //printf("CServerSocket::ProcessPacket OP_IDCHANGE %02x size=%ld\n", opcode, size);
#endif
            if (size >= sizeof(LoginAnswer_Struct))
            {
                static uint8_t state = 0;
                if (cur_server)
                {
                    if (size >= 8)
                    {
                        cur_server->SetTCPFlags(*((uint32_t*)(packet + 4)));
                    }
                    else
                    {
                        cur_server->SetTCPFlags(0);
                    }
                }

                LoginAnswer_Struct *la = (LoginAnswer_Struct *) packet;
                if (la->clientid == 0)
                {
                    SetConnectionState(CS_ERROR);
                    if (state > 0)
                    {
                        SetConnectionState(CS_ERROR);
                        ++state;
                        if (state > 3)
                        {
                            state = 0;
                        }
                    }
                    break;
                }
                if (theApp.dynprefs->Get<bool>("smart-lowid-check") == true)
                {
                    if (la->clientid >= 16777216)
                    state = 1;
                    else
                    {
                        if (state > 0)
                        {
                            SetConnectionState(CS_ERROR);
                            ++state;

                            if (state > 3)
                            {
                                state = 0;
                            }
                        }
                    }
                }
                // Added by sivka [Tarod/sivka -auto reconnect on LowID]
                if ((la->clientid < 16777216) &&
                //theApp.glob_prefs->ReconnectLowID() &&
                (serverconnect->GetLowIDTries() < 3))
                {
                    serverconnect->IncrementLowIDTries();
                    SetConnectionState(CS_DISCONNECTED);
                    theApp.xmuledlg->ShowConnectionState(false);
                    break;
                }
                serverconnect->ResetLowIDTries();
                // we need to know our client's HighID when sending our shared files (done indirectly on SetConnectionState)
                serverconnect->clientid = la->clientid;
                if (connectionstate != CS_CONNECTED)
                {
                    SetConnectionState(CS_CONNECTED);
                    // Added By Bouc7:
                    theApp.OnlineSig();
                }
                serverconnect->SetClientID(la->clientid);
                theApp.xmuledlg->AddLogLine(false, GetResString(IDS_NEWCLIENTID), la->clientid);
                for (POSITION pos = theApp.downloadqueue->filelist.GetHeadPosition() ; pos != NULL ; theApp.downloadqueue->filelist.GetNext(pos))
                {
                    CPartFile *cur_file = theApp.downloadqueue->filelist.GetAt(pos);
                    if (cur_file->GetStatus() == PS_READY)
                    {
                        cur_file->ResumeFile();
                    }
                }
                theApp.downloadqueue->filelist.GetCount();
            }
            break;
        }
    case OP_SEARCHRESULT:
        {
#if defined(__DEBUG__)
            //printf("CServerSocket::ProcessPacket OP_SEARCHRESULT %02x size=%ld\n", opcode, size);
#endif
            theApp.xmuledlg->searchwnd->SearchEnd((char *) packet, size, 0, 0);
            theApp.downloadqueue->AddDownDataOverheadServer(size);
            break;
        }
    case OP_FOUNDSOURCES:
        {
#if defined(__DEBUG__)
            //printf("CServerSocket::ProcessPacket OP_FOUNDSOURCES %02x size=%ld\n", opcode, size);
#endif
            CMemFile *sources = new CMemFile((BYTE *) packet, size);
            unsigned char fileid[16];
            sources->Read(fileid, 16);
            if (CPartFile *file = theApp.downloadqueue->GetFileByID(fileid))
            file->AddSources(sources, cur_server->GetIP(), cur_server->GetPort());
            delete sources;
            break;
        }
    case OP_SERVERSTATUS:
        {
#if defined(__DEBUG__)
            //printf("CServerSocket::ProcessPacket OP_SERVERSTATUS %02x size=%ld\n", opcode, size);
#endif
            // FIXME some statuspackets have a different size->why? structur?
            if (size > 7)
            {
                uint32_t cur_user;
                memcpy( &cur_user, packet, 4);
                uint32_t cur_files;
                memcpy( &cur_files, packet + 4, 4);
                update = theApp.serverlist->GetServerByAddress(cur_server->GetAddress(), cur_server->GetPort());
                if (update)
                {
                    update->SetUserCount(cur_user);
                    update->SetFileCount(cur_files);
                    theApp.xmuledlg->ShowUserCount(cur_user, cur_files);
                    theApp.xmuledlg->serverwnd->serverlistctrl->RefreshServer(update);
                }
            }
            break;
        }
        //<<--Working, but needs to be cleaned up..
    case OP_SERVERIDENT:
        {
#if defined(__DEBUG__)
            //printf("CServerSocket::ProcessPacket OP_SERVERIDENT %02x size=%ld\n", opcode, size);
#endif
            if (size > 37)
            {
                char *buffer = new char[size - 29];
                CServer *update = theApp.serverlist->GetServerByAddress(cur_server->GetAddress(), cur_server->GetPort());
                uint16_t num, num2;
                // 1st 30 char contain only server address & fillers:
                memcpy(buffer, &packet[30], size - 30);
                // length of server_name:
                memcpy( &num, &buffer[0], 2);
                if ((num + 2) <= (size - 30))
                {
                    char *temp = new char[size - 38 + 1];
                    memcpy(temp, &buffer[2], num);
                    //close the string:
                    temp[num] = 0;
                    update->SetListName(temp);
                    memcpy( &num2, &buffer[num + 6], 2);
                    if ((num2 + num + 8) <= (size - 30))
                    {
                        memcpy(temp, &buffer[num + 8], num2);
                        //close the string:
                        temp[num2] = 0;
                        update->SetDescription(temp);
                        theApp.xmuledlg->ShowConnectionState(true, update->GetListName());
                        theApp.xmuledlg->serverwnd->serverlistctrl->RefreshServer(update);
                    }
                    delete[] temp;
                }
                delete[] buffer;
            }
            break;
        }
        // tecxx 1609 2002 - add server's serverlist to own serverlist
    case OP_SERVERLIST:
        {
            {
#if defined(__DEBUG__)
                //printf("CServerSocket::ProcessPacket OP_SERVERLIST %02x size=%ld\n", opcode, size);
#endif
                CSafeMemFile *servers = new CSafeMemFile((BYTE *) packet, size);
                unsigned char count;
                // (servercount*(4bytes ip + 2bytes port) + 1byte servercount):
                if ((1 != servers->Read( &count, 1)) || ((int32_t)(count *6 + 1) > size))
                count = 0;
                int addcount = 0;
                while (count)
                {
                    uint32_t ip;
                    unsigned short port;
                    if (4 != servers->Read( &ip, 4))
                    break;
                    if (2 != servers->Read( &port, 2))
                    break;
                    in_addr host;
                    host.s_addr = ip;
                    CServer *srv = new CServer(port, inet_ntoa(host));
                    srv->SetListName(srv->GetFullIP());
                    if (!theApp.xmuledlg->serverwnd->serverlistctrl->AddServer(srv, true))
                    delete srv;
                    else
                    addcount++;
                    count--;
                }
                delete servers;
                if (addcount)
                theApp.xmuledlg->AddLogLine(false, GetResString(IDS_NEWSERVERS), addcount);
            }
            theApp.serverlist->SaveServermetToFile();
            break;
        }
    case OP_CALLBACKREQUESTED:
        {
#if defined(__DEBUG__)
            //printf("CServerSocket::ProcessPacket OP_CALLBACKREQUESTED %02x size=%ld\n", opcode, size);
#endif
            if (size == 6)
            {
                uint32_t dwIP;
                memcpy( &dwIP, packet, 4);
                uint16_t nPort;
                memcpy( &nPort, packet + 4, 2);
                CUpDownClient *client = theApp.clientlist->FindClientByIP(dwIP, nPort);
                if (client)
                {
                    (void) client->TryToConnect();
                }
                else
                {
                    client = new CUpDownClient(nPort, dwIP, 0, 0, 0);
                    theApp.clientlist->AddClient(client);
                    (void) client->TryToConnect();
                }
            }
            break;
        }
    default:
        {
#if defined(__DEBUG__)
            //printf("CServerSocket::ProcessPacket unknown OPCODE %02x size=%ld\n", opcode, size);
#endif
            break;
        }
    }
    return true;
}

void CServerSocket::ConnectToServer(CServer *server)
{
    cur_server = new CServer(server);
    theApp.xmuledlg->AddLogLine(false, GetResString(IDS_CONNECTINGTO), cur_server->GetListName(), cur_server->GetFullIP(), cur_server->GetPort());
    SetConnectionState(CS_CONNECTING);
    //if (!this->Connect(server->GetAddress(),server->GetPort())){
    wxIPV4address addr;
    addr.Hostname(server->GetAddress());
    addr.Service(server->GetPort());
    if (!this->Connect(addr, FALSE))
    {
        //this->GetLastError();:
        int error = errno;
        if (error != EINPROGRESS)
        {
            theApp.xmuledlg->AddLogLine(false, GetResString(IDS_ERR_CONNECTIONERROR), cur_server->GetListName(), cur_server->GetFullIP(), cur_server->GetPort(), error);
            SetConnectionState(CS_FATALERROR);
            return;
        }
    }
    info = server->GetListName();
    SetConnectionState(CS_CONNECTING);
}

void CServerSocket::OnLost(int nErrorCode)
{
#if defined(__DEBUG__)
    //printf("CServerSocket::OnLost\n");
#endif
    SetConnectionState(CS_DISCONNECTED);
}

void CServerSocket::PacketReceived(Packet *packet)
{
    if (packet->prot == OP_PACKEDPROT)
    {
        uint32_t uComprSize = packet->size;
        if (!packet->UnPackPacket(260000))
        {
            theApp.downloadqueue->AddDownDataOverheadServer(packet->size);
            return;
        }
        packet->prot = OP_EDONKEYPROT;
        ProcessPacket(packet->pBuffer, packet->size, packet->opcode);
    }
    else if(packet->prot == OP_EDONKEYPROT)
    {
        ProcessPacket(packet->pBuffer, packet->size, packet->opcode);
    }
    else
    {
        theApp.downloadqueue->AddDownDataOverheadServer(packet->size);
    }
}

void CServerSocket::OnClose(int nErrorCode)
{
    CEMSocket::OnClose(0);
    if (connectionstate == CS_CONNECTING)
    {
        SetConnectionState(CS_SERVERFULL);
    }
    else if(connectionstate == CS_CONNECTED)
    {
        SetConnectionState(CS_DISCONNECTED);
    }
    else
    {
        SetConnectionState(CS_NOTCONNECTED);
    }
    serverconnect->DestroySocket(this);
}

void CServerSocket::SetConnectionState(int8_t newstate)
{
    connectionstate = newstate;
    if (newstate < 1)
    {
        serverconnect->ConnectionFailed(this);
    }
    else if(newstate == CS_CONNECTED || newstate == CS_WAITFORLOGIN)
    {
        if (serverconnect)
        {
            serverconnect->ConnectionEstablished(this);
        }
    }
}

