// 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.

// ClientUDPSocket.cpp : implementation file
//

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

#include "ClientUDPSocket.h"                // Needed for Module's Protype(s) - audited 6 Nov 2004

#include "DownloadQueue.h"                  // Needed for CDownloadQueue - audited 5 Nov 2004
#include "NewSockets.h"                     // Needed for NewSocket_SendPacketOP - audited 5 Nov 2004
#include "opcodes.h"                        // Needed for OP_EMULEPROT - audited 4 Nov 2004
#include "packets.h"                        // Needed for Packet - audited 5 Nov 2004
#include "SharedFileList.h"                 // Needed for CSharedFileList - audited 5 Nov 2004
#include "updownclient.h"                   // Needed for CUpDownClient - audited 5 Nov 2004
#include "UploadQueue.h"                    // Needed for CUploadQueue - audited 5 Nov 2004
#include "xmule.h"                          // Needed for theApp - audited 4 Nov 2004
#include "xmuleIPV4Address.h"               // Needed for xmuleIPV4Address - audited 5 Nov 2004

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

#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <wx/socket.h>

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

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

void ClientUDPSocketEvt::HandleSocket(wxSocketEvent& event)
{
    CClientUDPSocket* soc = dynamic_cast<CClientUDPSocket *>(event.GetSocket());
    int id = event.GetSocketEvent();

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

IMPLEMENT_DYNAMIC_CLASS(CClientUDPSocket, wxDatagramSocket)

CClientUDPSocket::CClientUDPSocket(wxIPV4address address): wxDatagramSocket(address, wxSOCKET_NOWAIT)
{
    m_bWouldBlock = false;
    m_event_handler = new ClientUDPSocketEvt;
    SetEventHandler(*m_event_handler, -1);
    SetNotify(wxSOCKET_INPUT_FLAG |wxSOCKET_OUTPUT_FLAG);
    Notify(TRUE);
}

CClientUDPSocket::~CClientUDPSocket()
{
    SetNotify(0);
    Notify(FALSE);
    delete m_event_handler;
}

void CClientUDPSocket::OnReceive(int nErrorCode)
{
    unsigned char buffer[5000];
    wxIPV4address addr;
    RecvFrom(addr, buffer, 5000);
    int32_t length = LastCount();
    wxUint32 fromIP = GAddress_INET_GetHostAddress(addr.GetAddress());

    if ((buffer[0] == static_cast<unsigned int>(OP_EMULEPROT)) && (length != 0))
    {
        ProcessPacket(buffer + 2, length - 2, buffer[1], fromIP, addr.Service());
    }
}

bool CClientUDPSocket::ProcessPacket(unsigned char *packet, int16_t size, wxUint8 opcode, wxUint32 hostip, uint16_t port)
{
printf("====> CClientUDPSocket::ProcessPacket opcode=0x%02x size=%u\n",opcode,size);
    switch (opcode)
    {
    case OP_REASKFILEPING:
        {
            theApp.downloadqueue->AddDownDataOverheadFileRequest(size);
            if (size != 16)
            {
                break;
            }
            CKnownFile *reqfile = theApp.sharedfiles->GetFileByID((unsigned char *) packet);
            if (!reqfile)
            {
                Packet *response = new Packet(OP_FILENOTFOUND, 0, OP_EMULEPROT);
                NewSocket_SendPacketOP(1, OP_FILENOTFOUND, response, hostip, port);
                break;
            }
            CUpDownClient *sender = theApp.uploadqueue->GetWaitingClientByIP(hostip);
            if (sender)
            {
                sender->AddAskedCount();
                sender->SetUDPPort(port);
                sender->UDPFileReasked();
                break;
            }
            else
            {
                if (((uint32_t) theApp.uploadqueue->GetWaitingUserCount() + 50) > theApp.dynprefs->Get<long>("upload-queue-size"))
                {
                    Packet *response = new Packet(OP_QUEUEFULL, 0, OP_EMULEPROT);
                    NewSocket_SendPacketOP(1, OP_QUEUEFULL, response, hostip, port);
                }
            }
            break;
        }
    case OP_QUEUEFULL:
        {
            theApp.downloadqueue->AddDownDataOverheadOther(size);
            CUpDownClient *sender = theApp.downloadqueue->GetDownloadClientByIP(hostip);
            if (sender)
            {
                sender->SetRemoteQueueFull(true);
                sender->UDPReaskACK(0);
            }
            break;
        }
    case OP_REASKACK:
        {
            theApp.downloadqueue->AddDownDataOverheadFileRequest(size);
            CUpDownClient *sender = theApp.downloadqueue->GetDownloadClientByIP(hostip);
            if (sender)
            {
                if (size != 2)
                {
                    break;
                }
                uint16_t nRank;
                memcpy( &nRank, packet, 2);
                sender->SetRemoteQueueFull(false);
                sender->UDPReaskACK(nRank);
                sender->AddAskedCountDown();
            }
            break;
        }
    case OP_FILENOTFOUND:
        {
            theApp.downloadqueue->AddDownDataOverheadFileRequest(size);
            CUpDownClient *sender = theApp.downloadqueue->GetDownloadClientByIP(hostip);
            if (sender)
            {
                sender->UDPReaskFNF();
            }
            break;
        }
    default:
        return false;
    }
    return true;
}

void CClientUDPSocket::OnSend(int nErrorCode)
{
    if (nErrorCode)
    {
        return;
    }
    m_bWouldBlock = false;
    CTypedPtrList < CPtrList, UDPPack *> failed_packets;
    while (controlpacket_queue.GetHeadPosition() != 0 && !IsBusy())
    {
        UDPPack *cur_packet = controlpacket_queue.RemoveHead();
        char *sendbuffer = new char[cur_packet->packet->size + 2];
        memcpy(sendbuffer, cur_packet->packet->GetUDPHeader(), 2);
        memcpy(sendbuffer + 2, cur_packet->packet->pBuffer, cur_packet->packet->size);
        if (SendTo(sendbuffer, cur_packet->packet->size + 2, cur_packet->dwIP, cur_packet->nPort))
        {
            delete cur_packet->packet;
            delete cur_packet;
        }
        else if(++cur_packet->trial < 3)
        {
            failed_packets.AddTail(cur_packet);
        }
        else
        {
            delete cur_packet->packet;
            delete cur_packet;
        }
        delete[] sendbuffer;
    }
    for (POSITION pos = failed_packets.GetHeadPosition() ; pos ;)
    {
        UDPPack *packet = failed_packets.GetNext(pos);
        controlpacket_queue.AddTail(packet);
    }
    failed_packets.RemoveAll();
}

bool CClientUDPSocket::SendTo(char *lpBuf, int nBufLen, uint32_t dwIP, uint16_t nPort)
{
    if (Ok())
    {
        xmuleIPV4Address addr;
        struct in_addr tmpa;
        tmpa.s_addr = dwIP;
        addr.Hostname(tmpa. s_addr);
        addr.Service(nPort);
#if defined(__DEBUG__)
        //printf("CClientUDPSocket::SendTo %s:%d\n", (const char *) addr->IPAddress(), addr->Service());
#endif
        wxDatagramSocket::SendTo(addr, lpBuf, nBufLen);
    }
    else
    {
        return false;
    }

    if (Error())
    {
        uint32_t error = LastError();
        if (error == wxSOCKET_WOULDBLOCK)
        {
            m_bWouldBlock = true;
            return false;
        }
        else
        {
            return false;
        }
    }
    return true;
}

bool CClientUDPSocket::SendPacket(Packet *packet, uint32_t dwIP, uint16_t nPort)
{
//printf("CClientUDPSocket::SendPacket opcode=0x%02x size=%u\n",packet->opcode,packet->size);
    UDPPack *newpending = new UDPPack;
    newpending->dwIP = dwIP;
    newpending->nPort = nPort;
    newpending->packet = packet;
    newpending->trial = 0;
    if (IsBusy())
    {
        controlpacket_queue.AddTail(newpending);
    }
    else
    {
        char *sendbuffer = new char[packet->size + 2];
        memcpy(sendbuffer, packet->GetUDPHeader(), 2);
        memcpy(sendbuffer + 2, packet->pBuffer, packet->size);
        if (!SendTo(sendbuffer, packet->size + 2, dwIP, nPort))
        {
            controlpacket_queue.AddTail(newpending);
        }
        else
        {
            delete newpending->packet;
            delete newpending;
        }
        delete[] sendbuffer;
    }
    return true;
}

bool CClientUDPSocket::Create()
{
    // constructor does dirty work
    return true;
    //if (theApp.glob_prefs->GetUDPPort())
    //	return CAsyncSocket::Create(theApp.glob_prefs->GetUDPPort(),SOCK_DGRAM,FD_READ);
    //else
    //	return true;
}

