/*
 * qstat 2.14
 * by Steve Jankowski
 *
 * New Gamespy v2 query protocol
 * Copyright 2005 Steven Hartland
 *
 * Licensed under the Artistic License, see LICENSE.txt for license terms
 *
 */

#include <sys/types.h>
#ifndef _WIN32
#include <sys/socket.h>
#endif
#include <stdlib.h>
#include <stdio.h>

#include "debug.h"
#include "qstat.h"
#include "packet_manip.h"

query_status_t send_gs2_request_packet( struct qserver *server )
{
	// The below should work but seems to make no difference to what some
	// servers send
	if ( get_player_info )
	{
		server->type->status_packet[8] = 0xff;
		server->type->status_packet[9] = 0xff;
	}
	else
	{
		server->type->status_packet[8] = 0x00;
		server->type->status_packet[9] = 0x00;
	}

	return send_packet( server, server->type->status_packet, server->type->status_len );
}


// See the following for protocol details:
// http://dev.kquery.com/index.php?article=42
query_status_t deal_with_gs2_packet( struct qserver *server, char *rawpkt, int pktlen )
{
	char *ptr = rawpkt;
	char *end = rawpkt + pktlen;
	unsigned char type = 0;
	unsigned char no_players = 0;
	unsigned char total_players = 0;
	unsigned char no_teams = 0;
	unsigned char total_teams = 0;
	unsigned char no_headers = 0;
	char **headers = NULL;
	debug( 2, "processing packet..." );

	if ( pktlen < 15 )
	{
		// invalid packet?
		return PKT_ERROR;
	}

	server->n_servers++;
	if ( server->server_name == NULL)
	{
		server->ping_total += time_delta( &packet_recv_time, &server->packet_time1 );
	}
	else
	{
		gettimeofday( &server->packet_time1, NULL);
	}

	// Could check the header here should
	// match the 4 byte id sent
	ptr += 5;
	while ( 0 == type && ptr < end )
	{
		// server info:
		// name value pairs null seperated
		// empty name && value signifies the end of section
		char *var, *val;
		int var_len, val_len;

		var = ptr;
		var_len = strlen( var );

		if ( ptr + var_len + 2 > end )
		{
			if ( 0 != var_len )
			{
				malformed_packet( server, "no rule value" );
			}
			else if ( get_player_info )
			{
				malformed_packet( server, "no player headers" );
			}
			return PKT_ERROR;
		}
		ptr += var_len + 1;

		val = ptr;
		val_len = strlen( val );
		ptr += val_len + 1;
		debug( 2, "var:%s (%d)=%s (%d)\n", var, var_len, val, val_len );

		// Lets see what we've got
		if ( 0 == strcmp( var, "hostname" ) )
		{
			server->server_name = strdup( val );
		}
		else if( 0 == strcmp( var, "game_id" ) )
		{
			server->game = strdup( val );
		}
		else if( 0 == strcmp( var, "gamever" ) )
		{
			// format:
			// v1.0
			server->protocol_version = atoi( val+1 );
			add_rule( server, var, val, NO_FLAGS );
		}
		else if( 0 == strcmp( var, "mapname" ) )
		{
			server->map_name = strdup( val );
		}
		else if( 0 == strcmp( var, "map" ) )
		{
			// For BF2MC compatibility
			server->map_name = strdup( val );
		}
		else if( 0 == strcmp( var, "maxplayers" ) )
		{
			server->max_players = atoi( val );

		}
		else if( 0 == strcmp( var, "numplayers" ) )
		{
			server->num_players = no_players = atoi( val );
		}
		else if( 0 == strcmp( var, "hostport" ) )
		{
			change_server_port( server, atoi( val ), 0 );
		}
		else if ( 0 == var_len )
		{
			// check for end of section
			type = 1;
		}
		else
		{
			add_rule( server, var, val, NO_FLAGS );
		}
	}

	if ( 1 != type )
	{
		// no more info should be player headers here as we
		// requested it
		malformed_packet( server, "no player headers" );
		return PKT_ERROR;
	}

	// player info header
	// format:
	// first byte = player count
	// followed by null seperated header
	no_players = (unsigned char)*ptr;
	debug( 2, "No Players:%d\n", no_players );
	ptr++;

	if ( ptr >= end )
	{
		malformed_packet( server, "no player headers" );
		return PKT_ERROR;
	}

	while ( 1 == type && ptr < end )
	{
		// first we have the headers null seperated
		char **tmpp;
		char *head = ptr;
		int head_len = strlen( head );
		no_headers++;
		tmpp = (char**)realloc( headers, no_headers * sizeof( char* ) );
		if ( NULL == tmpp )
		{
			debug( 0, "Failed to realloc memory for headers\n" );
			if ( NULL != headers )
			{
				free( headers );
			}
			return MEM_ERROR;
		}

		headers = tmpp;
		headers[no_headers-1] = head;

		ptr += head_len + 1;

		// end of headers check
		if ( 0x00 == *ptr )
		{
			type = 2;
			ptr++;
		}
		debug( 2, "player header[%d] = '%s'", no_headers-1, head );
	}

	if ( 2 != type )
	{
		// no more info should be player info here as we
		// requested it
		malformed_packet( server, "no players" );
		return PKT_ERROR;
	}

	while( 2 == type && ptr < end )
	{
		// now each player details
		// add the player
		if ( 0x00 == *ptr )
		{
			// no players
			if ( 0 != no_players )
			{
				malformed_packet( server, "no players" );
				return PKT_ERROR;
			}
		}
		else
		{
			struct player *player = add_player( server, total_players );
			int i;
			for ( i = 0; i < no_headers; i++ )
			{
				char *header = headers[i];
				char *val;
				int val_len;

				if ( ptr >= end )
				{
					malformed_packet( server, "short player detail" );
					return PKT_ERROR;
				}
				val = ptr;
				val_len = strlen( val );
				ptr += val_len + 1;

				// lets see what we got
				if ( 0 == strcmp( header, "player_" ) )
				{
					player->name = strdup( val );
				}
				else if ( 0 == strcmp( header, "score_" ) )
				{
					player->score = atoi( val );
				}
				else if ( 0 == strcmp( header, "deaths_" ) )
				{
					player->deaths = atoi( val );
				}
				else if ( 0 == strcmp( header, "ping_" ) )
				{
					player->ping = atoi( val );
				}
				else if ( 0 == strcmp( header, "kills_" ) )
				{
					player->frags = atoi( val );
				}
				else if ( 0 == strcmp( header, "team_" ) )
				{
					player->team = atoi( val );
				}
				else
				{
					int len = strlen( header );
					if ( '_' == header[len-1] )
					{
						header[len-1] = '\0';
					}
					player_add_info( player, header, val, NO_FLAGS );
				}

				debug( 2, "Player[%d][%s]=%s\n", total_players, headers[i], val );
			}
			total_players++;
		}

		if ( total_players > no_players )
		{
			malformed_packet( server, "to many players %d > %d", total_players, no_players );
			return PKT_ERROR;
		}

		// check for end of player info
		if ( 0x00 == *ptr )
		{
			if ( total_players != no_players )
			{
				malformed_packet( server, "bad number of players %d != %d", total_players, no_players );
				return PKT_ERROR;
			}
			type = 3;
			ptr++;
		}
	}

	if ( 3 != type )
	{
		// no more info should be team info here as we
		// requested it
		malformed_packet( server, "no teams" );
		return PKT_ERROR;
	}

	no_teams = (unsigned char)*ptr;
	ptr++;

	debug( 2, "No teams:%d\n", no_teams );
	no_headers = 0;

	while ( 3 == type && ptr < end )
	{
		// first we have the headers null seperated
		char **tmpp;
		char *head = ptr;
		int head_len = strlen( head );
		no_headers++;
		tmpp = (char**)realloc( headers, no_headers * sizeof( char* ) );
		if ( NULL == tmpp )
		{
			debug( 0, "Failed to realloc memory for headers\n" );
			if ( NULL != headers )
			{
				free( headers );
			}
			return MEM_ERROR;
		}

		headers = tmpp;
		headers[no_headers-1] = head;

		ptr += head_len + 1;

		// end of headers check
		if ( 0x00 == *ptr )
		{
			type = 4;
			ptr++;
		}
	}

	if ( 4 != type )
	{
		// no more info should be team info here as we
		// requested it
		malformed_packet( server, "no teams" );
		return PKT_ERROR;
	}

	while( 4 == type && ptr < end )
	{
		// now each teams details
		int i;
		for ( i = 0; i < no_headers; i++ )
		{
			char *val;
			int val_len;

			if ( ptr >= end )
			{
				malformed_packet( server, "short team detail" );
				return PKT_ERROR;
			}
			val = ptr;
			val_len = strlen( val );
			ptr += val_len + 1;

			// lets see what we got
			if ( 0 == strcmp( headers[i], "team_t" ) )
			{
				// BF being stupid again teams 1 based instead of 0
				players_set_teamname( server, total_teams + 1, val );
			}
			debug( 2, "Team[%d][%s]=%s\n", total_teams, headers[i], val );
		}
		total_teams++;

		if ( total_teams > no_teams )
		{
			malformed_packet( server, "to many teams" );
			return PKT_ERROR;
		}
	}

	return DONE_FORCE;
}
