//////////////////////////////////////////////////////////////////////////////
//
// Asura_GameSpy.cpp
//
// Copyright (c) 2004 Rebellion. All rights reserved.
//
// Description:
//
//     common gamespy functionality e.g. GameName, VersionID etc.
//
//////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

#ifdef ASURA_SUPPORT_GAMESPY

// this file's header
#include "Network/GameSpy/Asura_GameSpy.h"

// asura includes
#include "Core/Asura_Memheap.h"
#include "Core/Asura_Multithread.h"
#include "Core/Asura_String.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "Network/GameSpy/Asura_GameSpy_GP.h"
#include "Network/GameSpy/Asura_GameSpy_HTTP.h"
#include "Network/GameSpy/Asura_GameSpy_QR2.h"
#include "Network/GameSpy/Asura_GameSpy_ServerBrowsing.h"

// middleware
#include "gamespy/common/gsAvailable.h"
#include "GameSpy/common/gsCommon.h"
#include "GameSpy/ghttp/ghttp.h"
#include "GameSpy/gstats/gstats.h"
#include "GameSpy/natneg/natneg.h"
#include "GameSpy/pt/pt.h"
#include "GameSpy/serverbrowsing/sb_internal.h"
#include "GameSpy/serverbrowsing/sb_serverbrowsing.h"

// standard includes
#include <string.h> // memcpy

// C O N S T A N T S /////////////////////////////////////////////////////////
// Use this define if Asura should handle GameSpy memory
#define USE_ASURA_MEMMANGER_FOR_GAMESPY
#ifdef USE_ASURA_MEMMANGER_FOR_GAMESPY
static const u_int uASURA_GAMESPY_HEAPSIZE = 192 * 1024; // was 0.5MB - need to profile
// declare our C-replacements for malloc/free etc.
extern "C" {
#ifdef GAMESPY_TRACK_MEMORY_ALLOCATIONS
	static void* Asura_GameSpy_Malloc(size_t uSize, const char* szFile, int iLine);
#else // GAMESPY_TRACK_MEMORY_ALLOCATIONS
	static void* Asura_GameSpy_Malloc(size_t uSize);
#endif // GAMESPY_TRACK_MEMORY_ALLOCATIONS
static void Asura_GameSpy_Free(void* pMem);
static void* Asura_GameSpy_Realloc(void* pMem, size_t uSize);
static void* Asura_GameSpy_Memalign(size_t boundary, size_t size);
};
#endif

// I N T E R N A L   D A T A /////////////////////////////////////////////////
class Asura_GameSpy_InitThreadData
{
public:

	GSIACResult m_eAvailableCheckResult;
	bool m_bRequestCancel;
	bool m_bThreadRunning;
	bool m_bDNSFailed;
	char m_cCurProgress;
	char m_cMaxProgress;
};
static volatile Asura_GameSpy_InitThreadData g_xInitData;

// G L O B A L S /////////////////////////////////////////////////////////////
static Asura_MemHeap* g_pxGameSpyMemHeap = NULL;
static void* g_pBuffer = NULL;
static bool g_bGameSpyDebugInfo = false;
static bool g_bDidTrackUsageCall = false;

// I N T E R N A L   F U N C T I O N S ///////////////////////////////////////

#ifdef GSI_COMMON_DEBUG
static void Asura_ProcessGameSpyDebug(GSIDebugCategory eCat, GSIDebugType eType,
									  GSIDebugLevel eLevel, const char* szFormat, 
									  va_list xArgs)
{
	if (g_bGameSpyDebugInfo)
	{
		char acBuffer[256];
		//va_start( xArgs, szFormat );
		Asura_PrintfToBfr( acBuffer, szFormat, xArgs, 256, true );
		va_end( xArgs );
		Asura_OutputToDebugger(("GS: %s", acBuffer));
	}
}
#endif

// S T A T I C   C L A S S   D A T A /////////////////////////////////////////
ASURA_GAMESPY_STATE Asura_GameSpy::s_eState;
char Asura_GameSpy::s_uConnectionProgress = 0;

// F U N C T I O N S /////////////////////////////////////////////////////////
void Asura_GameSpy::Initialise( void )
{
	Asura_Assert(s_eState == ASURA_GAMESPY_STATE_INVALID, "already initialised");

#ifdef USE_ASURA_MEMMANGER_FOR_GAMESPY
	if (g_pBuffer) return;

	g_pBuffer = Asura_Allocate( uASURA_GAMESPY_HEAPSIZE );
	if (g_pBuffer)
	{
		g_pxGameSpyMemHeap = new Asura_MemHeap( g_pBuffer, uASURA_GAMESPY_HEAPSIZE );		
	}
	gsiMemoryCallbacksSet( Asura_GameSpy_Malloc, Asura_GameSpy_Free, Asura_GameSpy_Realloc, Asura_GameSpy_Memalign );
#endif

#ifdef GSI_COMMON_DEBUG
	gsSetDebugLevel( GSIDebugCat_All, GSIDebugType_All, GSIDebugLevel_Verbose );
	gsSetDebugCallback( Asura_ProcessGameSpyDebug );
#endif

	// see if we need to start the available check
	if (g_xInitData.m_eAvailableCheckResult != GSIACAvailable)
	{
		g_xInitData.m_bThreadRunning = true;
		g_xInitData.m_bRequestCancel = false;
		g_xInitData.m_bDNSFailed = false;
		g_xInitData.m_cCurProgress = 0;
		if (Asura_Thread_System::CreateThread( "GameSpy init", InitThreadFunc, NULL ))
		{
			s_eState = ASURA_GAMESPY_STATE_AVAILABLE_CHECK;
			s_uConnectionProgress = 0;
		}
		else
		{
			// this only happens if thread creation fails
			g_xInitData.m_bThreadRunning = false;
			s_eState = ASURA_GAMESPY_STATE_INITIALISED;
			Asura_GameSpy_GP::Initialise();
		}
	}
	else
	{
		s_eState = ASURA_GAMESPY_STATE_INITIALISED;

		// init GP
		Asura_GameSpy_GP::Initialise();
	}
}

void Asura_GameSpy::Shutdown( void )
{
	Asura_Assert(s_eState != ASURA_GAMESPY_STATE_INVALID, "not initialised");
	if (s_eState == ASURA_GAMESPY_STATE_AVAILABLE_CHECK)
	{
		// wait for the thread to finish
		g_xInitData.m_bRequestCancel = true;
		while (g_xInitData.m_bThreadRunning)
		{
			Asura_Thread_System::DelayCurrentThread( 0.001f );
		}
	}
	
	if (g_bDidTrackUsageCall)
	{
		g_bDidTrackUsageCall = false;
		ghttpCleanup();
	}

	// shutdown GP and other SDKs, if we ever got round to initialising them.
	if (Asura_GameSpy_GP::IsInitialised())
	{
		Asura_GameSpy_GP::Shutdown();
	}
	if (Asura_GameSpy_ServerBrowsingSDK::IsInitialised())
	{
		Asura_GameSpy_ServerBrowsingSDK::Shutdown();
	}
	if (Asura_GameSpy_ServerBrowsing::IsInitialised())
	{
		Asura_GameSpy_ServerBrowsing::Shutdown();
	}
	if (Asura_GameSpy_QR2::IsInitialised())
	{
		Asura_GameSpy_QR2::Shutdown();
	}
	Asura_GameSpy_HTTP::Shutdown();
	FreeGame( NULL );

	NNFreeNegotiateList();

#ifdef USE_ASURA_MEMMANGER_FOR_GAMESPY
	// destructor will assert if anything is outstanding.
	delete g_pxGameSpyMemHeap;
	g_pxGameSpyMemHeap = NULL;
	Asura_Deallocate(g_pBuffer);
	g_pBuffer = NULL;
#endif
	
	s_eState = ASURA_GAMESPY_STATE_INVALID;
}

void Asura_GameSpy::Update( void )
{
	switch (s_eState)
	{
		case ASURA_GAMESPY_STATE_AVAILABLE_CHECK:
		{
			s_uConnectionProgress = (g_xInitData.m_cMaxProgress != 0) ? (g_xInitData.m_cCurProgress * 100u) / g_xInitData.m_cMaxProgress : 0;

			// waiting for thread to finish
			switch (g_xInitData.m_eAvailableCheckResult)
			{
				case GSIACAvailable:
				{
					if (g_xInitData.m_bDNSFailed)
					{
						s_eState = ASURA_GAMESPY_STATE_DNS_FAILED;
						g_xInitData.m_eAvailableCheckResult = GSIACWaiting;
					}
					else
					{
						// init GP
						Asura_GameSpy_GP::Initialise();

						static bool ls_bFirstTime = true;
						if (ls_bFirstTime)
						{
							ls_bFirstTime = false;
							// do mandatory Usage Tracking call
							char acVersion[16];
							Asura_PrintfToBfr( acVersion, "%u", 16, true, GetGameVersion() );
							ghttpStartup();
							ptTrackUsage( 0, GetProductID(), acVersion, 0, PTFalse );
							g_bDidTrackUsageCall = true;
						}

						s_eState = ASURA_GAMESPY_STATE_INITIALISED;
					}
					break;
				}
				case GSIACUnavailable:
				{
					s_eState = ASURA_GAMESPY_STATE_UNAVAILABLE;
					break;
				}
				case GSIACTemporarilyUnavailable:
				{
					s_eState = ASURA_GAMESPY_STATE_TEMPORARILY_UNAVAILABLE;
					break;
				}
				default:
				{
					break;
				}
			}// end switch on GSIACResult eResult = GSIAvailableCheckThink();
			break;
		}
		case ASURA_GAMESPY_STATE_INITIALISED:
		{
			Asura_GameSpy_GP::Update();
			// need this here to update the ptTrackUsage()
			// no harm in calling it all the time as it does almost nothing if there are no outstanding requests.
			ghttpThink();
			break;
		}
		default:
		{
			break;
		}
	}// end switch (s_eState)
}

void Asura_GameSpy::InitThreadFunc( const void* )
{
	int iRet = GSIStartAvailableCheck(GetGameName());

	GSIACResult eResult;
	do
	{
		eResult = GSIAvailableCheckThink();
		Asura_Thread_System::DelayCurrentThread( 0.001f );
	}
	while (eResult == GSIACWaiting && !g_xInitData.m_bRequestCancel);

	if (g_xInitData.m_bRequestCancel)
	{
		GSICancelAvailableCheck();
		g_xInitData.m_bThreadRunning = false;
		return;
	}

	// (MH - 02.07.2007) check return code was OK before doing DNS caching in case it went wrong due to DNS down.
	if (iRet == 0)
	{
		// do some DNS caching...
		char acBuffer[256];
		const char* aszPrefix[] = 
		{
			"motd",
			"gamestats",
			"gpcm",
		};
		const u_int uArrayASize = sizeof(aszPrefix)/sizeof(aszPrefix[0]);
		
		char acMasterServer[5];
		Asura_PrintfToBfr(acMasterServer, "ms%d", 5, true, Project_GetMasterServerNumber());
		const char* aszPrefixAfterGamename[] = 
		{
			"gamestats",
			acMasterServer,
			"master",
			"natneg1",
			"natneg2",
		};
		const u_int uArrayBSize = sizeof(aszPrefixAfterGamename)/sizeof(aszPrefixAfterGamename[0]);

		g_xInitData.m_cMaxProgress = uArrayASize + uArrayBSize + 1;
		g_xInitData.m_cCurProgress = 1; //Already done the first step

		for (u_int u = 0; u < uArrayASize; ++u)
		{
			Asura_PrintfToBfr( acBuffer, "%s.gamespy.com", 256, true, aszPrefix[u] );
			Asura_OutputToDebugger_Retail(( "Gamespy finding host %s...", acBuffer ));
			if (!gethostbyname(acBuffer))
			{
				Asura_OutputToDebugger_Retail(( "FAILED\n" ));
				g_xInitData.m_bDNSFailed = true;
				break;
			}
			Asura_OutputToDebugger_Retail(( "OK\n" ));
			g_xInitData.m_cCurProgress++;
			Asura_Thread_System::DelayCurrentThread( 0.001f );
			if (g_xInitData.m_bRequestCancel)
			{
				g_xInitData.m_bThreadRunning = false;
				return;
			}
		}
		for (u_int u = 0; u < uArrayBSize; ++u)
		{
			if (g_xInitData.m_bDNSFailed)
			{
				break;
			}

			Asura_PrintfToBfr( acBuffer, "%s.%s.gamespy.com", 256, true, GetGameName(), aszPrefixAfterGamename[u] );
			Asura_OutputToDebugger_Retail(( "Gamespy finding host %s...", acBuffer ));
			if (!gethostbyname(acBuffer))
			{
				Asura_OutputToDebugger_Retail(( "FAILED\n" ));
				g_xInitData.m_bDNSFailed = true;
				break;
			}
			Asura_OutputToDebugger_Retail(( "OK\n" ));
			g_xInitData.m_cCurProgress++;
			Asura_Thread_System::DelayCurrentThread( 0.001f );
			if (g_xInitData.m_bRequestCancel)
			{
				g_xInitData.m_bThreadRunning = false;
				return;
			}
		}
	}
	else
	{
		g_xInitData.m_bDNSFailed = true;
	}

	g_xInitData.m_eAvailableCheckResult = eResult;
	g_xInitData.m_bThreadRunning = false;
}


int Asura_GameSpy::GetGameVersion( void )
{
	return Project_GetGameVersion();
}

bool Asura_GameSpy::GetGameSpySocketErrorFlag()
{
	return false;//g_bGameSpySocketError != 0;
}

void Asura_GameSpy::ResetGameSpySocketErrorFlag()
{
	//g_bGameSpySocketError = 0;
}


// new gamespy stuff handles memory allocations slightly differently: you set callbacks 
// for the memory allc routines. Could keep this system if we hacked the GameSpy files but
// that's not a great idea, so for now allow GameSpy to do malloc/free etc.
#ifdef USE_ASURA_MEMMANGER_FOR_GAMESPY
extern "C" {

#ifdef GAMESPY_TRACK_MEMORY_ALLOCATIONS
	static void* Asura_GameSpy_Malloc(size_t uSize, const char* szFile, int iLine)
	{
		Asura_Assert(g_pxGameSpyMemHeap, "GameSpy not intialised. Memory allocation will fail");
		if (!g_pxGameSpyMemHeap)
		{
			return NULL;
		}
		// (MH - 20.12.2004) so that things still work, init the system here.
		// TY 06.05.07 - No file/line tracking in the new memheap
		return g_pxGameSpyMemHeap->Alloc(uSize);
	}
#else
	static void* Asura_GameSpy_Malloc(size_t uSize)
	{
		Asura_Assert(g_pxGameSpyMemHeap, "GameSpy not intialised. Will allocate GameSpy heap memory "
			"but this probably won't be freed. Add calls to Asura_GameSpy::Initialise() / Shutdown()");
		if (!g_pxGameSpyMemHeap)
		{
			Asura_GameSpy::Initialise();
		}
		// (MH - 20.12.2004) so that things still work, init the system here.
		return g_pxGameSpyMemHeap->Alloc(uSize);
	}
#endif
	
	static void Asura_GameSpy_Free(void* pMem)
	{
		Asura_Assert(g_pxGameSpyMemHeap, "GameSpy not intialised");
		g_pxGameSpyMemHeap->Free(pMem);
	}

	static void* Asura_GameSpy_Realloc(void* pMem, size_t uSize)
	{
		void* pNewMem = gsimalloc(uSize);
		if (pMem)
		{
			if (pNewMem)
			{
				memcpy(pNewMem, pMem, uSize);
			}
			g_pxGameSpyMemHeap->Free(pMem);
		}
		return pNewMem;
	}

	static void* Asura_GameSpy_Memalign(size_t boundary, size_t size)
	{
		Asura_Assert(false, "not supported yet");
		return 0;
	}

}; // end extern "C"
#endif

#endif //#ifdef ASURA_SUPPORT_GAMESPY
