/*
   Copyright (C) 2007 Will Franklin.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

   See the GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */

#include "matchmaker.h"
#include "../qcommon/md5.h"

mm_server_t *serverlist;

//================
// MM_SendMsgToGameServer
// Sends a command to the specified gameserver
//================
#define MM_CMD_KEY_SIZE 5
void MM_SendCmdToGameServer( mm_server_t *server, const char *cmd )
{
	netadr_t address;
	char key[MM_CMD_KEY_SIZE + 1], key2[32+1];
	char *cmd2;
	md5_byte_t digest[16];
	md5_state_t state;
	int i;
#ifdef LOCAL_TEST
	int filenum;
#else
	char query[MAX_QUERY_SIZE];
#endif

	if( !server || !cmd || !*cmd )
		return;

	// invalid ip?
	if( !NET_StringToAddress( server->ip, &address ) )
		return;

	// invalid ip?
	if( address.type != NA_IP )
		return;

	if( !address.port )
		address.port = BigShort( PORT_SERVER );

	// copy cmd, because the caller mightve used va(), which is gonna get reused lots
	// below, which will change the string
	cmd2 = MM_Malloc( strlen( cmd ) + 1 );
	Q_strncpyz( cmd2, cmd, strlen( cmd ) + 1 );

	// generate a random number between 0 and the biggest number the string can handle (filling all its chars)
	Q_snprintfz( key, sizeof( key ), "%0*u", MM_CMD_KEY_SIZE, ( unsigned int )brandom( 0, pow( 10, MM_CMD_KEY_SIZE ) - 1 ) );

	md5_init( &state );
	md5_append( &state, ( md5_byte_t * )key, MM_CMD_KEY_SIZE );
	md5_finish( &state, digest );

	// convert to hex
	memset( key2, 0, sizeof( key2 ) );
	for( i = 0; i < 16; i++ )
		Q_strncatz( key2, va( "%02x", digest[i] ), sizeof( key2 ) );

#ifdef LOCAL_TEST
	FS_FOpenFile( "mmcmd.txt", &filenum, FS_WRITE );
	FS_Printf( filenum, "%s\n", cmd2 );
	FS_FCloseFile( filenum );
#else
	// construct query
	Q_snprintfz( query, sizeof( query ), "INSERT INTO `%s` (`key`,`cmd`) VALUES ('%s','%s')", DBTABLE_SERVERCMDS, key2, cmd2 );
	// add to database
	if( DB_Query( db_handle, query ) != DB_SUCCESS )
		return;
#endif

	MM_Free( cmd2 );

	// tell gameserver
	Netchan_OutOfBandPrint( &socket_udp, &address, "cmd %s", key2 );

	// store this command
	Q_strncpyz( server->cmd.lastcmd, cmd, sizeof( server->cmd.lastcmd ) );
	server->cmd.senttime = realtime;
}

//================
// MM_GetServerFromList
// Return the server with the specified ip
//================
mm_server_t *MM_GetServerFromList( const char *ip )
{
	mm_server_t *server;

	for( server = serverlist; server; server = server->next )
	{
		if( !strcmp( server->ip, ip ) )
			return server;
	}

	return NULL;
}

//================
// MM_GetRandomServerFromList
// Return any random server from the list
//================
#ifdef OLD_GAMESERVER_CHECKING
mm_server_t *MM_GetRandomServerFromList( mm_ignoreserver_t *ignores )
{
	mm_server_t *server, *original;
	mm_ignoreserver_t *ignore, *prev = NULL;
	int count = 0, randno;
	qboolean first = qtrue;

	for( server = serverlist; server; server = server->next )
		count++;

	if( !count )
		return NULL;

	server = serverlist;
	randno = ( int )brandom( 0, count - 1 );
	// go to random server
	while( randno-- )
		server = server->next;

	if( !ignores )
		return server;

	// make sure we aren't supposed to ignore this server
	// if we are, go to the next one
	original = server;
	for( ignore = ignores; ignore; ignore = ignore->next )
	{
		// we've tried every server.
		if( !first && server == original )
			return NULL;

		if( !Q_stricmp( server->ip, ignore->ip ) )
		{
			// try next server
			if( server->next )
				server = server->next;
			else
				server = serverlist;

			// back to start of list
			ignore = ignores;
		}

		prev = ignore;
		first = qfalse;
	}

	// this server wasn't on the ignore list
	if( !ignore )
		return server;

	return NULL;
}
#endif

//================
// MM_AddServerToList
// Add a server to the serverlist
//================
mm_server_t *MM_AddServerToList( const char *ip )
{
	mm_server_t *server;

	if ( !ip || !*ip )
		return NULL;

	if ( MM_GetServerFromList( ip ) )
		return NULL;

	server = MM_Malloc( sizeof( mm_server_t ) );
	Q_strncpyz( server->ip, ip, sizeof( server->ip ) );

	server->next = serverlist;
	serverlist = server;

	return server;
}

//================
// MM_DeleteServerFromList
// Delete server and update list
//================
void MM_DeleteServerFromList( const char *ip )
{
	mm_server_t *current, *prev = NULL;

	if( !ip )
		return;

	for( current = serverlist; current; current = current->next )
	{
		if( !Q_stricmp( ip, current->ip ) )
			break;

		prev = current;
	}

	// server doesnt exist
	if( !current )
		return;

	// remove from list
	if( prev )
		prev->next = current->next;
	else
		serverlist = current->next;

	MM_Free( current );
}

//================
// MM_ClearServer
// Frees the server for other matches, DOESNT delete from serverlist
//================
void MM_ClearServer( mm_server_t *server )
{
	if( !server )
		return;

	server->match = NULL;
	memset( &server->cmd, 0, sizeof( server->cmd ) );
}

//================
// MM_ServerList
// Ouput serverlist to console
//================
void MM_ServerList( void )
{
	int count;
	mm_server_t *server;

	for ( server = serverlist, count = 0 ; server ; server = server->next, count++ )
		Com_Printf( "%s - ping: %d\n", server->ip, server->ping );

	Com_Printf( "%i gameserver(s)\n", count );
}

//================
// MM_CheckServerList
// Deletes any gameservers that havent sent a heartbeat recently
//================
void MM_CheckServerList( void )
{
	mm_server_t *server = serverlist, *next;
	netadr_t address;

	while( server )
	{
		// gameserver apparently didnt receive our command as it hasnt replied
		// resend
		if( server->cmd.senttime && server->cmd.senttime + PACKET_PING_TIME < realtime )
			MM_SendCmdToGameServer( server, server->cmd.lastcmd );

		// time to ping the server?
		if( server->lastping + SERVER_PING_TIME > realtime )
		{
			server = server->next;
			continue;
		}
		// server has not responded since last ping
		if( server->lastack < server->lastping )
		{
			server->noack++;
			Com_DPrintf( "Server %s did not respond to ping\n", server->ip );
		}

		// server hasn't responded for too long
		if ( server->noack >= UNACKNOWLEDGED_PINGS )
		{
			Com_Printf( "Server %s dropped after %d unacknowledged pings\n", server->ip, UNACKNOWLEDGED_PINGS );
			next = server->next;
			MM_DeleteServerFromList( server->ip );
			server = next;
			continue;
		}
		
		// ping the server
		if( !NET_StringToAddress( server->ip, &address ) )
			continue;

		if( !address.port )
			address.port = BigShort( PORT_SERVER );

		Netchan_OutOfBandPrint( &socket_udp, &address, "ping server" );
		server->lastping = realtime;

		server = server->next;
	}
}

//================
// MM_GetSuitableServer
// Finds a server with a nice ping
//================
mm_server_t *MM_GetSuitableServer( mm_match_t *match )
{
	mm_server_t *server, *server2;
	int i, ping = 0, deviation;

	if ( !serverlist )
		return NULL;

	// get an average ping for clients
	for( i = 0 ; i < match->maxclients ; i++ )
		ping += match->clients[i]->ping;
	ping = ( int )floor( ping / match->maxclients );

	// check the server has a similar ping
	for( server = serverlist ; server ; server = server->next )
	{
		if( abs( ping - server->ping ) <= MAX_PING_DEVIATION && !server->match )
			break;
	}

	// this match allows any ping, but lets try and find a niceish server
	if( match->ping_type == TYPE_ANY && !server )
	{
		deviation = 999;
		for( server2 = serverlist ; server2 ; server2 = server2->next )
		{
			// this server has a smaller deviation, lets use it
			if ( abs( ping - server->ping ) < deviation && !server->match )
			{
				server = server2;
				deviation = abs( ping - server->ping );
			}
		}
	}

	return server;
}
