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

#include "stdafx.h"
#include "emule.h"
#include "packets.h"
#include "serversocket.h"
#include "emuleDlg.h"
#include "opcodes.h"
#include "searchlist.h"
#include <time.h>
#include <afxmt.h>

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


CServerSocket::CServerSocket(CServerConnect* in_serverconnect){
	serverconnect = in_serverconnect;
	connectionstate = 0;
	cur_server = 0;
	m_bIsDeleting = false;
	info="";
}

CServerSocket::~CServerSocket(){
	if (cur_server)
		delete cur_server;
	cur_server = NULL;
}

void CServerSocket::OnConnect(int nErrorCode){
	CAsyncSocketEx::OnConnect(nErrorCode); // deadlake PROXYSUPPORT - changed to AsyncSocketEx
	switch (nErrorCode){
		case 0:{
			if (cur_server->HasDynIP()){
				SOCKADDR_IN sockAddr;
				memset(&sockAddr, 0, sizeof(sockAddr));
				uint32 nSockAddrLen = sizeof(sockAddr);
				GetPeerName((SOCKADDR*)&sockAddr,(int*)&nSockAddrLen);
				cur_server->SetID(sockAddr.sin_addr.S_un.S_addr);
				theApp.serverlist->GetServerByAddress(cur_server->GetAddress(),cur_server->GetPort())->SetID(sockAddr.sin_addr.S_un.S_addr);
			}
			SetConnectionState(CS_WAITFORLOGIN);
			break;
		}
		case WSAEADDRNOTAVAIL:
		case WSAECONNREFUSED:
		case WSAENETUNREACH:
		case WSAETIMEDOUT: 	
		case WSAEADDRINUSE:
			m_bIsDeleting = true;
			SetConnectionState(CS_SERVERDEAD);
			serverconnect->DestroySocket(this);
			return;
		// deadlake PROXYSUPPORT
		case WSAECONNABORTED:
			if (m_ProxyConnectFailed)
			{
				m_ProxyConnectFailed = false;
				m_bIsDeleting = true;
				SetConnectionState(CS_SERVERDEAD);
				serverconnect->DestroySocket(this);
				return;
			}
		default:	
			m_bIsDeleting = true;
			SetConnectionState(CS_FATALERROR);
			serverconnect->DestroySocket(this);
			return;
	}	 
}

void CServerSocket::OnReceive(int nErrorCode){
	if (connectionstate != CS_CONNECTED && !this->serverconnect->IsConnecting()){
		serverconnect->DestroySocket(this);
		return;
	}
	CEMSocket::OnReceive(nErrorCode);

}

bool CServerSocket::ProcessPacket(char* packet, int32 size, int8 opcode){
	try{
		CServer* update;
		switch(opcode){
			case OP_SERVERMESSAGE:{
				theApp.downloadqueue->AddDownDataOverheadServer(size);
				char* buffer = new char[size-1];
				memcpy(buffer,&packet[2],size-2);
				buffer[size-2] = 0;
				
				CString message(buffer);
				if (message.Find("[emDynIP: ") != (-1) && message.Find("]") != (-1) && message.Find("[emDynIP: ") < message.Find("]")){
					CString dynip = message.Mid(message.Find("[emDynIP: ")+10,message.Find("]") - (message.Find("[emDynIP: ")+10));
					dynip.Trim(" ");
					if ( dynip.GetLength() && dynip.GetLength() < 51){
						CServer* eserver = theApp.serverlist->GetServerByAddress(cur_server->GetAddress(),cur_server->GetPort());
						if (eserver){
							eserver->SetDynIP(dynip.GetBuffer());
							cur_server->SetDynIP(dynip.GetBuffer());
							theApp.emuledlg->serverwnd.serverlistctrl.RefreshServer(eserver);
						}
					}
				}
				theApp.emuledlg->AddServerMessageLine(buffer);
				delete[] buffer;
				break;
			}
			case OP_IDCHANGE:{
				theApp.downloadqueue->AddDownDataOverheadServer(size);
				if (size != sizeof(LoginAnswer_Struct)){
					throw GetResString(IDS_ERR_BADSERVERREPLY);
				}
				LoginAnswer_Struct* la = (LoginAnswer_Struct*) packet;
				if (la->clientid == 0){
//					SetConnectionState(CS_ERROR);
					uint8 state = theApp.glob_prefs->GetSmartIdState();
					if ( state > 0 ){
//						SetConnectionState(CS_ERROR);
						state++;
						if( state > 3 )
							theApp.glob_prefs->SetSmartIdState(0);
						else
							theApp.glob_prefs->SetSmartIdState(state);
					}
					break;
				}
				if( theApp.glob_prefs->GetSmartIdCheck() ){
					if (la->clientid >= 16777216 )
						theApp.glob_prefs->SetSmartIdState(1);
					else{
						uint8 state = theApp.glob_prefs->GetSmartIdState();
						if ( state > 0 ){
//							SetConnectionState(CS_ERROR);
							state++;
							if( state > 3 )
								theApp.glob_prefs->SetSmartIdState(0);
							else
								theApp.glob_prefs->SetSmartIdState(state);
							break;
						}
					}
				}
				if (connectionstate != CS_CONNECTED) {
					SetConnectionState(CS_CONNECTED);
					theApp.OnlineSig();       // Added By Bouc7 
				}
				serverconnect->SetClientID(la->clientid);
				theApp.emuledlg->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->GetStatus() == PS_EMPTY ){
						cur_file->ResumeFile();
					}
				}


				theApp.downloadqueue->filelist.GetCount();				
				break;
			}
			case OP_SEARCHRESULT:{
				theApp.downloadqueue->AddDownDataOverheadServer(size);
				CServer* cur_srv = (serverconnect) ? serverconnect->GetCurrentServer() : NULL;
				theApp.emuledlg->searchwnd.LocalSearchEnd(theApp.searchlist->ProcessSearchanswer(packet,size,(cur_srv)?cur_srv->GetIP():0,(cur_srv)?cur_srv->GetPort():0));
				break;
			}
			case OP_FOUNDSOURCES:{
				theApp.downloadqueue->AddDownDataOverheadServer(size);
				CSafeMemFile sources((BYTE*)packet,size);
				uchar fileid[16];
				sources.Read(fileid,16);
				if (CPartFile* file = theApp.downloadqueue->GetFileByID(fileid))
					file->AddSources(&sources,cur_server->GetIP(), cur_server->GetPort());
				break;
			}
			case OP_SERVERSTATUS:{
				theApp.downloadqueue->AddDownDataOverheadServer(size);
				// FIXME some statuspackets have a different size -> why? structur?
				if (size < 8)
					break;//throw "Invalid status packet";
				uint32 cur_user;
				memcpy(&cur_user,packet,4);
				uint32 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.emuledlg->ShowUserCount(cur_user, cur_files);
					theApp.emuledlg->serverwnd.serverlistctrl.RefreshServer( update );
				}
				break;
			}
			//<<--Working, but needs to be cleaned up.. 
			case OP_SERVERIDENT:{ 
				theApp.downloadqueue->AddDownDataOverheadServer(size);
				if (size<38) { 
					theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_KNOWNSERVERINFOREC)); 
					break;// throw "Invalid server info received"; 
				} 
				char* buffer = new char[size-29]; 
				CServer* update = theApp.serverlist->GetServerByAddress(cur_server->GetAddress(),cur_server->GetPort()); 
				uint16 num,num2; 
				char* temp=new char[size-38+1];// plus one byte for the trailing NUL char ('num' and 'num2' may be 0)
				memcpy(buffer,&packet[30],size-30);// 1st 30 char contain only server address & fillers 
				memcpy(&num,&buffer[0],2); // length of server_name 
				memcpy(temp,&buffer[2],num); 
				temp[num]=0;//close the string 
				update->SetListName(temp); 
				memcpy(&num2,&buffer[num+6],2); 
				memcpy (temp,&buffer[num+8],num2); 
				temp[num2]=0; //close the string 
				update->SetDescription(temp); 
				theApp.emuledlg->ShowConnectionState(true,update->GetListName()); 
				theApp.emuledlg->serverwnd.serverlistctrl.RefreshServer(update); 
				delete[] temp; 
				delete[] buffer; 
				break;
			} 
			// tecxx 1609 2002 - add server's serverlist to own serverlist
			case OP_SERVERLIST:{
				theApp.downloadqueue->AddDownDataOverheadServer(size);
				try{
					CSafeMemFile servers((BYTE*)packet,size);
					byte count;
					servers.Read(&count, 1);// check if packet is valid 
					if ((int32)(count*6 + 1) > size)	 // (servercount*(4bytes ip + 2bytes port) + 1byte servercount)
						count = 0;
					int addcount = 0;
					while(count)
					{
						uint32 ip;
						unsigned short port;
						servers.Read(&ip, 4);
						servers.Read(&port, 2);
						in_addr host;
						host.S_un.S_addr = ip;
						CServer* srv = new CServer(port, inet_ntoa(host));
						srv->SetListName(srv->GetFullIP());
						if (!theApp.emuledlg->serverwnd.serverlistctrl.AddServer(srv, true))
							delete srv;
						else
							addcount++;
						count--;
					}
					if (addcount)
						theApp.emuledlg->AddLogLine(false,GetResString(IDS_NEWSERVERS), addcount);
				}
				catch(CFileException* error){
					OUTPUT_DEBUG_TRACE();
					theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_BADSERVERLISTRECEIVED));
					error->Delete();	//memleak fix
				}
				break;
			}
			case OP_CALLBACKREQUESTED:{
				theApp.downloadqueue->AddDownDataOverheadServer(size);
				if (size == 6){
					uint32 dwIP;
					memcpy(&dwIP,packet,4);
					uint16 nPort;
					memcpy(&nPort,packet+4,2);
					CUpDownClient* client = theApp.clientlist->FindClientByIP(dwIP,nPort);
					if (client)
						client->TryToConnect();
					else{
						client = new CUpDownClient(nPort,dwIP,0,0,0);
						theApp.clientlist->AddClient(client);
						client->TryToConnect();
					}
				}
				break;
			}
			default:
				;
		}

		return true;
	}
	catch(CFileException* error){
		OUTPUT_DEBUG_TRACE();
		char szError[MAX_CFEXP_ERRORMSG];
		error->m_strFileName = "server packet";
		error->GetErrorMessage(szError,sizeof(szError));
		theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_PACKAGEHANDLING),szError);
		error->Delete();
		if (opcode==OP_SEARCHRESULT || opcode==OP_FOUNDSOURCES)
			return true;
	}
	catch(CMemoryException* error){
		OUTPUT_DEBUG_TRACE();
		theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_PACKAGEHANDLING),_T("CMemoryException"));
		error->Delete();
		if (opcode==OP_SEARCHRESULT || opcode==OP_FOUNDSOURCES)
			return true;
	}
	catch(CString error){
		OUTPUT_DEBUG_TRACE();
		theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_PACKAGEHANDLING),error.GetBuffer());
	}
	catch(...){
		OUTPUT_DEBUG_TRACE();
		theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_PACKAGEHANDLING),_T("Unknown exception"));
	}

	SetConnectionState(CS_DISCONNECTED);
	return false;
}

void CServerSocket::ConnectToServer(CServer* server){
	cur_server = new CServer(server);
	theApp.emuledlg->AddLogLine(false,GetResString(IDS_CONNECTINGTO),cur_server->GetListName(),cur_server->GetFullIP(),cur_server->GetPort());

	if (theApp.glob_prefs->IsProxyASCWOP() )
	{
		if (theApp.glob_prefs->GetProxy().UseProxy == true)
		{
			theApp.glob_prefs->SetProxyASCWOP(true);
			theApp.glob_prefs->SetUseProxy(false);
			theApp.emuledlg->AddLogLine(false,GetResString(IDS_ASCWOP_PROXYSUPPORT)+GetResString(IDS_DISABLED));
		}
		else
			theApp.glob_prefs->SetProxyASCWOP(false);
	}

	SetConnectionState(CS_CONNECTING);
	if (!this->Connect(server->GetAddress(),server->GetPort())){
		int error = this->GetLastError();
		if ( error != WSAEWOULDBLOCK){
		theApp.emuledlg->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::OnError(int nErrorCode){
	SetConnectionState(CS_DISCONNECTED);
	theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_SOCKET),cur_server->GetListName(),cur_server->GetFullIP(),cur_server->GetPort(), nErrorCode); 
}

void CServerSocket::PacketReceived(Packet* packet){
	ProcessPacket(packet->pBuffer,packet->size,packet->opcode);
}

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

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