#include <winsock2.h>
#include "wildcards.hh"
#include "hook.h"

/**************************************************************************************************************
Update average ping data
**************************************************************************************************************/

void HkTimerUpdatePingData()
{
	try {
		// for all players
		struct PlayerData *pPD = 0;
		while(pPD = Players.traverse_active(pPD))
		{
			uint iClientID = HkGetClientIdFromPD(pPD);
			if(ClientInfo[iClientID].tmF1TimeDisconnect)
				continue;

//			CDPClientProxy *cdpClient = g_cClientProxyArray[iClientID - 1];
//			u_long pPingPacket[2] = {3, 4, 5, 6, 7};
//			cdpClient->Send(&pPingPacket, sizeof(pPingPacket));
//			CDPServer::getServer()->SendTo(cdpClient, &pPingPacket, sizeof(pPingPacket));

			DPN_CONNECTION_INFO ci;
			if(HkGetConnectionStats(iClientID, ci) != HKE_OK)
				continue;

			///////////////////////////////////////////////////////////////
			// update ping data
			if(ClientInfo[iClientID].lstPing.size() >= set_iPingKickFrame)
			{
				// calculate average loss
				ClientInfo[iClientID].iAveragePing = 0;
				foreach(ClientInfo[iClientID].lstPing, uint, it)
					ClientInfo[iClientID].iAveragePing += (*it);

				ClientInfo[iClientID].iAveragePing /= (uint)ClientInfo[iClientID].lstPing.size();
			}

			// remove old pingdata
			while(ClientInfo[iClientID].lstPing.size() >= set_iPingKickFrame)
				ClientInfo[iClientID].lstPing.pop_back();

			ClientInfo[iClientID].lstPing.push_front(ci.dwRoundTripLatencyMS);
		}
	} catch(...) { AddLog("Exception in %s", __FUNCTION__); }
}

/**************************************************************************************************************
Update average loss data
**************************************************************************************************************/

void HkTimerUpdateLossData()
{
	try {
		// for all players
		struct PlayerData *pPD = 0;
		while(pPD = Players.traverse_active(pPD))
		{
			uint iClientID = HkGetClientIdFromPD(pPD);
			if(ClientInfo[iClientID].tmF1TimeDisconnect)
				continue;

			DPN_CONNECTION_INFO ci;
			if(HkGetConnectionStats(iClientID, ci) != HKE_OK)
				continue;

			///////////////////////////////////////////////////////////////
			// update loss data
			if(ClientInfo[iClientID].lstLoss.size() >= (set_iLossKickFrame / (LOSS_INTERVALL / 1000)))
			{
				// calculate average loss
				ClientInfo[iClientID].iAverageLoss = 0;
				foreach(ClientInfo[iClientID].lstLoss, uint, it)
					ClientInfo[iClientID].iAverageLoss += (*it);

				ClientInfo[iClientID].iAverageLoss /= (uint)ClientInfo[iClientID].lstLoss.size();
			}

			// remove old lossdata
			while(ClientInfo[iClientID].lstLoss.size() >= (set_iLossKickFrame / (LOSS_INTERVALL / 1000)))
				ClientInfo[iClientID].lstLoss.pop_back();

//			CDPClientProxy *cdpClient = g_cClientProxyArray[iClientID - 1];
//			double d = cdpClient->GetLinkSaturation();		
//			ClientInfo[iClientID].lstLoss.push_front((uint)(cdpClient->GetLinkSaturation() * 100)); // loss per sec
			ClientInfo[iClientID].lstLoss.push_front(ci.dwBytesRetried - ClientInfo[iClientID].iLastLoss); // loss per sec
			ClientInfo[iClientID].iLastLoss = ci.dwBytesRetried;
		}
	} catch(...) { AddLog("Exception in %s", __FUNCTION__); }
}

/**************************************************************************************************************
check if players should be kicked
**************************************************************************************************************/

void HkTimerCheckKick()
{
	try {
		// for all players
		struct PlayerData *pPD = 0;
		while(pPD = Players.traverse_active(pPD))
		{
			uint iClientID = HkGetClientIdFromPD(pPD);

			if(ClientInfo[iClientID].tmKickTime)
			{	
				if(timeInMS() >= ClientInfo[iClientID].tmKickTime) 
					HkKick(ARG_CLIENTID(iClientID)); // kick time expired

				continue; // player will be kicked anyway
			}

			if(set_iAntiBaseIdle)
			{ // anti base-idle check
				uint iBaseID;
				pub::Player::GetBase(iClientID, iBaseID);
				if(iBaseID && ClientInfo[iClientID].iBaseEnterTime)
				{
					if((time(0) - ClientInfo[iClientID].iBaseEnterTime) >= set_iAntiBaseIdle)
					{
						HkAddKickLog(iClientID, L"Base idling");
						HkMsgAndKick(iClientID, L"Base idling", set_iKickMsgPeriod);
					}
				}
			}

			if(set_iAntiCharMenuIdle)
			{ // anti charmenu-idle check
				if(HkIsInCharSelectMenu(iClientID))	{
					if(!ClientInfo[iClientID].iCharMenuEnterTime)
						ClientInfo[iClientID].iCharMenuEnterTime = (uint)time(0);
					else if((time(0) - ClientInfo[iClientID].iCharMenuEnterTime) >= set_iAntiCharMenuIdle) {
						HkAddKickLog(iClientID, L"Charmenu idling");
						HkKick(ARG_CLIENTID(iClientID));
						continue;
					}
				} else
					ClientInfo[iClientID].iCharMenuEnterTime = 0;
			}

			if(set_iLossKick)
			{ // check if loss is too high
				if(ClientInfo[iClientID].iAverageLoss > set_iLossKick)
				{
					HkAddKickLog(iClientID, L"High loss");
					HkMsgAndKick(iClientID, L"High loss", set_iKickMsgPeriod);
				}
			}

			if(set_iPingKick)
			{ // check if ping is too high
				if(ClientInfo[iClientID].iAveragePing > set_iPingKick)
				{
					HkAddKickLog(iClientID, L"High ping");
					HkMsgAndKick(iClientID, L"High ping", set_iKickMsgPeriod);
				}
			}
		}
	} catch(...) { AddLog("Exception in %s", __FUNCTION__); }
}

/**************************************************************************************************************
Check if NPC spawns should be disabled
**************************************************************************************************************/

void HkTimerNPCAndF1Check()
{
	try {
		struct PlayerData *pPD = 0;
		while(pPD = Players.traverse_active(pPD))
		{
			uint iClientID = HkGetClientIdFromPD(pPD);

			if(ClientInfo[iClientID].tmF1Time && (timeInMS() >= ClientInfo[iClientID].tmF1Time)) { // f1
				Server.CharacterInfoReq(iClientID, false);
				ClientInfo[iClientID].tmF1Time = 0;
			} else if(ClientInfo[iClientID].tmF1TimeDisconnect && (timeInMS() >= ClientInfo[iClientID].tmF1TimeDisconnect)) {
				ulong lArray[64] = {0};
				lArray[26] = iClientID;
				__asm
				{
					pushad
					lea ecx, lArray
					mov eax, [hModRemoteClient]
					add eax, ADDR_RC_DISCONNECT
					call eax ; disconncet
					popad
				}

				ClientInfo[iClientID].tmF1TimeDisconnect = 0;
				continue;
			}
		}

		// npc
		if(set_iDisableNPCSpawns && (g_iServerLoad >= set_iDisableNPCSpawns))
			HkChangeNPCSpawn(true); // serverload too high, disable npcs
		else
			HkChangeNPCSpawn(false);
	} catch(...) { AddLog("Exception in %s", __FUNCTION__); }
}

/**************************************************************************************************************
**************************************************************************************************************/

list<BASE_INFO> lstBases;

void HkTimerCheckIfBaseDestroyed()
{
	try {
		foreach(lstBases, BASE_INFO, it)
		{
			float fHealth;
			float fMaxHealth;
			pub::SpaceObj::GetHealth((*it).iObjectID, fHealth, fMaxHealth);
			if(fHealth > 0)
				(*it).bDestroyed = false;
			else if(!fHealth && !(*it).bDestroyed && (fMaxHealth > 0)) {
				(*it).bDestroyed = true;
				// event
				ProcessEvent(L"basedestroy basename=%s objecthash=%u",
								stows((*it).scBasename).c_str(),
								(*it).iObjectID);
			}
		}
	} catch(...) { AddLog("Exception in %s", __FUNCTION__); }
}

/**************************************************************************************************************
**************************************************************************************************************/

CRITICAL_SECTION csIPResolve;
list<RESOLVE_IP> g_lstResolveIPs;
list<RESOLVE_IP> g_lstResolveIPsResult;
HANDLE hThreadResolver;

void HkThreadResolver()
{
	try {
		while(1)
		{
			EnterCriticalSection(&csIPResolve);
			list<RESOLVE_IP> lstMyResolveIPs = g_lstResolveIPs;
			g_lstResolveIPs.clear();
			LeaveCriticalSection(&csIPResolve);

			foreach(lstMyResolveIPs, RESOLVE_IP, it)
			{
				ulong addr = inet_addr(wstos(it->wscIP).c_str());
				hostent *host = gethostbyaddr((const char*)&addr, sizeof(addr), AF_INET);
				if(host)
					it->wscHostname = stows(host->h_name);
			}

			EnterCriticalSection(&csIPResolve);
			foreach(lstMyResolveIPs, RESOLVE_IP, it2)
			{
				if(it2->wscHostname.length())
					g_lstResolveIPsResult.push_back(*it2);
			}
			LeaveCriticalSection(&csIPResolve);

			Sleep(50);
		}
	} catch(...) { AddLog("Exception in %s", __FUNCTION__); }
}

/**************************************************************************************************************
**************************************************************************************************************/

void HkTimerCheckResolveResults()
{
	try {
		EnterCriticalSection(&csIPResolve);
		foreach(g_lstResolveIPsResult, RESOLVE_IP, it)
		{
			if(it->iConnects != ClientInfo[it->iClientID].iConnects)
				continue; // outdated

			// check if banned
			foreach(set_lstBans, wstring, itb)
			{
				if(Wildcard::wildcardfit(wstos(*itb).c_str(), wstos(it->wscHostname).c_str()))
				{
					HkAddKickLog(it->iClientID, L"IP/Hostname ban(%s matches %s)", it->wscHostname.c_str(), (*itb).c_str());
					if(set_bBanAccountOnMatch)
						HkBan(ARG_CLIENTID(it->iClientID), true);
					HkKick(ARG_CLIENTID(it->iClientID));
				}
			}
			ClientInfo[it->iClientID].wscHostname = it->wscHostname;
		}

		g_lstResolveIPsResult.clear();
		LeaveCriticalSection(&csIPResolve);
	} catch(...) { AddLog("Exception in %s", __FUNCTION__); }
}

/**************************************************************************************************************
**************************************************************************************************************/

void HkTimerCloakHandler()
{
	try {
		// for all players
		struct PlayerData *pPD = 0;
		while(pPD = Players.traverse_active(pPD))
		{
			uint iClientID = HkGetClientIdFromPD(pPD);
			
			if (ClientInfo[iClientID].bCanCloak) {
				// send cloak state for uncloaked cloak-able players (only for them in space)
				// this is the code to fix the bug where players wouldnt always see uncloaked players

				uint iShip = 0;
				pub::Player::GetShip(iClientID, iShip);
				if (iShip) {

					if(!ClientInfo[iClientID].bCloaked && !ClientInfo[iClientID].bIsCloaking) {
						XActivateEquip ActivateEq;
						ActivateEq.bActivate = false;
						ActivateEq.l1 = iShip;
						ActivateEq.sID = ClientInfo[iClientID].iCloakSlot;
						Server.ActivateEquip(iClientID,ActivateEq);
					}
					
					// check cloak timings

					mstime tmTimeNow = timeInMS();

					if(ClientInfo[iClientID].bWantsCloak && (tmTimeNow - ClientInfo[iClientID].tmCloakTime) > set_iCloakWarmup)
						HkCloak(iClientID);

					if(ClientInfo[iClientID].bIsCloaking && (tmTimeNow - ClientInfo[iClientID].tmCloakTime) > ClientInfo[iClientID].iCloakingTime) {
						ClientInfo[iClientID].bIsCloaking = false;
						ClientInfo[iClientID].bCloaked = true;
						ClientInfo[iClientID].tmCloakTime = tmTimeNow;
					}

					if(set_iCloakLimit != 0 && ClientInfo[iClientID].bCloaked && (tmTimeNow - ClientInfo[iClientID].tmCloakTime) > set_iCloakLimit) {
						HkUnCloak(iClientID);
					}

				}

			}
		}

	} catch(...) { AddLog("Exception in %s", __FUNCTION__); }

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**************************************************************************************************************
Called when _SendMessage is sent
NOT USED ATM
**************************************************************************************************************/

struct MSGSTRUCT
{
	uint iShipID; // ? or charid? or archetype?
	uint iClientID;
};

void __stdcall HkCb_Message(uint iMsg, MSGSTRUCT *msg)
{
	try {
		uint i = msg->iClientID;

		FILE *f = fopen("msg.txt", "at");
		fprintf(f, "%.4d %.4d\n", iMsg, i);
		fclose(f);
	} catch(...) { AddLog("Exception in %s", __FUNCTION__); }
}

__declspec(naked) void _SendMessageHook()
{
	__asm 
	{
		push [esp+0Ch]
		push [esp+0Ch]
		call HkCb_Message
		ret
	}
}

