/**
*
* \file Asura_GameSpy_GP.cpp
*
* Created by Richard May - 19/01/2004 10:30:52
*
* Copyright (C) 2002 Rebellion, All Rights Reserved.
*
* \brief
*
**/



/************/
/* Includes.*/
/************/

// main asura header
#include "Core/Asura.h"

#ifdef ASURA_SUPPORT_GAMESPY

// local header
#include "Network/GameSpy/Asura_GameSpy_GP.h"

// asura
#include "Core/Asura_String.h"
#include "Core/Asura_Time_System.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "ChunkSystem/Asura_Chunk_Macros.h"
#ifdef ASURA_USE_GUI_MENU_SYSTEM
#include "Interface/Asura_CommandConsole.h"
#endif
#include "Network/GameSpy/Asura_GameSpy.h"
#include "Network/GameSpy/Asura_GameSpy_PersistentStorage.h"	// for saving recent buddy messages

// library
#include <wchar.h>

// D E F I N E S /////////////////////////////////////////////////////////////
//#define HACK_USE_BHDTEST_ACCOUNT 1 // MH account used for testing.

// C O N S T A N T S /////////////////////////////////////////////////////////
static const u_int uASURA_GSGP_MESSAGE_ARCHIVE_CHUNKVERSION = 0;
static const int iASURA_GSGP_BUDDYMESSAGE_INDEX = 0x0C50E7FE; // hash id of Asura_GameSpy_GP_BuddyMessages
DEBUGVAR bool dbg_bGPReconnectsToPS = false;

// C L A S S   D E F I N I T I O N S /////////////////////////////////////////
class Asura_GSGP_Internal
{
public:

	static void InitPersistentStorage();
	static void UpdatePersistentStorage();
	static void ShutdownPersistentStorage();

	static void ConnectToPersistentStorage();
	static void DisconnectFromPersistentStorage();

#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	static void ArchiveMessage(const Asura_GameSpy_GP_BuddyMessage& xMessage);

	static u_int NumberOfArchivedBuddyMessages()
	{
		return s_uNumArchivedBuddyMessages;
	}

	static Asura_GameSpy_GP_BuddyMessage* GetArchivedBuddyMessage(u_int uMessageIndex);

private:

	static u_int GetOldestIndex();
	static void SaveBuddyMessageArchive();

	// chunk load/save
	static bool FillArchivedMessagesFromChunkData( const char* pcData, u_int uDataLength );
	static u_int GetArchivedMessagesChunkDataSize();
	static void CreateChunkData( char* pcBuffer );

	static void RestoreBuddyMessagesCallback( bool bSuccess, const char* pcData, u_int uDataLength, void* pInstance );
	static void SaveBuddyMessagesCallback( bool bSuccess, void* pInstance );

	static bool s_bReadyForArchiving;
	static bool s_bRequestedSavedData;
	static u_int s_uNumArchivedBuddyMessages;
	static Asura_GameSpy_GP_BuddyMessage* s_axArchivedBuddyMessages;
#endif
};

#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
class Asura_GameSpy_GP_Buddy
{

public:

	Asura_UnicodeChar		m_awcName[uASURA_GAMESPY_NICKNAME_MAX_LENGTH];
	int						m_iProfileID;
	ASURA_GSGP_ACTIVESTATUS	m_eStatus;
	u_int					m_uIP;
	u_short					m_usPort;
};

class Asura_GameSpy_GP_BuddyRequest
{
public:

	int m_iSenderProfileID;
	Asura_UnicodeChar m_awcSenderNick[uASURA_GAMESPY_NICKNAME_MAX_LENGTH];
};

class  Asura_GameSpy_GP_BuddyMessage
{
public:

	u_int m_uTimeStamp;
	int m_iSenderProfileID;
	Asura_UnicodeChar m_awcSenderNick[uASURA_GAMESPY_NICKNAME_MAX_LENGTH];
	char m_acMessage[uASURA_GAMESPY_MAX_BUDDY_MESSAGE_LENGTH];
};
#endif


// S T A T I C   C L A S S   D A T A /////////////////////////////////////////
GPConnection		   Asura_GameSpy_GP::s_xConnection;
Asura_GSGP_ProfileID   Asura_GameSpy_GP::s_iCurrentProfile = -1;
Asura_GSGP_ProfileInfo Asura_GameSpy_GP::s_xCurrentProfileInfo;
Asura_UnicodeChar*     Asura_GameSpy_GP::s_awszSuggestedNicknames[];
int					   Asura_GameSpy_GP::s_iCurrentStatus  = ASURA_GSGP_STATUS_READY;
bool				   Asura_GameSpy_GP::s_bConnectSuccess = false;
bool				   Asura_GameSpy_GP::s_bCreateSuccess  = false;
bool				   Asura_GameSpy_GP::s_bUniqueNickValid = false;
bool				   Asura_GameSpy_GP::s_bIsInitialised = false;
bool				   Asura_GameSpy_GP::s_bHasEverLoggedIn = false;
int					   Asura_GameSpy_GP::s_iNumSuggestions = 0;
char				   Asura_GameSpy_GP::s_acCurrentNick[];
char				   Asura_GameSpy_GP::s_acCurrentPassword[];

#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
u_int					Asura_GameSpy_GP::s_uNumBuddies;
Asura_GameSpy_GP_Buddy*	Asura_GameSpy_GP::s_axBuddyList;
bool					Asura_GameSpy_GP::s_bIsReverseBuddiesResultOK;
u_int					Asura_GameSpy_GP::s_uNumReverseBuddies;
Asura_GameSpy_GP_Buddy*	Asura_GameSpy_GP::s_axReverseBuddyList;

u_int							Asura_GameSpy_GP::s_uNumPendingBuddyRequests;
Asura_GameSpy_GP_BuddyRequest*	Asura_GameSpy_GP::s_axBuddyRequests;

u_int							Asura_GameSpy_GP::s_uNumBuddyMessages;
Asura_GameSpy_GP_BuddyMessage*	Asura_GameSpy_GP::s_axBuddyMessages;
#endif // #if ASURA_GAMESPY_GP_SUPPORT_BUDDIES

void (*Asura_GameSpy_GP::s_pfnProfileSearch)(void*, int, int*,
		char**, char**, char**, char**, char**);

#ifdef ASURA_USE_GUI_MENU_SYSTEM
// storage for vars setup in menus
Asura_UnicodeChar* Asura_GameSpy_GP::s_pwcMenuNick;
Asura_UnicodeChar* Asura_GameSpy_GP::s_pwcMenuEmail;
Asura_UnicodeChar* Asura_GameSpy_GP::s_pwcMenuPass;
GPErrorCode Asura_GameSpy_GP::s_eErrorCode;
#endif

#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
bool Asura_GSGP_Internal::s_bReadyForArchiving;
bool Asura_GSGP_Internal::s_bRequestedSavedData;
u_int Asura_GSGP_Internal::s_uNumArchivedBuddyMessages;
Asura_GameSpy_GP_BuddyMessage* Asura_GSGP_Internal::s_axArchivedBuddyMessages;
#endif

// P R O T O T Y P E S ///////////////////////////////////////////////////////
#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
static void FillIPAndPortFromLocation(const char* szLocation, u_int& uIP, u_short& usPort);
#endif

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

/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::Initialise
 *
 * Author        : Richard May
 *
 * Description   : Main initialisation. Connects to server
 *
 *************************************************************/
void Asura_GameSpy_GP::Initialise( void )
{
	Asura_Assert(!s_bIsInitialised, "already initialised");
	if (s_bIsInitialised) return;

	// log into default namespace
	GPResult eResult = gpInitialize( &s_xConnection, Asura_GameSpy::GetGameID(), 1 );
	Asura_Assert( (eResult == GP_NO_ERROR), "Failed to initialise GP!" );

	// (MH - 22.12.2004) disable info caching as we will keep our own copies
	eResult = gpDisable( &s_xConnection, GP_INFO_CACHING );
	Asura_Assert( (eResult == GP_NO_ERROR), "gpDisable failed" );

	// (MH - 15.12.2004) set various callbacks for buddy functionality
	gpSetCallback( &s_xConnection, GP_ERROR, ErrorCallback, 0 );
	gpSetCallback( &s_xConnection, GP_RECV_BUDDY_REQUEST, BuddyRequestCallback, 0 );
	gpSetCallback( &s_xConnection, GP_RECV_BUDDY_STATUS, BuddyStatusCallback, 0 );
	gpSetCallback( &s_xConnection, GP_RECV_BUDDY_MESSAGE, BuddyMessageCallback, 0 );
	gpSetCallback( &s_xConnection, GP_RECV_GAME_INVITE, GameInviteCallback, 0 );
	// MRD - 11.08.2006 add some more callbacks as these are now needed to pass GameSpy review
	gpSetCallback( &s_xConnection, GP_TRANSFER_CALLBACK, GameTransferCallback, 0 );
	gpSetCallback( &s_xConnection, GP_RECV_BUDDY_AUTH, GameRecvBuddyAuthCallback, 0 );
	gpSetCallback( &s_xConnection, GP_RECV_BUDDY_REVOKE, GameRecvBuddyRevokeCallback, 0 );

	
	s_iCurrentStatus  = ASURA_GSGP_STATUS_READY;
	s_bConnectSuccess = false;
	s_bCreateSuccess  = false;
	s_acCurrentNick[0] = 0;
	s_acCurrentPassword[0] = 0;

	// setup suggested nickname buffers
	for( u_int u=0; u<uASURA_GAMESPY_MAX_NICKNAME_SUGGESTIONS; ++u )
	{
		s_awszSuggestedNicknames[u] = new Asura_UnicodeChar[uASURA_GAMESPY_NICKNAME_MAX_LENGTH];
	}

#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	// init buddy data
	s_uNumBuddies = 0;
	s_axBuddyList = new Asura_GameSpy_GP_Buddy[uASURA_GAMESPY_MAX_BUDDIES];
	s_bIsReverseBuddiesResultOK = true;
	s_uNumReverseBuddies = 0;
	s_axReverseBuddyList = new Asura_GameSpy_GP_Buddy[uASURA_GAMESPY_MAX_BUDDIES];
	s_uNumPendingBuddyRequests = 0;
	s_axBuddyRequests = new Asura_GameSpy_GP_BuddyRequest[uASURA_GAMESPY_MAX_BUDDY_REQUESTS];
	s_uNumBuddyMessages = 0;
	s_axBuddyMessages = new Asura_GameSpy_GP_BuddyMessage[uASURA_GAMESPY_MAX_BUDDY_MESSAGES];
#endif // #if ASURA_GAMESPY_GP_SUPPORT_BUDDIES

	// create arrays etc. for persistent storage
	Asura_GSGP_Internal::InitPersistentStorage();

#ifdef ASURA_USE_GUI_MENU_SYSTEM
	s_pwcMenuNick = new Asura_UnicodeChar[GP_NICK_LEN];
	s_pwcMenuNick[0] = 0;
	s_pwcMenuEmail = new Asura_UnicodeChar[GP_EMAIL_LEN];
	s_pwcMenuEmail[0] = 0;
	s_pwcMenuPass = new Asura_UnicodeChar[GP_PASSWORD_LEN];
	s_pwcMenuPass[0] = 0;

#ifdef HACK_USE_BHDTEST_ACCOUNT
	// HACK - fill in with a valid account
	wcscpy( s_pwcMenuNick, L"BHDTEST2" );
	wcscpy( s_pwcMenuEmail, L"BHDTEST2@BHDTEST.COM" );
	wcscpy( s_pwcMenuPass, L"PASSWORD" );
#endif // #ifdef HACK_USE_BHDTEST_ACCOUNT

#if ASURA_GAMESPY_GP_ALLOW_NONUNIQUENICKS
	u_int uMaxNickBufferLen = GP_NICK_LEN;
#else
	u_int uMaxNickBufferLen = GP_UNIQUENICK_LEN;
#endif
	Asura_CommandConsole::AddVar( L"Menus.GameSpyNick", s_pwcMenuNick, uMaxNickBufferLen );
	Asura_CommandConsole::AddVar( L"Menus.GameSpyEmail", s_pwcMenuEmail, GP_EMAIL_LEN );
	Asura_CommandConsole::AddVar( L"Menus.GameSpyPass", s_pwcMenuPass, GP_PASSWORD_LEN );
	Asura_CommandConsole::AddCmd( L"Menus.GameSpyLogin", ConnectProfileFromMenus );
	Asura_CommandConsole::AddCmd( L"Menus.GameSpyCreate", CreateProfileFromMenus );
	Asura_CommandConsole::AddCmd( L"Menus.GameSpySuggestNicks", RequestNickSuggestionsFromMenus );
	Asura_CommandConsole::AddCmd( L"Menus.GameSpyGetNickSuggestion", GetNickSuggestionFromMenus );
	Asura_CommandConsole::AddCmd( L"Menus.GameSpyUseNickSuggestion", UseNickSuggestionFromMenus );

	// status var
	Asura_CommandConsole::AddVar( L"Menus.GameSpyGPStatus", &s_iCurrentStatus );
	Asura_CommandConsole::MakeVarReadOnly( L"Menus.GameSpyGPStatus" );
	Asura_CommandConsole::SetVarHelpText( L"Menus.GameSpyGPStatus", "Values can be OR'd together.\n"
		"0:idle\n1:login\n2:newprof\n4:nickreg\n8:suggestnick\n16:getinfo\n32:revbuddy." );

	// connection success var
	Asura_CommandConsole::AddVar( L"Menus.GameSpyGPConnected", &s_bConnectSuccess);
	Asura_CommandConsole::MakeVarReadOnly( L"Menus.GameSpyGPConnected" );
	Asura_CommandConsole::SetVarHelpText( L"Menus.GameSpyGPConnected", "Whether connection succeeded." );

	// last error var
	Asura_CommandConsole::AddVar( L"Menus.GameSpyGPErrorCode", reinterpret_cast<int*>(&s_eErrorCode));
	Asura_CommandConsole::MakeVarReadOnly( L"Menus.GameSpyGPErrorCode" );
	Asura_CommandConsole::SetVarHelpText( L"Menus.GameSpyGPErrorCode", "from GPErrorCode in gp.h." );
#endif // ASURA_USE_GUI_MENU_SYSTEM

	// done - set initialised status
	s_bIsInitialised = true;
}
/* end of function Initialise */

/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::Shutdown
 *
 * Author        : Richard May
 *
 * Description   : Main shutdown function. Disconnects
 *
 *************************************************************/
void Asura_GameSpy_GP::Shutdown( void )
{
	Asura_Assert(s_bIsInitialised, "not initialised");
	if (!s_bIsInitialised) return;

	gpDestroy( &s_xConnection );
	
	s_iCurrentProfile  = -1;
	s_iCurrentStatus   = ASURA_GSGP_STATUS_READY;
	s_bConnectSuccess  = false;
	s_bCreateSuccess   = false;
	s_bUniqueNickValid = false;
	s_iNumSuggestions  = 0;

	// clear up suggested nickname buffers
	for( u_int u=0; u<uASURA_GAMESPY_MAX_NICKNAME_SUGGESTIONS; ++u )
	{
		delete [] s_awszSuggestedNicknames[u]; // handles null case internally, or should!
		s_awszSuggestedNicknames[u] = 0;
	}

#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	// destroy buddy data
	s_uNumBuddies = 0;
	delete[] s_axBuddyList;
	s_axBuddyList = NULL;

	s_uNumReverseBuddies = 0;
	delete[] s_axReverseBuddyList;
	s_axReverseBuddyList = NULL;

	s_uNumPendingBuddyRequests = 0;
	delete[] s_axBuddyRequests;
	s_axBuddyRequests = NULL;
	
	s_uNumBuddyMessages = 0;
	delete[] s_axBuddyMessages;
	s_axBuddyMessages = NULL;
#endif // #if ASURA_GAMESPY_GP_SUPPORT_BUDDIES

	Asura_GSGP_Internal::ShutdownPersistentStorage();

#ifdef ASURA_USE_GUI_MENU_SYSTEM
	delete[] s_pwcMenuNick;
	s_pwcMenuNick = 0;
	delete[] s_pwcMenuEmail;
	s_pwcMenuEmail = 0;
	delete[] s_pwcMenuPass;
	s_pwcMenuPass = 0;

	// only remove the commands if console wasn't reset already.
	if (Asura_CommandConsole_VarRepository::Find( L"Menus.GameSpyNick" ) )
	{
		Asura_CommandConsole::RemoveVar( L"Menus.GameSpyNick" );
		Asura_CommandConsole::RemoveVar( L"Menus.GameSpyEmail" );
		Asura_CommandConsole::RemoveVar( L"Menus.GameSpyPass" );
		Asura_CommandConsole::RemoveCmd( L"Menus.GameSpyLogin" );
		Asura_CommandConsole::RemoveCmd( L"Menus.GameSpyCreate" );
		Asura_CommandConsole::RemoveCmd( L"Menus.GameSpySuggestNicks" );
		Asura_CommandConsole::RemoveCmd( L"Menus.GameSpyGetNickSuggestion" );
		Asura_CommandConsole::RemoveCmd( L"Menus.GameSpyUseNickSuggestion" );
		Asura_CommandConsole::RemoveVar( L"Menus.GameSpyGPStatus" );
		Asura_CommandConsole::RemoveVar( L"Menus.GameSpyGPConnected" );
		Asura_CommandConsole::RemoveVar( L"Menus.GameSpyGPErrorCode" );
	}
#endif
		
	s_bIsInitialised = false;
}
/* end of function Shutdown */



/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::Update
 *
 * Author        : Richard May
 *
 * Description   : Calls GameSpy GP's think function
 *
 *************************************************************/
bool g_bDisplayBuddies = false;
int g_iHACKBuddyInvite = -1;
int g_iAcceptBuddyRequest = -1;
int g_iRefuseBuddyRequest = -1;
int g_iDeleteBuddy = -1;
int g_iDisplayReverseBuddies = 0;
int g_iRevokeBuddy = -1;
int g_iSendBuddyMessage = -1;

void Asura_GameSpy_GP::Update( void )
{
	if (!s_bIsInitialised) return;
	//if( s_iCurrentStatus & ASURA_GSGP_STATUS_BUSY )
	{	
		gpProcess(&s_xConnection);
	}

	if (IsLoggedIn())
	{
		Asura_GSGP_Internal::UpdatePersistentStorage();
	}


#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	if (g_bDisplayBuddies)
	{
		for (u_int u = 0; u < s_uNumBuddies; ++u)
		{
			Asura_OutputToDebugger(( "Buddy %u = %S\n", u, GetBuddyName(u) ));
		}

		g_bDisplayBuddies = false;
	}

	if (g_iHACKBuddyInvite != -1)
	{
		SendBuddyRequest(g_iHACKBuddyInvite);
		g_iHACKBuddyInvite = -1;
	}

	if (GetNumPendingBuddyRequests())
	{
		if (g_iAcceptBuddyRequest != -1)
		{
			AcceptBuddyRequest(g_iAcceptBuddyRequest);
			g_iAcceptBuddyRequest = -1;
			g_iRefuseBuddyRequest = -1;
		}
		if (g_iRefuseBuddyRequest != -1)
		{
			RefuseBuddyRequest(g_iRefuseBuddyRequest);
			g_iAcceptBuddyRequest = -1;
			g_iRefuseBuddyRequest = -1;
		}
	}

	if (g_iDeleteBuddy != -1)
	{
		DeleteBuddy(g_iDeleteBuddy);
		g_iDeleteBuddy = -1;
	}

    if (g_iDisplayReverseBuddies)
	{
		if (g_iDisplayReverseBuddies == 1)
		{
			SearchForReverseBuddies();
			g_iDisplayReverseBuddies = 2;
		}
		if (!IsSearchingForReverseBuddies())
		{
			if (WasReverseBuddiesSuccessful())
			{
				Asura_OutputToDebugger(("ReverseBuddies:-\n"));
				for (u_int u = 0; u < GetNumReverseBuddies(); ++u)
				{
					Asura_OutputToDebugger(("ReverseBuddy %d = %S, ID = %d\n", u, GetReverseBuddyName(u), GetReverseBuddyProfileID(u)));
				}
			}
			else
			{
				Asura_OutputToDebugger(("ReverseBuddy list failed\n"));
			}
		
			g_iDisplayReverseBuddies = 0;
		}
	}
	if (g_iRevokeBuddy != -1)
	{
		RevokeBuddy(g_iRevokeBuddy);
		g_iRevokeBuddy = -1;
	}

	if (g_iSendBuddyMessage != -1)
	{
		SendBuddyMessage( g_iSendBuddyMessage, "test message: hello!" );
		g_iSendBuddyMessage = -1;
	}
#endif // #if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
}
/* end of function Update */

/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::ConnectProfile
 *
 * Author        : Richard May
 *
 * Description   : Connects to an existing gamespy profile
 *
 *************************************************************/
ASURA_GSGP_RESULT Asura_GameSpy_GP::ConnectProfile( const Asura_UnicodeChar* wszNickname, const Asura_UnicodeChar* wszEmail, const Asura_UnicodeChar* wszPassword )
{
	Asura_Assert(!IsLoggedIn(), "already logged in");
	
	char szNick[GP_NICK_LEN];
	char szEmail[GP_EMAIL_LEN];
	char szPass[GP_PASSWORD_LEN];

	Asura_PrintfToBfr( szNick,  "%S", GP_NICK_LEN,     true, wszNickname );
	Asura_PrintfToBfr( szEmail, "%S", GP_EMAIL_LEN,    true, wszEmail );
	Asura_PrintfToBfr( szPass,  "%S", GP_PASSWORD_LEN, true, wszPassword );

	// (MH - 09.07.2007) added minimal checking on parameters. Not bothering with validity
	// checks as you never know what GameSpy has let people get away with in the past, e.g. 
	// no @ and . in the email etc.
	if (!strlen(szNick)) 
	{
#ifdef ASURA_USE_GUI_MENU_SYSTEM
		s_eErrorCode = GP_LOGIN_BAD_NICK;
#endif
		return ASURA_GSGP_ERROR_SERVER;
	}
	if (!strlen(szEmail))
	{
#ifdef ASURA_USE_GUI_MENU_SYSTEM
		s_eErrorCode = GP_LOGIN_BAD_EMAIL;
#endif
		return ASURA_GSGP_ERROR_SERVER;
	}
	if (!strlen(szPass))
	{
#ifdef ASURA_USE_GUI_MENU_SYSTEM
		s_eErrorCode = GP_LOGIN_BAD_PASSWORD;
#endif
		return ASURA_GSGP_ERROR_SERVER;
	}

	s_iCurrentStatus |= ASURA_GSGP_STATUSFLAG_LOGGINGIN;
	
	Asura_PrintfToBfr( s_acCurrentNick, "%s", uASURA_GAMESPY_NICKNAME_MAX_LENGTH, true, szNick );
	Asura_PrintfToBfr( s_acCurrentPassword, "%s", uASURA_GAMESPY_PASSWORD_MAX_LENGTH, true, szPass );

	//ASURA_GSGP_RESULT eResult = (ASURA_GSGP_RESULT)gpConnectUniqueNick( &s_xConnection, szNick, szPass, GP_NO_FIREWALL, GP_NON_BLOCKING, ConnectCallback, 0 );	
	ASURA_GSGP_RESULT eResult = (ASURA_GSGP_RESULT)gpConnect( &s_xConnection, szNick, szEmail, szPass, GP_NO_FIREWALL, GP_NON_BLOCKING, ConnectCallback, 0 );	
	Asura_Assert(eResult == ASURA_GSGP_OK, "gpConnect failed");
	return eResult;
}
/* end of function ConnectProfile */


/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::CreateProfile
 *
 * Author        : Richard May
 *
 * Description   : Creates a new gamespy profile
 *
 *************************************************************/
ASURA_GSGP_RESULT Asura_GameSpy_GP::CreateProfile( const Asura_UnicodeChar* wszNickname, const Asura_UnicodeChar* wszEmail, const Asura_UnicodeChar* wszPassword, bool bAutoConnect/*=true*/ )
{

	char szNick[GP_NICK_LEN];
	char szEmail[GP_EMAIL_LEN];
	char szPass[GP_PASSWORD_LEN];
	char* szCdKey = NULL;

	Asura_PrintfToBfr( szNick,  "%S", GP_NICK_LEN, true, wszNickname );
	Asura_PrintfToBfr( szEmail, "%S", GP_EMAIL_LEN, true, wszEmail );
	Asura_PrintfToBfr( szPass,  "%S", GP_PASSWORD_LEN, true, wszPassword );

	// (MH - 11.06.2007) check for various issues with invalid nicknames here.
	// Backslash, comma and apostrophe characters are not always correctly identified by the SDK.
	// There's no point asking GameSpy to do something invalid anyway.
	// Uniquenick must be between 3 and 20 characters long and should not start with a number or one 
	// of these four symbols: @+#: It should not contain the \x005c (backslash),  ' (apostrophe) or 
	// , (comma) characters or any spaces.

	if (strstr(szNick, "\\") || strstr(szNick, "'") || strstr(szNick, ",") || strstr(szNick, " ") ||
		szNick[0] == '@' || szNick[0] == '#' || szNick[0] == '+' || szNick[0] == ':' || 
		(szNick[0] >= '0' && szNick[0] <= '9') || 
		strlen(szNick) < 3 || strlen(szNick) > 20)
	{
#ifdef ASURA_USE_GUI_MENU_SYSTEM
		s_eErrorCode = GP_NEWUSER_UNIQUENICK_INVALID;
#endif
		return ASURA_GSGP_ERROR_SERVER;
	}
	if (!strlen(szEmail) || !strstr(szEmail, "@") || !strstr(szEmail,"."))
	{
#ifdef ASURA_USE_GUI_MENU_SYSTEM
		s_eErrorCode = GP_LOGIN_BAD_EMAIL;
#endif
		return ASURA_GSGP_ERROR_SERVER;
	}
	if (!strlen(szPass))
	{
#ifdef ASURA_USE_GUI_MENU_SYSTEM
		s_eErrorCode = GP_LOGIN_BAD_PASSWORD;
#endif
		return ASURA_GSGP_ERROR_SERVER;
	}

	s_iCurrentStatus |= ASURA_GSGP_STATUSFLAG_CREATINGPROFILE;

	Asura_PrintfToBfr( s_acCurrentNick, "%s", uASURA_GAMESPY_NICKNAME_MAX_LENGTH, true, szNick );
	Asura_PrintfToBfr( s_acCurrentPassword, "%s", uASURA_GAMESPY_PASSWORD_MAX_LENGTH, true, szPass );

	ASURA_GSGP_RESULT eResult = ASURA_GSGP_ERROR_PARAMETER;
	Asura_Assert(bAutoConnect, "assuming autoconnect when s_bConnectSuccess set to true in ConnectCallback");

	if( bAutoConnect )
	{
		Asura_Assert(!IsLoggedIn(), "already logged in");
		eResult = (ASURA_GSGP_RESULT)gpConnectNewUser
			( &s_xConnection, szNick, szNick, szEmail, szPass, szCdKey, 
				GP_NO_FIREWALL, GP_NON_BLOCKING, CreateCallback, 0 );
	}
//	else
//	{
//		eResult = (ASURA_GSGP_RESULT)gpNewUser
//			( &s_xConnection, szNick, szNick, szEmail, szPass, 
//				szCdKey, GP_NON_BLOCKING, CreateCallback, 0 );
//	}	
	Asura_Assert(eResult == ASURA_GSGP_OK, "new user call failed.");
	if (eResult != ASURA_GSGP_OK)
	{
		s_iCurrentStatus &= ~ASURA_GSGP_STATUSFLAG_CREATINGPROFILE;
	}
	return eResult;
}
/* end of function CreateProfile */

/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::DisconnectProfile
 *
 * Author        : Mike Healey
 *
 * Description   : disconnect the profile (logout)
 *
 *************************************************************/
bool Asura_GameSpy_GP::DisconnectProfile()
{
	Asura_Assert(IsLoggedIn(), "can't log out - not logged in");
	if (!IsLoggedIn()) return false;

	Asura_GSGP_Internal::DisconnectFromPersistentStorage();

	gpDisconnect( &s_xConnection );
	s_iCurrentProfile  = -1;

	// reset connect vars
	s_bConnectSuccess  = false;
	s_bCreateSuccess   = false;
	s_bUniqueNickValid = false;
	s_iNumSuggestions  = 0;
#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	// reset buddy data
	s_uNumBuddies = 0;
	s_uNumReverseBuddies = 0;
	s_uNumPendingBuddyRequests = 0;
	s_uNumBuddyMessages = 0;
#endif

	return true;
}
/* end of function DisconnectProfile */

/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::ConnectCallback
 *
 * Author        : Richard May
 *
 * Description   : Callback for the connect to profile function
 *
 *************************************************************/
void Asura_GameSpy_GP::ConnectCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
	GPConnectResponseArg* pxResponse = static_cast<GPConnectResponseArg*> (pArg);
	s_iCurrentStatus &= ~ASURA_GSGP_STATUSFLAG_LOGGINGIN;

	if(pxResponse->result == GP_NO_ERROR)
	{
		s_iCurrentProfile = pxResponse->profile;
		s_bConnectSuccess = true;
		s_bHasEverLoggedIn = true;

		if( !strcmp(pxResponse->uniquenick,"@unregistered") || !strcmp(pxResponse->uniquenick,"@expired") )
		{
			s_bUniqueNickValid = false;
		}
		else
		{
			s_bUniqueNickValid = true;
		}
		
#if !ASURA_GAMESPY_GP_ALLOW_NONUNIQUENICKS
		if (!s_bUniqueNickValid)
		{
#ifdef ASURA_USE_GUI_MENU_SYSTEM
			s_eErrorCode = GP_LOGIN_BAD_UNIQUENICK;
#else
			Asura_Assert(false, "must handle 'not unique nick' case");
#endif
			DisconnectProfile();
		}
		else
#endif
		{
#ifdef ASURA_ASSERT
			const GPResult eResult =
#endif
				gpSetStatus( &s_xConnection, GP_ONLINE, "Online", "" );
			Asura_Assert(eResult == GP_NO_ERROR, "gpSetStatus error");
			// we're connected so we can start fetching messages from the network.
			Asura_GSGP_Internal::ConnectToPersistentStorage();
		}
	}
	else
	{
		Asura_Assert( false, "Connection to profile failed!" );
		s_bConnectSuccess = false;
	}
}
/* end of function ConnectCallback */


/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::CreateCallback
 *
 * Author        : Richard May
 *
 * Description   : Callback for the create profile function
 *
 *************************************************************/
void Asura_GameSpy_GP::CreateCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
	GPNewUserResponseArg* pxResponse = static_cast<GPNewUserResponseArg*> (pArg);
	s_iCurrentStatus &= ~ASURA_GSGP_STATUSFLAG_CREATINGPROFILE;

	if(pxResponse->result == GP_NO_ERROR)
	{
		// success!		
		s_iCurrentProfile  = pxResponse->profile;
#ifdef ASURA_ASSERT
		const GPResult eResult =
#endif
            gpSetStatus( &s_xConnection, GP_ONLINE, "Online", "" );
		Asura_Assert(eResult == GP_NO_ERROR, "gpSetStatus error");
		s_bCreateSuccess   = true;
		s_bConnectSuccess  = true;
		s_bUniqueNickValid = true;
		s_bHasEverLoggedIn = true;

		// we're connected so we can start fetching messages from the network.
		Asura_GSGP_Internal::ConnectToPersistentStorage();

		RequestProfileInfo(pxResponse->profile);
	}
	else
	{
#ifdef ASURA_ASSERT
		char* szError = "unknown";
	
		GPErrorCode xErrorCode = GP_GENERAL;
		GPResult eResult = gpGetErrorCode( &s_xConnection, &xErrorCode );
		Asura_Assert(eResult == GP_NO_ERROR, "gpGetErrorCode failed");

		if(xErrorCode == GP_NEWUSER_BAD_PASSWORD)
		{
			szError = "Invalid password";
		}
		else if(xErrorCode == GP_NEWUSER_BAD_NICK)
		{
			szError = "Profile already exists";
		}
		else if(xErrorCode == GP_NEWUSER_UNIQUENICK_INUSE)
		{
			szError = "Uniquenick in use";
		}
		else if(xErrorCode == GP_NEWUSER_UNIQUENICK_INVALID)
		{
			szError = "Uniquenick invalid";
		}

		Asura_Assertf( false, ("Create Profile failed: %s",szError) );
#endif // #ifdef ASURA_ASSERT

		s_bCreateSuccess = false;
	}
}
/* end of function CreateCallback */


/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::RequestProfileInfo
 *
 * Author        : Richard May
 *
 * Description   : Gets information for the supplied profile
 *
 *************************************************************/
ASURA_GSGP_RESULT Asura_GameSpy_GP::RequestProfileInfo( Asura_GSGP_ProfileID iProfileID )
{
	if( s_iCurrentStatus & ASURA_GSGP_STATUSFLAG_GETTINGINFO ) return ASURA_GSGP_WORKING;

	s_iCurrentStatus |= ASURA_GSGP_STATUSFLAG_GETTINGINFO;
	
	return (ASURA_GSGP_RESULT)gpGetInfo( &s_xConnection, iProfileID, GP_CHECK_CACHE, GP_NON_BLOCKING, RequestInfoCallback1, 0 );	
}
/* end of function RequestProfileInfo */


/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::RequestInfoCallback1
 *
 * Author        : Richard May
 *
 * Description   : Callback for request info
 *
 *************************************************************/
void Asura_GameSpy_GP::RequestInfoCallback1( GPConnection* pxConnection, void* pArg, void* pParam )
{
	Asura_Assert( s_iCurrentStatus & ASURA_GSGP_STATUSFLAG_GETTINGINFO, "unexpected callback" );
	
	GPGetInfoResponseArg* pxResponse = static_cast<GPGetInfoResponseArg*> (pArg);	

	s_xCurrentProfileInfo = *pxResponse;	

	s_iCurrentStatus &= ~ASURA_GSGP_STATUSFLAG_GETTINGINFO;
}
/* end of function RequestInfoCallback1 */

/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::RequestProfileInfoCustom
 *
 * Author        : Richard May
 *
 * Description   : Gets information for the supplied profile
 *
 *************************************************************/
ASURA_GSGP_RESULT Asura_GameSpy_GP::RequestProfileInfoCustom( Asura_GSGP_ProfileID iProfileID, GPCallback pfCallBack, void* pData )
{
	return (ASURA_GSGP_RESULT)gpGetInfo( &s_xConnection, iProfileID, GP_CHECK_CACHE, GP_NON_BLOCKING, pfCallBack, pData );	
}
/* end of function RequestProfileInfoCustom */



/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::SetProfileSearchCallBack
 *
 * Author        : Neil Young
 *
 * Description   : Sets the project function to call when search results are returned
 *
 *************************************************************/
//SetProfileSearchCallBack must be called before ProfileSearch.
//the callback function passed in will be called when a search result from ProfileSearch is received
//parameters passed back are:
//void* user data passed in to ProfileSearch
//int number of matches
//int* array of ints storing the profile IDs of each match
//char** array of char*s pointing to the nick of each match
//char** " unique nicks
//char** " firstnames
//char** " lastnames
//char** " email addresses
void Asura_GameSpy_GP::SetProfileSearchCallBack(void (*pfnProfileSearch)(void*, int, int*,
	char**, char**, char**, char**, char**))
{
	s_pfnProfileSearch = pfnProfileSearch;
}

/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::ProfileSearch
 *
 * Author        : Neil Young
 *
 * Description   : Searches for the profile indicated
 *
 *************************************************************/
ASURA_GSGP_RESULT Asura_GameSpy_GP::ProfileSearch(const char szNick[GP_NICK_LEN],
		const char szUniqueNick[GP_UNIQUENICK_LEN], const char szEmail[GP_EMAIL_LEN],
		const char szFirstName[GP_FIRSTNAME_LEN], const char szLastName[GP_LASTNAME_LEN], void* pParam)
{
	Asura_Assert(s_pfnProfileSearch, "profile search callback not set");
	if (!s_pfnProfileSearch)
	{
		return ASURA_GSGP_ERROR_PARAMETER;
	}
	return (ASURA_GSGP_RESULT)gpProfileSearch(&s_xConnection, szNick, szUniqueNick, szEmail, szFirstName, szLastName, 0,
		GP_NON_BLOCKING, ProfileSearchCallback, pParam);
}
/* end of function ProfileSearch */

/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::ProfileSearchCallback
 *
 * Author        : Neil Young
 *
 * Description   : Callback for search results
 *
 *************************************************************/
void Asura_GameSpy_GP::ProfileSearchCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
	Asura_Assert(s_pfnProfileSearch, "profile search callback not set");
	
	if (!s_pfnProfileSearch)
	{
		return;
	}
	GPProfileSearchResponseArg* pxResponse = static_cast<GPProfileSearchResponseArg*>(pArg);

	int * piProfileIDs = 0;
	char ** pszNicks = 0;
	char ** pszUniqueNicks = 0;
	char ** pszFirstNames = 0;
	char ** pszLastNames = 0;
	char ** pszEmails = 0;
	if (pxResponse->numMatches)
	{
		piProfileIDs = new int[pxResponse->numMatches];
		pszNicks = new char*[pxResponse->numMatches];
		pszUniqueNicks = new char*[pxResponse->numMatches];
		pszFirstNames = new char*[pxResponse->numMatches];
		pszLastNames = new char*[pxResponse->numMatches];
		pszEmails = new char*[pxResponse->numMatches];
		for (int i = 0; i < pxResponse->numMatches; i++)
		{
			piProfileIDs[i] = pxResponse->matches[i].profile;
			pszNicks[i] = pxResponse->matches[i].nick;
			pszUniqueNicks[i] = pxResponse->matches[i].uniquenick;
			pszFirstNames[i] = pxResponse->matches[i].firstname;
			pszLastNames[i] = pxResponse->matches[i].lastname;
			pszEmails[i] = pxResponse->matches[i].email;
		}
	}
	s_pfnProfileSearch(pParam, pxResponse->numMatches, piProfileIDs, pszNicks, pszUniqueNicks,
		pszFirstNames, pszLastNames, pszEmails);
	delete [] piProfileIDs;
	delete [] pszNicks;
	delete [] pszUniqueNicks;
	delete [] pszFirstNames;
	delete [] pszLastNames;
	delete [] pszEmails;
}
/* end of function GetInfoCallback */


/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::RequestBuddyInfoCallback
 *
 * Author        : Mike Healey
 *
 * Description   : Callback for buddy info request
 *
 *************************************************************/
void Asura_GameSpy_GP::RequestBuddyInfoCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	GPGetInfoResponseArg* pxResponse = static_cast<GPGetInfoResponseArg*> (pArg);	

	u_int uBuddyIndex = GetBuddyIndexFromProfileID(pxResponse->profile);
	Asura_Assert(uBuddyIndex < uASURA_GAMESPY_MAX_BUDDIES, "invalid buddy index");

	if (uBuddyIndex < uASURA_GAMESPY_MAX_BUDDIES)
	{
		Asura_PrintfToBfr(s_axBuddyList[uBuddyIndex].m_awcName, L"%S", uASURA_GAMESPY_NICKNAME_MAX_LENGTH, true, pxResponse->nick);
	}
#endif
}
/* end of function GetInfoCallback */

/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::RequestBuddyInfoCallback
 *
 * Author        : Mike Healey
 *
 * Description   : Callback for info request from message received code. 
 *
 *************************************************************/
void Asura_GameSpy_GP::RequestMessageSenderInfoCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	GPGetInfoResponseArg* pxResponse = static_cast<GPGetInfoResponseArg*>(pArg);
	
	// any received buddy messages that we don't have the nick for yet are checked to see
	// if they match this profile ID.
	for (u_int u = 0; u < s_uNumBuddyMessages; ++u)
	{
		if ( (s_axBuddyMessages[u].m_iSenderProfileID == pxResponse->profile) && (s_axBuddyMessages[u].m_awcSenderNick[0] == 0) )
		{
			Asura_PrintfToBfr( s_axBuddyMessages[u].m_awcSenderNick, L"%S", uASURA_GAMESPY_NICKNAME_MAX_LENGTH, true, pxResponse->nick );
		}
	}
#endif
}
/* end of function GetInfoCallback */

/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::GetRequestedProfileInfo
 *
 * Author        : Richard May
 *
 * Description   : Allows the user to retrieve the profile info
 *				   they requested previously. Returns a pointer
 *				   when info is ready, otherwise returns null
 *
 *************************************************************/
const Asura_GSGP_ProfileInfo* Asura_GameSpy_GP::GetRequestedProfileInfo( void )
{
	if( s_iCurrentStatus & ASURA_GSGP_STATUSFLAG_GETTINGINFO ) return NULL;	
	return &s_xCurrentProfileInfo;	
}
/* end of function GetRequestedProfileInfo */


/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::GetErrorCode
 *
 * Author        : Richard May
 *
 * Description   : Retrieves the gamespy errorcode for the
 *				   last operation
 *
 *************************************************************/
ASURA_GSGP_ERRORCODE Asura_GameSpy_GP::GetErrorCode( void )
{
	GPErrorCode eError = GP_GENERAL;
#ifdef ASURA_ASSERT
	const GPResult eResult =
#endif
        gpGetErrorCode( &s_xConnection, &eError );
	Asura_Assert(eResult == GP_NO_ERROR, "gpGetErrorCode failed");

	return (ASURA_GSGP_ERRORCODE)eError;
}
/* end of function GetErrorCode */


/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::RequestSuggestNickname
 *
 * Author        : Richard May
 *
 * Description   : Retrieves a selection of suggested nicknames
 *				   based on a supplied string. 
 *
 *************************************************************/
ASURA_GSGP_RESULT Asura_GameSpy_GP::RequestSuggestNickname( const Asura_UnicodeChar* wszNickname )
{
	if( s_iCurrentStatus & ASURA_GSGP_STATUSFLAG_GETTINGSUGGESTIONS ) return ASURA_GSGP_WORKING;
	if( !wszNickname ) return ASURA_GSGP_ERROR_PARAMETER;
	
	// convert to ascii as we seemto be using gamespy this way...
	char szAsciiNick[uASURA_GAMESPY_NICKNAME_MAX_LENGTH];
	Asura_PrintfToBfr( szAsciiNick, "%S", uASURA_GAMESPY_NICKNAME_MAX_LENGTH, true, wszNickname );

	s_iCurrentStatus |= ASURA_GSGP_STATUSFLAG_GETTINGSUGGESTIONS;

	s_iNumSuggestions = 0;
	return (ASURA_GSGP_RESULT)gpSuggestUniqueNick( &s_xConnection, szAsciiNick, GP_NON_BLOCKING, SuggestNickCallback, &s_iNumSuggestions );	
}
/* end of function SuggestNickname */


/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::SuggestNickCallback
 *
 * Author        : Richard May
 *
 * Description   : Callback for suggesting nicknames
 *
 *************************************************************/
void Asura_GameSpy_GP::SuggestNickCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
	GPSuggestUniqueNickResponseArg* pxResponse = static_cast<GPSuggestUniqueNickResponseArg*> (pArg);
	int* piSuggestions = static_cast<int*> (pParam);

	if( pxResponse->result == GP_NO_ERROR )
	{
		// set number of suggestions pointer
		*piSuggestions = pxResponse->numSuggestedNicks;
		
		// copy to static buffer
		for( int i=0; i<pxResponse->numSuggestedNicks; ++i )
		{
			Asura_PrintfToBfr( s_awszSuggestedNicknames[i], L"%S", uASURA_GAMESPY_NICKNAME_MAX_LENGTH, true, pxResponse->suggestedNicks[i] );			
		}		
	}

	s_iCurrentStatus &= ~ASURA_GSGP_STATUSFLAG_GETTINGSUGGESTIONS;
}
/* end of function SuggestNickCallback */


/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::GetSuggestedNicknames
 *
 * Author        : Richard May
 *
 * Description   : 
 *
 *************************************************************/
Asura_UnicodeChar* const* Asura_GameSpy_GP::GetSuggestedNicknames( int* piSuggestions )
{
	if( s_iCurrentStatus & ASURA_GSGP_STATUSFLAG_GETTINGSUGGESTIONS ) return NULL;	
	if( !piSuggestions ) return NULL;

	*piSuggestions = s_iNumSuggestions;
	return s_awszSuggestedNicknames;	
}
/* end of function GetSuggestedNicknames */


/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::RegisterUniqueNickname
 *
 * Author        : Richard May
 *
 * Description   : Registers a unique nickname with for the current profile
 *
 *************************************************************/
ASURA_GSGP_RESULT Asura_GameSpy_GP::RegisterUniqueNickname( const Asura_UnicodeChar* wszNickname )
{
	char szAsciiNick[uASURA_GAMESPY_NICKNAME_MAX_LENGTH];
	Asura_PrintfToBfr( szAsciiNick, "%S", uASURA_GAMESPY_NICKNAME_MAX_LENGTH, true, wszNickname );

	s_iCurrentStatus |= ASURA_GSGP_STATUSFLAG_REGISTERINGNICK;

	return (ASURA_GSGP_RESULT)gpRegisterUniqueNick( &s_xConnection, szAsciiNick, NULL, GP_NON_BLOCKING, RegisterNickCallback, 0 );
}
/* end of function RegisterUniqueNickname */


/*************************************************************
 *
 * Name          : Asura_GameSpy_GP::RegisterNickCallback
 *
 * Author        : Richard May
 *
 * Description   : Blank callback
 *
 *************************************************************/
void Asura_GameSpy_GP::RegisterNickCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
	GPRegisterUniqueNickResponseArg* pxResponse = static_cast<GPRegisterUniqueNickResponseArg*> (pArg);
	if( pxResponse->result == GP_NO_ERROR )
	{
		s_bUniqueNickValid = true;
	}
	else
	{
		s_bUniqueNickValid = false;
	}
	
	s_iCurrentStatus &= ~ASURA_GSGP_STATUSFLAG_REGISTERINGNICK;
}
/* end of function RegisterNickCallback */

void Asura_GameSpy_GP::BuddyStatusCallback( GPConnection* pxConnection, void* pArg, void* pParam ) 
{
#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	const GPRecvBuddyStatusArg* pxStatusArg = reinterpret_cast<const GPRecvBuddyStatusArg*>(pArg);

	int iBuddyIndex = pxStatusArg->index;
	GPBuddyStatus xStatus;
	GPResult eResult = gpGetBuddyStatus( &s_xConnection, iBuddyIndex, &xStatus );
	Asura_Assert(eResult == GP_NO_ERROR, "gpGetBuddyStatus error");
	
	if (eResult == GP_NO_ERROR)
	{
		Asura_OutputToDebugger(("buddy status received for profile ID %d: status = %d (%s), IP = %u.%u.%u.%u:%d\n", 
			xStatus.profile, xStatus.status, xStatus.statusString,
			(xStatus.ip >> 0) & 0xff, (xStatus.ip >> 8) & 0xff, (xStatus.ip >> 16) & 0xff, 
			(xStatus.ip >> 24) & 0xff, xStatus.port));

		u_int uBuddyIndex = GetBuddyIndexFromProfileID(xStatus.profile);

		if (uBuddyIndex == uASURA_GAMESPY_MAX_BUDDIES)
		{
			// add this buddy to our list
			Asura_Assert(s_uNumBuddies < uASURA_GAMESPY_MAX_BUDDIES, "too many buddies");
			if (s_uNumBuddies < uASURA_GAMESPY_MAX_BUDDIES)
			{
				uBuddyIndex = s_uNumBuddies++;
				// clear name and set profileID
				s_axBuddyList[uBuddyIndex].m_awcName[0] = 0;
				s_axBuddyList[uBuddyIndex].m_iProfileID = xStatus.profile;

				// request the info for this buddy.
				eResult = gpGetInfo( &s_xConnection, xStatus.profile, GP_CHECK_CACHE, GP_NON_BLOCKING, RequestBuddyInfoCallback, NULL );
				Asura_Assert(eResult == GP_NO_ERROR, "gpGetInfo error");

				Asura_OutputToDebugger(("added new buddy, requesting info\n"));
			}
		}
		else
		{
			Asura_OutputToDebugger(("existing buddy %S, setting new status\n", s_axBuddyList[uBuddyIndex].m_awcName));
		}

		// set updated status
		if (uBuddyIndex < uASURA_GAMESPY_MAX_BUDDIES)
		{
			s_axBuddyList[uBuddyIndex].m_eStatus = (ASURA_GSGP_ACTIVESTATUS)xStatus.status;
			FillIPAndPortFromLocation(xStatus.locationString, s_axBuddyList[uBuddyIndex].m_uIP, s_axBuddyList[uBuddyIndex].m_usPort);
		}
	}
#endif // #if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
}

void Asura_GameSpy_GP::BuddyMessageCallback( GPConnection* pxConnection, void* pArg, void* pParam ) 
{
#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	const GPRecvBuddyMessageArg* pxMessage = reinterpret_cast<const GPRecvBuddyMessageArg*>(pArg);
	Asura_Time xTime;
	xTime.FillFromLibCTime(pxMessage->date);
	xTime.ConvertUTCToLocalTime();
	Asura_OutputToDebugger_Retail(("buddy message received: %s. sent at %S (%d)\n", pxMessage->message, Asura_Time_System::GetDateAndTimeString(xTime, ASURA_DATE_MINIMAL), pxMessage->date));
	
	time_t xTimeTTime = pxMessage->date;
	tm* pxTime = localtime( &xTimeTTime );
	Asura_OutputToDebugger_Retail(("localtime version of time: %02d:%02d:%02d\n", pxTime->tm_hour, pxTime->tm_min, pxTime->tm_sec ));
	

	u_int uMessageIndex;
	if (s_uNumBuddyMessages < uASURA_GAMESPY_MAX_BUDDY_MESSAGES)
	{
		uMessageIndex = s_uNumBuddyMessages++;
	}
	else
	{
		// find oldest message
		uMessageIndex = 0;
		u_int uOldestDate = UINT_MAX;
		for (u_int u = 0; u < uASURA_GAMESPY_MAX_BUDDY_MESSAGES; ++u)
		{
			if (s_axBuddyMessages[u].m_uTimeStamp < uOldestDate)
			{
				uOldestDate = s_axBuddyMessages[u].m_uTimeStamp;
				uMessageIndex = u;
			}
		}
	}

	s_axBuddyMessages[uMessageIndex].m_uTimeStamp = pxMessage->date;
	s_axBuddyMessages[uMessageIndex].m_iSenderProfileID = pxMessage->profile;
	Asura_PrintfToBfr(s_axBuddyMessages[uMessageIndex].m_acMessage, "%s", uASURA_GAMESPY_MAX_BUDDY_MESSAGE_LENGTH, true, pxMessage->message);
	u_int uBuddyIndex = GetBuddyIndexFromProfileID(pxMessage->profile);
	if (uBuddyIndex >= uASURA_GAMESPY_MAX_BUDDIES)
	{
		// not a buddy so get the nick from the server
#ifdef ASURA_ASSERT
		const GPResult eResult =
#endif
            gpGetInfo( &s_xConnection, pxMessage->profile, GP_CHECK_CACHE, GP_NON_BLOCKING, RequestMessageSenderInfoCallback, 0 );
		Asura_Assert(eResult == GP_NO_ERROR, "gpGetInfo error");
		s_axBuddyMessages[uMessageIndex].m_awcSenderNick[0] = 0;
	}
	else
	{
		Asura_PrintfToBfr(s_axBuddyMessages[uMessageIndex].m_awcSenderNick, L"%s", uASURA_GAMESPY_NICKNAME_MAX_LENGTH, true, GetBuddyName(uBuddyIndex));
	}

	Asura_GSGP_Internal::ArchiveMessage(s_axBuddyMessages[uMessageIndex]);
#endif // #if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
}

void Asura_GameSpy_GP::ErrorCallback( GPConnection* pxConnection, void* pArg, void* pParam ) 
{
	GPErrorArg* pxError = reinterpret_cast<GPErrorArg*>(pArg);
	Asura_OutputToDebugger_Retail(("GP %s error %s\n", (pxError->fatal == GP_FATAL) ? "fatal" : "non-fatal", pxError->errorString));
	Asura_Assertf(false, ("GP error %s", pxError->errorString));
	if (pxError->fatal == GP_FATAL)
	{
		// connection has been disconnected. We need to login again.
		if (IsLoggedIn())
		{
			DisconnectProfile();
		}
		s_iCurrentStatus = ASURA_GSGP_STATUS_READY;
	}

#ifdef ASURA_USE_GUI_MENU_SYSTEM
	s_eErrorCode  = pxError->errorCode;
#endif // #ifdef ASURA_USE_GUI_MENU_SYSTEM
}

void Asura_GameSpy_GP::BuddyRequestCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	const GPRecvBuddyRequestArg* pxRequest = reinterpret_cast<const GPRecvBuddyRequestArg*>(pArg);
	Asura_OutputToDebugger_Retail(("buddy request received from %s (ID %i)\n", pxRequest->reason, pxRequest->profile));

	for (u_int u = 0; u < s_uNumPendingBuddyRequests; ++u)
	{
		if (s_axBuddyRequests[u].m_iSenderProfileID == pxRequest->profile)
		{
			Asura_Assert(false, "request already pending");
			return;
		}
	}

	Asura_Assert(s_uNumPendingBuddyRequests < uASURA_GAMESPY_MAX_BUDDY_REQUESTS, "too many pending requests");
	if (s_uNumPendingBuddyRequests < uASURA_GAMESPY_MAX_BUDDY_REQUESTS)
	{
		u_int uIndex = s_uNumPendingBuddyRequests++;
		s_axBuddyRequests[uIndex].m_iSenderProfileID = pxRequest->profile;
		// we always put the nick in the reason. Should really look it up using gpGetInfo.
		Asura_PrintfToBfr( s_axBuddyRequests[uIndex].m_awcSenderNick, L"%S", uASURA_GAMESPY_NICKNAME_MAX_LENGTH, true, pxRequest->reason );
	}
#endif
}

void Asura_GameSpy_GP::GameInviteCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
	GPRecvGameInviteArg* pxInvite = reinterpret_cast<GPRecvGameInviteArg*>(pArg);
	Asura_OutputToDebugger_Retail(("Game invite received for product ID %d from profile ID %d\n", pxInvite->productID, pxInvite->profile));
}


void Asura_GameSpy_GP::GameTransferCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
    // (MH - 19.06.2007) can only do this if NOFILE is not defined
#ifndef NOFILE
	GPTransferCallbackArg* pxTransferArg = reinterpret_cast<GPTransferCallbackArg*>(pArg);
	if (GP_TRANSFER_SEND_REQUEST == pxTransferArg->type)
	{
		char acBuffer[128];
		Asura_PrintfToBfr( acBuffer, "%s does not support file transfers", 128, true, Asura_GameSpy::GetGameTitle() );
		gpRejectTransfer( pxConnection, pxTransferArg->transfer, acBuffer );
	}
#endif
}
void Asura_GameSpy_GP::GameRecvBuddyAuthCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
	// MRD buddy auth's are handled in our code elsewhere so we don't need to do anything here
}
void Asura_GameSpy_GP::GameRecvBuddyRevokeCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
	// MRD again, revokes are handled in our system, so we don't need to handle this ehre
}

// in-game status
void Asura_GameSpy_GP::SetActiveStatus( ASURA_GSGP_ACTIVESTATUS eStatus, const char* szStatusText, const char* szAddress )
{
	char acAddress[GP_LOCATION_STRING_LEN] = "";
	if (szAddress)
	{
		Asura_PrintfToBfr( acAddress, "%s://%s", GP_LOCATION_STRING_LEN, true, Asura_GameSpy::GetGameName(), szAddress );
	}
	GPEnum eGPStatus = (GPEnum)eStatus;
#ifdef ASURA_ASSERT
	const GPResult eResult =
#endif
        gpSetStatus( &s_xConnection, eGPStatus, szStatusText, acAddress );
	Asura_Assert(eResult == GP_NO_ERROR, "gpSetStatus error");
}
// end of in-game status

#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
// basic buddy listing functions
u_int Asura_GameSpy_GP::GetNumBuddies( void )
{
	return s_uNumBuddies;
}

const Asura_UnicodeChar* Asura_GameSpy_GP::GetBuddyName( u_int uBuddyIndex )
{
	Asura_Assert(uBuddyIndex < s_uNumBuddies, "invalid buddy index");
	if (uBuddyIndex < s_uNumBuddies)
	{
		return s_axBuddyList[uBuddyIndex].m_awcName;
	}
	return L"";
}

const int Asura_GameSpy_GP::GetBuddyProfileID( u_int uBuddyIndex )
{
	Asura_Assert(uBuddyIndex < s_uNumBuddies, "invalid buddy index");
	if (uBuddyIndex < s_uNumBuddies)
	{
		return s_axBuddyList[uBuddyIndex].m_iProfileID;
	}
	return -1;
}

ASURA_GSGP_ACTIVESTATUS Asura_GameSpy_GP::GetBuddyStatus( u_int uBuddyIndex, u_int* puIP, u_short* pusPort )
{
	Asura_Assert(uBuddyIndex < s_uNumBuddies, "invalid buddy index");
	if (uBuddyIndex < s_uNumBuddies)
	{
		if (s_axBuddyList[uBuddyIndex].m_eStatus == ASURA_GSGP_STATUS_PLAYING)
		{
			if (puIP)
			{
				*puIP = s_axBuddyList[uBuddyIndex].m_uIP;
			}	
			if (pusPort)
			{
				*pusPort = s_axBuddyList[uBuddyIndex].m_usPort;
			}	
		}
		else
		{
			if (puIP)
			{
				*puIP = 0;
			}	
			if (pusPort)
			{
				*pusPort = 0;
			}	
		}
		return (ASURA_GSGP_ACTIVESTATUS)s_axBuddyList[uBuddyIndex].m_eStatus;
	}
	return ASURA_GSGP_STATUS_UNKNOWN;
}

void Asura_GameSpy_GP::DeleteBuddy( u_int uBuddyIndex )
{
	if (uBuddyIndex < s_uNumBuddies)
	{
#ifdef ASURA_ASSERT
		const GPResult eResult =
#endif
            gpDeleteBuddy( &s_xConnection, s_axBuddyList[uBuddyIndex].m_iProfileID );
		Asura_Assert(eResult == GP_NO_ERROR, "gpDeleteBuddy error");

		// put last one in this slot
		s_axBuddyList[uBuddyIndex] = s_axBuddyList[s_uNumBuddies - 1];

		// reduce number of buddies
		--s_uNumBuddies;
	}
}

// returns uASURA_GAMESPY_MAX_BUDDIES if not found.
u_int Asura_GameSpy_GP::GetBuddyIndexFromProfileID( int iProfileID )
{
	for (u_int u = 0; u < s_uNumBuddies; ++u)
	{
		if (s_axBuddyList[u].m_iProfileID == iProfileID)
		{
			return u;
		}
	}
	return uASURA_GAMESPY_MAX_BUDDIES;
}
// end of basic buddy listing functions


// buddy requests functions
void Asura_GameSpy_GP::SendBuddyRequest( int iBuddyProfileID )
{
	// first see if this is already a buddy
	if (GetBuddyIndexFromProfileID( iBuddyProfileID ) != uASURA_GAMESPY_MAX_BUDDIES )
	{
		Asura_Assert(false, "already a buddy!");
		return;
	}

#ifdef ASURA_ASSERT
	const GPResult eResult =
#endif
        gpSendBuddyRequest( &s_xConnection, iBuddyProfileID, s_acCurrentNick );
	Asura_Assert(eResult == GP_NO_ERROR, "gpSendBuddyRequest failed");
}

const Asura_UnicodeChar* Asura_GameSpy_GP::GetBuddyRequestSender( u_int uIndex )
{
	Asura_Assert(uIndex < s_uNumPendingBuddyRequests, "invalid request");
	if (uIndex < s_uNumPendingBuddyRequests)
	{
		return s_axBuddyRequests[uIndex].m_awcSenderNick;
	}
	return L"???";
}

const int Asura_GameSpy_GP::GetBuddyRequestSenderID( u_int uIndex )
{
	Asura_Assert(uIndex < s_uNumPendingBuddyRequests, "invalid request");
	if (uIndex < s_uNumPendingBuddyRequests)
	{
		return s_axBuddyRequests[uIndex].m_iSenderProfileID;
	}
	return 0;
}

void Asura_GameSpy_GP::AcceptBuddyRequest( u_int uIndex )
{
	Asura_Assert(uIndex < s_uNumPendingBuddyRequests, "invalid request");
	if (uIndex < s_uNumPendingBuddyRequests)
	{
#ifdef ASURA_ASSERT
		const GPResult eResult =
#endif
            gpAuthBuddyRequest(&s_xConnection, s_axBuddyRequests[uIndex].m_iSenderProfileID );
		Asura_Assert(eResult == GP_NO_ERROR, "gpAuthBuddyRequest failed");

		// swap with last and reduce num pending
		s_axBuddyRequests[uIndex] = s_axBuddyRequests[--s_uNumPendingBuddyRequests];
	}
}

void Asura_GameSpy_GP::RefuseBuddyRequest( u_int uIndex )
{
	Asura_Assert(uIndex < s_uNumPendingBuddyRequests, "invalid request");
	if (uIndex < s_uNumPendingBuddyRequests)
	{
#ifdef ASURA_ASSERT
		const GPResult eResult =
#endif
            gpDenyBuddyRequest(&s_xConnection, s_axBuddyRequests[uIndex].m_iSenderProfileID );
		Asura_Assert(eResult == GP_NO_ERROR, "gpDenyBuddyRequest failed");

		// swap with last and reduce num pending
		s_axBuddyRequests[uIndex] = s_axBuddyRequests[--s_uNumPendingBuddyRequests];
	}
}
// end of buddy requests functions

// Reverse Buddy functions - for finding profiles that I've allowed to put me on their buddy list
bool Asura_GameSpy_GP::SearchForReverseBuddies()
{
	if (s_iCurrentStatus & ASURA_GSGP_STATUSFLAG_GETTINGREVERSEBUDDIES)
	{
		return false;
	}

	GPResult eResult = gpGetReverseBuddies( &s_xConnection, GP_NON_BLOCKING, ReverseBuddyCallback, 0 );
	Asura_Assert(eResult == GP_NO_ERROR, "gpGetReverseBuddies failed");
	
	if (eResult == GP_NO_ERROR)
	{
		s_iCurrentStatus |= ASURA_GSGP_STATUSFLAG_GETTINGREVERSEBUDDIES;
		return true;
	}
	return false;
}

void Asura_GameSpy_GP::ReverseBuddyCallback( GPConnection* pxConnection, void* pArg, void* pParam )
{
	Asura_Assert(s_iCurrentStatus & ASURA_GSGP_STATUSFLAG_GETTINGREVERSEBUDDIES, "unexpected callback");
	s_iCurrentStatus &= ~ASURA_GSGP_STATUSFLAG_GETTINGREVERSEBUDDIES;

	const GPGetReverseBuddiesResponseArg* pxResponse = reinterpret_cast<const GPGetReverseBuddiesResponseArg*>(pArg);
	
	if (pxResponse->result == GP_NO_ERROR)
	{
		s_bIsReverseBuddiesResultOK = true;

		s_uNumReverseBuddies = static_cast<u_int>(pxResponse->numProfiles);
		Asura_Assert(s_uNumReverseBuddies < uASURA_GAMESPY_MAX_BUDDIES, "too many reverse buddies");
		if (s_uNumReverseBuddies > uASURA_GAMESPY_MAX_BUDDIES)
		{
			s_uNumReverseBuddies = uASURA_GAMESPY_MAX_BUDDIES;
		}

		for (u_int u = 0; u < s_uNumReverseBuddies; ++u)
		{
			const GPProfileSearchMatch& xProfile = pxResponse->profiles[u];
			Asura_PrintfToBfr(s_axReverseBuddyList[u].m_awcName, L"%S", uASURA_GAMESPY_NICKNAME_MAX_LENGTH, true, xProfile.nick);
			s_axReverseBuddyList[u].m_eStatus = ASURA_GSGP_STATUS_UNKNOWN;
			s_axReverseBuddyList[u].m_iProfileID = xProfile.profile;
		}
	}
	else
	{
		s_bIsReverseBuddiesResultOK = false;
	}
}

bool Asura_GameSpy_GP::IsSearchingForReverseBuddies()
{
	return (s_iCurrentStatus & ASURA_GSGP_STATUSFLAG_GETTINGREVERSEBUDDIES) != 0;
}

bool Asura_GameSpy_GP::WasReverseBuddiesSuccessful()
{
	Asura_Assert(!IsSearchingForReverseBuddies(), "still searching");
	return s_bIsReverseBuddiesResultOK;
}

u_int Asura_GameSpy_GP::GetNumReverseBuddies()
{
	Asura_Assert(!IsSearchingForReverseBuddies(), "still searching");
	return s_uNumReverseBuddies;
}

int Asura_GameSpy_GP::GetReverseBuddyProfileID(u_int uReverseBuddyIndex)
{
	Asura_Assert(!IsSearchingForReverseBuddies(), "still searching");
	Asura_Assert(uReverseBuddyIndex < s_uNumReverseBuddies, "invalid buddy index");
	if (uReverseBuddyIndex < s_uNumReverseBuddies)
	{
		return s_axReverseBuddyList[uReverseBuddyIndex].m_iProfileID;
	}
	return -1;
}

const Asura_UnicodeChar* Asura_GameSpy_GP::GetReverseBuddyName(u_int uReverseBuddyIndex)
{
	Asura_Assert(!IsSearchingForReverseBuddies(), "still searching");
	Asura_Assert(uReverseBuddyIndex < s_uNumReverseBuddies, "invalid buddy index");
	if (uReverseBuddyIndex < s_uNumReverseBuddies)
	{
		return s_axReverseBuddyList[uReverseBuddyIndex].m_awcName;
	}
	return L"";
}

bool Asura_GameSpy_GP::RevokeBuddy(u_int uReverseBuddyIndex)
{
	Asura_Assert(!IsSearchingForReverseBuddies(), "still searching");
	Asura_Assert(uReverseBuddyIndex < s_uNumReverseBuddies, "invalid buddy index");
	if (uReverseBuddyIndex < s_uNumReverseBuddies)
	{
		GPResult eResult = gpRevokeBuddyAuthorization( &s_xConnection, GetReverseBuddyProfileID(uReverseBuddyIndex) );
		Asura_Assert(eResult == GP_NO_ERROR, "gpGetReverseBuddies failed");

		return eResult == GP_NO_ERROR;
	}
	return false;
}
// end of Reverse Buddy functions

bool Asura_GameSpy_GP::RevokeBuddyByProfileID(int iBuddyProfileID)
{
	GPResult eResult = gpRevokeBuddyAuthorization( &s_xConnection, iBuddyProfileID );
	Asura_Assert(eResult == GP_NO_ERROR, "gpGetReverseBuddies failed");

	return eResult == GP_NO_ERROR;
}


// Buddy messaging functions
void Asura_GameSpy_GP::SendBuddyMessage( u_int uBuddyIndex, const char* szMessage )
{
	Asura_Assert(uBuddyIndex < s_uNumBuddies, "invalid buddy index");
	Asura_Assert(strlen(szMessage) < uASURA_GAMESPY_MAX_BUDDY_MESSAGE_LENGTH, "message too long");
	if (uBuddyIndex < s_uNumBuddies)
	{
#ifdef ASURA_ASSERT
		const GPResult eResult =
#endif
            gpSendBuddyMessage( &s_xConnection, s_axBuddyList[uBuddyIndex].m_iProfileID, szMessage );
		Asura_Assert(eResult == GP_NO_ERROR, "gpSendBuddyMessage failed");
	}
}

const Asura_UnicodeChar* Asura_GameSpy_GP::GetBuddyMessageSender( u_int uIndex )
{
	Asura_Assert(uIndex < s_uNumBuddyMessages, "invalid message index");
	if (uIndex < s_uNumBuddyMessages)
	{
		return s_axBuddyMessages[uIndex].m_awcSenderNick;
	}
	return L"";
}

const int Asura_GameSpy_GP::GetBuddyMessageSenderID( u_int uIndex )
{
	Asura_Assert(uIndex < s_uNumBuddyMessages, "invalid message index");
	if (uIndex < s_uNumBuddyMessages)
	{
		return s_axBuddyMessages[uIndex].m_iSenderProfileID;
	}
	return 0;
}

const char* Asura_GameSpy_GP::GetBuddyMessage( u_int uIndex )
{
	Asura_Assert(uIndex < s_uNumBuddyMessages, "invalid message index");
	if (uIndex < s_uNumBuddyMessages)
	{
		return s_axBuddyMessages[uIndex].m_acMessage;
	}
	return "";
}

u_int Asura_GameSpy_GP::GetBuddyMessageTimeSent( u_int uIndex )
{
	Asura_Assert(uIndex < s_uNumBuddyMessages, "invalid message index");
	if (uIndex < s_uNumBuddyMessages)
	{
		return s_axBuddyMessages[uIndex].m_uTimeStamp;
	}
	return 0;
}

u_int Asura_GameSpy_GP::GetNumArchivedBuddyMessages( void )
{
	return Asura_GSGP_Internal::NumberOfArchivedBuddyMessages();
}

const Asura_UnicodeChar* Asura_GameSpy_GP::GetArchivedBuddyMessageSender( u_int uArchiveIndex )
{
	if (uArchiveIndex < Asura_GSGP_Internal::NumberOfArchivedBuddyMessages())
	{
		return Asura_GSGP_Internal::GetArchivedBuddyMessage(uArchiveIndex)->m_awcSenderNick;
	}
	else
	{
		return L"";
	}
}

const int Asura_GameSpy_GP::GetArchivedBuddyMessageSenderID( u_int uIndex )
{
	if (uIndex < Asura_GSGP_Internal::NumberOfArchivedBuddyMessages())
	{
		return Asura_GSGP_Internal::GetArchivedBuddyMessage(uIndex)->m_iSenderProfileID;
	}
	else
	{
		return 0;
	}
}

const char* Asura_GameSpy_GP::GetArchivedBuddyMessage( u_int uIndex )
{
	if (uIndex < Asura_GSGP_Internal::NumberOfArchivedBuddyMessages())
	{
		return Asura_GSGP_Internal::GetArchivedBuddyMessage(uIndex)->m_acMessage;
	}
	else
	{
		return "";
	}
}

u_int Asura_GameSpy_GP::GetArchivedBuddyMessageTimeSent( u_int uIndex )
{
	if (uIndex < Asura_GSGP_Internal::NumberOfArchivedBuddyMessages())
	{
		return Asura_GSGP_Internal::GetArchivedBuddyMessage(uIndex)->m_uTimeStamp;
	}
	else
	{
		return 0;
	}
}


#endif // #if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
// end of Buddy messaging functions

#ifdef ASURA_USE_GUI_MENU_SYSTEM
void Asura_GameSpy_GP::InitMenuLoginDetails( const Asura_UnicodeChar* wszNick, const Asura_UnicodeChar* wszEmail, const Asura_UnicodeChar* wszPass )
{
	Asura_Assert( wszNick && wszEmail && wszPass, "must specify valid strings");
#if ASURA_GAMESPY_GP_ALLOW_NONUNIQUENICKS
	Asura_Assert( wcslen(wszNick) < GP_UNIQUENICK_LEN, "nick too long for unique nick");
#endif
	Asura_PrintfToBfr(s_pwcMenuNick, L"%s", GP_NICK_LEN, true, wszNick);
	Asura_PrintfToBfr(s_pwcMenuEmail, L"%s", GP_EMAIL_LEN, true, wszEmail);
	Asura_PrintfToBfr(s_pwcMenuPass, L"%s", GP_PASSWORD_LEN, true, wszPass);
}

bool Asura_GameSpy_GP::ConnectProfileFromMenus(bool bHelp)
{
	if (bHelp)
	{
		return true;
	}
	Asura_Assert(IsInitialised(), "can't connect - not initialised");
	if (!IsInitialised())
	{
		return false;
	}
	// reset error code
	s_eErrorCode = GP_GENERAL;
	return ASURA_GSGP_OK == ConnectProfile( s_pwcMenuNick, s_pwcMenuEmail, s_pwcMenuPass );
}

bool Asura_GameSpy_GP::CreateProfileFromMenus(bool bHelp)
{
	if (bHelp)
	{
		return true;
	}
	Asura_Assert(IsInitialised(), "can't create profile - not initialised");
	if (!IsInitialised())
	{
		return false;
	}
	// reset error code
	s_eErrorCode = GP_GENERAL;
	return ASURA_GSGP_OK == CreateProfile( s_pwcMenuNick, s_pwcMenuEmail, s_pwcMenuPass );
}

bool Asura_GameSpy_GP::RequestNickSuggestionsFromMenus(bool bHelp)
{
	if (bHelp)
	{
		return true;
	}
	Asura_Assert(IsInitialised(), "can't create profile - not initialised");
	if (!IsInitialised())
	{
		return false;
	}
	// reset error code
	s_eErrorCode = GP_GENERAL;
	return ASURA_GSGP_OK == RequestSuggestNickname( s_pwcMenuNick );
}

bool Asura_GameSpy_GP::GetNickSuggestionFromMenus(u_int uIndex, bool bHelp, bool bInterrogate)
{
	if (bHelp || bInterrogate)
	{
		return true;
	}
	else
	{
		if ( static_cast<int>(uIndex) < s_iNumSuggestions)
		{
			Asura_CommandConsole::Printf(s_awszSuggestedNicknames[uIndex]);
		}
		else
		{
			return false;
		}
	}
	return true;
}

bool Asura_GameSpy_GP::UseNickSuggestionFromMenus(u_int uIndex, bool bHelp, bool bInterrogate)
{
	if (bHelp || bInterrogate)
	{
		return true;
	}
	else
	{
		if ( static_cast<int>(uIndex) < s_iNumSuggestions )
		{
			Asura_PrintfToBfr(s_pwcMenuNick, L"%s", GP_NICK_LEN, true, s_awszSuggestedNicknames[uIndex]);
		}
		else
		{
			Asura_Assert(false, "invalid nick suggestion index");
			return false;
		}
	}
	return true;
}
#endif // #ifdef ASURA_USE_GUI_MENU_SYSTEM

// internal class functions
void Asura_GSGP_Internal::InitPersistentStorage()
{
#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	s_bReadyForArchiving = false;
	s_bRequestedSavedData = false;

	Asura_Assert(!s_axArchivedBuddyMessages, "already initialised");
	s_axArchivedBuddyMessages = new Asura_GameSpy_GP_BuddyMessage[uASURA_GAMESPY_MAX_BUDDY_MESSAGES_IN_ARCHIVE];
	s_uNumArchivedBuddyMessages = 0;
#endif
}

void Asura_GSGP_Internal::ShutdownPersistentStorage()
{
#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	delete[] s_axArchivedBuddyMessages;
	s_axArchivedBuddyMessages = NULL;
	s_uNumArchivedBuddyMessages = 0;
#endif
	Asura_GameSpy_PersistentStorage::Shutdown();
}

void Asura_GSGP_Internal::ConnectToPersistentStorage()
{
#ifdef ASURA_ASSERT
	const bool bOK =
#endif
        Asura_GameSpy_PersistentStorage::Initialise();
	// if this fails, buddy messages may be lost...
	Asura_Assert(bOK, "failed to init persistent storage");
}

void Asura_GSGP_Internal::DisconnectFromPersistentStorage()
{
#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	s_bReadyForArchiving = false;
	s_bRequestedSavedData = false;
	s_uNumArchivedBuddyMessages = 0;
	Asura_GameSpy_PersistentStorage::Shutdown();
#endif
}

void Asura_GSGP_Internal::UpdatePersistentStorage()
{
#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
	if (!s_bRequestedSavedData)
	{
		if (Asura_GameSpy_PersistentStorage::IsIdle())
		{
#ifdef ASURA_ASSERT
			const bool bOK =
#endif
                Asura_GameSpy_PersistentStorage::GetDataBlock( iASURA_GSGP_BUDDYMESSAGE_INDEX, Asura_GSGP_Internal::RestoreBuddyMessagesCallback, ASURA_GSPS_DATATYPE_PRIVATE_READWRITE );
			Asura_Assert(bOK, "failed to request buddy message data");
			s_bRequestedSavedData = true;
		}
	}
#endif

	bool bPSOK = Asura_GameSpy_PersistentStorage::Update();
	if (bPSOK)
	{
		if (Asura_GameSpy_PersistentStorage::GetState() == ASURA_GSPS_STATE_AUTH_FAILED)
		{
			Asura_Assert(false, "PS auth failed");
			bPSOK = false;
			Asura_GameSpy_PersistentStorage::Shutdown();
		}
	}
	if ( (!bPSOK) && dbg_bGPReconnectsToPS)
	{
		Asura_GameSpy_PersistentStorage::Initialise();
	}
}

#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
void Asura_GSGP_Internal::ArchiveMessage(const Asura_GameSpy_GP_BuddyMessage& xMessage)
{
	if (!s_bReadyForArchiving)
	{
		return;
	}

	u_int uNewIndex;
	if (s_uNumArchivedBuddyMessages < uASURA_GAMESPY_MAX_BUDDY_MESSAGES_IN_ARCHIVE)
	{
		// can fit archive and current set in archive.
		uNewIndex = s_uNumArchivedBuddyMessages;
		++s_uNumArchivedBuddyMessages;
		if (s_uNumArchivedBuddyMessages > uASURA_GAMESPY_MAX_BUDDY_MESSAGES_IN_ARCHIVE)
		{
			s_uNumArchivedBuddyMessages = uASURA_GAMESPY_MAX_BUDDY_MESSAGES_IN_ARCHIVE;
		}
	}
	else
	{
		u_int uOldestIndex = GetOldestIndex();
		uNewIndex = uOldestIndex;
	}

	// copy message
	memcpy(&s_axArchivedBuddyMessages[uNewIndex], &xMessage, sizeof(Asura_GameSpy_GP_BuddyMessage));

	SaveBuddyMessageArchive();
}

Asura_GameSpy_GP_BuddyMessage* Asura_GSGP_Internal::GetArchivedBuddyMessage(u_int uMessageIndex)
{
	Asura_Assert(uMessageIndex < s_uNumArchivedBuddyMessages,"Buddy message index out of range");
	if (uMessageIndex < s_uNumArchivedBuddyMessages)
	{
		return &s_axArchivedBuddyMessages[uMessageIndex];
	}
	else
	{
		return 0;
	}
}

void Asura_GSGP_Internal::SaveBuddyMessageArchive()
{
	// now we've incorporated the existing messages, save the archive back
	u_int uDataSize = GetArchivedMessagesChunkDataSize();
	char* const pcBuffer = new char[uDataSize];
	if (pcBuffer)
	{
		CreateChunkData( pcBuffer );

		Asura_GameSpy_PersistentStorage::SetDataBlock( iASURA_GSGP_BUDDYMESSAGE_INDEX, pcBuffer, uDataSize, SaveBuddyMessagesCallback, ASURA_GSPS_DATATYPE_PRIVATE_READWRITE );
		delete[] pcBuffer;
	}
}

u_int Asura_GSGP_Internal::GetOldestIndex()
{
	// need to find oldest archive message.
	u_int uOldestDate = UINT_MAX;
	u_int uOldestIndex = 0;
	for (u_int u = 0; u < s_uNumArchivedBuddyMessages; ++u)
	{
		if (s_axArchivedBuddyMessages[u].m_uTimeStamp < uOldestDate)
		{
			uOldestDate = s_axArchivedBuddyMessages[u].m_uTimeStamp;
			uOldestIndex = u;
		}
	}
	return uOldestIndex;
}

bool Asura_GSGP_Internal::FillArchivedMessagesFromChunkData( const char* pcData, u_int uDataLength )
{
	const char* pcCurData = pcData;
	// first check version
	u_int uVersion;
	ASURA_CHUNK_EXTRACT( pcCurData, uVersion, u_int );
	if (uVersion != uASURA_GSGP_MESSAGE_ARCHIVE_CHUNKVERSION)
	{
		return false;
	}

	// get num messages
	u_int uNumMsgsInArchive;
	ASURA_CHUNK_EXTRACT( pcCurData, uNumMsgsInArchive, u_int );
	Asura_Assert(uNumMsgsInArchive <= uASURA_GAMESPY_MAX_BUDDY_MESSAGES_IN_ARCHIVE, "too many msgs in archive");
	if (uNumMsgsInArchive > uASURA_GAMESPY_MAX_BUDDY_MESSAGES_IN_ARCHIVE)
	{
		Asura_Assert(false, "invalid data");
		return false;
	}

	Asura_GameSpy_GP_BuddyMessage* pxCurMsg = s_axArchivedBuddyMessages;
	for (u_int u =0 ; u < uNumMsgsInArchive; ++u)
	{
		// profileID and timestamp
		ASURA_CHUNK_EXTRACT( pcCurData, pxCurMsg->m_iSenderProfileID, int );
		ASURA_CHUNK_EXTRACT( pcCurData, pxCurMsg->m_uTimeStamp, u_int );
		// length of name string
		u_int uTempLength;
		ASURA_CHUNK_EXTRACT( pcCurData, uTempLength, u_int );
		if (uTempLength > uASURA_GAMESPY_NICKNAME_MAX_LENGTH - 1)
		{
			Asura_Assert(false, "invalid data");
			return false;
		}
		// extract name
		char acTempBuffer[uASURA_GAMESPY_NICKNAME_MAX_LENGTH];
		memcpy( acTempBuffer, pcCurData, uTempLength );
		pcCurData += uTempLength;
		acTempBuffer[uTempLength] = 0;
		// copy name to stored nick
		Asura_PrintfToBfr( pxCurMsg->m_awcSenderNick, L"%S", uASURA_GAMESPY_NICKNAME_MAX_LENGTH, true, acTempBuffer );

		// extract msg
		ASURA_CHUNK_EXTRACT( pcCurData, uTempLength, u_int );
		if (uTempLength > uASURA_GAMESPY_MAX_BUDDY_MESSAGE_LENGTH - 1)
		{
			Asura_Assert(false, "invalid data");
			return false;
		}
		memcpy( pxCurMsg->m_acMessage, pcCurData, uTempLength );
		pcCurData += uTempLength;
		pxCurMsg->m_acMessage[uTempLength] = 0;

		// on to the next one.
		++pxCurMsg;
	}

	s_uNumArchivedBuddyMessages = uNumMsgsInArchive;
	Asura_Assert( pcCurData == pcData + uDataLength, "read incorrect amount of data");

	return true;
}

u_int Asura_GSGP_Internal::GetArchivedMessagesChunkDataSize()
{
	u_int uSize = 0;

	// version
	// u_int uVersion = uASURA_GSGP_MESSAGE_ARCHIVE_CHUNKVERSION;
	ASURA_CHUNK_SIZE( uSize, uASURA_GSGP_MESSAGE_ARCHIVE_CHUNKVERSION, u_int );
	
	// num messages
	ASURA_CHUNK_SIZE( uSize, s_uNumArchivedBuddyMessages, u_int );

	Asura_GameSpy_GP_BuddyMessage* pxCurMsg = s_axArchivedBuddyMessages;
	for (u_int u =0 ; u < s_uNumArchivedBuddyMessages; ++u)
	{
		// profileID and timestamp
		ASURA_CHUNK_SIZE( uSize, pxCurMsg->m_iSenderProfileID, int );
		ASURA_CHUNK_SIZE( uSize, pxCurMsg->m_uTimeStamp, u_int );
		// length of name string
		u_int uTempLength = wcslen( pxCurMsg->m_awcSenderNick );
		ASURA_CHUNK_SIZE( uSize, uTempLength, u_int );
		// name string
		uSize += uTempLength;
		// length of msg
		uTempLength = strlen(pxCurMsg->m_acMessage);
		ASURA_CHUNK_SIZE( uSize, uTempLength, u_int );
		// msg
		uSize += uTempLength;

		// on to the next one.
		++pxCurMsg;
	}

	return uSize;
}

void Asura_GSGP_Internal::CreateChunkData( char* pcCurData )
{
	// version
	u_int uVersion = uASURA_GSGP_MESSAGE_ARCHIVE_CHUNKVERSION;
	ASURA_CHUNK_FILL( pcCurData, uVersion, u_int );
	ASURA_CHUNK_FILL( pcCurData, s_uNumArchivedBuddyMessages, u_int );

	Asura_GameSpy_GP_BuddyMessage* pxCurMsg = s_axArchivedBuddyMessages;
	for (u_int u =0 ; u < s_uNumArchivedBuddyMessages; ++u)
	{
		// profileID and timestamp
		ASURA_CHUNK_FILL( pcCurData, pxCurMsg->m_iSenderProfileID, int );
		ASURA_CHUNK_FILL( pcCurData, pxCurMsg->m_uTimeStamp, u_int );
		// length of name string
		u_int uTempLength = wcslen( pxCurMsg->m_awcSenderNick );
		ASURA_CHUNK_FILL( pcCurData, uTempLength, u_int );
		// name string
		char acTempBuffer[uASURA_GAMESPY_NICKNAME_MAX_LENGTH];
		Asura_PrintfToBfr( acTempBuffer, "%S", uASURA_GAMESPY_NICKNAME_MAX_LENGTH, true, pxCurMsg->m_awcSenderNick );
		memcpy( pcCurData, acTempBuffer, uTempLength );
		pcCurData += uTempLength;
		// length of msg
		uTempLength = strlen(pxCurMsg->m_acMessage);
		ASURA_CHUNK_FILL( pcCurData, uTempLength, u_int );
		//  msg
		memcpy( pcCurData, pxCurMsg->m_acMessage, uTempLength );
		pcCurData += uTempLength;

		// on to the next one.
		++pxCurMsg;
	}
}

void Asura_GSGP_Internal::RestoreBuddyMessagesCallback( bool bSuccess, const char* pcData, u_int uDataLength, void* pInstance )
{
	// note that any errors mean that stored messages are lost, new ones will still be saved though.
	Asura_Assert(bSuccess, "failed to get archived buddy messages");
	if (bSuccess) 
	{
		// need to fill out our archive with these messages and any that we already have.
		// first populate the archive with these messages
		FillArchivedMessagesFromChunkData( pcData, uDataLength );
	}

	s_bReadyForArchiving = true;

	u_int uNumExistingMessages = Asura_GameSpy_GP::GetNumBuddyMessages();
	if (!uNumExistingMessages)
	{
		// nothing new to save
		return;
	}

	// we're likely to get here if a buddy message was waiting for us when we logged in so that
	// it arrived before the old messages were downloaded.
	u_int uFirstNewArchiveIndex;// archive index of first existing message that will be overwritten

	if (s_uNumArchivedBuddyMessages < uASURA_GAMESPY_MAX_BUDDY_MESSAGES_IN_ARCHIVE)
	{
		// can fit archive and current set in archive.
		uFirstNewArchiveIndex = s_uNumArchivedBuddyMessages;
		s_uNumArchivedBuddyMessages += uNumExistingMessages;
		if (s_uNumArchivedBuddyMessages > uASURA_GAMESPY_MAX_BUDDY_MESSAGES_IN_ARCHIVE)
		{
			s_uNumArchivedBuddyMessages = uASURA_GAMESPY_MAX_BUDDY_MESSAGES_IN_ARCHIVE;
		}
	}
	else
	{
		u_int uOldestIndex = GetOldestIndex();
		uFirstNewArchiveIndex = uOldestIndex;
	}

	for (u_int u = 0; u < uNumExistingMessages; ++u)
	{
		u_int uIndex = (uFirstNewArchiveIndex + u) % uASURA_GAMESPY_MAX_BUDDY_MESSAGES_IN_ARCHIVE;
		// copy message
		memcpy(&s_axArchivedBuddyMessages[uIndex], &Asura_GameSpy_GP::s_axBuddyMessages[u], sizeof(Asura_GameSpy_GP_BuddyMessage));
	}

	SaveBuddyMessageArchive();
}

void Asura_GSGP_Internal::SaveBuddyMessagesCallback( bool bSuccess, void* pInstance )
{
	Asura_Assert( bSuccess, "saving message archive failed");
}
#endif // #if ASURA_GAMESPY_GP_SUPPORT_BUDDIES


// L O C A L   F U N C T I O N   D E F I N I T I O N S ///////////////////////
#if ASURA_GAMESPY_GP_SUPPORT_BUDDIES
static void FillIPAndPortFromLocation(const char* szLocation, u_int& uIP, u_short& usPort)
{
	char acGameName[32];
	Asura_PrintfToBfr(acGameName, "%s://", 32, true, Asura_GameSpy::GetGameName());
	const char* szAddress = strstr(szLocation, acGameName);
	if (!szAddress)
	{
		uIP = 0;
		usPort = 0;
		return;
	}

	// skip it
	szAddress += strlen(acGameName);

	// is there a port?
	const char* szPort = strchr(szAddress, ':');
	if (!szPort)
	{
		uIP = 0;
		usPort = 0;
		return;
	}

	// create address string
	char acAddress[32];
	memcpy(acAddress, szAddress, 32);
	// terminate temp address string
	if ((szPort - szAddress) < 32)
	{
		acAddress[szPort - szAddress] = 0;
	}
	else
	{
		acAddress[31] = 0;
	}

	uIP = inet_addr( acAddress );
	usPort = static_cast<u_short>(atoi(szPort + 1));
}
#endif // #if ASURA_GAMESPY_GP_SUPPORT_BUDDIES

#endif //#ifdef ASURA_SUPPORT_GAMESPY
/****************/
/* End of file. */
/****************/
