// HTNetClient.cpp: implementation of the HTNetClient class.
//
//////////////////////////////////////////////////////////////////////

#include "global.h"

// This GUID allows DirectPlay to find other instances of the same game on
// the network.  So it must be unique for every game, and the same for 
// every instance of that game.  // {EDE9493E-6AC8-4f15-8D01-8B163200B966}
GUID g_guidApp = { 0xede9493e, 0x6ac8, 0x4f15, { 0x8d, 0x1, 0x8b, 0x16, 0x32, 0x0, 0xb9, 0x66 } };

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

HTNetClient* dummyPtr = NULL;

HRESULT WINAPI HTNetClient::DPMessageForwarder( PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer )
{
	return dummyPtr->DPMessageHandler( pvUserContext, dwMessageId, pMsgBuffer );
}
/*
HRESULT WINAPI HTNetClient::DPLobbyMessageHandler( PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer )
{
	return dummyPtr->CDPLobbyMessageHandler( pvUserContext, dwMessageId, pMsgBuffer );
}
*/

#include <string.h>
HTNetClient::HTNetClient()
{
	InitializeCriticalSection( &m_csHostList );
	InitializeCriticalSection( &m_csPlayerData );
	initialized = false;
	m_bSearchingForSessions = false;
	dummyPtr = this;

}

// These should be part of the parent application
//IDirectPlay8Client*     g_pDPClient                   = NULL;    // DirectPlay peer object
//IDirectPlay8LobbiedApplication* g_pLobbiedApp         = NULL;    // DirectPlay lobbied app 
//BOOL                    g_bWasLobbyLaunched           = FALSE;   // TRUE if lobby launched

#include "objbase.h"

bool HTNetClient::Init()
{
	//DPNHANDLE hLobbyLaunchedConnection = NULL;
	HRESULT hr;
	if (!m_pDPClient)
	{
	
		// Create IDirectPlay8Client
		if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8Client, NULL, 
														CLSCTX_INPROC_SERVER,
														IID_IDirectPlay8Client, 
														(LPVOID*) &m_pDPClient ) ) )
		{
			DXTRACE_ERR( TEXT("CoCreateInstance"), hr );
			return false;
		}
		
		/*
		// Create IDirectPlay8LobbiedApplication
		if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8LobbiedApplication, NULL, 
														CLSCTX_INPROC_SERVER,
														IID_IDirectPlay8LobbiedApplication, 
														(LPVOID*) &g_pLobbiedApp ) ) )
		{
			DXTRACE_ERR( TEXT("CoCreateInstance"), hr );
			return false;
		}
		*/

	}

	if (!initialized)
	{
		// Init IDirectPlay8Client
		if( FAILED( hr = m_pDPClient->Initialize( NULL, DPMessageForwarder, 0 ) ) )
		{
			DXTRACE_ERR( TEXT("Initialize"), hr );
			return false;
		}
		
		/*
		// Init IDirectPlay8LobbiedApplication.  Before this Initialize() returns 
		// a DPL_MSGID_CONNECT msg may come in to the DirectPlayLobbyMessageHandler 
		// so be prepared ahead of time.
		if( FAILED( hr = g_pLobbiedApp->Initialize( NULL, DPLobbyMessageHandler, 
																&hLobbyLaunchedConnection, 0 ) ) )
		{
			DXTRACE_ERR( TEXT("Initialize"), hr );
			return false;
		}
		*/
		
		// IDirectPlay8LobbiedApplication::Initialize returns a handle to a connnection
		// if we have been lobby launced.  Initialize is guanteeded to return after 
		// the DPL_MSGID_CONNECT msg has been processed.  So unless a we are expected 
		// multiple lobby connections, we do not need to remember the lobby connection
		// handle since it will be recorded upon the DPL_MSGID_CONNECT msg.
		//g_bWasLobbyLaunched = ( hLobbyLaunchedConnection != NULL );

		//m_pDPClient         = g_pDPClient;
		//m_pLobbiedApp       = g_pLobbiedApp;
		m_bHaveConnectionSettingsFromLobby = FALSE;
		m_hLobbyClient      = NULL;
		m_bSearchingForSessions = false;

		m_hostList.clear();
		

		int i;
		for (i=0; i < MAX_PLAYERS; i++)
		{
			m_PlayerSlotValid[i] = i==LOCAL_PLAYER_IDX;		// Set slot 0 to valid -- ALWAYS!
			m_PlayerSlotUpdated[i] = true;
		}

		m_ConnectionState = HTCS_NOT_CONNECTED;


		// Query for the enum host timeout for this SP
		DPN_SP_CAPS dpspCaps;
		ZeroMemory( &dpspCaps, sizeof(DPN_SP_CAPS) );
		dpspCaps.dwSize = sizeof(DPN_SP_CAPS);
		m_pDPClient->GetSPCaps( &CLSID_DP8SP_TCPIP, &dpspCaps, 0 );

		// Set the host expire time to around 3 times
		// length of the dwDefaultEnumRetryInterval
		m_dwHostExpireInterval = dpspCaps.dwDefaultEnumRetryInterval * 3;
	}

	initialized = true;
	return initialized;
}

HTNetClient::~HTNetClient()
{
	StopSearchingForServers();

	/*
	if (m_pDPClient)
	{
		//m_pDPClient->Close(0);
		SAFE_RELEASE(m_pDPClient);
	}
	*/
	/*
	if (m_pLobbiedApp)
	{
		m_pLobbiedApp->Close(0);
		SAFE_RELEASE(m_pLobbiedApp);
	}
	*/

	DeleteCriticalSection( &m_csHostList );
	DeleteCriticalSection( &m_csPlayerData );
}

int HTNetClient::GetNumFoundServers()
{
	return m_hostList.size();
}
char* HTNetClient::GetFoundServerName(int servernum)
{
	static char str[MAX_PATH] = "";
	static int numCalls = 0;

	EnterCriticalSection( &m_csHostList );
	if (servernum >= 0 && servernum < m_hostList.size())
	{
		if (m_hostList[servernum].pAppDesc->pwszSessionName)
			DXUtil_ConvertWideStringToGeneric( str, m_hostList[servernum].pAppDesc->pwszSessionName );
		else
			sprintf(str, "UNNAMED SERVER");

		if (m_hostList[servernum].pAppDesc->guidInstance == game.netserver.GetInstanceGuid())
			strcat(str, "(Local Server)");

		if (numCalls++ < 1)
		{
			printf("\r Local  GUID: %8X-%4hX-%4hX-%4hX-%4hX%4hX%4hX\n",game.netserver.GetInstanceGuid());
			printf("\r Server GUID: %8X-%4hX-%4hX-%4hX-%4hX%4hX%4hX\n",m_hostList[servernum].pAppDesc->guidInstance);
		}
	}
	else
		strncpy(str,"ERR: Server # out of range",MAX_PATH);
	LeaveCriticalSection( &m_csHostList );

	return str;
}
int HTNetClient::GetFoundServerPing(int servernum)
{
	int ping;

	EnterCriticalSection( &m_csHostList );
	if (servernum >= 0 && servernum < m_hostList.size())
		ping = m_hostList[servernum].dwRTTms;
	else
		ping = 9999;
	LeaveCriticalSection( &m_csHostList );

	return ping;
}
int HTNetClient::GetFoundServerNumPlayers(int servernum)
{
	int nPlayers;
	EnterCriticalSection( &m_csHostList );
	if (servernum >= 0 && servernum < m_hostList.size())
		nPlayers = m_hostList[servernum].pAppDesc->dwCurrentPlayers-1;		// -1 to ignore host player
	else
		nPlayers = 0;
	LeaveCriticalSection( &m_csHostList );
	return nPlayers;
}
int HTNetClient::GetFoundServerMaxPlayres(int servernum)
{
	int nPlayers;
	EnterCriticalSection( &m_csHostList );
	if (servernum >= 0 && servernum < m_hostList.size())
		nPlayers = m_hostList[servernum].pAppDesc->dwMaxPlayers-1;		// -1 to ignore host player
	else
		nPlayers = 0;
	LeaveCriticalSection( &m_csHostList );
	return nPlayers;
}


void HTNetClient::SetSearching(bool search)
{
	if (search && !m_bSearchingForSessions)
		StartSearchingForServers();
	else if (!search && m_bSearchingForSessions)
		StopSearchingForServers();
}

void HTNetClient::SetPlayerName(char* newname)
{	strcpy(playerName,newname);	}
void HTNetClient::SetServerName(char* newname)
{	strcpy(serverName,newname);	}

void HTNetClient::StopSearchingForServers()
{
	// Until the CancelAsyncOperation returns, it is possible
	// to still receive host enumerations
	if( m_hEnumAsyncOp )
		m_pDPClient->CancelAsyncOperation( m_hEnumAsyncOp, 0 );
		// Cancel all outstanding requests
		//m_pDPClient->CancelAsyncOperation( NULL, 0 );
	
	m_bSearchingForSessions = false;
}


HRESULT WINAPI HTNetClient::StartSearchingForServers()
{
   HRESULT hr;
	
	DPN_APPLICATION_DESC   dpnAppDesc;
	IDirectPlay8Address*   pDP8AddressHost  = NULL;
	IDirectPlay8Address*   pDP8AddressLocal = NULL;
	WCHAR*                 wszHostName      = NULL;

	m_bHostListChanged = false;
	
	// Create the local device address object
	if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8Address, NULL, 
		CLSCTX_ALL, IID_IDirectPlay8Address,
		(LPVOID*) &pDP8AddressLocal ) ) )
	{
		DXTRACE_ERR( TEXT("CoCreateInstance"), hr );
		goto LCleanup;
	}
	
	// Set IP service provider
	if( FAILED( hr = pDP8AddressLocal->SetSP( &CLSID_DP8SP_TCPIP ) ) )
	{
		DXTRACE_ERR( TEXT("SetSP"), hr );
		goto LCleanup;
	}
	
	// Create the remote host address object
	if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8Address, NULL, 
		CLSCTX_ALL, IID_IDirectPlay8Address,
		(LPVOID*) &pDP8AddressHost ) ) )
	{
		DXTRACE_ERR( TEXT("CoCreateInstance"), hr );
		goto LCleanup;
	}
	
	// Set IP service provider
	if( FAILED( hr = pDP8AddressHost->SetSP( &CLSID_DP8SP_TCPIP ) ) )
	{
		DXTRACE_ERR( TEXT("SetSP"), hr );
		goto LCleanup;
	}
	
	// Set the remote host name (if provided)
	if( serverName[0] != 0 )
	{
		DWORD dwPort = 0;
		// Parse out port if it exists (expected form of "xxx.xxx.xxx.xxx:port")
		char* strPort = strchr( serverName, ':' );
		if( NULL != strPort )
		{
			// Chop off :port from end of strIPAddress
			TCHAR* strEndOfIP = strPort;
			*strEndOfIP = 0;
			
			// Get port number from strPort
			strPort++;
			dwPort = atoi( strPort );
		}
		wszHostName = new WCHAR[strlen(serverName)+1];
		DXUtil_ConvertGenericStringToWide( wszHostName, serverName );
		
		hr = pDP8AddressHost->AddComponent( DPNA_KEY_HOSTNAME, wszHostName, 
			(wcslen(wszHostName)+1)*sizeof(WCHAR), 
			DPNA_DATATYPE_STRING );
		if( FAILED(hr) )
		{
			DXTRACE_ERR( TEXT("AddComponent"), hr );
			goto LCleanup;
		}
		// If a port was specified in the IP string, then add it.
		// Games will typically hard code the port so the user need not know it
		if( dwPort != 0 )
		{
			hr = pDP8AddressHost->AddComponent( DPNA_KEY_PORT, 
				&dwPort, sizeof(dwPort),
				DPNA_DATATYPE_DWORD );
			if( FAILED(hr) )
			{
				DXTRACE_ERR( TEXT("AddComponent"), hr );
				goto LCleanup;
			}
		}
	}

	// Initialize app description function
	ZeroMemory( &dpnAppDesc, sizeof( DPN_APPLICATION_DESC ) );
	dpnAppDesc.dwSize = sizeof( DPN_APPLICATION_DESC );
	dpnAppDesc.guidApplication = g_guidApp;
	
	// Enumerate all StressMazeApp hosts running on IP service providers
	hr = m_pDPClient->EnumHosts( &dpnAppDesc, pDP8AddressHost, 
		pDP8AddressLocal, NULL, 
		0, INFINITE, 0, INFINITE, NULL, 
		&m_hEnumAsyncOp, 0 );
	
	if( FAILED(hr) )
	{
		if( hr != DPNERR_INVALIDDEVICEADDRESS && 
			hr != DPNERR_ADDRESSING ) // This will be returned if the ip address is is invalid. 
			DXTRACE_ERR( TEXT("EnumHosts"), hr );
		goto LCleanup;
	}

	m_bSearchingForSessions = true;

LCleanup:
	SAFE_RELEASE( pDP8AddressHost);
	SAFE_RELEASE( pDP8AddressLocal );
	SAFE_DELETE( wszHostName );
	
	if( hr == DPNERR_PENDING )
		hr = DPN_OK;

	return hr;
	
}

void HTNetClient::RemoveOldServerEntries()
{
	DWORD dwCurrentTime = timeGetTime();
	
	// Need to enter critical section to update list:
	EnterCriticalSection( &m_csHostList );

	// Now iterate through and remove entries that are too old
	HostListType::iterator it = m_hostList.begin();
	//for (it = m_hostList.begin(); it != m_hostList.end(); it++)
	while (it != m_hostList.end())
	{
		if (it->dwLastPollTime < dwCurrentTime - m_dwHostExpireInterval)
		{
			it = m_hostList.erase(it);
			m_bHostListChanged = true;
		}
		else
			it++;
	}

	// Leave CS
	LeaveCriticalSection( &m_csHostList );
}

//-----------------------------------------------------------------------------
// Name: MessageHandler
// Desc: Handler for DirectPlay messages.  This function is called by
//       the DirectPlay message handler pool of threads, so be careful of thread
//       synchronization problems with shared memory
//-----------------------------------------------------------------------------
HRESULT WINAPI HTNetClient::DPMessageHandler(PVOID pvUserContext,
														 DWORD dwMessageId,
														 PVOID pMsgBuffer )
{
	// Try not to stay in this message handler for too long, otherwise
	// there will be a backlog of data.  The best solution is to
	// queue data as it comes in, and then handle it on other threads.
	
	// This function is called by the DirectPlay message handler pool of
	// threads, so be careful of thread synchronization problems with shared memory
	
	switch(dwMessageId)
	{
		//--------------------------------------------------------
		//--- CLIENT MESSAGES:
		//--------------------------------------------------------
		case DPN_MSGID_TERMINATE_SESSION:
			PDPNMSG_TERMINATE_SESSION pTerminateSessionMsg;
			pTerminateSessionMsg = (PDPNMSG_TERMINATE_SESSION) pMsgBuffer;

			m_ConnectionState = HTCS_CONNECTION_TERMINATED;
			break;

		case DPN_MSGID_RECEIVE:
		{
			PDPNMSG_RECEIVE pReceiveMsg = (PDPNMSG_RECEIVE) pMsgBuffer;
			HTMSG_GENERIC* pMsg = (HTMSG_GENERIC*) pReceiveMsg->pReceiveData;
			switch (pMsg->dwType)
			{
				// Setup the game -- specify the map & stuff
				case HT_MSGID_SETUP_GAME:
				{
					HTMSG_SETUP_GAME* pSetupGameMsg = (HTMSG_SETUP_GAME*)pMsg;
					// Copy into local buffer for later processing
					m_recvSetupMsg = *pSetupGameMsg;
					// Indicate that it's ready and waiting for processing
					m_SetupMsgReady = true;
					printf("\n*SETUP GAME*\n");
					break;
				}
				// Set the local DPNID -- local is ALWAYS #0
				case HT_MSGID_SET_ID:
				{
					HTMSG_SET_ID* pSetIDMsg = (HTMSG_SET_ID*)pMsg;
					m_PlayerInfo[LOCAL_PLAYER_IDX].pid = pSetIDMsg->pid;
					printf("\n*SETTING ID*\n");
					break;
				}

				// Add a new player to the player list
				case HT_MSGID_CREATE_PLAYER:
				{
					HTMSG_CREATE_PLAYER* pCreatePlayerMsg = (HTMSG_CREATE_PLAYER*)pMsg;
					CreatePlayer(pCreatePlayerMsg);
					printf("\n*CREATING PLAYER*\n");
					break;
				}
				case HT_MSGID_DESTROY_PLAYER:
				{
					HTMSG_DESTROY_PLAYER* pDestroyPlayerMsg = (HTMSG_DESTROY_PLAYER*)pMsg;
					DestroyPlayer(pDestroyPlayerMsg);
					break;
				}
				case HT_MSGID_UPDATE_PLAYER:
				{
					HTMSG_UPDATE_PLAYER* pUpdatePlayerMsg = (HTMSG_UPDATE_PLAYER*)pMsg;
					NetUpdatePlayer(pUpdatePlayerMsg);
					break;
				}
				case HT_MSGID_CHAT:
				{
					HTMSG_CHAT_FULL* pChatMsg = (HTMSG_CHAT_FULL*)pMsg;
					PushChatMessage(pChatMsg);
					break;
				}
				case HT_MSGID_NOTIFY_KILL:
				case HT_MSGID_NOTIFY_SUICIDE:
				case HT_MSGID_NOTIFY_BURNED:
				{
					HTMSG_NOTIFY_KILL* pKillMsg = (HTMSG_NOTIFY_KILL*)pMsg;
					HTMSG_GAME_EVENT event;
					event.eventID = pMsg->dwType;
					event.player1 = pKillMsg->pid1;
					event.player2 = pKillMsg->pid2;
					PushEvent( &event );
					break;
				}
				default:
					printf("HTNetClient Receive packet: Unknown packet type %ld\n",(long)(pMsg->dwType));
					break;
			}
			break;
		}
		//--------------------------------------------------------
		//--- CONNECTION WIZARD MESSAGES:
		//--------------------------------------------------------
		case DPN_MSGID_ENUM_HOSTS_RESPONSE:
		{
			PDPNMSG_ENUM_HOSTS_RESPONSE pEnumHostsResponseMsg;
			pEnumHostsResponseMsg = (PDPNMSG_ENUM_HOSTS_RESPONSE)pMsgBuffer;
			
			// Take note of the host response
			NoteDPlayEnumResponse( pEnumHostsResponseMsg );
			break;
		}

		// An async op completed -- e.g. searching for hosts
		case DPN_MSGID_ASYNC_OP_COMPLETE:
		{
			PDPNMSG_ASYNC_OP_COMPLETE pAsyncOpCompleteMsg;
			pAsyncOpCompleteMsg = (PDPNMSG_ASYNC_OP_COMPLETE)pMsgBuffer;
			
			if( pAsyncOpCompleteMsg->hAsyncOp == m_hEnumAsyncOp )
			{
				// Keep the old list around even though it's out of date --
				// After a few seconds, the timed RemoveOldEntries should clear
				// stuff out. =)
				//m_hostList.clear();

				m_hEnumAsyncOp = NULL;
				m_bSearchingForSessions = false;
			}
			break;
		}
			
		case DPN_MSGID_CONNECT_COMPLETE:
		{
			PDPNMSG_CONNECT_COMPLETE pConnectCompleteMsg;
			pConnectCompleteMsg = (PDPNMSG_CONNECT_COMPLETE)pMsgBuffer;
			
			// Set m_hrConnectComplete, then set an event letting
			// everyone know that the DPN_MSGID_CONNECT_COMPLETE msg
			// has been handled

			if (FAILED(pConnectCompleteMsg->hResultCode))
				m_ConnectionState = HTCS_CONNECTION_FAILED;
			else
				m_ConnectionState = HTCS_CONNECTED;

			break;
		}
	}
	
	return S_OK;
}

void HTNetClient::PushChatMessage(HTMSG_CHAT_FULL* msg)
{
	EnterCriticalSection(&m_csPlayerData);
	m_recvMessages.push(*msg);
	LeaveCriticalSection(&m_csPlayerData);
}
int HTNetClient::GetNumQueuedChatMessages()
{
	int numQueued;
	EnterCriticalSection(&m_csPlayerData);
	numQueued = m_recvMessages.size();
	LeaveCriticalSection(&m_csPlayerData);
	return numQueued;
}
HTMSG_CHAT_FULL HTNetClient::GetNextChatMessage()
{
	HTMSG_CHAT_FULL nextMsg;
	EnterCriticalSection(&m_csPlayerData);
	nextMsg = m_recvMessages.top();
	m_recvMessages.pop();
	LeaveCriticalSection(&m_csPlayerData);
	return nextMsg;
}

void HTNetClient::PushEvent(HTMSG_GAME_EVENT* event)
{
	EnterCriticalSection(&m_csPlayerData);
	m_recvEvents.push(*event);
	LeaveCriticalSection(&m_csPlayerData);
}
int HTNetClient::GetNumQueuedEvents()
{
	int numQueued;
	EnterCriticalSection(&m_csPlayerData);
	numQueued = m_recvEvents.size();
	LeaveCriticalSection(&m_csPlayerData);
	return numQueued;
}
HTMSG_GAME_EVENT HTNetClient::GetNextEvent()
{
	HTMSG_GAME_EVENT nextEvent;
	EnterCriticalSection(&m_csPlayerData);
	nextEvent = m_recvEvents.top();
	m_recvEvents.pop();
	LeaveCriticalSection(&m_csPlayerData);
	return nextEvent;
}


int HTNetClient::GetPlayerIdx(DPNID pid)
{
	int i,idx=0;
	EnterCriticalSection(&m_csPlayerData);
	for (i=0; i < MAX_PLAYERS; i++)
		if (m_PlayerInfo[i].pid == pid)
			idx = i;
	LeaveCriticalSection(&m_csPlayerData);
	return idx;
}


//-----------------------------------------------------------------------------
// Name: LobbyMessageHandler
// Desc: Handler for DirectPlay messages.  This function is called by
//       the DirectPlay lobby message handler pool of threads, so be careful of thread
//       synchronization problems with shared memory
//-----------------------------------------------------------------------------
/*
HRESULT WINAPI HTNetClient::CDPLobbyMessageHandler(PVOID pvUserContext,
												  DWORD dwMessageId,
												  PVOID pMsgBuffer )
{
	HRESULT hr = S_OK;

	printf("\rgot lobby message   \n");
	// We'll ignore this for now

	switch(dwMessageId)
	{
		case DPL_MSGID_CONNECT:
		{
			// This message will be processed when a lobby connection has been
			// established. If you were lobby launched then
			// IDirectPlay8LobbiedApplication::Initialize()
			// waits until this message has been processed before returning, so
			// take care not to deadlock by making calls that need to be handled by
			// the thread who called Initialize().  The same is true for WaitForConnection()
			
			PDPL_MESSAGE_CONNECT pConnectMsg;
			pConnectMsg = (PDPL_MESSAGE_CONNECT)pMsgBuffer;
			PDPL_CONNECTION_SETTINGS pSettings = pConnectMsg->pdplConnectionSettings;
			
			m_hLobbyClient = pConnectMsg->hConnectId;

			
			//if( FAILED( hr = m_pDPClient->RegisterLobby( m_hLobbyClient, m_pLobbiedApp,
				//DPNLOBBY_REGISTER ) ) )
				//return DXTRACE_ERR( TEXT("RegisterLobby"), hr );
			
			if( pSettings == NULL )
			{
				// There aren't connection settings from the lobby
				m_bHaveConnectionSettingsFromLobby = FALSE;
			}
			else
			{
				// Record the player name if found
				if( pSettings->pwszPlayerName != NULL )
				{
					char strPlayerName[MAX_PATH];
					DXUtil_ConvertWideStringToGeneric( strPlayerName, pSettings->pwszPlayerName );
					strncpy( playerName, strPlayerName , MAX_PATH );
				}
				else
				{
					//strcpy( m_strLocalPlayerName, TEXT("Unknown player name") );
					strcpy( playerName, TEXT("HoverDude") );
				}
				
				m_bHaveConnectionSettingsFromLobby = TRUE;
			}
			
			// Tell everyone we have a lobby connection now
			//--SetEvent( m_hLobbyConnectionEvent );
			break;
		}
	}
	
	return S_OK;
}
*/

void HTNetClient::NoteDPlayEnumResponse( PDPNMSG_ENUM_HOSTS_RESPONSE pEnumHostsResponseMsg )
{
	HRESULT hr = S_OK;
	
	// This function is called from the DirectPlay message handler so it could be
	// called simultaneously from multiple threads, so enter a critical section
	// to assure that it we don't get race conditions.  Locking the entire
	// function is crude, and could be more optimal but is effective for this
	// simple sample
	EnterCriticalSection( &m_csHostList );
	
	const DPN_APPLICATION_DESC* pResponseMsgAppDesc =
		pEnumHostsResponseMsg->pApplicationDescription;

	DPHostEnumInfo hostInfo;
	int i,foundHostIdx;
	bool foundHostInList;

	ZeroMemory( &hostInfo, sizeof(DPHostEnumInfo) );

	// Look for a matching session instance GUID.
	foundHostInList = false;
	for (i=0; i < m_hostList.size(); i++)
		if (m_hostList[i].pAppDesc->guidInstance == pResponseMsgAppDesc->guidInstance)
		{
			foundHostIdx = i;
			foundHostInList = true;
			hostInfo = m_hostList[i];
		}
	
	// Update the pDPHostEnum with new information
	char strName[MAX_PATH] = "";
	if( pResponseMsgAppDesc->pwszSessionName )
		DXUtil_ConvertWideStringToGeneric( strName, pResponseMsgAppDesc->pwszSessionName );
	
	// Cleanup any old enum
	if( hostInfo.pAppDesc )
	{
		SAFE_DELETE_ARRAY( hostInfo.pAppDesc->pwszSessionName );
		SAFE_DELETE_ARRAY( hostInfo.pAppDesc );
	}
	SAFE_RELEASE( hostInfo.pHostAddr );
	SAFE_RELEASE( hostInfo.pDeviceAddr );
	
	//
	// Duplicate pEnumHostsResponseMsg->pAddressSender in hostInfo.pHostAddr.
	// Duplicate pEnumHostsResponseMsg->pAddressDevice in hostInfo.pDeviceAddr.
	//
	if( FAILED( hr = pEnumHostsResponseMsg->pAddressSender->Duplicate( &hostInfo.pHostAddr ) ) )
	{
		DXTRACE_ERR( TEXT("Duplicate"), hr );
		goto LCleanup;
	}
	
	if( FAILED( hr = pEnumHostsResponseMsg->pAddressDevice->Duplicate( &hostInfo.pDeviceAddr ) ) )
	{
		DXTRACE_ERR( TEXT("Duplicate"), hr );
		goto LCleanup;
	}
	
	// Deep copy the DPN_APPLICATION_DESC from
	hostInfo.pAppDesc = new DPN_APPLICATION_DESC;
	ZeroMemory( hostInfo.pAppDesc, sizeof(DPN_APPLICATION_DESC) );
	memcpy( hostInfo.pAppDesc, pResponseMsgAppDesc, sizeof(DPN_APPLICATION_DESC) );
	if( pResponseMsgAppDesc->pwszSessionName )
	{
		hostInfo.pAppDesc->pwszSessionName = new WCHAR[ wcslen(pResponseMsgAppDesc->pwszSessionName)+1 ];
		wcscpy( hostInfo.pAppDesc->pwszSessionName,
			pResponseMsgAppDesc->pwszSessionName );
	}
	
	// Update the time this was done, so that we can expire this host
	// if it doesn't refresh w/in a certain amount of time
	hostInfo.dwLastPollTime = timeGetTime();
	
	// Check to see if the current number of players changed
	TCHAR szSessionTemp[MAX_PATH];
	if( pResponseMsgAppDesc->dwMaxPlayers > 0 )
	{
		wsprintf( szSessionTemp, TEXT("%s (%d/%d) (%dms)"), strName,
			pResponseMsgAppDesc->dwCurrentPlayers - 1,  // ignore the host player
			pResponseMsgAppDesc->dwMaxPlayers - 1,      // ignore the host player
			pEnumHostsResponseMsg->dwRoundTripLatencyMS );
	}
	else
	{
		wsprintf( szSessionTemp, TEXT("%s (%d) (%dms)"), strName,
			pResponseMsgAppDesc->dwCurrentPlayers - 1,  // ignore the host player
			pEnumHostsResponseMsg->dwRoundTripLatencyMS );
	}
	hostInfo.dwRTTms = pEnumHostsResponseMsg->dwRoundTripLatencyMS;
	
	// if this node was previously invalidated, or the session name is now
	// different the session list in the dialog needs to be updated
	if( ( hostInfo.bValid == FALSE ) ||
		( strcmp( hostInfo.szSession, szSessionTemp ) != 0 ) )
	{
		m_bHostListChanged = true;
	}
	strncpy( hostInfo.szSession, szSessionTemp , MAX_PATH );
	
	// This host is now valid
	hostInfo.bValid = TRUE;
	if (!foundHostInList)
		m_hostList.push_back(hostInfo);
	else
		m_hostList[foundHostIdx] = hostInfo;
	
LCleanup:
	LeaveCriticalSection( &m_csHostList );
	
	//return hr;
}

bool HTNetClient::UpdateServerList()
{
	RemoveOldServerEntries();

	bool changed = m_bHostListChanged;
	m_bHostListChanged = false;			// Reset the changed flag when the update func is called
	return changed;							// ... and return the value of it before clearing.
}

bool HTNetClient::StartConnectingToServer(int servernum)
{
	HRESULT hr;

	// Can't connect to a server unless you are already disconnected!
	if (m_ConnectionState != HTCS_NOT_CONNECTED)
	{
		printf("HTNetClient Error: Trying to connect when not in NOT_CONNECTED state (state=%d)\n",(int)m_ConnectionState);
		return false;
	}

	EnterCriticalSection(&m_csHostList);

	// Make sure the server still exists
	if (servernum < 0 || servernum >= m_hostList.size())
	{
		printf("HTNetClient Error: Invalid server number: %d\n",(int)servernum);
		LeaveCriticalSection(&m_csHostList);
		return false;
	}

	// Get the host info
	DPHostEnumInfo selectedHostInfo = m_hostList[servernum];

	// Update connection state
	m_ConnectionState = HTCS_CONNECTING;

	// Get peer info (??)
	WCHAR wszPeerName[MAX_PLAYER_NAME+1];
	DXUtil_ConvertGenericStringToWide( wszPeerName, playerName, MAX_PLAYER_NAME+1 );

	HTMSG_PLAYER_CONFIG pConfig;
	pConfig.nextRespawnModelNum = game.menu.workingPlayerTankModel;

	DPN_PLAYER_INFO dpPlayerInfo;
	ZeroMemory( &dpPlayerInfo, sizeof(DPN_PLAYER_INFO) );
	dpPlayerInfo.dwSize = sizeof(DPN_PLAYER_INFO);
	dpPlayerInfo.dwDataSize = sizeof(HTMSG_PLAYER_CONFIG);
	dpPlayerInfo.pvData = &pConfig;
	dpPlayerInfo.dwInfoFlags = DPNINFO_NAME | DPNINFO_DATA;
	dpPlayerInfo.pwszName = wszPeerName;

	// Set the peer info and force a synchronized operation
	hr = m_pDPClient->SetClientInfo( &dpPlayerInfo, NULL, NULL, DPNOP_SYNC );
	if (FAILED(hr))
	{
		printf("HTNetClient Error: Could not set client info\n");
		LeaveCriticalSection(&m_csHostList);
		return false;
	}

	// Connect to the server
	hr = m_pDPClient->Connect(
			selectedHostInfo.pAppDesc,			// Application description
			selectedHostInfo.pHostAddr,		// Address of the host of the session
			selectedHostInfo.pDeviceAddr,		// Address of the local device the enum responses came in on
			NULL, NULL,								// Reserved (security and credentials)
			NULL, 0,									// User connect data & size
			NULL, &m_hConnectAsyncOp,			// Async context & async handle (??)
			0											// Do NOT allow DX to pop up an extra window for extra address info.
				);										// Esp. since they probably won't ever see it if fullscreen

	LeaveCriticalSection(&m_csHostList);

	if (hr != E_PENDING && FAILED(hr))
	{
		printf("HTNetClient Error: Could not connect to server\n");
		return false;
	}

	return true;
}

void HTNetClient::CreatePlayer(HTMSG_CREATE_PLAYER *pPlayerInfo)
{
	int i;
	int newPlayerIdx = -1;

	HTC_PLAYER_LOCK();

	if (pPlayerInfo->pid == m_PlayerInfo[LOCAL_PLAYER_IDX].pid)
	{
		printf("\n*Adding local player*\n");
		newPlayerIdx = LOCAL_PLAYER_IDX;
	}
	else
	{
		for (i=0; i < MAX_PLAYERS; i++)
			if (!m_PlayerSlotValid[i])
			{
				newPlayerIdx = i;
				break;
			}
		if (newPlayerIdx == -1)
		{
			printf("HTNetClient Error: Could not create new player -- no free slots!\n");
			return;
		}
	}

	//m_pid[newPlayerIdx] = pPlayerInfo->pid;
	m_PlayerInfo[newPlayerIdx] = *pPlayerInfo;
	// Just to be sure
	strncpy(m_PlayerInfo[newPlayerIdx].playerName, pPlayerInfo->playerName, MAX_PLAYER_NAME);
	ZeroMemory(&m_recvPlayerStatus[newPlayerIdx],sizeof(HTMSG_UPDATE_PLAYER));
	
	m_PlayerSlotValid[newPlayerIdx] = true;
	m_PlayerSlotUpdated[newPlayerIdx] = true;
	game.world.tank[newPlayerIdx].Reset(&game.world);
	game.world.tank[newPlayerIdx].active = true;
	m_PlayerInfo[newPlayerIdx].modelnum = pPlayerInfo->modelnum;
	m_nActivePlayers++;

	char hudmesg[MAX_PATH];
	sprintf(hudmesg,"%s has joined the game",pPlayerInfo->playerName);
	game.hud.AddMessage(MESG_GAME_EVENT,0,hudmesg);

	HTC_PLAYER_UNLOCK();
}

void HTNetClient::DestroyPlayer(HTMSG_DESTROY_PLAYER *pPlayerInfo)
{
	int i, oldPlayerIdx=-1;

	HTC_PLAYER_LOCK();

	for (i=0; i < MAX_PLAYERS; i++)
		if (m_PlayerSlotValid[i])
			if (m_PlayerInfo[i].pid == pPlayerInfo->pid)
			{
				oldPlayerIdx = i;
				break;
			}

	if (oldPlayerIdx == -1)
	{
		printf("HTNetClient Error: Could not destroy player -- not found in valid list!\n");
		return;
	}

	char hudmesg[MAX_PATH];
	sprintf(hudmesg,"%s has left the game",m_PlayerInfo[oldPlayerIdx].playerName);
	game.hud.AddMessage(MESG_GAME_EVENT,0,hudmesg);

	game.world.tank[oldPlayerIdx].active = false;
	m_PlayerSlotValid[oldPlayerIdx] = false;
	m_nActivePlayers--;

	ZeroMemory( &m_recvPlayerStatus[oldPlayerIdx], sizeof(HTMSG_UPDATE_PLAYER) );
	ZeroMemory( &m_PlayerInfo[oldPlayerIdx], sizeof(HTMSG_CREATE_PLAYER) );

	HTC_PLAYER_UNLOCK();
}

void HTNetClient::NetUpdatePlayer(HTMSG_UPDATE_PLAYER *pPlayerInfo)
{
	int i, playerIdx=-1;

	HTC_PLAYER_LOCK();

	for (i=0; i < MAX_PLAYERS; i++)
		if (m_PlayerSlotValid[i])
			if (m_PlayerInfo[i].pid == pPlayerInfo->pid)
			{
				playerIdx = i;
				break;
			}

	if (playerIdx == -1)
	{
		printf("HTNetClient Error: Could not update player -- not found in valid list!\n");
		return;
	}

	m_recvPlayerStatus[playerIdx] = *pPlayerInfo;
	m_PlayerSlotUpdated[playerIdx] = true;

	HTC_PLAYER_UNLOCK();
}

bool HTNetClient::AbortNetworkConnect()
{
	HRESULT hr;
	bool ret;
	initialized = false;
	if (m_ConnectionState == HTCS_CONNECTING)
	{
		hr = m_pDPClient->CancelAsyncOperation(m_hConnectAsyncOp,DPNCANCEL_CONNECT);
		ret = (hr == S_OK);
		if (ret)
			m_ConnectionState = HTCS_NOT_CONNECTED;
		else
			printf("HTNetClient Error: Could not close async connection operation\n");
		return ret;
	}
	else if (m_ConnectionState == HTCS_CONNECTED)
	{
		//m_pLobbiedApp->Close(0);
		hr = m_pDPClient->Close(0);
		ret = (hr == S_OK);
		if (ret)
			m_ConnectionState = HTCS_NOT_CONNECTED;
		else
			printf("HTNetClient Error: Could not close CONNECTED connection\n");
		return ret;
	}
	// Other possibilities are FAILED and TERMINATED connections --
	// just call those NOT_CONNECTED now.
	else
	{
		m_ConnectionState = HTCS_NOT_CONNECTED;
		return true;
	}
	return false;
}

void HTNetClient::SendChatMesg(const char* msg)
{
	HTMSG_CHAT_FULL chatMsg;
	chatMsg.dwType = HT_MSGID_CHAT;
	chatMsg.pidFrom = m_PlayerInfo[LOCAL_PLAYER_IDX].pid;
	chatMsg.messageLength = min(strlen(msg),MAX_PATH);
	strncpy(chatMsg.msg,msg,MAX_PATH);

	DPN_BUFFER_DESC bufferDesc;
	bufferDesc.dwBufferSize = sizeof(HTMSG_CHAT_HEADER) + chatMsg.messageLength + 1;
	bufferDesc.pBufferData = (BYTE*) &chatMsg;

	DPNHANDLE hAsync;
	m_pDPClient->Send( &bufferDesc, 1, 0, NULL, &hAsync, 0 );
}

void HTNetClient::SendServerCommand(HT_COMMAND_ID_TYPE cmd)
{
	HTMSG_COMMAND_TO_SERVER cmdMsg;
	cmdMsg.dwType = HT_MSGID_COMMAND_TO_SERVER;
	cmdMsg.pid = m_PlayerInfo[LOCAL_PLAYER_IDX].pid;
	cmdMsg.commandID = cmd;

	DPN_BUFFER_DESC bufferDesc;
	bufferDesc.dwBufferSize = sizeof(HTMSG_COMMAND_TO_SERVER);
	bufferDesc.pBufferData = (BYTE*) &cmdMsg;

	DPNHANDLE hAsync;
	m_pDPClient->Send( &bufferDesc, 1, 0, NULL, &hAsync, 0 );
}


void HTNetClient::SendServerUpdate(InputStatus_type input)
{
	HTMSG_UPDATE_INPUT updateMsg;
	updateMsg.dwType = HT_MSGID_UPDATE_INPUT;
	updateMsg.pid    = m_PlayerInfo[LOCAL_PLAYER_IDX].pid;
	updateMsg.input.left = input.left;
	updateMsg.input.right = input.right;
	updateMsg.input.thrust = input.thrust;
	updateMsg.input.shield = input.shield;
	updateMsg.input.brake = input.brake;
	updateMsg.input.fire = input.fire;
	updateMsg.input.dir = game.world.tank[LOCAL_PLAYER_IDX].model.dir * 10;
	updateMsg.input.engine = game.world.tank[LOCAL_PLAYER_IDX].hoverStatus!=HOVER_OFF;

	DPN_BUFFER_DESC bufferDesc;
	bufferDesc.dwBufferSize = sizeof(HTMSG_UPDATE_INPUT);
	bufferDesc.pBufferData = (BYTE*) &updateMsg;

	DPNHANDLE hAsync;
	m_pDPClient->Send( &bufferDesc, 1, 0, NULL, &hAsync, 0 );
}

int HTNetClient::GetNextModelNum(TankObject* t)
{
	int i;
	for (i=0; i < MAX_PLAYERS; i++)
		if (t == &game.world.tank[i])
			break;
	if (i >= MAX_PLAYERS)
		return 0;
	int mnum;
	EnterCriticalSection(&m_csPlayerData);
	mnum=m_PlayerInfo[i].modelnum;
	LeaveCriticalSection(&m_csPlayerData);
	return mnum;
}

void HTNetClient::Disconnect()
{
	AbortNetworkConnect();
}
