/*
   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"

mm_matchlist_t *matchlist;

//================
// MM_SendMsgToClients
// Sends a message to a client
//================
void MM_SendMsgToClient( qboolean reliable, mm_client_t *client, const char *format, ... )
{
	socket_t *socket;
	va_list argptr;
	char cmd[1024], hash[HASH_SIZE + 1];
	mm_packet_t *packet;

	if( !client )
		return;

	if( client->address->type == NA_NOTRANSMIT )
		return;

	va_start( argptr, format );
	Q_vsnprintfz( cmd, sizeof( cmd ), format, argptr );
	va_end( argptr );

	if( !cmd )
		return;

	if( client->address->type == NA_LOOPBACK )
		socket = &socket_loopback_server;
	else
		socket = &socket_udp;

	Netchan_OutOfBandPrint( socket, client->address, "%s", cmd );

	if( !reliable )
		return;

	MM_Hash( hash, cmd );
	// if the packet already exists, just update the senttime
	for( packet = client->packets ; packet ; packet = packet->next )
	{
		if( !strcmp( packet->hash, hash ) )
		{
			packet->senttime = realtime;
			return;
		}
	}

	// add to clients packet list
	packet = MM_Malloc( sizeof( mm_packet_t ) );
	packet->cmd = MM_Malloc( strlen( cmd ) + 1 );
	strcpy( packet->cmd, cmd );
	strcpy( packet->hash, hash );
	packet->senttime = realtime;
	packet->next = client->packets;
	client->packets = packet;
}

//================
// MM_SendMsgToClients
// Sends a common message to all clients connected to the match
//================
void MM_SendMsgToClients( qboolean reliable, const mm_match_t *match, const char *format, ... )
{
	int i;
	va_list	argptr;
	char msg[1024];

	va_start( argptr, format );
	Q_vsnprintfz( msg, sizeof( msg ), format, argptr );
	va_end( argptr );

	if( !match || !*msg )
		return;

	for( i = 0; i < match->maxclients; i++ )
		MM_SendMsgToClient( reliable, match->clients[i], "%s", msg );
}

//================
// MM_MatchesCriteria
// Checks that the match matches the criteria that was requested
//================
qboolean MM_MatchesCriteria( mm_match_t *match, int gametype, mm_type_t skill_type, int skill_level, mm_type_t ping_type, int ping )
{
	int i;

	if( !match )
		return qfalse;

	if( MM_ClientCount( match ) == match->maxclients )
		return qfalse;

	if( match->gametype != gametype )
		return qfalse;

	if( match->skill_type == TYPE_ANY )
		return qtrue;

	if( match->skill_level > skill_level + SKILL_LEVEL_TOLERANCE &&
	    match->skill_level < skill_level - SKILL_LEVEL_TOLERANCE )
		return qfalse;

	if ( ping_type == TYPE_ANY )
		return qtrue;

	for( i = 0 ; i < match->maxclients ; i++ )
	{
		if ( match->clients[i] && abs( match->clients[i]->ping - ping ) > MAX_PING_DEVIATION )
			return qfalse;
	}

	return qtrue;
}

//================
// MM_MatchesList
// Prints the list of matches to console
//================
void MM_MatchesList( void )
{
	int i = 0, matchno = 0;
	mm_match_t *match = matchlist->first;

	while( match )
	{
		matchno++;
		Com_Printf( "------------\nMatch %d\n ping type: %s\n gametype: %s\n skill type: %s\n clients\n",
		            matchno,
		            match->ping_type == TYPE_ANY ? "any" : "dependent",
		            MM_GetGameTypeNameByTag( match->gametype ),
		            match->skill_type == TYPE_ANY ? "any" : "dependent" );
		for( i = 0; i < match->maxclients; i++ )
		{
			if( match->clients[i] )
				Com_Printf( "  %s %s (%d): %s - ping: %d\n",
				           match->clients[i]->uid < 0 ? "U" : "R", // registered or not
				           match->clients[i]->nickname,
				           match->clients[i]->uid,
				           NET_AddressToString( match->clients[i]->address ),
									 match->clients[i]->ping );
		}

		match = match->next;
	}

	Com_Printf( "------------\n%d matches\n", matchno );
}


//================
// MM_PingClients
// Pings all clients connected to a match
//================
void MM_PingClients( void )
{
	mm_match_t *match = matchlist->first, *nextmatch;
	int matchno = 1, i;

	while( match )
	{
		// has PING_TIME passed since last ping
		if( match->lastping + MATCH_PING_TIME < realtime )
		{
			// lets check if we've had acknowledgements from clients since our last ping
			for( i = 0; i < match->maxclients; i++ )
			{
				if( !match->clients[i] )
					continue;

				// client hasnt responded since previous ping
				if( match->clients[i]->lastack < match->lastping )
				{
					match->clients[i]->noack++;
					Com_DPrintf( "Client %s did not acknowledge ping\n", match->clients[i]->nickname );
				}

				// no response from pings, drop client
				if( match->clients[i]->noack == UNACKNOWLEDGED_PINGS )
				{
					MM_SendMsgToClients( qtrue, match, "drop %d", match->clients[i]->uid );
					Com_Printf( "Client %s dropped after %d unacknowledged pings\n",
					            match->clients[i]->nickname,
					            UNACKNOWLEDGED_PINGS );
					MM_FreeClient( match->clients[i] );
					match->clients[i] = NULL;
					continue;
				}
			} // for loop

			MM_SendMsgToClients( qfalse, match, "ping" );
			Com_DPrintf( "Pings sent for match %d\n", matchno );
			match->lastping = realtime;
		} // lastping + PING_TIME < realtime

		// we need to delete the match if theres no-one in it anymore!
		if( !MM_ClientCount( match ) )
		{
			nextmatch = match->next;
			MM_FreeMatch( match );
			match = nextmatch;
		}
		else
			match = match->next;

		matchno++;
	} // while loop
}

//================
// MM_CheckMatches
// Check to see if we can find a server for matches awaiting servers
//================
#define CHECK_INTERVAL 15000
void MM_CheckMatches( void )
{
	mm_match_t *match;

	for( match = matchlist->first ; match ; match = match->next )
	{
		if( !match->awaitingserver )
			continue;

		// a client may have left
		if( MM_ClientCount( match ) != match->maxclients )
		{
			MM_SendMsgToClients( qfalse, match, "status waiting for players" );
			match->awaitingserver = 0;
			continue;
		}

		if( realtime - match->awaitingserver >= CHECK_INTERVAL )
			MM_FindAndLockServer( match );
	}
}

//================
// MM_GetSkillLevelByStats
// Calculates skill level for userid by retrieving weapstats from the database
// returns -1 on error
//================
int MM_GetSkillLevelByStats( const int uid )
{
	int i;
	MYSQL_RES *res;
	MYSQL_ROW row;
	char query[MAX_QUERY_SIZE];
	float level = 0;
	mm_supported_items_t *item;

	if( !db_handle )
	{
		Com_Printf( "MM_GetSkillLevelByStats: No database connection\n" );
		return -1;
	}

	if( !uid )
		return 0;

	Q_snprintfz( query, sizeof( query ), "SELECT " );

	for( item = supported_items; item->short_name; item++ )
		Q_strncatz( query, va( "`%s`,", item->short_name ), sizeof( query ) );

	// remove trailing comma
	query[strlen( query ) - 1] = '\0';

	Q_strncatz( query, va( " FROM `%s` WHERE `id`='%d'", DBTABLE_USERSTATS, uid ), sizeof( query ) );

	if( DB_Query( db_handle, query ) != DB_SUCCESS )
		return -1;

	if( DB_FetchResult( db_handle, &res ) != DB_SUCCESS )
		return -1;

	// no data found in the database
	if( !DB_NumRows( res ) )
		return 0;

	if( DB_FetchRow( res, &row, 0 ) != DB_SUCCESS )
	{
		DB_FreeResult( &res );
		return -1;
	}
	for( i = 0, item = supported_items; item->short_name; i++, item++ )
		level += atoi( row[i] ) * item->multiplier;

	DB_FreeResult( &res );
	return ( int ) ceil( level );
}

//================
// MM_ClearMatches
// Delete all the matches in the matches list
//================
void MM_ClearMatches( void )
{
	mm_match_t *match, *next;

	match = matchlist->first;

	while( match )
	{
		next = match->next;
		MM_FreeMatch( match );
		match = next;
	}
}

#ifdef OLD_GAMESERVER_CHECKING
void MM_AddToIgnoreList( mm_match_t *match, const char *ip )
{
	mm_ignoreserver_t *ignore;

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

	ignore = MM_Malloc( sizeof( mm_ignoreserver_t ) );
	Q_strncpyz( ignore->ip, ip, sizeof( ignore->ip ) );
	ignore->next = match->ignores;
	match->ignores = ignore;
}
#endif

void MM_CheckClientPackets( void )
{
	int i;
	mm_match_t *match;
	mm_packet_t *packet;

	for( match = matchlist->first ; match ; match = match->next )
	{
		for( i = 0 ; i < match->maxclients ; i++ )
		{
			if( !match->clients[i] )
				continue;

			for( packet = match->clients[i]->packets ; packet ; packet = packet->next )
			{
				// the packet doesnt seem to have been received successfully
				// resend
				if( packet->senttime + PACKET_PING_TIME < realtime )
					MM_SendMsgToClient( qtrue, match->clients[i], packet->cmd );
			}
		}
	}
}
