// 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.xmule-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"
#else
#	include "ListenSocket.h"
#	include "opcodes.h"
#include "packets.h"                        // Packet
#include "Preferences.h"                    // CPreferences::GetUseHash
#	include "SearchDlg.h"
#	include "SearchList.h"
#include "ServerList.h"                     // CServerList
#include "ServerListCtrl.h"                 // CServerListCtrl - audited 5 Nov 2004
#	include "ServerSocket.h"
#	include "ServerWnd.h"
#	include "SharedFileList.h"
#	include "sockets.h"
#	include "SysTray.h"
#	include "UDPSocket.h"
#	include "xmule.h"
#	include "xmuleDlg.h"
#endif

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

#include <ctime>
#include <iostream>                         // Needed for std::cout std::endl

using std::cout;
using std::endl;
using std::map;

void CServerConnect::TryAnotherConnectionrequest()
{
    if (connectionattempts.size() < (theApp.dynprefs->Get<bool>("use-safe-connect") ? 1 : 2))
    {
        CServer* next_server = used_list->GetNextServer();

        if (next_server == NULL)
        {
            if (connectionattempts.empty() == true)
            {
                theApp.xmuledlg->AddLogLine(true, GetResString(IDS_OUTOFSERVERS));
                ConnectToAnyServer(lastStartAt);
            }
        }
        else
        {
            // Barry - Only auto-connect to static server option
            if (theApp.dynprefs->Get<bool>("auto-static") == true)
            {
                if (next_server->IsStaticMember())
                {
                    ConnectToServer(next_server, true);
                }
            }
            else
            {
                ConnectToServer(next_server, true);
            }
        }
    }
}

void CServerConnect::ConnectToAnyServer(uint32_t startAt, bool prioSort, bool isAuto)
{
    lastStartAt = startAt;
    StopConnectionTry();
    Disconnect();
    theApp.xmuledlg->ShowConnectionState(false, "", true);
    connecting = true;
    singleconnecting = false;
    // Barry - Only auto-connect to static server option
    if ((theApp.dynprefs->Get<bool>("auto-static") == true) && isAuto)
    {
        bool anystatic = false;
        CServer *next_server;
        used_list->SetServerPosition(startAt);
        while ((next_server = used_list->GetNextServer()) != NULL)
        {
            if (next_server->IsStaticMember())
            {
                anystatic = true;
                break;
            }
        }
        if (!anystatic)
        {
            connecting = false;
            theApp.xmuledlg->AddLogLine(true, GetResString(IDS_ERR_NOVALIDSERVERSFOUND));
            return;
        }
    }
    used_list->SetServerPosition(startAt);
    if ((theApp.dynprefs->Get<bool>("priority-system") == true) && prioSort)
    {
        used_list->Sort();
    }
    if (used_list->GetServerCount() == 0)
    {
        connecting = false;
        theApp.xmuledlg->AddLogLine(true, GetResString(IDS_ERR_NOVALIDSERVERSFOUND));
        return;
    }
    theApp.listensocket->Process();
    TryAnotherConnectionrequest();
}

void CServerConnect::ConnectToServer(CServer *server, bool multiconnect)
{
    if (!multiconnect)
    {
        StopConnectionTry();
        Disconnect();
    }
    connecting = true;
    singleconnecting =!multiconnect;
    CServerSocket* newsocket = new CServerSocket(this);
    m_lstOpenSockets.AddTail((void *&) newsocket);
    //newsocket->Create(0,SOCK_STREAM,FD_READ|FD_WRITE|FD_CLOSE|FD_CONNECT,NULL);
    newsocket->ConnectToServer(server);
    connectionattempts[GetTickCount()] = newsocket;
}

void CServerConnect::StopConnectionTry()
{
    connectionattempts.clear();
    connecting = false;
    singleconnecting = false;
    if (m_idRetryTimer.IsRunning())
    {
        //KillTimer(NULL, m_idRetryTimer);
        m_idRetryTimer.Stop();
    }
}

void CServerConnect::ConnectionEstablished(CServerSocket *sender)
{
    if (connecting == false)
    {
        // we are already connected to another server
        if (connectedsocket != NULL)
        {
            DestroySocket(sender);
            connectedsocket = NULL;
            connected = false;
            theApp.xmuledlg->ShowConnectionState(false, "");
            theApp.stat_serverConnectTime = 0;
            return;
        }
    }
    InitLocalIP();
    if (sender->GetConnectionState() == CS_WAITFORLOGIN)
    {
        theApp.xmuledlg->AddLogLine(false, GetResString(IDS_CONNECTEDTOREQ), sender->cur_server->GetListName(), sender->cur_server->GetFullIP(), sender->cur_server->GetPort());
        //send loginpacket
        CServer *update = theApp.serverlist->GetServerByAddress(sender->cur_server->GetAddress(), sender->cur_server->GetPort());
        if (update)
        {
            update->ResetFailedCount();
            theApp.xmuledlg->serverwnd->serverlistctrl->RefreshServer(update);
        }
        CMemFile *data = new CMemFile();
        data->Write(theApp.glob_prefs->GetUserHash(), 16);
        uint32_t clientid = GetClientID();
        data->Write( &clientid, 4);
        uint16_t port = static_cast<uint16_t>(theApp.dynprefs->Get<long>("tcp-port"));
        data->Write( &port, 2);
        uint32_t tagcount = 4;
        data->Write( &tagcount, 4);
        CTag tagName(CT_NAME, const_cast<char *>(theApp.dynprefs->Get<wxString>("nickname").c_str()));
        tagName.WriteTagToFile(data);
        CTag tagVersion(CT_VERSION, EDONKEYVERSION);
        tagVersion.WriteTagToFile(data);
        CTag tagPort(CT_PORT, static_cast<uint16_t>(theApp.dynprefs->Get<long>("tcp-port")));
        tagPort.WriteTagToFile(data);
        // I am zlib aware! :-):
        CTag tagFlags(0x20, 0x00000001);
        tagFlags.WriteTagToFile(data);
        Packet *packet = new Packet(data);
        packet->opcode = OP_LOGINREQUEST;
        SendPacket(packet, true, sender);
        delete data;
    }
    else if (sender->GetConnectionState() == CS_CONNECTED)
    {
        theApp.stat_reconnects++;
        theApp.stat_serverConnectTime = GetTickCount();
        connected = true;
        theApp.xmuledlg->AddLogLine(true, GetResString(IDS_CONNECTEDTO), sender->cur_server->GetListName());
        theApp.xmuledlg->ShowConnectionState(true, sender->cur_server->GetListName());
        connectedsocket = sender;
        StopConnectionTry();
        theApp.sharedfiles->SendListToServer();
        theApp.xmuledlg->serverwnd->serverlistctrl->RemoveDeadServer();
        // tecxx 1609 2002 - serverlist update
        if (theApp.dynprefs->Get<bool>("add-server-server"))
        {
            Packet *packet = new Packet(OP_GETSERVERLIST, 0);
            SendPacket(packet, true);
        }
    }
}

bool CServerConnect::SendPacket(Packet *packet, bool delpacket, CServerSocket *to)
{
    if (!to)
    {
        if (connected)
        {
            connectedsocket->SendPacket(packet, delpacket, true);
        }
        else
        return false;
    }
    else
    {
        to->SendPacket(packet, delpacket, true);
    }
    return true;
}

bool CServerConnect::SendUDPPacket(Packet *packet, CServer *host, bool delpacket)
{
    //printf("CServerConnect::SendUDPPacket opcode=0x%02x size=%u\n", packet->opcode, packet->size);
    if (connected)
    {
        udpsocket->SendPacket(packet, host);
    }
    if (delpacket)
    {
        delete packet;
    }
    return true;
}

void CServerConnect::ConnectionFailed(CServerSocket *sender)
{
    if (connecting ||sender == connectedsocket)
    {
        CServer *update;
        switch (sender->GetConnectionState())
        {
        case CS_FATALERROR:
            theApp.xmuledlg->AddLogLine(true, GetResString(IDS_ERR_FATAL));
            break;
        case CS_DISCONNECTED:
            theApp.xmuledlg->AddLogLine(true, GetResString(IDS_ERR_LOSTC), sender->cur_server->GetListName(), sender->cur_server->GetFullIP(), sender->cur_server->GetPort());
            break;
        case CS_SERVERDEAD:
            //<<--:
            theApp.xmuledlg->AddLogLine(false, GetResString(IDS_ERR_DEAD), sender->cur_server->GetListName(), sender->cur_server->GetFullIP(), sender->cur_server->GetPort());
            update = theApp.serverlist->GetServerByAddress(sender->cur_server->GetAddress(), sender->cur_server->GetPort());
            if (update)
            {
                update->AddFailedCount();
                theApp.xmuledlg->serverwnd->serverlistctrl->RefreshServer(update);
            }
            break;
        case CS_ERROR:
            break;
        case CS_SERVERFULL:
            theApp.xmuledlg->AddLogLine(false, GetResString(IDS_ERR_FULL), sender->cur_server->GetListName(), sender->cur_server->GetFullIP(), sender->cur_server->GetPort());
            break;
        case CS_NOTCONNECTED:;
            break;
        }
        // IMPORTANT: mark this socket not to be deleted in StopConnectionTry(),
        // because it will delete itself after this function!
        sender->m_bIsDeleting = true;
        switch (sender->GetConnectionState())
        {
        case CS_FATALERROR:
            {
                bool autoretry = !singleconnecting;
                StopConnectionTry();
                if ((theApp.dynprefs->Get<bool>("reconnect-on-loss") == true) && (autoretry) && (!m_idRetryTimer.IsRunning()))
                {
                    theApp.xmuledlg->AddLogLine(false, GetResString(IDS_RECONNECT), CS_RETRYCONNECTTIME);
                    //m_idRetryTimer= SetTimer(NULL, 0, 1000*CS_RETRYCONNECTTIME, (TIMERPROC)RetryConnectCallback);
                    m_idRetryTimer.SetOwner(theApp.xmuledlg, 4333);
                    m_idRetryTimer.Start(1000 *CS_RETRYCONNECTTIME);
                }
                theApp.xmuledlg->ShowConnectionState(false, "", true);
                break;
            }
        case CS_DISCONNECTED:
            {
                connected = false;
                if (connectedsocket)
                connectedsocket->Close();
                connectedsocket = NULL;
                wxCommandEvent evt;
                theApp.xmuledlg->searchwnd->ButtonCancel(evt);
                //			printf("Reconn %d conn %d\n",app_prefs->Reconnect(),connecting);
                if (theApp.dynprefs->Get<bool>("reconnect-on-loss") && !connecting)
                {
                    ConnectToAnyServer();
                }
                if (theApp.glob_prefs->GetNotifierPopOnImportantError())
                {
                    theApp.xmuledlg->ShowNotifier(GetResString(IDS_CONNECTIONLOST), TBN_IMPORTANTEVENT, false);
                }
                theApp.xmuledlg->ShowConnectionState(false, "", true);
                break;
            }
        case CS_ERROR:
        case CS_NOTCONNECTED:
            {
                if (!connecting)
                break;
                theApp.xmuledlg->AddLogLine(false, GetResString(IDS_ERR_CONFAILED), sender->info.GetData(), sender->cur_server->GetFullIP(), sender->cur_server->GetPort());
            }
        case CS_SERVERDEAD:
        case CS_SERVERFULL:
            {
                if (!connecting)
                break;
                if (singleconnecting)
                {
                    StopConnectionTry();
                    break;
                }

                CServerSocket* tmpsock;

                map<uint32_t, CServerSocket *>::iterator it;
                it = connectionattempts.begin();

                while (it != connectionattempts.end())
                {
                    tmpsock = it->second;

                    if (tmpsock == sender)
                    {
                        connectionattempts.erase(it->first);
                        delete tmpsock;
                        break;
                    }
                    ++it;
                }

                TryAnotherConnectionrequest();
            }
        }
        theApp.xmuledlg->ShowConnectionState(false, "", true);
    }
}

void CServerConnect::CheckForTimeout()
{
    DWORD maxage = GetTickCount() - CONSERVTIMEOUT;
    DWORD tmpkey;
    CServerSocket* tmpsock;

    map<uint32_t, CServerSocket *>::iterator it;
    it = connectionattempts.begin();

    while (it != connectionattempts.end())
    {
        tmpkey = it->first;
        tmpsock = it->second;

        if (!tmpsock)
        {
            theApp.xmuledlg->AddLogLine(false, "Error: Socket invalid at timeoutcheck");
            connectionattempts.erase(it++);
            continue;
        }
        else if (tmpkey <= maxage)
        {
            theApp.xmuledlg->AddLogLine(false, GetResString(IDS_ERR_CONTIMEOUT), tmpsock->info.GetData(), tmpsock->cur_server->GetFullIP(), tmpsock->cur_server->GetPort());
            connectionattempts.erase(it++);
            TryAnotherConnectionrequest();
            DestroySocket(tmpsock);
            continue;
        }

        ++it;
    }
}

bool CServerConnect::Disconnect()
{
    if (connected &&connectedsocket)
    {
        DestroySocket(connectedsocket);
        connectedsocket = NULL;
        connected = false;
        theApp.xmuledlg->ShowConnectionState(false, "");
        theApp.stat_serverConnectTime = 0;
        //theApp.xmuledlg->serverwnd->servermsgbox.AppendText(wxString("\n\n\n\n"));
        return true;
    }
    else
    return false;
}

CServerConnect::CServerConnect(CServerList *in_serverlist, CPreferences *in_prefs)
{
    connectedsocket = NULL;
    app_prefs = in_prefs;
    used_list = in_serverlist;
    max_simcons = (theApp.dynprefs->Get<bool>("use-safe-connect")) ? 1 : 2;
    connecting = false;
    connected = false;
    clientid = 0;
    singleconnecting = false;
    wxIPV4address tmp;
    tmp.AnyAddress();
    tmp.Service(static_cast<uint16_t>(theApp.dynprefs->Get<long>("tcp-port")) + 10);
    // initalize socket for udp packets:
    udpsocket = new CUDPSocket(this, tmp);
    //udpsocket->Create();
    m_idRetryTimer.SetOwner(theApp.xmuledlg, 4333);
    lastStartAt = 0;
    InitLocalIP();
    m_lowid_tries = 0;
    //added by sivka/tarod
}

CServerConnect::~CServerConnect()
{
/*  K...I have *no* idea how this got here or what I was thinking...it causes crashes, so it's gone.
    for (POSITION pos = m_lstOpenSockets.GetHeadPosition() ; pos != NULL ;)
    {
        CServerSocket* tmp((CServerSocket *)m_lstOpenSockets.GetNext(pos));
        m_lstOpenSockets.RemoveAt(pos);
        tmp->Destroy();
        delete tmp;
    }
*/
    m_idRetryTimer.Stop();
    StopConnectionTry();
    DestroySocket(connectedsocket);
    connectedsocket = NULL;
    udpsocket->Destroy();
}

CServer *CServerConnect::GetCurrentServer()
{
    if (IsConnected() &&connectedsocket)
    {
        return connectedsocket->cur_server;
    }
    else
    {
        return NULL;
    }
}

void CServerConnect::SetClientID(uint32_t newid)
{
    clientid = newid;
    theApp.xmuledlg->ShowConnectionState(IsConnected(), GetCurrentServer()->GetListName());
}

void CServerConnect::DestroySocket(CServerSocket *pSck)
{
    if (pSck != NULL)
    {
        // remove socket from list of opened sockets
        for (POSITION pos = m_lstOpenSockets.GetHeadPosition() ; pos != NULL ;)
        {
            if ((CServerSocket *) m_lstOpenSockets.GetNext(pos) == pSck)
            {
                m_lstOpenSockets.RemoveAt(pos);
                break;
            }
        }
        pSck->Destroy();
        delete pSck;
        pSck = NULL;
    }
}

void CServerConnect::InitLocalIP()
{
    int fd, retcode;
    struct ifreq ifr;
    struct sockaddr_in sin;
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd)
    {
        strcpy(ifr.ifr_name, "ppp0");
        retcode = ioctl(fd, SIOCGIFADDR, &ifr);
        if (!retcode)
        {
            memcpy( &sin, &ifr.ifr_addr, sizeof(struct sockaddr));
            m_nLocalIP = sin.sin_addr.s_addr;
            cout << "Local IP: " << m_nLocalIP << endl;
        }
        else
        {
            m_nLocalIP = 0;
        }
        if (!m_nLocalIP)
        {
            strcpy(ifr.ifr_name, "eth0");
            retcode = ioctl(fd, SIOCGIFADDR, &ifr);
            memcpy( &sin, &ifr.ifr_addr, sizeof(struct sockaddr));
            m_nLocalIP = sin.sin_addr.s_addr;
        }
        close(fd);
    }
    else
    {
        m_nLocalIP = 0;
    }
}

bool CServerConnect::IsLocalServer(uint32_t dwIP, uint16_t nPort)
{
    if (IsConnected())
    {
        if (connectedsocket->cur_server->GetIP() == dwIP &&connectedsocket->cur_server->GetPort() == nPort)
        return true;
    }
    return false;
}

