//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

// hud_servers.cpp
#include "hud.h"
#include "cl_util.h"
#include "hud_servers_priv.h"
#include "hud_servers.h"
#include "net_api.h"
#include <string.h>
#ifdef _WIN32
#include "winsani_in.h"
#include <winsock.h>
#include "winsani_out.h"
#else
#define __cdecl
#include <arpa/inet.h>
#endif
static int	context_id;

// Default master server address in case we can't read any from valvecomm.lst file
#define VALVE_MASTER_ADDRESS "half-life.east.won.net"
#define PORT_MASTER	 27010
#define PORT_SERVER  27015

// File where we really should look for master servers
#define MASTER_PARSE_FILE "valvecomm.lst"

#define MAX_QUERIES 20

#define NET_API gEngfuncs.pNetAPI

static CHudServers *g_pServers = NULL;

/*
===================
ListResponse

Callback from engine
===================
*/
void NET_CALLBACK ListResponse( struct net_response_s *response )
{
	if ( g_pServers )
	{
		g_pServers->ListResponse( response );
	}
}

/*
===================
ServerResponse

Callback from engine
===================
*/
void NET_CALLBACK ServerResponse( struct net_response_s *response )
{
	if ( g_pServers )
	{
		g_pServers->ServerResponse( response );
	}
}

/*
===================
PingResponse

Callback from engine
===================
*/
void NET_CALLBACK PingResponse( struct net_response_s *response )
{
	if ( g_pServers )
	{
		g_pServers->PingResponse( response );
	}
}

/*
===================
RulesResponse

Callback from engine
===================
*/
void NET_CALLBACK RulesResponse( struct net_response_s *response )
{
	if ( g_pServers )
	{
		g_pServers->RulesResponse( response );
	}
}
/*
===================
PlayersResponse

Callback from engine
===================
*/
void NET_CALLBACK PlayersResponse( struct net_response_s *response )
{
	if ( g_pServers )
	{
		g_pServers->PlayersResponse( response );
	}
}
/*
===================
ListResponse

===================
*/
void CHudServers::ListResponse( struct net_response_s *response )
{
	request_t *list;
	request_t *p;
	int c = 0;

	if ( !( response->error == NET_SUCCESS ) )
		return;

	if ( response->type != NETAPI_REQUEST_SERVERLIST )
		return;

	if ( response->response )
	{
		list = ( request_t * ) response->response;
		while ( list )
		{
			c++;

			//if ( c < 40 )
			{
				// Copy from parsed stuff
				p = new request_t;
				p->context = -1;
				p->remote_address = list->remote_address;
				p->next = m_pServerList;
				m_pServerList = p;
			}

			// Move on
			list = list->next;
		}
	}

	gEngfuncs.Con_Printf( "got list\n" );

	m_nQuerying = 1;
	m_nActiveQueries = 0;
}

/*
===================
ServerResponse

===================
*/
void CHudServers::ServerResponse( struct net_response_s *response )
{
	char *szresponse;
	request_t *p;
	server_t *browser;
	int	len;
	char sz[ 32 ];

	// Remove from active list
	p = FindRequest( response->context, m_pActiveList );
	if ( p )
	{
		RemoveServerFromList( &m_pActiveList, p );
		m_nActiveQueries--;
	}
	
	if ( response->error != NET_SUCCESS )
		return;
	
	switch ( response->type )
	{
	case NETAPI_REQUEST_DETAILS:
		if ( response->response )
		{
			szresponse = (char *)response->response;
			len = strlen( szresponse ) + 100 + 1;
			sprintf( sz, "%i", (int)( 1000.0 * response->ping ) );

			browser = new server_t;
			browser->remote_address = response->remote_address;
			browser->info = new char[ len ];
			browser->ping = (int)( 1000.0 * response->ping );
			strcpy( browser->info, szresponse );

			NET_API->SetValueForKey( browser->info, "address", gEngfuncs.pNetAPI->AdrToString( &response->remote_address ), len );
			NET_API->SetValueForKey( browser->info, "ping", sz, len );
			
			AddServer( &m_pServers, browser );
		}
		break;
	default:
		break;
	}
}

/*
===================
PingResponse

===================
*/
void CHudServers::PingResponse( struct net_response_s *response )
{
	char sz[ 32 ];

	if ( response->error != NET_SUCCESS )
		return;

	switch ( response->type )
	{
	case NETAPI_REQUEST_PING:
		sprintf( sz, "%.2f", 1000.0 * response->ping );

		gEngfuncs.Con_Printf( "ping == %s\n", sz );
		break;
	default:
		break;
	}
}

/*
===================
RulesResponse

===================
*/
void CHudServers::RulesResponse( struct net_response_s *response )
{
	char *szresponse;
	
	if ( response->error != NET_SUCCESS )
		return;
	
	switch ( response->type )
	{
	case NETAPI_REQUEST_RULES:
		if ( response->response )
		{
			szresponse = (char *)response->response;

			gEngfuncs.Con_Printf( "rules %s\n", szresponse );
		}
		break;
	default:
		break;
	}
}

/*
===================
PlayersResponse

===================
*/
void CHudServers::PlayersResponse( struct net_response_s *response )
{
	char *szresponse;

	if ( response->error != NET_SUCCESS )
		return;
	
	switch ( response->type )
	{
	case NETAPI_REQUEST_PLAYERS:
		if ( response->response )
		{
			szresponse = (char *)response->response;

			gEngfuncs.Con_Printf( "players %s\n", szresponse );
		}
		break;
	default:
		break;
	}
}

/*
===================
CompareServers

Return 1 if p1 is "less than" p2, 0 otherwise
===================
*/
int	CHudServers::CompareServers( server_t *p1, server_t *p2 )
{
	const char *n1, *n2;

	if ( p1->ping < p2->ping )
		return 1;

	if ( p1->ping == p2->ping )
	{
		// Pings equal, sort by second key:  hostname
		if ( p1->info && p2->info )
		{
			n1 = NET_API->ValueForKey( p1->info, "hostname" );
			n2 = NET_API->ValueForKey( p2->info, "hostname" );

			if ( n1 && n2 )
			{
				if ( stricmp( n1, n2 ) < 0 )
					return 1;
			}
		}
	}

	return 0;
}

/*
===================
AddServer

===================
*/
void CHudServers::AddServer( server_t **ppList, server_t *p )
{
server_t *list;

	if ( !ppList || ! p )
		return;

	m_nServerCount++;

	// What sort key?  Ping?
	list = *ppList;

	// Head of list?
	if ( !list )
	{
		p->next = NULL;
		*ppList = p;
		return;
	}

	// Put on head of list
	if ( CompareServers( p, list ) )
	{
		p->next = *ppList;
		*ppList = p;
	}
	else
	{
		while ( list->next )
		{
			// Insert before list next
			if ( CompareServers( p, list->next ) )
			{
				p->next = list->next->next;
				list->next = p;
				return;
			}

			list = list->next;
		}

		// Just add at end
		p->next = NULL;
		list->next = p;
	}
}

/*
===================
Think

===================
*/
void CHudServers::Think( double time )
{
	m_fElapsed += time;

	if ( !m_nRequesting )
		return;

	if ( !m_nQuerying )
		return;

	QueryThink();

	if ( ServerListSize() > 0 )
		return;

	m_dStarted = 0.0;
	m_nRequesting = 0;
	m_nDone = 0;
	m_nQuerying = 0;
	m_nActiveQueries = 0;
}

/*
===================
QueryThink

===================
*/
void CHudServers::QueryThink( void )
{
	request_t *p;

	if ( !m_nRequesting || m_nDone )
		return;

	if ( !m_nQuerying )
		return;

	if ( m_nActiveQueries > MAX_QUERIES )
		return;

	// Nothing left
	if ( !m_pServerList )
		return;

	while ( 1 )
	{
		p = m_pServerList;

		// No more in list?
		if ( !p )
			break;

		// Move to next
		m_pServerList = m_pServerList->next;

		// Setup context_id
		p->context = context_id;

		// Start up query on this one
		NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, 0, 2.0, &p->remote_address, ::ServerResponse );

		// Increment active list
		m_nActiveQueries++;

		// Add to active list
		p->next = m_pActiveList;
		m_pActiveList = p;

		// Too many active?
		if ( m_nActiveQueries > MAX_QUERIES )
			break;
	}
}

/*
==================
ServerListSize

# of servers in active query and in pending to be queried lists
==================
*/
int	CHudServers::ServerListSize( void )
{
	int c = 0;
	request_t *p;

	p = m_pServerList;
	while ( p )
	{
		c++;
		p = p->next;
	}

	p = m_pActiveList;
	while ( p )
	{
		c++;
		p = p->next;
	}

	return c;
}

/*
===================
FindRequest

Look up a request by context id
===================
*/
CHudServers::request_t *CHudServers::FindRequest( int context, request_t *pList )
{
	request_t *p;
	p = pList;
	while ( p )
	{
		if ( context == p->context )
			return p;

		p = p->next;
	}
	return NULL;
}

/*
===================
RemoveServerFromList

Remote, but don't delete, item from *ppList
===================
*/
void CHudServers::RemoveServerFromList( request_t **ppList, request_t *item )
{
	request_t *p, *n;
	request_t *newlist = NULL;

	if ( !ppList )
		return;

	p = *ppList;
	while ( p )
	{
		n = p->next;
		if ( p != item )
		{
			p->next = newlist;
			newlist = p;
		}
		p = n;
	}
	*ppList = newlist;
}

/*
===================
ClearRequestList

===================
*/
void CHudServers::ClearRequestList( request_t **ppList )
{
	request_t *p, *n;

	if ( !ppList )
		return;

	p = *ppList;
	while ( p )
	{
		n = p->next;
		delete p;
		p = n;
	}
	*ppList = NULL;
}

/*
===================
ClearServerList

===================
*/
void CHudServers::ClearServerList( server_t **ppList )
{
	server_t *p, *n;

	if ( !ppList )
		return;

	p = *ppList;
	while ( p )
	{
		n = p->next;
		delete[] p->info;
		delete p;
		p = n;
	}
	*ppList = NULL;
}

int CompareField( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname, int iSortOrder )
{
	const char *sz1, *sz2;
	float fv1, fv2;

	sz1 = NET_API->ValueForKey( p1->info, fieldname );
	sz2 = NET_API->ValueForKey( p2->info, fieldname );

	fv1 = atof( sz1 );
	fv2 = atof( sz2 );

	if ( fv1 && fv2 )
	{
		if ( fv1 > fv2 )
			return iSortOrder;
		else if ( fv1 < fv2 )
			return -iSortOrder;
		else
			return 0;
	}

	// String compare
	return stricmp( sz1, sz2 );
}

int ServerListCompareFunc( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname )
{
	if (!p1 || !p2)  // No meaningful comparison
		return 0;  

	int iSortOrder = 1;

	int retval = 0;

	retval = CompareField( p1, p2, fieldname, iSortOrder );

	return retval;
}

static char g_fieldname[ 256 ];
int __cdecl FnServerCompare(const void *elem1, const void *elem2 )
{
	CHudServers::server_t *list1, *list2;

	list1 = *(CHudServers::server_t **)elem1;
	list2 = *(CHudServers::server_t **)elem2;

	return ServerListCompareFunc( list1, list2, g_fieldname );
}

void CHudServers::SortServers( const char *fieldname )
{
	server_t *p;
	// Create a list
	if ( !m_pServers )
		return;

	strcpy( g_fieldname, fieldname );

	int i;
	int c = 0;

	p = m_pServers;
	while ( p )
	{
		c++;
		p = p->next;
	}

	server_t **pSortArray;
	
	pSortArray = new server_t *[ c ];
	memset( pSortArray, 0, c  * sizeof( server_t * ) );

	// Now copy the list into the pSortArray:
	p = m_pServers;
	i = 0;
	while ( p )
	{
		pSortArray[ i++ ] = p;
		p = p->next;
	}

	// Now do that actual sorting.
	size_t nCount = c;
	size_t nSize  = sizeof( server_t * );

	qsort(
		pSortArray,
		(size_t)nCount,
		(size_t)nSize,
		FnServerCompare
	);

	// Now rebuild the list.
	m_pServers = pSortArray[0];
	for ( i = 0; i < c - 1; i++ )
	{
		pSortArray[ i ]->next = pSortArray[ i + 1 ];
	}
	pSortArray[ c - 1 ]->next = NULL;

	// Clean Up.
	delete[] pSortArray;
}

/*
===================
GetServer

Return particular server
===================
*/
CHudServers::server_t *CHudServers::GetServer( int server )
{
	int c = 0;
	server_t *p;

	p = m_pServers;
	while ( p )
	{
		if ( c == server )
			return p;

		c++;
		p = p->next;
	}
	return NULL;
}

/*
===================
GetServerInfo

Return info ( key/value ) string for particular server
===================
*/
char *CHudServers::GetServerInfo( int server )
{
	server_t *p = GetServer( server );
	if ( p )
	{
		return p->info;
	}
	return NULL;
}

/*
===================
CancelRequest

Kill all pending requests in engine
===================
*/
void CHudServers::CancelRequest( void )
{
	m_nRequesting = 0;
	m_nQuerying   = 0;
	m_nDone		  = 1;

	NET_API->CancelAllRequests();
}

/*
==================
LoadMasterAddresses

Loads the master server addresses from file and into the passed in array
==================
*/
int CHudServers::LoadMasterAddresses( int maxservers, int *count, netadr_t *padr )
{
	int			i;
	char		szMaster[ 256 ];
	char		szMasterFile[256];
	char		*pbuffer = NULL;
	char		*pstart = NULL ;
	netadr_t	adr;
	char		szAdr[64];
	int			nPort;
	int			nCount = 0;
	bool		bIgnore;
	int			nDefaultPort;

	// Assume default master and master file
	strcpy( szMaster, VALVE_MASTER_ADDRESS );    // IP:PORT string
	strcpy( szMasterFile, MASTER_PARSE_FILE );

	// See if there is a command line override
	i = gEngfuncs.CheckParm( "-comm", &pstart );
	if ( i && pstart )
	{
		strcpy (szMasterFile, pstart );
	}

	// Read them in from proper file
	pbuffer = (char *)gEngfuncs.COM_LoadFile( szMasterFile, 5, NULL ); // Use malloc
	if ( !pbuffer )
	{
		goto finish_master;
	}

	pstart = pbuffer;

	while ( nCount < maxservers )
	{
		pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken );

		if ( strlen(m_szToken) <= 0)
			break;

		bIgnore = true;

		if ( !stricmp( m_szToken, "Master" ) )
		{
			nDefaultPort = PORT_MASTER;
			bIgnore = FALSE;
		}

		// Now parse all addresses between { }
		pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken );
		if ( strlen(m_szToken) <= 0 )
			break;

		if ( stricmp ( m_szToken, "{" ) )
			break;

		// Parse addresses until we get to "}"
		while ( nCount < maxservers )
		{
			char base[256];

			// Now parse all addresses between { }
			pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken );
			
			if (strlen(m_szToken) <= 0)
				break;

			if ( !stricmp ( m_szToken, "}" ) )
				break;
			
			sprintf( base, "%s", m_szToken );
				
			pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken );
			
			if (strlen(m_szToken) <= 0)
				break;

			if ( stricmp( m_szToken, ":" ) )
				break;

			pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken );
			
			if (strlen(m_szToken) <= 0)
				break;

			nPort = atoi ( m_szToken );
			if ( !nPort )
				nPort = nDefaultPort;

			sprintf( szAdr, "%s:%i", base, nPort );

			// Can we resolve it any better
			if ( !NET_API->StringToAdr( szAdr, &adr ) )
				bIgnore = true;

			if ( !bIgnore )
			{
				padr[ nCount++ ] = adr;
			}
		}
	}

finish_master:
	if ( !nCount )
	{
		sprintf( szMaster, VALVE_MASTER_ADDRESS );    // IP:PORT string

		// Convert to netadr_t
		if ( NET_API->StringToAdr ( szMaster, &adr ) )
		{
		
			padr[ nCount++ ] = adr;
		}
	}

	*count = nCount;

	if ( pbuffer )
	{
		gEngfuncs.COM_FreeFile( pbuffer );
	}

	return ( nCount > 0 ) ? 1 : 0;
}

/*
===================
RequestList

Request list of game servers from master
===================
*/
void CHudServers::RequestList( void )
{
	m_nRequesting	= 1;
	m_nDone			= 0;
	m_dStarted		= m_fElapsed;

	int	count = 0;
	netadr_t adr;

	if ( !LoadMasterAddresses( 1, &count, &adr ) )
	{
		gEngfuncs.Con_DPrintf( "SendRequest:  Unable to read master server addresses\n" );
		return;
	}

	ClearRequestList( &m_pActiveList );
	ClearRequestList( &m_pServerList );
	ClearServerList( &m_pServers );

	m_nServerCount = 0;

	// Make sure networking system has started.
	NET_API->InitNetworking();

	// Kill off left overs if any
	NET_API->CancelAllRequests();

	// Request Server List from master
	NET_API->SendRequest( context_id++, NETAPI_REQUEST_SERVERLIST, 0, 5.0, &adr, ::ListResponse );
}

void CHudServers::RequestBroadcastList( int clearpending )
{
	m_nRequesting	= 1;
	m_nDone			= 0;
	m_dStarted		= m_fElapsed;

	netadr_t		adr;
	memset( &adr, 0, sizeof( adr ) );

	if ( clearpending )
	{
		ClearRequestList( &m_pActiveList );
		ClearRequestList( &m_pServerList );
		ClearServerList( &m_pServers );

		m_nServerCount = 0;
	}

	// Make sure to byte swap server if necessary ( using "host" to "net" conversion
	adr.port = htons( PORT_SERVER );

	// Make sure networking system has started.
	NET_API->InitNetworking();

	if ( clearpending )
	{
		// Kill off left overs if any
		NET_API->CancelAllRequests();
	}

	adr.type = NA_BROADCAST;

	// Request Servers from LAN via IP
	NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse );

	adr.type = NA_BROADCAST_IPX;

	// Request Servers from LAN via IPX ( if supported )
	NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse );
}

void CHudServers::ServerPing( int server )
{
	server_t *p;
	
	p = GetServer( server );
	if ( !p )
		return;

	// Make sure networking system has started.
	NET_API->InitNetworking();

	// Request Server List from master
	NET_API->SendRequest( context_id++, NETAPI_REQUEST_PING, 0, 5.0, &p->remote_address, ::PingResponse );
}

void CHudServers::ServerRules( int server )
{
	server_t *p;
	
	p = GetServer( server );
	if ( !p )
		return;

	// Make sure networking system has started.
	NET_API->InitNetworking();

	// Request Server List from master
	NET_API->SendRequest( context_id++, NETAPI_REQUEST_RULES, 0, 5.0, &p->remote_address, ::RulesResponse );
}

void CHudServers::ServerPlayers( int server )
{
	server_t *p;
	
	p = GetServer( server );
	if ( !p )
		return;

	// Make sure networking system has started.
	NET_API->InitNetworking();

	// Request Server List from master
	NET_API->SendRequest( context_id++, NETAPI_REQUEST_PLAYERS, 0, 5.0, &p->remote_address, ::PlayersResponse );
}

int CHudServers::isQuerying()
{
	return m_nRequesting ? 1 : 0;
}


/*
===================
GetServerCount

Return number of servers in browser list
===================
*/
int CHudServers::GetServerCount( void )
{
	return m_nServerCount;
}

/*
===================
CHudServers

===================
*/
CHudServers::CHudServers( void )
{
	m_nRequesting		= 0;
	m_dStarted			= 0.0;
	m_nDone				= 0;
	m_pServerList		= NULL;
	m_pServers			= NULL;
	m_pActiveList		= NULL;
	m_nQuerying			= 0;
	m_nActiveQueries	= 0;
	
	m_fElapsed			= 0.0;


	m_pPingRequest		= NULL;
	m_pRulesRequest		= NULL;
	m_pPlayersRequest	= NULL;
}

/*
===================
~CHudServers

===================
*/
CHudServers::~CHudServers( void )
{
	ClearRequestList( &m_pActiveList );
	ClearRequestList( &m_pServerList );
	ClearServerList( &m_pServers );

	m_nServerCount = 0;

	if ( m_pPingRequest )
	{
		delete m_pPingRequest;
		m_pPingRequest = NULL;

	}
	
	if ( m_pRulesRequest )
	{
		delete m_pRulesRequest;
		m_pRulesRequest = NULL;
	}

	if ( m_pPlayersRequest )
	{
		delete m_pPlayersRequest;
		m_pPlayersRequest = NULL;
	}
}

///////////////////////////////
//
// PUBLIC APIs
//
///////////////////////////////

/*
===================
ServersGetCount

===================
*/
int	ServersGetCount( void )
{
	if ( g_pServers )
	{
		return g_pServers->GetServerCount();
	}
	return 0;
}

int	ServersIsQuerying( void )
{
	if ( g_pServers )
	{
		return g_pServers->isQuerying();
	}
	return 0;
}

/*
===================
ServersGetInfo

===================
*/
const char *ServersGetInfo( int server )
{
	if ( g_pServers )
	{
		return g_pServers->GetServerInfo( server );
	}
	
	return NULL;
}

void SortServers( const char *fieldname )
{
	if ( g_pServers )
	{
		g_pServers->SortServers( fieldname );
	}
}

/*
===================
ServersShutdown

===================
*/
void ServersShutdown( void )
{
	if ( g_pServers )
	{
		delete g_pServers;
		g_pServers = NULL;
	}
}

/*
===================
ServersInit

===================
*/
void ServersInit( void )
{
	// Kill any previous instance
	ServersShutdown();
	
	g_pServers = new CHudServers();
}

/*
===================
ServersThink

===================
*/
void ServersThink( double time )
{
	if ( g_pServers )
	{
		g_pServers->Think( time );
	}
}

/*
===================
ServersCancel

===================
*/
void ServersCancel( void )
{
	if ( g_pServers )
	{
		g_pServers->CancelRequest();
	}
}

// Requests
/*
===================
ServersList

===================
*/
void ServersList( void )
{
	if ( g_pServers )
	{
		g_pServers->RequestList();
	}
}

void BroadcastServersList( int clearpending )
{
	if ( g_pServers )
	{
		g_pServers->RequestBroadcastList( clearpending );
	}
}

void ServerPing( int server )
{
	if ( g_pServers )
	{
		g_pServers->ServerPing( server );
	}
}

void ServerRules( int server )
{
	if ( g_pServers )
	{
		g_pServers->ServerRules( server );
	}
}

void ServerPlayers( int server )
{
	if ( g_pServers )
	{
		g_pServers->ServerPlayers( server );
	}
}
