//this file is part of eMule
//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.

// ClientUDPSocket.cpp : implementation file
//

#include "stdafx.h"
#include "emule.h"
#include "ClientUDPSocket.h"
#include "opcodes.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif


// CClientUDPSocket

CClientUDPSocket::CClientUDPSocket()
{
	m_bWouldBlock = false;
}

CClientUDPSocket::~CClientUDPSocket()
{
}

void CClientUDPSocket::OnReceive(int nErrorCode){
	char buffer[5000];
	CString serverbuffer;
	uint32 port;
	int32 length = ReceiveFrom(buffer,5000,serverbuffer,port);
	if (((uint8)buffer[0] == OP_EMULEPROT) && length != SOCKET_ERROR)
		ProcessPacket(buffer+2,length-2,buffer[1],serverbuffer.GetBuffer(),port);
	
}

bool CClientUDPSocket::ProcessPacket(char* packet, int16 size, int8 opcode, char* host, uint16 port){
	try{
		switch(opcode){
			case OP_REASKFILEPING:
			{
				theApp.downloadqueue->AddDownDataOverheadFileRequest(size);
				if (size != 16){
					ASSERT ( false );
					break;
				}
				CKnownFile* reqfile = theApp.sharedfiles->GetFileByID((uchar*)packet);
				if (!reqfile){
					Packet* response = new Packet(OP_FILENOTFOUND,0,OP_EMULEPROT);
					theApp.uploadqueue->AddUpDataOverheadFileRequest(response->size);
					SendPacket(response,inet_addr(host),port);
					break;
				}
				CUpDownClient* sender = theApp.uploadqueue->GetWaitingClientByIP(inet_addr(host));
				if (sender){
					sender->SetUDPPort(port);
					sender->UDPFileReasked();
					break;
				}
				else{
					if (((uint32)theApp.uploadqueue->GetWaitingUserCount() + 50) > theApp.glob_prefs->GetQueueSize()){
						Packet* response = new Packet(OP_QUEUEFULL,0,OP_EMULEPROT);
						theApp.uploadqueue->AddUpDataOverheadFileRequest(response->size);
						SendPacket(response,inet_addr(host),port);
					}
					break;
				}
				break;
			}
			case OP_QUEUEFULL:
			{
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				CUpDownClient* sender = theApp.downloadqueue->GetDownloadClientByIP(inet_addr(host));
				if (sender){
					sender->SetRemoteQueueFull(true);
					sender->UDPReaskACK(0);
				}
//				else
//					ASSERT (false); //Need to double check these asserts to make sure it's running well.
				break;
			}
			case OP_REASKACK:
			{
				theApp.downloadqueue->AddDownDataOverheadFileRequest(size);
				CUpDownClient* sender = theApp.downloadqueue->GetDownloadClientByIP(inet_addr(host));
				if (sender){
					if (size != 2){
//						ASSERT ( false ); //Need to double check these asserts to make sure it's running well.
						break;
					}
					uint16 nRank;
					memcpy(&nRank,packet,2);
					sender->SetRemoteQueueFull(false);
					sender->UDPReaskACK(nRank);
					sender->AddAskedCountDown();
				}
//				else
//					ASSERT (false);//Need to double check these asserts to make sure it's running well.
				break;
			}
			case OP_FILENOTFOUND:
			{
				theApp.downloadqueue->AddDownDataOverheadFileRequest(size);
				CUpDownClient* sender = theApp.downloadqueue->GetDownloadClientByIP(inet_addr(host));
				if (sender){
					sender->UDPReaskFNF();
				}
				else
//					ASSERT (false); //Need to double check these asserts to make sure it's running well.
				break;
			}
			default:
				return false;
		}
		return true;
	}
	catch(...){
		OUTPUT_DEBUG_TRACE();
		theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_UDP_MISCONF));
		return false;
	}
}

void CClientUDPSocket::OnSend(int nErrorCode){
	if (nErrorCode){
		return;
	}
	m_bWouldBlock = false;
	while (controlpacket_queue.GetHeadPosition() != 0 && !IsBusy()){
		UDPPack* cur_packet = controlpacket_queue.GetHead();
		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) ){
			controlpacket_queue.RemoveHead();
			delete cur_packet->packet;
			delete cur_packet;
		}
		delete[] sendbuffer;
	}

}

int CClientUDPSocket::SendTo(char* lpBuf,int nBufLen,uint32 dwIP, uint16 nPort){
	in_addr host;
	host.S_un.S_addr = dwIP;
	uint32 result = CAsyncSocket::SendTo(lpBuf,nBufLen,nPort,inet_ntoa(host));
	if (result == (uint32)SOCKET_ERROR){
		uint32 error = GetLastError();
		if (error == WSAEWOULDBLOCK){
			m_bWouldBlock = true;
			return -1;
		}
		else{
/*Barry - Ignore assert	
			ASSERT ( false );
*/
			return -1;
		}
	}
	return 0;
}


bool CClientUDPSocket::SendPacket(Packet* packet, uint32 dwIP, uint16 nPort){
	UDPPack* newpending = new UDPPack;
	newpending->dwIP = dwIP;
	newpending->nPort = nPort;
	newpending->packet = packet;
	if ( IsBusy() ){
		controlpacket_queue.AddTail(newpending);
		return true;
	}
	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(){
	if (theApp.glob_prefs->GetUDPPort())
		return CAsyncSocket::Create(theApp.glob_prefs->GetUDPPort(),SOCK_DGRAM,FD_READ);
	else
		return true;
}