//////////////////////////////////////////////////////////////////////////////
//
// Asura_GameSpy_ServerBrowsing.cpp
//
// Copyright (c) 2003 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

// 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_ServerBrowsing.h"


// asura independent
#include "Core/Asura_Maths.h"
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#ifdef ASURA_USE_GUI_MENU_SYSTEM
#include "Interface/Asura_CommandConsole.h"
#endif
#include "Network/GameSpy/Asura_GameSpy.h"
#include "Network/GameSpy/Asura_GameSpy_GT2.h"
#include "Network/GameSpy/Asura_GameSpy_QR2.h"
#include "Network/GameSpy/Asura_GameSpy_Peer.h"

// middleware
#include "GameSpy/qr2/qr2regkeys.h"
#include "GameSpy/serverbrowsing/sb_serverbrowsing.h"

// S T A T I C   C L A S S   D A T A /////////////////////////////////////////

// Asura_GameSpy_ServerBrowsingSDK
bool Asura_GameSpy_ServerBrowsingSDK::s_bInitialised;
ServerBrowser Asura_GameSpy_ServerBrowsingSDK::s_pxServerBrowser;
bool Asura_GameSpy_ServerBrowsingSDK::s_bLan;
bool Asura_GameSpy_ServerBrowsingSDK::s_bIsUpdating;
Asura_GameSpy_ServerBrowsing::KeyType Asura_GameSpy_ServerBrowsingSDK::s_eSortKey = Asura_GameSpy_ServerBrowsing::NumKeyTypes;
bool Asura_GameSpy_ServerBrowsingSDK::s_bSortAscending;

unsigned char* Asura_GameSpy_ServerBrowsingSDK::s_aucBasicFields = 0;
u_int Asura_GameSpy_ServerBrowsingSDK::s_uNumBasicFields = 0;
bool Asura_GameSpy_ServerBrowsingSDK::s_bAssignedBasicFields = false;

// Asura_GameSpy_ServerBrowsing
bool Asura_GameSpy_ServerBrowsing::s_bInitialised;
const char* Asura_GameSpy_ServerBrowsing::s_szFilter;
char Asura_GameSpy_ServerBrowsing::s_acFilterBuffer[MAX_FILTER_LEN];
SBServer* Asura_GameSpy_ServerBrowsing::s_apxServerList;
u_int Asura_GameSpy_ServerBrowsing::s_uMaxServers;
u_int Asura_GameSpy_ServerBrowsing::s_uNumServers;
u_int Asura_GameSpy_ServerBrowsing::s_uMaxPing = 0;
bool* Asura_GameSpy_ServerBrowsing::s_pbServersRequested;
u_int Asura_GameSpy_ServerBrowsing::s_uNumUnrequestedServers;
bool Asura_GameSpy_ServerBrowsing::s_bUsePeerSDK;

// G L O B A L   D A T A /////////////////////////////////////////////////////
static unsigned char g_aucBasicFields[] = 
{
	HOSTNAME_KEY, 
	HOSTPORT_KEY, 
	GAMETYPE_KEY, 
	MAPNAME_KEY, 
	NUMPLAYERS_KEY, 
	MAXPLAYERS_KEY, 
	ASURA_MAPID_KEY, 
	ASURA_GAMETYPEID_KEY, 
	GAMEVER_KEY, 
	PASSWORD_KEY,
	/* New Keys go here*/
	0,
};

static const char* g_aszKeyNames[Asura_GameSpy_ServerBrowsing::NumKeyTypes] = 
{
	"gamever",		// KeyType_GameVersion
	"hostname",		// KeyType_ServerNameString,
	"gametype",		// KeyType_GameTypeString,
	"mapname",		// KeyType_MapNameString,
	"gametypeid",	// KeyType_GameTypeID,
	"mapid",		// KeyType_MapID,
	"numplayers",	// KeyType_NumPlayers,
	"maxplayers",	// KeyType_MaxPlayers,
	"ping",			// KeyType_Ping,
	"password",		// KeyType_Password,
};

static const char* g_aszNumericalOperators[Asura_GameSpy_ServerBrowsing::NumNumericalOperators] = 
{
	"=",	// BinaryOperator_Equals,
	"!=",	// BinaryOperator_NotEqual,
	"<",	// BinaryOperator_LessThan,
	"<=",	// BinaryOperator_LessThanOrEquals,
	">",	// BinaryOperator_GreaterThan,
	">=",	// BinaryOperator_GreaterThanOrEquals,
};

u_int g_uTimeOfLastRefresh;
bool g_bRefreshRequested;
DEBUGVAR u_int dbg_uMinTimeBetweenUpdates = 5000;	// milliseconds.

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

// A S U R A _ G A M E S P Y _ S E R V E R B R O W S I N G S D K /////////////
#ifdef ASURA_USE_GUI_MENU_SYSTEM
void Asura_GameSpy_ServerBrowsingSDK::AddConsoleCmdsAndVars()
{
	const Asura_UnicodeChar* wszRefreshingVarName = L"Menus.GameSpyServerListRefreshing";
	Asura_CommandConsole::AddVar( wszRefreshingVarName,&s_bIsUpdating );
	Asura_CommandConsole::MakeVarReadOnly( wszRefreshingVarName );
}
#endif

void Asura_GameSpy_ServerBrowsingSDK::Initialise( bool bLan, u_int uMaxConcurrentUpdates /* = 10 */, u_int uMaxServers /* = 256 */ )
{
	Asura_Assert(!s_bInitialised, "already initialised");
	if (!s_bInitialised)
	{
		// (MH - 29.04.2004) hack to cope with LAN mode possibly not being able to check for gs availability
		if (bLan)
		{
			__GSIACResult = GSIACAvailable;
		}

		// SNH - 20/01/2004 11:52:30 - since we're using mapid and gametypeid as basic keys, they need to be registered before we do a refresh.
		Asura_GameSpy_QR2::RegisterCustomKeys();

		const char* szGameName = Asura_GameSpy::GetGameName();
		const char* szGameKey = Asura_GameSpy::GetGameKey();
		u_int uMinVersion = Asura_GameSpy::GetGameVersion();

		// MRD GS updated, now requires to specify looking on lan (SBFalse)
		s_pxServerBrowser = ServerBrowserNew(szGameName, szGameName, szGameKey, uMinVersion, uMaxConcurrentUpdates, QVERSION_QR2, SBFalse, SBCallback, NULL);
		Asura_Assert(s_pxServerBrowser, "failed to create server browser. Was the GSIAvailable check done?");

		if (s_pxServerBrowser)
		{
			s_bLan = bLan;
			s_bIsUpdating = false;
			s_bInitialised = true;

			Asura_GameSpy_ServerBrowsing::Initialise( uMaxServers, false );
		}

		g_uTimeOfLastRefresh = 0;
		g_bRefreshRequested = 0;

		PopulateBasicFields();
	}
}

void Asura_GameSpy_ServerBrowsingSDK::Shutdown( void )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK::Shutdown - not initialised!");
	if (s_bInitialised)
	{
		g_uTimeOfLastRefresh = 0;
		g_bRefreshRequested = 0;

		ServerBrowserFree(s_pxServerBrowser);
		s_pxServerBrowser = NULL;
		
		SetBasicFields( NULL, 0 );

		s_bInitialised = false;

		Asura_GameSpy_ServerBrowsing::Shutdown();
	}
}


void Asura_GameSpy_ServerBrowsingSDK::Update( void )
{
	Asura_Assert(s_pxServerBrowser, "server browser object not created");
	if (s_pxServerBrowser)
	{
		if (g_bRefreshRequested)
		{
			Refresh();
		}

		ServerBrowserThink(s_pxServerBrowser);
	}
}


void Asura_GameSpy_ServerBrowsingSDK::Refresh( void )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK::Refresh - not initialised!");
	if (s_bInitialised)
	{
		if (g_bRefreshRequested)
		{
			Asura_Assert(s_bIsUpdating, "should already be updating");
		}
		else
		{
			Asura_Assert(!s_bIsUpdating, "already updating");
			if (s_bIsUpdating)
			{
				return;
			}
		}

		// enforece minimum time between updates. If request comes in sooner, say that we've
		// started updating but wait until the time has elapsed before actually sending the 
		// request. This should reduce the chances of lock up bugs appearing.
		u_int uCurTime = Asura_Timers::GetMSecsSinceStart();
		if (g_uTimeOfLastRefresh + dbg_uMinTimeBetweenUpdates > uCurTime)
		{
			if (!g_bRefreshRequested)
			{
				Asura_OutputToDebugger(("queueing server browser update\n"));
				g_bRefreshRequested = true;
				s_bIsUpdating = true;
			}
			return;
		}
		if (g_bRefreshRequested)
		{
			Asura_OutputToDebugger(("doing queued server browser update\n"));
			g_bRefreshRequested = false;
		}
		g_uTimeOfLastRefresh = uCurTime;

		ServerBrowserClear(s_pxServerBrowser);
		Asura_GameSpy_ServerBrowsing::Reset();
		s_bIsUpdating = true;

		if (s_bLan)
		{
			// broadcast to LAN on 50 ports starting from default port. Potential 
			// for servers not being found (if they are using non-standard ports)
			ServerBrowserLANUpdate(s_pxServerBrowser, SBTrue, ASURA_GAMESPY_GT2_DEFAULT_PORT, ASURA_GAMESPY_GT2_DEFAULT_PORT + 50);
		}
		else
		{
			SBError eUpdateErr = sbe_noerror;
			if (s_uNumBasicFields)
			{
				eUpdateErr = ServerBrowserLimitUpdate(s_pxServerBrowser, SBTrue, SBTrue, s_aucBasicFields,
					static_cast<int>(s_uNumBasicFields),
					const_cast<char*>(Asura_GameSpy_ServerBrowsing::GetFilter()),
					Asura_GameSpy_ServerBrowsing::GetMaxServers());
			}
			else
			{
				unsigned char aucBasicFields[] = {HOSTNAME_KEY, HOSTPORT_KEY, GAMETYPE_KEY, MAPNAME_KEY, NUMPLAYERS_KEY, MAXPLAYERS_KEY, ASURA_MAPID_KEY, ASURA_GAMETYPEID_KEY, GAMEVER_KEY, PASSWORD_KEY};
				int iNumFields = sizeof(aucBasicFields) / sizeof(aucBasicFields[0]);
				eUpdateErr = ServerBrowserLimitUpdate(s_pxServerBrowser, SBTrue, SBTrue, aucBasicFields, 
					iNumFields, 
					const_cast<char*>(Asura_GameSpy_ServerBrowsing::GetFilter()),
					Asura_GameSpy_ServerBrowsing::GetMaxServers());
			}

			if (eUpdateErr != sbe_noerror)
			{
				Asura_Assertf(false,("Attempt to update server browser list from gamespy failed (error 0x%08x)", eUpdateErr));
				s_bIsUpdating = false;
			}
		}
	}
}

void Asura_GameSpy_ServerBrowsingSDK::SetBasicFields(unsigned char* aucBasicFields, u_int uNumBasicFields)
{
	if (!s_bInitialised)
	{
		Asura_Assert(s_bInitialised,"ServerBrowsing SDK not initalised");
		return;
	}

	if (s_bAssignedBasicFields)
	{
		if (s_aucBasicFields) delete[] s_aucBasicFields;
		s_bAssignedBasicFields = false;
	}
		
	s_uNumBasicFields = uNumBasicFields;
	s_aucBasicFields = 0;
	if (uNumBasicFields)
	{
		Asura_Assert(aucBasicFields, "No basic fields list passed");
		s_aucBasicFields = aucBasicFields;
	}
}

void Asura_GameSpy_ServerBrowsingSDK::PopulateBasicFields( void )
{
	u_int uBasicFieldCount = 0;
	u_int uCustomFieldCount = Asura_GameSpy_QR2::GetNumberOfCustomServerKeys();

	while (g_aucBasicFields[uBasicFieldCount++]) {}
	uBasicFieldCount--;

	s_uNumBasicFields = uBasicFieldCount + uCustomFieldCount;
	s_aucBasicFields = new u_char[s_uNumBasicFields];
	Asura_Mem::Copy( s_aucBasicFields, g_aucBasicFields, sizeof(u_char) * uBasicFieldCount );

	while (uCustomFieldCount--)
	{
		s_aucBasicFields[uBasicFieldCount++] = static_cast<u_char>(Asura_GameSpy_QR2::GetCustomServerKeyID( uCustomFieldCount ));
	}	

	s_bAssignedBasicFields = true;
};

void Asura_GameSpy_ServerBrowsingSDK::SBCallback(ServerBrowser pxServerBrowser, SBCallbackReason eReason, SBServer pxServer, void* pUserData)
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		switch (eReason)
		{
			case sbc_serveradded:
			{
				//a server was added to the list, may just have an IP & port at this point
				// Wait for info to be available before adding to list
#if _DEBUG
				const char* szAddress = SBServerGetPublicAddress(pxServer);
				u_int uPort = SBServerGetPublicQueryPort(pxServer);
				Asura_OutputToDebugger(("Server added %s:%d\n", szAddress, uPort));
				if (SBServerHasPrivateAddress(pxServer))
				{
					szAddress = SBServerGetPrivateAddress(pxServer);
					Asura_OutputToDebugger(("private address = %s\n", szAddress));
				}
#endif

				// if we don't have all the server keys, wait for them...
				if(SBFalse == SBServerHasBasicKeys(pxServer))
				{
					break;
				}
				// otherwise this is effectively a sbc_serverupdated msg, so fallthrough.
			}

			case sbc_serverupdated:
			{
				//server information has been updated - either basic or full information is now available about this server.

				Asura_GameSpy_ServerBrowsing::AddServer(pxServer);
				break;
			}

			case sbc_serverupdatefailed:
			{
				//an attempt to retrieve information about this server, either directly or from the master, failed
				// most likely this is a timeout
#if _DEBUG
				Asura_OutputToDebugger(("update failed on server with public/private address = %s/%s\n", 
					SBServerGetPublicAddress(pxServer), SBServerGetPrivateAddress(pxServer)));
#endif
				// -fallthrough to server deleted case so this server will not be listed anymore.
			}

			case sbc_serverdeleted:
			{
				//a server was removed from the list
				Asura_GameSpy_ServerBrowsing::RemoveServer(pxServer);
				break;
			}

			case sbc_updatecomplete:
			{
				//the server query engine is now idle
				s_bIsUpdating = false;
				// do the automatic sort if required
				if (s_eSortKey != Asura_GameSpy_ServerBrowsing::NumKeyTypes)
				{
					SortList( s_eSortKey, s_bSortAscending );
				}
				break;
			}

			case sbc_queryerror:
			{
				//the master returned an error string for the provided query
				// most likely this is a parse error in the filter string.
				Asura_Assert(false, "server browsing query error");
				break;
			}

			case sbc_serverchallengereceived:
			{
				Asura_OutputToDebugger(("sbc_serverchallengereceived\n"));
				break;
			}
		}
	}// if (s_bInitialised)
}


// SNH - 08/12/2003 11:48:19 - TODO: should we be using aux update async instead? 
//	At the moment this will wait for all server keys before returning...
bool Asura_GameSpy_ServerBrowsingSDK::GetFullServerDetails( u_int uServerIndex )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		SBServer pxServer = Asura_GameSpy_ServerBrowsing::GetServer(uServerIndex);
		Asura_Assert( pxServer, "invalid server ID");

		if (pxServer)
		{
			if(!SBServerHasFullKeys(pxServer))
			{
				SBError sbe_Result = ServerBrowserAuxUpdateServer(s_pxServerBrowser, pxServer, SBFalse, SBTrue);
				if(sbe_Result != sbe_noerror)
				{
					Asura_Assert(false, "Server aux update failed");
					return false;
				}
			}

			return true;
		}
	}
	return false;
}


void Asura_GameSpy_ServerBrowsingSDK::SortList(Asura_GameSpy_ServerBrowsing::KeyType eKeyType, bool bAscending)
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		Asura_Assert(s_pxServerBrowser, "server browser object not created");
		
		// if updating, queue the sort for later.
		s_eSortKey = eKeyType;
		s_bSortAscending = bAscending;
		if (s_bIsUpdating)
		{
			return;
		}

		SBCompareMode eCmpMode = sbcm_int;
		if(Asura_GameSpy_ServerBrowsing::IsStringFilter(eKeyType))
		{
			eCmpMode = sbcm_stricase;
		}

		ServerBrowserSort(s_pxServerBrowser, bAscending ? SBTrue : SBFalse, const_cast<char*>(g_aszKeyNames[eKeyType]), eCmpMode);

		// now need to clear the list, and add those from the server browser object.
		Asura_GameSpy_ServerBrowsing::Reset();
		for (int i = 0; i < ServerBrowserCount(s_pxServerBrowser); ++i)
		{
			SBServer pxServer = ServerBrowserGetServer(s_pxServerBrowser, i);

			// this server may not actually have any valid data (version, map etc.) associated with it, but we will
			// let the AddServer function deal with this itself.
			Asura_GameSpy_ServerBrowsing::AddServer(pxServer);
		}
	}
}


u_int Asura_GameSpy_ServerBrowsingSDK::GetMyPublicIPAddress( void )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		return ServerBrowserGetMyPublicIPAddr(s_pxServerBrowser);
	}
	return 0;
}



bool Asura_GameSpy_ServerBrowsingSDK::SendNatNegotiateCookie( const char* szIP, u_short usPort, int iCookie )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{	
		SBError eError = ServerBrowserSendNatNegotiateCookieToServer( s_pxServerBrowser, szIP, usPort, iCookie);
		return (eError == sbe_noerror);
	}
	return false;
}


// A S U R A _ G A M E S P Y _ S E R V E R B R O W S I N G ///////////////////
void Asura_GameSpy_ServerBrowsing::Initialise( u_int uMaxServers, bool bUsePeerSDK )
{
	Asura_Assert(!s_bInitialised, "already initialised");
	if (!s_bInitialised)
	{
		s_apxServerList = new SBServer[uMaxServers];
		s_uMaxServers = uMaxServers;
		s_uNumServers = 0;
		s_uMaxPing = 0;

		s_pbServersRequested = new bool[uMaxServers];
		s_uNumUnrequestedServers = 0;
		
		s_bUsePeerSDK = bUsePeerSDK;

		s_bInitialised = true;

		ResetFilter();
	}
}

void Asura_GameSpy_ServerBrowsing::Reset( void )
{
	s_uNumServers = 0;
	s_uNumUnrequestedServers = 0;
}

void Asura_GameSpy_ServerBrowsing::Shutdown( void )
{
	if (s_bInitialised)
	{
		delete[] s_apxServerList;
		s_apxServerList = NULL;

		delete[] s_pbServersRequested;
		s_pbServersRequested = NULL;

		s_bInitialised = false;
	}
	s_uNumServers = 0;
}

void Asura_GameSpy_ServerBrowsing::AddServer( SBServer pxServer )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		// we want to immediately reject any server if it doesn't have the basic key value pairs we need
		if(SBFalse == SBServerHasBasicKeys(pxServer))
		{
			Asura_OutputToDebugger(("Rejected adding server without basic keys\n"));
			return;
		}

		// don't add if ping is greater than a specified maximum. 
		if (s_uMaxPing)
		{
			if (SBServerGetPing(pxServer) > static_cast<int>(s_uMaxPing))
			{
				return;
			}
		}
		
		// don't add server if version number is different to our own
		int iGameVer = SBServerGetIntValue(pxServer, "gamever", -1);
		if (iGameVer == -1)
		{
			Asura_Assert(false, "failed to get gamever, will not add server");
			Asura_OutputToDebugger_Retail(("not adding server due to unknown gamever.\n"));
			return;
		}
		else if (iGameVer != Asura_GameSpy::GetGameVersion())
		{
			Asura_OutputToDebugger_Retail(("not adding server with gamever = %d.\n", iGameVer));
			return;
		}

		bool bListed = false;
		for (u_int u = 0; u < s_uNumServers; ++u)
		{
			if (s_apxServerList[u] == pxServer)
			{
				bListed = true;
				break;
			}
		}
		
		if (!bListed)
		{
			if (s_uNumServers < s_uMaxServers)
			{
				s_pbServersRequested[s_uNumServers] = false;
				s_uNumUnrequestedServers++;
				s_apxServerList[s_uNumServers++] = pxServer;
			}
		}
	}
}

void Asura_GameSpy_ServerBrowsing::RemoveServer( SBServer pxServer )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		for (u_int u = 0; u < s_uNumServers; ++u)
		{
			if (s_apxServerList[u] == pxServer)
			{
				// swap with last and decrement num servers
				s_apxServerList[u] = s_apxServerList[--s_uNumServers];
				break;
			}
		}
	}
}

SBServer Asura_GameSpy_ServerBrowsing::GetServer( u_int uServerIndex )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		if (uServerIndex < s_uNumServers)
		{
			return s_apxServerList[uServerIndex];
		}
		Asura_Assert(false, "invalid server index");
	}
	return NULL;
}

void Asura_GameSpy_ServerBrowsing::SetServerRequested(u_int uServerIndex, bool bListed)
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");

		if(bListed && !s_pbServersRequested[uServerIndex])
		{
			s_uNumUnrequestedServers--;
		}
		else if(s_pbServersRequested[uServerIndex] && !bListed)
		{
			s_uNumUnrequestedServers++;
		}

		s_pbServersRequested[uServerIndex] = bListed;
	}
}

bool Asura_GameSpy_ServerBrowsing::HasServerBeenRequested(u_int uServerIndex)
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		Asura_Assert(uServerIndex < s_uNumServers, "invalid server ID");
		return s_pbServersRequested[uServerIndex];
	}
	return false;
}

bool Asura_GameSpy_ServerBrowsing::ServerHasBasicKeys(u_int uServerIndex)
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		Asura_Assert(uServerIndex < s_uNumServers, "invalid server ID");
			
		if(SBServerHasBasicKeys(s_apxServerList[uServerIndex]) == SBTrue)
		{
			return true;
		}
	}
	return false;
}

bool Asura_GameSpy_ServerBrowsing::ServerHasFullKeys(u_int uServerIndex)
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		Asura_Assert(uServerIndex < s_uNumServers, "invalid server ID");

		if(SBServerHasFullKeys(s_apxServerList[uServerIndex]) == SBTrue)
		{
			return true;
		}
	}
	return false;
}


// functions to retrieve info for a specific server
const char* Asura_GameSpy_ServerBrowsing::GetServerName( u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetStringValue(s_apxServerList[uServerIndex], "hostname", "????????");
}

// SNH - 01/07/2004 12:01:26 - convert the ascii / UTF8 server name to unicode
void Asura_GameSpy_ServerBrowsing::GetServerName( u_int uServerIndex, Asura_UnicodeChar* wszNameBuffer, u_int uBufferLength)
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	Asura_Assert( wszNameBuffer, "");

	Asura_UnicodeChar wszTemp[uASURA_GAMESPY_PEER_SERVERNAME_MAXLEN];
	
	UTF8ToUCS2String(GetServerName(uServerIndex), reinterpret_cast<u_short*>(wszTemp));
	Asura_PrintfToBfr(wszNameBuffer, L"%s", uBufferLength, true, wszTemp);
}

const char* Asura_GameSpy_ServerBrowsing::GetGameMode( u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetStringValue(s_apxServerList[uServerIndex], "gamemode", "");
}

const char* Asura_GameSpy_ServerBrowsing::GetGameType( u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetStringValue(s_apxServerList[uServerIndex], "gametype", "????????");
}

int Asura_GameSpy_ServerBrowsing::GetGameTypeID( u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetIntValue(s_apxServerList[uServerIndex], "gametypeid", 0);
}

const char* Asura_GameSpy_ServerBrowsing::GetMapName( u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetStringValue(s_apxServerList[uServerIndex], "mapname", "????????");
}

int Asura_GameSpy_ServerBrowsing::GetMapID( u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetIntValue(s_apxServerList[uServerIndex], "mapid", 0);
}

int Asura_GameSpy_ServerBrowsing::GetNumPlayers( u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetIntValue(s_apxServerList[uServerIndex], "numplayers", 0);
}

int Asura_GameSpy_ServerBrowsing::GetMaxPlayers( u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetIntValue(s_apxServerList[uServerIndex], "maxplayers", 0);
}

int Asura_GameSpy_ServerBrowsing::GetPing( u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetPing(s_apxServerList[uServerIndex]);
}
	
int Asura_GameSpy_ServerBrowsing::GetGameTimeLimit( u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetIntValue(s_apxServerList[uServerIndex], "timelimit", 0);
}

int Asura_GameSpy_ServerBrowsing::GetElapsedGameTime(u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetIntValue(s_apxServerList[uServerIndex], "timeelapsed", 0);
}

int Asura_GameSpy_ServerBrowsing::GetGameScoreLimit(u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetIntValue(s_apxServerList[uServerIndex], "fraglimit", 0);
}

int Asura_GameSpy_ServerBrowsing::GetGameTeams(u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetIntValue(s_apxServerList[uServerIndex], "numteams", 0);
}

bool Asura_GameSpy_ServerBrowsing::GetGameHasPassword( u_int uServerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	if(SBServerGetBoolValue(s_apxServerList[uServerIndex], "password", SBFalse))
	{
		return true;
	}
	return false;
}

const char* Asura_GameSpy_ServerBrowsing::GetCustomKey( u_int uServerIndex, char* szKeyName)
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetStringValue(s_apxServerList[uServerIndex], szKeyName, "");
}

int Asura_GameSpy_ServerBrowsing::GetCustomKeyAsInt( u_int uServerIndex, char* szKeyName)
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetIntValue(s_apxServerList[uServerIndex], szKeyName, 0);
}

bool Asura_GameSpy_ServerBrowsing::GetCustomKeyAsBool( u_int uServerIndex, char* szKeyName)
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	if(SBServerGetBoolValue(s_apxServerList[uServerIndex], szKeyName, SBFalse))
	{
		return true;
	}
	return false;
}

// player details
const char* Asura_GameSpy_ServerBrowsing::GetPlayerName( u_int uServerIndex, u_int uPlayerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetPlayerStringValue(s_apxServerList[uServerIndex], uPlayerIndex, "player", "???????");
}

int Asura_GameSpy_ServerBrowsing::GetPlayerTeam( u_int uServerIndex, u_int uPlayerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetPlayerIntValue(s_apxServerList[uServerIndex], uPlayerIndex, "team", 0);
}

int Asura_GameSpy_ServerBrowsing::GetPlayerScore( u_int uServerIndex, u_int uPlayerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetPlayerIntValue(s_apxServerList[uServerIndex], uPlayerIndex, "score", 0);
}

int Asura_GameSpy_ServerBrowsing::GetPlayerDeaths( u_int uServerIndex, u_int uPlayerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetPlayerIntValue(s_apxServerList[uServerIndex], uPlayerIndex, "deaths", 0);
}

int Asura_GameSpy_ServerBrowsing::GetPlayerPing( u_int uServerIndex, u_int uPlayerIndex )
{
	Asura_Assert( uServerIndex < s_uNumServers, "invalid server ID");
	return SBServerGetPlayerIntValue(s_apxServerList[uServerIndex], uPlayerIndex, "ping", -1);
} 

// filtering
bool Asura_GameSpy_ServerBrowsing::IsStringFilter( KeyType eKeyType )
{
	switch (eKeyType)
	{
		case KeyType_ServerNameString:
		case KeyType_GameTypeString:
		case KeyType_MapNameString:
			return true;
		default:
			return false;
	}
}

bool Asura_GameSpy_ServerBrowsing::IsIntFilter( KeyType eKeyType )
{
	switch (eKeyType)
	{
		case KeyType_GameVersion:
		case KeyType_GameTypeID:
		case KeyType_MapID:
		case KeyType_NumPlayers:
		case KeyType_MaxPlayers:
		case KeyType_Ping:
		case KeyType_Password:
			return true;
		default:
			return false;
	}
}

bool Asura_GameSpy_ServerBrowsing::IsFloatFilter( KeyType eKeyType )
{
	return false;
}

void Asura_GameSpy_ServerBrowsing::ResetFilter( void )
{
	s_szFilter = s_acFilterBuffer;
	s_acFilterBuffer[0] = 0;

	s_uMaxPing = 0;

	// (MH - 16.03.2004) always add filter on game version
	AddIntFilter( KeyType_GameVersion, BinaryOperator_Equals, Asura_GameSpy::GetGameVersion() );
}

bool Asura_GameSpy_ServerBrowsing::AddFilter( const char* szFilter )
{
	u_int uNumCharsInFilter = strlen(szFilter);
	u_int uCurBufferLen = strlen(s_acFilterBuffer);
	if (uCurBufferLen)
	{
		uNumCharsInFilter += strlen(" and ");
	}

	if (uCurBufferLen + uNumCharsInFilter < MAX_FILTER_LEN)
	{
		if (uCurBufferLen)
		{
			strcat(s_acFilterBuffer, " and ");
		}
		strcat(s_acFilterBuffer, szFilter);
		return true;
	}
	else
	{
		// not enough room to add this filter.
		Asura_OutputToDebugger(("no room to add filter.\n"));
		return false;
	}
}


bool Asura_GameSpy_ServerBrowsing::AddStringFilter( KeyType eKeyType, StringFilterOperator eOperator, const char* szValue )
{
	Asura_Assert(IsStringFilter(eKeyType), "invalid key type for filter");

	const char* szKeyName = g_aszKeyNames[eKeyType];
	char acThisFilter[MAX_FILTER_LEN] = "";

	switch (eOperator) 
	{
		case StringOperator_Equals:
		{
			Asura_PrintfToBfr(acThisFilter, "%s = '%s'", MAX_FILTER_LEN, true, szKeyName, szValue);
			break;
		}
		
		case StringOperator_Contains:
		{
			Asura_PrintfToBfr(acThisFilter, "%s like '%%%s%%'", MAX_FILTER_LEN, true, szKeyName, szValue);
			break;
		}
	}

	return AddFilter(acThisFilter);
}


bool Asura_GameSpy_ServerBrowsing::AddIntFilter( KeyType eKeyType, NumericalFilterOperator eOperator, int iValue )
{
	Asura_Assert(IsIntFilter(eKeyType), "invalid key type for filter");
	Asura_Assert(eKeyType != KeyType_Ping, "can't filter on ping");

	const char* szKeyName = g_aszKeyNames[eKeyType];
	const char* szOperatorString = g_aszNumericalOperators[eOperator];
	char acThisFilter[MAX_FILTER_LEN] = "";

	Asura_PrintfToBfr(acThisFilter, "%s %s %d", MAX_FILTER_LEN, true, szKeyName, szOperatorString, iValue);
	
	return AddFilter(acThisFilter);
}


bool Asura_GameSpy_ServerBrowsing::AddFloatFilter( KeyType eKeyType, NumericalFilterOperator eOperator, float fValue )
{
	Asura_Assert(IsFloatFilter(eKeyType), "invalid key type for filter");

	const char* szKeyName = g_aszKeyNames[eKeyType];
	const char* szOperatorString = g_aszNumericalOperators[eOperator];
	char acThisFilter[MAX_FILTER_LEN] = "";

	Asura_PrintfToBfr(acThisFilter, "%s %s %f", MAX_FILTER_LEN, true, szKeyName, szOperatorString, Asura_Maths::FtoD(fValue));
	
	return AddFilter(acThisFilter);
}


bool Asura_GameSpy_ServerBrowsing::AddComparisonFilter( KeyType eKeyTypeLHS, NumericalFilterOperator eOperator, KeyType eKeyTypeRHS )
{
	Asura_Assert( (IsFloatFilter(eKeyTypeLHS) && IsFloatFilter(eKeyTypeRHS)) || (IsIntFilter(eKeyTypeLHS) && IsIntFilter(eKeyTypeRHS)), "invalid key type for filter" );

	const char* szKeyNameLHS = g_aszKeyNames[eKeyTypeLHS];
	const char* szKeyNameRHS = g_aszKeyNames[eKeyTypeRHS];
	const char* szOperatorString = g_aszNumericalOperators[eOperator];
	char acThisFilter[MAX_FILTER_LEN] = "";

	Asura_PrintfToBfr(acThisFilter, "%s %s %s", MAX_FILTER_LEN, true, szKeyNameLHS, szOperatorString, szKeyNameRHS);
	
	return AddFilter(acThisFilter);
}


bool Asura_GameSpy_ServerBrowsing::AddMinPlayersFilter( u_int uMinPlayers )
{
	return AddIntFilter( KeyType_NumPlayers, BinaryOperator_GreaterThanOrEquals, static_cast<int>(uMinPlayers));
}


bool Asura_GameSpy_ServerBrowsing::AddNotFullFilter( void )
{
	return AddComparisonFilter( KeyType_NumPlayers, BinaryOperator_LessThan, KeyType_MaxPlayers );
}


u_int Asura_GameSpy_ServerBrowsing::GetMyPublicIPAddress( void )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsing not initialised!");
	if (s_bUsePeerSDK)
	{
		return Asura_GameSpy_Peer::GetMyPublicIPAddress();
	}
	else
	{
		return Asura_GameSpy_ServerBrowsingSDK::GetMyPublicIPAddress();
	}
}


bool Asura_GameSpy_ServerBrowsing::IsBehindSameNat( SBServer pxServer )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsing not initialised!");
	if (s_bInitialised)
	{
		if (pxServer)
		{
			u_int uServerPublicAddress = SBServerGetPublicInetAddress(pxServer);
			u_int uMyPublicAddress = GetMyPublicIPAddress();
			return (uServerPublicAddress == uMyPublicAddress);
		}
	}
	return false;
}

bool Asura_GameSpy_ServerBrowsing::NeedToUseNatNeg( u_int uServerIndex )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsing not initialised!");
	if (s_bInitialised)
	{
		SBServer pxServer = Asura_GameSpy_ServerBrowsing::GetServer(uServerIndex);
		Asura_Assert( pxServer, "invalid server ID");

		if (pxServer)
		{
			// check updated from http://www.poweredbygamespy.com/secure/kb/questions.php?questionid=383
			bool bServerHasPrivateAddress = (SBTrue == SBServerHasPrivateAddress(pxServer));
			if (bServerHasPrivateAddress && IsBehindSameNat(pxServer))
			{
				// can connect directly using private address
				return false;
			}
			else if (!bServerHasPrivateAddress && (SBServerDirectConnect(pxServer) == SBTrue))
			{
				// SBServerDirectConnect is SDK function to find out whether public address can be used directly.
				// If it can, connect to public address (if there is no private address)
				return false;
			}
			else
			{
				// must use Nat Neg
				return true;
			}
		}
	}
	return false;
}
void Asura_GameSpy_ServerBrowsing::SendNatNegCookie( u_int uServerIndex, int iCookie )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingnot initialised!");
	if (s_bInitialised)
	{
		u_short usPort = GetServerHostPort(uServerIndex);
		bool bOK;
		if (s_bUsePeerSDK)
		{
			u_int uIP = GetServerInetAddress(uServerIndex);
			bOK = Asura_GameSpy_Peer::SendNatNegotiateCookie(uIP, usPort, iCookie);
		}
		else
		{
			char* szIP = const_cast<char*>(GetServerIPAddress(uServerIndex));
			bOK = Asura_GameSpy_ServerBrowsingSDK::SendNatNegotiateCookie(szIP, usPort, iCookie);
		}
		Asura_Assert(bOK, "failed to send nat negotiate cookie");
	}
}

const char* Asura_GameSpy_ServerBrowsing::GetServerIPAddress( u_int uServerIndex )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		SBServer pxServer = Asura_GameSpy_ServerBrowsing::GetServer(uServerIndex);
		Asura_Assert( pxServer, "invalid server ID");

		if (pxServer)
		{
			if (IsBehindSameNat(pxServer))
			{
				return SBServerGetPrivateAddress(pxServer);
			}
			else
			{
				return SBServerGetPublicAddress(pxServer);
			}
		}
	}
	return "0.0.0.0";
}

u_int Asura_GameSpy_ServerBrowsing::GetServerInetAddress( u_int uServerIndex )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		SBServer pxServer = Asura_GameSpy_ServerBrowsing::GetServer(uServerIndex);
		Asura_Assert( pxServer, "invalid server ID");

		if (pxServer)
		{
			if (IsBehindSameNat(pxServer))
			{
				return SBServerGetPrivateInetAddress(pxServer);
			}
			else
			{
				return SBServerGetPublicInetAddress(pxServer);
			}
		}
	}
	return 0;
}

u_short Asura_GameSpy_ServerBrowsing::GetServerHostPort( u_int uServerIndex )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		SBServer pxServer = Asura_GameSpy_ServerBrowsing::GetServer(uServerIndex);
		Asura_Assert( pxServer, "invalid server ID");

		if (pxServer)
		{
			if (IsBehindSameNat(pxServer))
			{
				return SBServerGetPrivateQueryPort(pxServer);
			}
			else
			{
				return SBServerGetPublicQueryPort(pxServer);
			}
		}
	}
	return 0;
}

u_int Asura_GameSpy_ServerBrowsing::GetServerPublicInetAddress( u_int uServerIndex )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		SBServer pxServer = Asura_GameSpy_ServerBrowsing::GetServer(uServerIndex);
		Asura_Assert( pxServer, "invalid server ID");

		if (pxServer)
		{
			return SBServerGetPublicInetAddress(pxServer);
		}
	}
	return 0;
}

u_short Asura_GameSpy_ServerBrowsing::GetServerPublicHostPort( u_int uServerIndex )
{
	Asura_Assert(s_bInitialised, "Asura_GameSpy_ServerBrowsingSDK not initialised!");
	if (s_bInitialised)
	{
		SBServer pxServer = Asura_GameSpy_ServerBrowsing::GetServer(uServerIndex);
		Asura_Assert( pxServer, "invalid server ID");

		if (pxServer)
		{
			return SBServerGetPublicQueryPort(pxServer);
		}
	}
	return 0;
}

#endif //#ifdef ASURA_SUPPORT_GAMESPY
