//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 "updownclient.h"
#include "emule.h"
#include "uploadqueue.h"
#include "Clientlist.h"

//	members of CUpDownClient
//	which are used by down and uploading functions 

CUpDownClient::CUpDownClient(CClientReqSocket* sender){
	socket = sender;
	reqfile = 0;
	Init();
}

// Modified by Tarod (LS tweak)
CUpDownClient::CUpDownClient(uint16 in_port, uint32 in_userid,uint32 in_serverip, uint16 in_serverport,CPartFile* in_reqfile, CSourceEntry* source_entry){
	socket = 0;
	Init();

	m_sourceEntry = source_entry ; // Added by Tarod (LS tweak)
	if (m_sourceEntry) m_sourceEntry->AddRef(); // HACK by Md5Chap 2002-11-24 (Used reference)
	m_nUserID = in_userid;
	m_nUserPort = in_port;
	if (!HasLowID())
		sprintf(m_szFullUserIP,"%i.%i.%i.%i",(uint8)m_nUserID,(uint8)(m_nUserID>>8),(uint8)(m_nUserID>>16),(uint8)(m_nUserID>>24));
	m_dwServerIP = in_serverip;
	m_nServerPort = in_serverport;
	reqfile = in_reqfile;
}

void CUpDownClient::Init(){
	memset(m_szFullUserIP,0,21);
	credits = 0;
	m_nAvDownDatarate = 0;
	m_nAvUpDatarate = 0;
	m_byChatstate = 0;
	m_cShowDR = 0;
	m_nUDPPort = 0;
	m_cFailed = 0;
	m_dwBanTime = 0;
	m_nMaxSendAllowed = 0;
	m_nTransferedUp = 0;
	m_cSendblock = 0;
	m_cAsked = 0;
	m_cDownAsked = 0;
	dataratems = 0;
	m_nUpDatarate = 0;
	m_pszUsername = 0;
	m_dwUserIP = 0;
	m_nUserID = 0;
	m_nServerPort = 0;
	m_bBanned = false;
	m_bIsBotuser =false;
	m_bFileListRequested = false;
	m_dwLastUpRequest = 0;
	m_bEmuleProtocol = false;
	usedcompressiondown = false;
	m_bUsedComprUp = false;
	m_bCompleteSource = false;
	m_bFriendSlot = false;
	m_bCommentDirty = false;
	m_bReaskPending = false;
	m_bUDPPending = false;
	m_byEmuleVersion = 0;
	m_nUserPort = 0;
	m_nPartCount = 0;
	m_abyPartStatus = 0;
	m_dwLastAskedTime = 0;
	m_nDownloadState = 0;
	m_pszClientFilename = 0;
	m_dwUploadTime = 0;
	m_nTransferedDown = 0;
	m_nDownDatarate = 0;
	m_nDownDataRateMS = 0;
	m_byUploadState = 0;
	m_dwLastBlockReceived = 0;
	m_byEmuleVersion = 0;
	m_byDataCompVer = 0;
	m_byUDPVer = 0;
	m_bySourceExchangeVer = 0;
	m_byAcceptCommentVer = 0;
	m_byExtendedRequestsVer = 0;
	m_byDataTarodVer = 0; // Added by Tarod
	m_TarodVersionNumber = 0;
	m_sourceEntry = (CSourceEntry*)NULL; // Added by Tarod (LS tweak)
	m_nRemoteQueueRank = 0;
	m_dwLastSourceRequest = 0;
	m_dwLastSourceAnswer = 0;
	m_Friend = NULL;
	m_iRate=0;
	m_strComment="";
	m_nSessionUp = 0;
	m_bRemoteQueueFull = false;
	memset( m_achUserHash, 0, 16);
	SetWaitStartTime();
	if (socket){
		SOCKADDR_IN sockAddr;
		memset(&sockAddr, 0, sizeof(sockAddr));
		uint32 nSockAddrLen = sizeof(sockAddr);
		socket->GetPeerName((SOCKADDR*)&sockAddr,(int*)&nSockAddrLen);
		m_dwUserIP = sockAddr.sin_addr.S_un.S_addr;
		strcpy(m_szFullUserIP,inet_ntoa(sockAddr.sin_addr));
	}
}

CUpDownClient::~CUpDownClient(){
	//Beep(400,5);
	theApp.clientlist->RemoveClient(this);
	if (m_Friend){
		m_Friend->m_LinkedClient = NULL;
		theApp.friendlist->RefreshFriend(m_Friend);
		m_Friend = NULL;
	}
	if (m_pszClientFilename)
		delete[] m_pszClientFilename;
	if (socket){
		socket->client = 0;
		socket->Safe_Delete();
	}
	if (m_pszUsername)
		delete[] m_pszUsername;
	if (m_abyPartStatus)
		delete[] m_abyPartStatus;
	
	ClearUploadBlockRequests();

	for (POSITION pos = m_DownloadBlocks_list.GetHeadPosition();pos != 0;m_DownloadBlocks_list.GetNext(pos))
		delete m_DownloadBlocks_list.GetAt(pos);
	m_DownloadBlocks_list.RemoveAll();
	for (POSITION pos = m_RequestedFiles_list.GetHeadPosition();pos != 0;m_RequestedFiles_list.GetNext(pos))
		delete m_RequestedFiles_list.GetAt(pos);
	m_RequestedFiles_list.RemoveAll();
	for (POSITION pos = m_PendingBlocks_list.GetHeadPosition();pos != 0;m_PendingBlocks_list.GetNext(pos)){
		Pending_Block_Struct *pending = m_PendingBlocks_list.GetAt(pos);
		delete pending->block;
		// Not always allocated
		if (pending->zStream) delete pending->zStream;
		delete pending;
	}
	m_PendingBlocks_list.RemoveAll();
	m_AvarageUDR_list.RemoveAll();
	m_AvarageDDR_list.RemoveAll();
	DEBUG_ONLY (theApp.listensocket->Debug_ClientDeleted(this));
	// HACK by Md5Chap 2002-11-26 (Increment failedconnections here,
	// because of m_cfailed>0 we can be sure that we could not connect
	// to the other client. We can also check to have only this reference left, and
	// then adding a failed connection. This prevents a heavy referenced source to
	// be outdated at once.)
	if (m_sourceEntry && m_cFailed>0 && m_sourceEntry->GetRef()<=2 ) m_sourceEntry->IncrementFailedConnections();
	if (m_sourceEntry) m_sourceEntry->Release(true); // HACK by Md5Chap 2002-11-24 (autodelete, because it could be last one)
}

void CUpDownClient::ProcessHelloPacket(char* pachPacket, uint32 nSize){
	CSafeMemFile* data = new CSafeMemFile((BYTE*)pachPacket,nSize);
	uint8 hashsize;
	data->Read(&hashsize,1);
	this->ProcessHelloTypePacket(data);
	delete data;
}

void CUpDownClient::ProcessHelloAnswer(char* pachPacket, uint32 nSize){
	CSafeMemFile* data = new CSafeMemFile((BYTE*)pachPacket,nSize);
	this->ProcessHelloTypePacket(data);
	delete data;
}

void CUpDownClient::ProcessHelloTypePacket(CSafeMemFile* data){
	data->Read(&m_achUserHash,16);
	uint32 nUserID;
	data->Read(&nUserID,4);
	if (!m_nUserID || nUserID < 16777216)
		m_nUserID = nUserID;
	data->Read(&m_nUserPort,2); // hmm clientport is sent twice - why?
	uint32	tagcount;
	data->Read(&tagcount,4);
	for (int i = 0;i != tagcount; i++){
		CTag* temptag = new CTag(data);
		switch(temptag->tag->specialtag){
			case CT_NAME:
				if (m_pszUsername)
					delete[] m_pszUsername;
				m_pszUsername = nstrdup(temptag->tag->stringvalue);
				break;
			case CT_VERSION:
				m_nClientVersion = temptag->tag->intvalue;
				break;
			case CT_PORT:
				m_nUserPort = temptag->tag->intvalue;
				break;
		}
		delete temptag;
	}
	data->Read(&m_dwServerIP,4);
	data->Read(&m_nServerPort,2);
	// tecxx 1609 2002 - add client's servet to serverlist (Moved to uploadqueue.cpp)

	SOCKADDR_IN sockAddr;
	memset(&sockAddr, 0, sizeof(sockAddr));
	uint32 nSockAddrLen = sizeof(sockAddr);
	socket->GetPeerName((SOCKADDR*)&sockAddr,(int*)&nSockAddrLen);
	m_dwUserIP = sockAddr.sin_addr.S_un.S_addr;
	strcpy(m_szFullUserIP,inet_ntoa(sockAddr.sin_addr));
	// get client credits
	// key, 255.255.0.0 subnetmask

	if(!HasLowID() && m_nUserID != m_dwUserIP)
		m_nUserID = m_dwUserIP;

	uchar key[16];
	memcpy(key,m_achUserHash,16);
	credits = theApp.clientcredits->GetCredit(key);
	if ( (m_Friend = theApp.friendlist->LinkFriend(key, m_dwUserIP, m_nUserPort) ) != NULL){
		m_Friend->m_LinkedClient = this;
		m_Friend->m_dwHasHash = 1;
		for( int i = 0; i < 16; i++ )
			m_Friend->m_abyUserhash[i] = GetUserHash()[i];
		m_Friend->m_strName.Format("%s", m_pszUsername);
		m_Friend->m_dwLastUsedIP = m_dwUserIP;
		m_Friend->m_nLastUsedPort = m_nUserPort;
		theApp.friendlist->RefreshFriend(m_Friend);
	}
}

void CUpDownClient::SendHelloPacket(){
	CMemFile* data = new CMemFile();
	uint8 hashsize = 16;
	data->Write(&hashsize,1);
	SendHelloTypePacket(data);
	Packet* packet = new Packet(data);
	delete data;
	packet->opcode = OP_HELLO;
	if (socket)
		socket->SendPacket(packet,true);
}

void CUpDownClient::SendMuleInfoPacket(bool bAnswer){
	CMemFile* data = new CMemFile();
	uint8 version = CURRENT_VERSION_SHORT;
	data->Write(&version,1);
	uint8 protversion = EMULE_PROTOCOL;
	data->Write(&protversion,1);
	uint32 tagcount = 7;
	data->Write(&tagcount,4);
	CTag tag(ET_COMPRESSION,1);
	tag.WriteTagToFile(data);
	CTag tag2(ET_UDPVER,2);
	tag2.WriteTagToFile(data);
	CTag tag3(ET_UDPPORT,theApp.glob_prefs->GetUDPPort());
	tag3.WriteTagToFile(data);
	CTag tag4(ET_SOURCEEXCHANGE,1);
	tag4.WriteTagToFile(data);
	CTag tag5(ET_COMMENTS,1);
	tag5.WriteTagToFile(data);
	CTag tag6(ET_TAROD, 1);		// To check for Tarod (just curiosity)
	tag6.WriteTagToFile(data);	// It do not harms at all
	CTag tag7(ET_TAROD_VERSION, TAROD_VERSION);	// lugdunum asked for it
	tag7.WriteTagToFile(data);
//	CTag tag8(ET_EXTENDEDREQUEST,1);
//	tag8.WriteTagToFile(data);
	Packet* packet = new Packet(data,OP_EMULEPROT);
	delete data;
	if (!bAnswer)
		packet->opcode = OP_EMULEINFO;
	else
		packet->opcode = OP_EMULEINFOANSWER;
	if (socket)
		socket->SendPacket(packet,true,true);
}

void CUpDownClient::ProcessMuleInfoPacket(char* pachPacket, uint32 nSize){
	CSafeMemFile* data = new CSafeMemFile((BYTE*)pachPacket,nSize);
	data->Read(&m_byEmuleVersion,1);
	uint8 protversion;
	data->Read(&protversion,1);

	//implicitly supported options by older clients
	if (protversion == EMULE_PROTOCOL) {
		//in the future do not use version to guess about new features

		if((m_byEmuleVersion < 0x25 && m_byEmuleVersion > 0x22) || m_byEmuleVersion == 0x2B)
			m_byUDPVer = 1;

		if((m_byEmuleVersion < 0x25 && m_byEmuleVersion > 0x21) || m_byEmuleVersion == 0x2B)
			m_bySourceExchangeVer = 1;

		if(m_byEmuleVersion == 0x24)
			m_byAcceptCommentVer = 1;

	} else {
		delete data;
		return;
	}
	m_bEmuleProtocol = true;

	uint32 tagcount;
	data->Read(&tagcount,4);
	for (int i = 0;i != tagcount; i++){
		CTag* temptag = new CTag(data);
		switch(temptag->tag->specialtag){
			case ET_COMPRESSION:
				m_byDataCompVer = temptag->tag->intvalue;
				break;
			case ET_UDPPORT:
				m_nUDPPort = temptag->tag->intvalue;
				break;
			case ET_UDPVER:
				m_byUDPVer = temptag->tag->intvalue;
				break;
			case ET_SOURCEEXCHANGE:
				m_bySourceExchangeVer = temptag->tag->intvalue;
				break;
			case ET_COMMENTS:
				m_byAcceptCommentVer = temptag->tag->intvalue;
				break;
			case ET_TAROD:	// I insist it do not harm, just curiosity
				m_byDataTarodVer = temptag->tag->intvalue;
				break;
			case ET_TAROD_VERSION: // lugdunum asked for it -do not remove
				m_TarodVersionNumber = temptag->tag->intvalue;
				break;
//			case ET_EXTENDEDREQUEST:
//				m_byExtendedRequestsVer = temptag->tag->intvalue;
//				break;
		}
		delete temptag;
	}
	if( m_byDataCompVer == 0 ){
		m_bySourceExchangeVer = 0;
		m_byExtendedRequestsVer = 0;
		m_byAcceptCommentVer = 0;
		m_nUDPPort = 0;
	}
	delete data;
}

void CUpDownClient::SendHelloAnswer(){
	CMemFile* data = new CMemFile();
	SendHelloTypePacket(data);
	Packet* packet = new Packet(data);
	delete data;
	packet->opcode = OP_HELLOANSWER;
	if (socket)
		socket->SendPacket(packet,true);
}

void CUpDownClient::SendHelloTypePacket(CMemFile* data){
	// By MAverick
	// Emulate eDonkey or MlDonkey in case of a connection to a MlDonkey
	// This patch is introduced to conteract the MlDonkey's politics against 
	// eMule. (eMules can only occupy 1/3 of the upload queue of a MlDonkey)
	uchar hash[16];
	memcpy(hash, theApp.glob_prefs->GetUserHash(), 16);
	if (GetClientSoft() == SO_MLDONKEY) {
		hash[5] = 'M';
		hash[14] = 'L';
	}
	data->Write(hash, 16);
	// END Patch Maverick
	
	
	//data->Write(theApp.glob_prefs->GetUserHash(),16);
	
	
	uint32 clientid = theApp.serverconnect->GetClientID();
	data->Write(&clientid,4);
	uint16 nPort = theApp.glob_prefs->GetPort();
	data->Write(&nPort,2);
	uint32 tagcount = 3;
	data->Write(&tagcount,4);
	CTag* tag = new CTag(CT_NAME,theApp.glob_prefs->GetUserNick());
	tag->WriteTagToFile(data);
	delete tag;
	tag = new CTag(CT_VERSION,EDONKEYVERSION);
	tag->WriteTagToFile(data);
	delete tag;
	tag = new CTag(CT_PORT,theApp.glob_prefs->GetPort());
	tag->WriteTagToFile(data);
	delete tag;
	uint32 dwIP;
	if (theApp.serverconnect->IsConnected()){
		dwIP = theApp.serverconnect->GetCurrentServer()->GetIP();
		nPort = theApp.serverconnect->GetCurrentServer()->GetPort();
	}
	else{
		nPort = 0;
		dwIP = 0;
	}
	data->Write(&dwIP,4);
	data->Write(&nPort,2);
}

void CUpDownClient::ProcessMuleCommentPacket(char* pachPacket, uint32 nSize){
	if (nSize>2){
		CSafeMemFile data((BYTE*)pachPacket,nSize);
		int length;
		data.Read(&m_iRate,sizeof(m_iRate));
		data.Read(&length,sizeof(length));
		theApp.emuledlg->AddDebugLogLine(true,"File rating received: %i",m_iRate);
		if (length>50) length=50;
		if (length>0){
			char* desc=new char[length+1];
			memset(desc,0,length+1);
			data.Read(desc,length);
			theApp.emuledlg->AddDebugLogLine(true,"File description received: %s",desc);
			m_strComment.Format("%s",desc);
			delete desc;
		}

	}
}
void CUpDownClient::Disconnected(){
	ASSERT(theApp.clientlist->Debug_IsValidClient(this));
	if (GetUploadState() == US_UPLOADING)
		theApp.uploadqueue->RemoveFromUploadQueue(this);
	if (GetDownloadState() == DS_DOWNLOADING){
		SetDownloadState(DS_ONQUEUE);
	}
	ASSERT(theApp.clientlist->Debug_IsValidClient(this));
	//check if this client is needed in any way, if not delete it
	bool bDelete = true;
	switch(m_byUploadState){
		case US_ONUPLOADQUEUE:
			bDelete = false;
	};
	switch(m_nDownloadState){
		case DS_ONQUEUE:
		case DS_TOOMANYCONNS:
		case DS_NONEEDEDPARTS:
		case DS_LOWTOLOWIP:
			bDelete = false;
	};

	switch(m_byUploadState){
		case US_CONNECTING:
		case US_WAITCALLBACK:
		case US_ERROR:
			bDelete = true;
	};
	switch(m_nDownloadState){
		case DS_CONNECTING:
		{
			m_cFailed++;
			if (m_cFailed <= 2){
				TryToConnect();
				return;
			}
		}
		case DS_WAITCALLBACK:
		case DS_ERROR:
			bDelete = true;
	};
	

	if (GetChatState()){
		bDelete = false;
		theApp.emuledlg->chatwnd.chatselector.ConnectingResult(this,false);
	}
	if (socket){
		ASSERT (theApp.listensocket->IsValidSocket(socket));
		socket->Safe_Delete();
	}
	socket = 0;
	if (m_bFileListRequested){
		theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_SHAREDFILES_FAILED),GetUserName());
		m_bFileListRequested = false;
	}
	if (m_Friend)
		theApp.friendlist->RefreshFriend(m_Friend);
	if (bDelete){
		delete this;
	}

}

void CUpDownClient::TryToConnect(bool bIgnoreMaxCon){
	if (theApp.listensocket->TooManySockets() && !bIgnoreMaxCon && !(socket && socket->IsConnected())){
		Disconnected();
		return;
	}
	if ((theApp.serverconnect->GetClientID() < 16777216) && HasLowID()){
		if (GetDownloadState() == DS_CONNECTING)
			SetDownloadState(DS_LOWTOLOWIP);
		else if (GetDownloadState() == DS_REQHASHSET){
			SetDownloadState(DS_ONQUEUE);
			reqfile->hashsetneeded = true;
		}
		if (GetUploadState() == US_CONNECTING){
			Disconnected();
		}
	}

	if (!socket){
		socket = new CClientReqSocket(theApp.glob_prefs,this);
		if (!socket->Create()){
			socket->Safe_Delete();
			return;
		}
	}
	else if (!socket->IsConnected()){
		socket->Safe_Delete();
		socket = new CClientReqSocket(theApp.glob_prefs,this);
		if (!socket->Create()){
			socket->Safe_Delete();
			return;
		}
	}
	else{
		ConnectionEstablished();
		return;
	}
	// MOD Note: Do not change this part - Merkur
	if (HasLowID()){
		if (GetDownloadState() == DS_CONNECTING)
			SetDownloadState(DS_WAITCALLBACK);
		if (GetUploadState() == US_CONNECTING){
			Disconnected();
			return;
		}

		if (theApp.serverconnect->IsLocalServer(m_dwServerIP,m_nServerPort)){
			Packet* packet = new Packet(OP_CALLBACKREQUEST,4);
			memcpy(packet->pBuffer,&m_nUserID,4);
			theApp.serverconnect->SendPacket(packet);
		}
		else{
			if (GetUploadState() == US_NONE && (!GetRemoteQueueRank() || m_bReaskPending) ){
				theApp.downloadqueue->RemoveSource(this);
				Disconnected();
				return;
			}
			else{
				if (GetDownloadState() == DS_WAITCALLBACK){
					m_bReaskPending = true;
					SetDownloadState(DS_ONQUEUE);
				}
			}
		}
	}
	// MOD Note - end
	else{
		socket->Connect(GetFullIP(),GetUserPort());
		SendHelloPacket();
	}
}

void CUpDownClient::ConnectionEstablished(){
	m_cFailed = 0;
	// Added by Tarod (to add LS sources)
	if (m_sourceEntry != (CSourceEntry*)NULL) {
		m_sourceEntry->ResetFailedConnections();
		m_sourceEntry->ResetLastSeenTime(); // HACK by Md5Chap 2002-11-26 (moved here,
											// because aging will be better here.)
	}
	// End by Tarod

	// ok we have a connection, lets see if we want anything from this client
	if (GetChatState() == MS_CONNECTING)
		theApp.emuledlg->chatwnd.chatselector.ConnectingResult(this,true);
	switch(GetDownloadState()){
		case DS_CONNECTING:
		case DS_WAITCALLBACK:
			m_bReaskPending = false;
			SetDownloadState(DS_CONNECTED);
			SendFileRequest();
	}
	if (m_bReaskPending){
		m_bReaskPending = false;
		if (GetDownloadState() != DS_NONE && GetDownloadState() != DS_DOWNLOADING){
			SetDownloadState(DS_CONNECTED);
			SendFileRequest();
		}
	}
	switch(GetUploadState()){
		case US_CONNECTING:
		case US_WAITCALLBACK:
			if (theApp.uploadqueue->IsDownloading(this)){
				SetUploadState(US_UPLOADING);
				Packet* packet = new Packet(OP_ACCEPTUPLOADREQ,0);
				socket->SendPacket(packet,true);
			}
	}
	if (m_bFileListRequested){
		Packet* packet = new Packet(OP_ASKSHAREDFILES,0);
		socket->SendPacket(packet,true,true);
	}
}

uint8 CUpDownClient::GetClientSoft(){
	if (!m_pszUsername)
		return SO_UNKNOWN;
	if (m_achUserHash[5] == 13 && m_achUserHash[14] == 110)
		return SO_OLDEMULE;
	if (m_achUserHash[5] == 14 && m_achUserHash[14] == 111)
		return SO_EMULE;
	if (m_achUserHash[5] == 'M' && m_achUserHash[14] == 'L')
		return SO_MLDONKEY;

	return SO_EDONKEY;
}

void CUpDownClient::SetUserName(char* pszNewName){
	if (m_pszUsername)
		delete[] m_pszUsername;
	m_pszUsername = nstrdup(pszNewName);
}

void CUpDownClient::RequestSharedFileList(){
	theApp.emuledlg->AddDebugLogLine(true,GetResString(IDS_SHAREDFILES_REQUEST),GetUserName());
	m_bFileListRequested = true;
	TryToConnect(true);
}

void CUpDownClient::ProcessSharedFileList(char* pachPacket, uint32 nSize){
	if (m_bFileListRequested){
		m_bFileListRequested = false;
		theApp.searchlist->ProcessSearchanswer(pachPacket,nSize,this);
	}
}