//
// Asura_PC_DirectPlay.cpp
//
// Created by Kevin Lea - 13/06/2001 11:34:22
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Network/Asura_PC_DirectPlay.h"

// Engine independent.
#include "Core/Asura_Maths.h"
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_Assert.h"
#include "Network/Asura_Network.h"
#include "Network/Asura_Message.h"

// Engine platform-specific.
#include "Core/Asura_dxutil.h"
#include "Network/Asura_PC_DirectPlay_Client.h"
#include "Network/Asura_PC_DirectPlay_Server.h"
#ifdef ASURA_PC_SUPPORT_GCDKEY
#include "Network/Asura_PC_GCDKey.h"
#endif


// D E B U G V A R S /////////////////////////////////////////////////////////
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
DEBUGVAR float dbg_fTimeBetweenBandwidthCalculations = 5.0f;
DEBUGVAR u_int dbg_uDirectPlaySendTimeout = 1000;	// ms
DEBUGVAR u_int dbg_uDirectPlaySendTimeoutGuaranteed = 0;	// ms
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

// (MH - 01.09.2003) note: guaranteed messages that have not been attempted
// to send over the wire will timeout unless the timeout is set to 0. We may
// want to break the connection if a guaranteed message has sat in the send 
// queue for longer than a largish number of seconds as that indicates that
// the connection is not able to keep up with the send rate of the game.

// G L O B A L S /////////////////////////////////////////////////////////////
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
static CRITICAL_SECTION g_xProcessingMessages;
static bool g_bMessageCriticalSectionValid = false;
static float g_fTimeSinceLastBandwidthCalculation;
static u_int g_uLastSentCount;
static u_int g_uLastReceiveCount;
static u_int g_uSendBPS;
static u_int g_uReceiveBPS;
static u_int g_uPeakSendBPS;
static u_int g_uPeakReceiveBPS;
static float g_fDroppedPercent;
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////
void Asura_Network::PlatformInitialise(void)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	InitializeCriticalSection(&g_xProcessingMessages);
	g_bMessageCriticalSectionValid = true;

	#ifdef ASURA_PC_SUPPORT_GCDKEY
	//RAR - To be safe assume player is allowed to join , until we get to a point where we
	//can contact the server to check.
	Asura_PC_GCDKey::SetAuthorised(true);
	Asura_PC_GCDKey::SetErrorMessage(0);
	#endif

	Asura_PC_DirectPlay::Initialise();

	g_fTimeSinceLastBandwidthCalculation = 0.0f;
	g_uLastSentCount = 0;
	g_uLastReceiveCount = 0;
	g_uPeakSendBPS = 0;
	g_uPeakReceiveBPS = 0;
	g_fDroppedPercent = 0.0f;
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}


void Asura_Network::PlatformShutdown(void)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	Platform_LockMessageQueue();
	Asura_PC_DirectPlay::Shutdown();
	Platform_UnlockMessageQueue();

	g_bMessageCriticalSectionValid = false;
	DeleteCriticalSection(&g_xProcessingMessages);
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}


void Asura_Network::PlatformUpdate(void)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	if (!IsServer())
	{
		if (IsChoosingSession())
		{
			Asura_PC_DirectPlay::ShowSessions();
		}
	
		if (IsConnectionOkay())
		{
		//	Asura_PC_DirectPlay::ClientSendMessage();
		}
	}
	else
	{
//		Asura_PC_DirectPlay::ServerSendMessage();
	}
	
	Asura_PC_DirectPlay::UpdateBandwidthStats();
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}

void Asura_Network::Platform_StartGame()
{
	// (MH - 21.08.2003) this use to set server ready.
}

void Asura_Network::Platform_SetServerLANReporting(bool bEnable)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	Asura_Assert(IsServer() && IsExternalNetworkActive(), "");
	Asura_PC_DirectPlay::SetServerReady(bEnable);
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}

// Kevin Lea - 14/06/2001 10:20:45 - Asura_Network client stuff
void Asura_Network::PlatformStartClient(const char* szIPAddressToConnectTo)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
    #ifdef ASURA_PC_SUPPORT_GCDKEY
	Asura_PC_GCDKey::SetWaitingForAuthorisation(true);
	Asura_PC_GCDKey::SetAuthorised(false);
	#endif
	Asura_PC_DirectPlay::StartClientAndFindSessions(szIPAddressToConnectTo);
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}


void Asura_Network::PlatformStopClient(void)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	if (Asura_PC_DirectPlay::EnumSessionsActive)
	{
		Asura_PC_DirectPlay::StopSessionsEnum();
		Asura_PC_DirectPlay::DeleteSessionsInfo();
	}

    Asura_PC_DirectPlay_Client::DestroyInstance( &Asura_PC_DirectPlay::s_pxClient );
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}


//void Asura_Network::Platform_ConnectToSession(u_int uSessionIndex)
//{
//#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
//	#ifdef ASURA_PC_SUPPORT_GCDKEY
//	Asura_PC_GCDKey::SetWaitingForAuthorisation(true);
//	Asura_PC_GCDKey::SetAuthorised(false);
//	#endif
//	Asura_PC_DirectPlay::ConnectToSession(uSessionIndex);
//#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
//}

// Kevin Lea - 14/06/2001 10:56:24 - Asura_Network Server stuff
bool Asura_Network::PlatformStartServer(void)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	s_bChoosingSession = true;
    #ifdef ASURA_PC_SUPPORT_GCDKEY
	if(!IsDedicatedServer())
	{
		Asura_PC_GCDKey::SetWaitingForAuthorisation(true);
		Asura_PC_GCDKey::SetAuthorised(false);
	}
	#endif
	return Asura_PC_DirectPlay::StartServerAndHostSession();
#else 
	return true;
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}

void Asura_Network::PlatformStopServer( void )
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
    Asura_PC_DirectPlay_Server::DestroyInstance( &Asura_PC_DirectPlay::s_pxServer );
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}

void Asura_Network::Platform_SendMessageToServer(const Asura_Message* pxMessage)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	DPN_BUFFER_DESC buffer;

	Asura_PC_DirectPlay::PackMessageIntoBuffer(pxMessage,buffer);

	u_int uTimeout;
	u_int uFlags = DPNSEND_NONSEQUENTIAL;
	if (pxMessage->m_bGuaranteed) 
	{
		uTimeout = dbg_uDirectPlaySendTimeoutGuaranteed;
		uFlags |= DPNSEND_GUARANTEED | DPNSEND_PRIORITY_HIGH;
	}
	else
	{
		uTimeout = dbg_uDirectPlaySendTimeout;
	}

	DPNHANDLE dummy;

	//HRESULT hr = 
	Asura_PC_DirectPlay::s_pxClient->Send
	(
		&buffer, 
		1, // number of buffers
		uTimeout, // timeout (ms)
		(void*)pxMessage->m_bGuaranteed,	// ptr to user context, abusing it for check on guaranteed
		&dummy,
		uFlags
	);	
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}


void Asura_Network::Platform_SendMessageToAllClients(const Asura_Message* pxMessage)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	DPN_BUFFER_DESC buffer;

	Asura_PC_DirectPlay::PackMessageIntoBuffer(pxMessage,buffer);

	u_int uTimeout;
	u_int uFlags = DPNSEND_NOLOOPBACK | DPNSEND_NONSEQUENTIAL;
	if (pxMessage->m_bGuaranteed) 
	{
		uTimeout = dbg_uDirectPlaySendTimeoutGuaranteed;
		uFlags |= DPNSEND_GUARANTEED | DPNSEND_PRIORITY_HIGH;
	}
	else
	{
		uTimeout = dbg_uDirectPlaySendTimeout;
	}
	
	DPNHANDLE dummy;

	//HRESULT hr = 
	Asura_PC_DirectPlay::s_pxServer->SendTo
	(
		DPNID_ALL_PLAYERS_GROUP,
		&buffer, 
		1, // number of buffers
		uTimeout, // timeout (ms)
		(void*)pxMessage->m_bGuaranteed,	// ptr to user context, abusing it for check on guaranteed
		&dummy,
		uFlags 
	);
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

}


void Asura_Network::Platform_SendMessageToClientMachine(const Asura_Message* pxMessage, int iMachine)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	Asura_Assert(iMachine>=0 && iMachine<(int)Asura_Network::GetMaxNumberOfMachines(), "invalid machine");

	/// DWE - 28/08/2003 15:07:38 - Removing this assert as 
	/// the player has been correctly removed from the game but there 
	/// may still be messages in the buffer to send.
	/// I guess DirectPlay will handle these correctly (i.e. do nothing)
//	Asura_Assert(Asura_PC_DirectPlay::PlayerInfo[iMachine].IsValid, "invalid machine");


	DPN_BUFFER_DESC xBuffer;

	Asura_PC_DirectPlay::PackMessageIntoBuffer(pxMessage,xBuffer);

	u_int uTimeout;
	u_int uFlags = DPNSEND_NOLOOPBACK | DPNSEND_NONSEQUENTIAL;
	if (pxMessage->m_bGuaranteed) 
	{
		uTimeout = dbg_uDirectPlaySendTimeoutGuaranteed;
		uFlags |= DPNSEND_GUARANTEED | DPNSEND_PRIORITY_HIGH;
	}
	else
	{
		uTimeout = dbg_uDirectPlaySendTimeout;
	}

	DPNHANDLE xDummy;

	//HRESULT hr = 
	Asura_PC_DirectPlay::s_pxServer->SendTo
	(
		Asura_PC_DirectPlay::PlayerInfo[iMachine].NetworkID,
		&xBuffer, 
		1, // number of buffers
		uTimeout, // timeout (ms)
		(void*)pxMessage->m_bGuaranteed,	// ptr to user context, abusing it for check on guaranteed
		&xDummy,
		uFlags 
	);
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}


void Asura_Network::Platform_LockMessageQueue(void)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	Asura_Assert(g_bMessageCriticalSectionValid,"Critical section not valid");
	EnterCriticalSection(&g_xProcessingMessages);
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}


void Asura_Network::Platform_UnlockMessageQueue(void)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	Asura_Assert(g_bMessageCriticalSectionValid,"Critical section not valid");
	LeaveCriticalSection(&g_xProcessingMessages);
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}


float Asura_Network::Platform_GetLatency(void)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	if (s_bServer) return 0;

	DPN_CONNECTION_INFO info;
	info.dwSize = sizeof(DPN_CONNECTION_INFO);

	Asura_PC_DirectPlay::s_pxClient->GetConnectionInfo(&info,0);

	return (float)info.dwRoundTripLatencyMS/1000.0f;
#else 
	return 0.0f;
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}

u_int Asura_Network::Platform_GetLatencyForPlayer(Asura_Guid uPlayerGuid)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	DPN_CONNECTION_INFO dnConnectInfo;
	ZeroMemory(&dnConnectInfo, sizeof(DPN_CONNECTION_INFO));
	dnConnectInfo.dwSize = sizeof(DPN_CONNECTION_INFO);

	for(u_int uPlayer = 0; uPlayer < Asura_Network::GetMaxNumberOfMachines(); uPlayer++)
	{
		if((u_int)Asura_PC_DirectPlay::PlayerInfo[uPlayer].ClientGuid == uPlayerGuid)
		{
			HRESULT hr = Asura_PC_DirectPlay::s_pxServer->GetConnectionInfo(Asura_PC_DirectPlay::PlayerInfo[uPlayer].NetworkID, &dnConnectInfo, 0);

			if(SUCCEEDED(hr))
			{
				return dnConnectInfo.dwRoundTripLatencyMS;
			}
			else
			{
				return 0;
			}
		}
	}
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

	return 0;
}

#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
void Asura_PC_DirectPlay::UpdateBandwidthStats(void)
{
	g_fTimeSinceLastBandwidthCalculation += Asura_Timers::GetFrameTime();
	if (g_fTimeSinceLastBandwidthCalculation > dbg_fTimeBetweenBandwidthCalculations)
	{
		DPN_CONNECTION_INFO xInfo;
		xInfo.dwSize = sizeof(DPN_CONNECTION_INFO);

		u_int uNewSentCount = 0;
		u_int uNewReceivedCount = 0;
		u_int uNewDroppedPacketCount = 0;
		u_int uNewPacketCount = 0;

		if (Asura_Network::IsServer()) 
		{
			for (u_int uMachine = 0; uMachine < Asura_Network::GetMaxNumberOfMachines(); uMachine++)
			{
				if (Asura_PC_DirectPlay::PlayerInfo[uMachine].IsValid)
				{
					HRESULT hr = Asura_PC_DirectPlay::s_pxServer->GetConnectionInfo(Asura_PC_DirectPlay::PlayerInfo[uMachine].NetworkID, &xInfo, 0);
					//Asura_Assert(!FAILED(hr), "GetConnectionInfo() failed");
					if (!FAILED(hr))
					{
						uNewReceivedCount += xInfo.dwBytesReceivedGuaranteed;
						uNewReceivedCount += xInfo.dwBytesReceivedNonGuaranteed;
						uNewSentCount += xInfo.dwBytesSentGuaranteed;
						uNewSentCount += xInfo.dwBytesSentNonGuaranteed;
						uNewPacketCount += xInfo.dwPacketsSentGuaranteed;
						uNewPacketCount += xInfo.dwPacketsSentNonGuaranteed;
						uNewDroppedPacketCount += xInfo.dwPacketsDropped;
					}
				}
			}
		}
		else
		{
			HRESULT hr = Asura_PC_DirectPlay::s_pxClient->GetConnectionInfo(&xInfo,0);
			//Asura_Assert(!FAILED(hr), "GetConnectionInfo() failed"); // this can happen if connection isn't completed
			if (!FAILED(hr))
			{
				uNewReceivedCount = xInfo.dwBytesReceivedGuaranteed;
				uNewReceivedCount += xInfo.dwBytesReceivedNonGuaranteed;
				uNewSentCount = xInfo.dwBytesSentGuaranteed;
				uNewSentCount += xInfo.dwBytesSentNonGuaranteed;
				uNewPacketCount += xInfo.dwPacketsSentGuaranteed;
				uNewPacketCount += xInfo.dwPacketsSentNonGuaranteed;
				uNewDroppedPacketCount += xInfo.dwPacketsDropped;
			}
		}

		float fSentThisPeriod = static_cast<float>(uNewSentCount - g_uLastSentCount);
		float fReceivedThisPeriod = static_cast<float>(uNewReceivedCount - g_uLastReceiveCount);
		
		g_uSendBPS = Asura_Maths::FloatToInt(fSentThisPeriod / g_fTimeSinceLastBandwidthCalculation);
		g_uReceiveBPS = Asura_Maths::FloatToInt(fReceivedThisPeriod / g_fTimeSinceLastBandwidthCalculation);
		if (uNewPacketCount)
		{
			g_fDroppedPercent = 100.0f * static_cast<float>(uNewDroppedPacketCount) / static_cast<float>(uNewPacketCount);
		}
		else
		{
			g_fDroppedPercent = 0.0f;
		}
		if (g_uSendBPS > g_uPeakSendBPS)
		{
			g_uPeakSendBPS = g_uSendBPS;
		}
		if (g_uReceiveBPS > g_uPeakReceiveBPS)
		{
			g_uPeakReceiveBPS = g_uReceiveBPS;
		}
		
		g_uLastSentCount = uNewSentCount;
		g_uLastReceiveCount = uNewReceivedCount;
		g_fTimeSinceLastBandwidthCalculation = 0.0f;
	}
}
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

void Asura_Network::Platform_GetNetworkBandwidthUsage(u_int& uSend, u_int& uReceive)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	uSend = g_uSendBPS;
	uReceive = g_uReceiveBPS;
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}

void Asura_Network::Platform_GetPeakNetworkBandwidthUsage(u_int& uSend, u_int& uReceive)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	uSend = g_uPeakSendBPS;
	uReceive = g_uPeakReceiveBPS;
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}

float Asura_Network::Platform_GetPacketLossPercent()
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	return g_fDroppedPercent;
#else
	return 0.0f;
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

}

void Asura_Network::Platform_EndOfFrame(void)
{}

//void Asura_Network::Platform_UpdateProjectSessionData(Asura_Network_SessionData* pxSessionData, const u_int uSessionDataSize)
//{
//#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
//	if(!s_bServer) return;
//
//	Asura_Assert(Asura_Network::IsNetworkInitialised(), "Not a valid server");
//
//	DWORD dwSize = 0;
//	HRESULT hr = Asura_PC_DirectPlay::s_pxServer->GetApplicationDesc(NULL, &dwSize, 0);
//	if(hr != DPNERR_BUFFERTOOSMALL)
//	{
//		return;
//	}
//
//	DPN_APPLICATION_DESC* pAppDesc = (DPN_APPLICATION_DESC*) new BYTE[dwSize];
//	if(!pAppDesc)
//		return;
//
//	ZeroMemory(pAppDesc, dwSize);
//	pAppDesc->dwSize = sizeof(DPN_APPLICATION_DESC);
//
//	hr = Asura_PC_DirectPlay::s_pxServer->GetApplicationDesc(pAppDesc, &dwSize, 0);
//	if(FAILED(hr))
//	{
//		delete [] pAppDesc;
//		return;
//	}
//
//	if (Asura_PC_DirectPlay::s_uPasswordHash)
//	{
//		pxSessionData->m_usFlags |= ASURA_NETWORK_SESSIONDATAFLAG_REQPASSWORD;
//	}
//	else
//	{
//		pxSessionData->m_usFlags &= ~ASURA_NETWORK_SESSIONDATAFLAG_REQPASSWORD;
//	}
//
//	// other members of the app desc will hold their previous values, so no need to set them.
//	// update the project data now.
//	pAppDesc->pvApplicationReservedData = reinterpret_cast<void*>(pxSessionData);
//	pAppDesc->dwApplicationReservedDataSize = uSessionDataSize;
//
//	hr = Asura_PC_DirectPlay::s_pxServer->SetApplicationDesc(pAppDesc, 0);
//	
//	// free memory.
//	delete [] pAppDesc;
//#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
//};
//
//
//void Asura_Network::Platform_UpdateProjectSessionData(const Asura_Network_SessionData* pxSessionData, const u_int uSessionDataSize)
//{
//  Asura_Assert(false, "DirectPlay networking shouldn't use this function");
//  // this is a replacement for DPs ApplicationDesc.
//}
//
//
//// SNH - 19/08/2003 10:21:08 - call this function on client machines to get the current project network session
//void Asura_Network::Platform_GetCurrentSessionInfo(void* pxSessionData, const u_int uSessionDataSize)
//{
//#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
//	Asura_Assert(Asura_Network::IsExternalNetworkActive() && Asura_Network::IsConnectionOkay(), "Network not initialised");
//	Asura_Assert(pxSessionData, "Session data buffer not found");
//
//	if(!pxSessionData || !Asura_Network::IsExternalNetworkActive() || !Asura_Network::IsConnectionOkay())
//	{
//		return;
//	}
//
//	DWORD dwSize = 0;
//	HRESULT hr = Asura_PC_DirectPlay::s_pxClient->GetApplicationDesc(NULL, &dwSize, 0);
//	if(hr != DPNERR_BUFFERTOOSMALL)
//	{
//		return;
//	}
//
//	DPN_APPLICATION_DESC* pAppDesc = (DPN_APPLICATION_DESC*) new BYTE[dwSize];
//	if(!pAppDesc)
//		return;
//
//	ZeroMemory(pAppDesc, dwSize);
//	pAppDesc->dwSize = sizeof(DPN_APPLICATION_DESC);
//
//	hr = Asura_PC_DirectPlay::s_pxClient->GetApplicationDesc(pAppDesc, &dwSize, 0);
//	if(FAILED(hr))
//	{
//		delete [] pAppDesc;
//		return;
//	}
//
//	Asura_Assert(uSessionDataSize == pAppDesc->dwApplicationReservedDataSize, "Session data sizes don't match");
//	memcpy(pxSessionData, pAppDesc->pvApplicationReservedData, uSessionDataSize);
//	
//	// free memory.
//	delete [] pAppDesc;
//#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
//}

void Asura_Network::Platform_SetServerName(const Asura_UnicodeChar* wszServerName)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
    Asura_PC_DirectPlay::SetServerName(wszServerName);
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
}

void Asura_Network::Platform_RemoveMachineFromGame( int iMachine, float fDelay )
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	Asura_Assert(false, "not implemented yet");
#endif
}
