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

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

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

#define TM_XML_PREFIX "<?xml version=\"1.0\"?>\n<methodCall>\n<methodName>system.multicall</methodName>\n<params><param><value><array><data>\n"
#define TM_XML_SUFFIX "</data></array></value></param></params>\n</methodCall>"
#define TM_SERVERINFO "<value><struct><member><name>methodName</name><value>GetServerOptions</value></member><member><name>params</name><value><array><data></data></array></value></member></struct></value>\n<value><struct><member><name>methodName</name><value>GetCurrentChallengeInfo</value></member><member><name>params</name><value><array><data></data></array></value></member></struct></value>\n"
#define TM_PLAYERLIST "<value><struct><member><name>methodName</name><value>GetPlayerList</value></member><member><name>params</name><value><array><data><value><i4>100</i4></value><value><i4>0</i4></value></data></array></value></member></struct></value>\n"
#define TM_AUTH_TEMPLATE "<value><struct>\n<member><name>methodName</name><value><string>Authenticate</string></value></member>\n<member><name>params</name><value><array><data>\n<value><string>%s</string></value>\n<value><string>%s</string></value></data></array></value></member></struct></value>\n"

query_status_t send_tm_request_packet( struct qserver *server )
{
	char buf[2048];
	char *xmlp = buf + 8;
	unsigned int len;
	char *user = get_param_value( server, "user", NULL );
	char *password = get_param_value( server, "password", NULL );

	if ( ! server->protocol_version )
	{
		// No seen the version yet wait
		// register_send here to ensure that timeouts function correctly
		return register_send( server );
	}

	// build the query xml
	len = sprintf( xmlp, TM_XML_PREFIX );

	if ( user != NULL && password != NULL )
	{
		len += sprintf( xmlp + len, TM_AUTH_TEMPLATE, user, password );
	}
	else
	{
		// Default to User / User
		len += sprintf( xmlp + len, TM_AUTH_TEMPLATE, "User", "User" );
	}

	// Always get Player info otherwise player count is invalid
	// TODO: add more calls to get full player info?
	server->flags |= TF_PLAYER_QUERY|TF_RULES_QUERY;
	len += sprintf( xmlp + len, TM_SERVERINFO );
	len += sprintf( xmlp + len, TM_PLAYERLIST );
	len += sprintf( xmlp + len, TM_XML_SUFFIX );

	// First 4 bytes is the length of the request
	memcpy( buf, &len, 4 );
	// Second 4 bytes is the handle identifier ( id )
	memcpy( buf+4, &server->challenge, 4 );

	// prep the details we need for multi packet responses
	// we expect at least 1 packet response
	server->saved_data.pkt_max = 1;

	return send_packet( server, buf, len + 8 );
}


query_status_t deal_with_tm_packet( struct qserver *server, char *rawpkt, int pktlen )
{
	char *s;
	char *pkt = rawpkt;
	char *key = NULL, *value = NULL, *tmpp = NULL;
	char fullname[256];
	struct player *player = NULL;
	int pkt_max = server->saved_data.pkt_max;
	unsigned total_len, expected_len;
	int method_response = 1;
	debug( 2, "processing..." );

	s = rawpkt;

	// We may get the setup handle and the protocol version in one packet we may not
	// So we continue to parse if we see the handle
	if ( 4 <= pktlen && 0 == memcmp( pkt, "\x0b\x00\x00\x00", 4 ) )
	{
		// setup handle identifier
		// greater 2^31 = XML-RPC, less = callback
		server->challenge = 0x80000001;
		if ( 4 == pktlen )
		{
			return 0;
		}
		pktlen -= 4;
		pkt += 4;
	}

	if ( 11 <= pktlen && 1 == sscanf( pkt, "GBXRemote %d", &server->protocol_version ) )
	{
		// Got protocol version send request
		send_tm_request_packet( server );
		return 0;
	}

	if ( 8 <= pktlen && 0 == memcmp( pkt+4, &server->challenge, 4 ) )
	{
		// first 4 bytes = the length
		// Note: We use pkt_id to store the length of the expected packet
		// this could cause loss but very unlikely
		unsigned long len;
		memcpy( &len, rawpkt, 4 );

		// second 4 bytes = handle identifier we sent in the request
		if ( 8 == pktlen )
		{
			// split packet
			// we have at least one more packet coming
			if ( ! add_packet( server, len, 0, 2, pktlen, rawpkt, 1 ) )
			{
				// fatal error e.g. out of memory
				return -1;
			}
			return 0;
		}
		else
		{
			// ensure the length is stored
			server->saved_data.pkt_id = (int)len;
			s += 8;
		}
	}

	total_len = combined_length( server, server->saved_data.pkt_id );
	expected_len = server->saved_data.pkt_id;
	debug( 2, "total: %d, expected: %d\n", total_len, expected_len );
	if ( total_len < expected_len + 8 )
	{
		// we dont have a complete response add the packet
		int last, new_max;
		if ( total_len + pktlen >= expected_len + 8 )
		{
			last = 1;
			new_max = pkt_max;
		}
		else
		{
			last = 0;
			new_max = pkt_max + 1;
		}

		if ( ! add_packet( server, server->saved_data.pkt_id, pkt_max - 1, new_max, pktlen, rawpkt, 1 ) )
		{
			// fatal error e.g. out of memory
			return -1;
		}

		if ( last )
		{
			// we are the last packet run combine to call us back
			return combine_packets( server );
		}
		return 0;
	}

	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);
	}

	// Terminate the packet data
	pkt = (char*)malloc( pktlen + 1 );
	if ( NULL == pkt )
	{
		debug( 0, "Failed to malloc memory for packet terminator\n" );
		return MEM_ERROR;
	}
	memcpy( pkt, rawpkt, pktlen );
	pkt[pktlen] = '\0';

//fprintf( stderr, "S=%s\n", s );
	s = strtok( pkt + 8, "\015\012" );
	while ( NULL != s )
	{
//fprintf( stderr, "S=%s\n", s );
		if ( 0 == strncmp( s, "<member><name>", 14 ) )
		{
			key = s + 14;
			tmpp = strstr( key, "</name>" );
			if ( NULL != tmpp )
			{
				*tmpp = '\0';
			}
			s = strtok( NULL, "\015\012" );
			value = NULL;
			continue;
		}
		else if ( NULL != key && 0 == strncmp( s, "<value>", 7 ) )
		{
			// value
			s += 7;
			if ( 0 == strncmp( s, "<string>", 8 ) )
			{
				// String
				value = s+8;
				tmpp = strstr( s, "</string>" );
			}
			else if ( 0 == strncmp( s, "<i4>", 4 ) )
			{
				// Int
				value = s+4;
				tmpp = strstr( s, "</i4>" );
			}
			else if ( 0 == strncmp( s, "<boolean>", 9 ) )
			{
				// Boolean
				value = s+9;
				tmpp = strstr( s, "</boolean>" );
			}
			else if ( 0 == strncmp( s, "<double>", 8 ) )
			{
				// Double
				value = s+8;
				tmpp = strstr( s, "</double>" );
			}
			// also have struct and array but not interested in those

			if ( NULL != tmpp )
			{
				*tmpp = '\0';
			}

			if ( NULL != value )
			{
				debug( 4, "%s = %s\n", key, value );
			}
		}
		else if ( 0 == strncmp( s, "</struct>", 9 ) && 3 > method_response )
		{
			// end of method response
			method_response++;
		}

		if ( NULL != value && NULL != key )
		{
			switch( method_response )
			{
			case 1:
				// GetServerOptions response
				if ( 0 == strcmp( "Name", key ) )
				{
					server->server_name = strdup( value );
				}
				else if ( 0 == strcmp( "CurrentMaxPlayers", key ) )
				{
					server->max_players = atoi( value );
				}
				else
				{
					sprintf( fullname, "server.%s", key );
					add_rule( server, fullname, value, NO_FLAGS);
				}
				break;
			case 2:
				// GetCurrentChallengeInfo response
				if ( 0 == strcmp( "Name", key ) )
				{
					server->map_name = strdup( value );
				}
				else
				{
					sprintf( fullname, "challenge.%s", key );
					add_rule( server, fullname, value, NO_FLAGS);
				}
				break;
			case 3:
				// GetPlayerList response
				// Player info
				if ( 0 == strcmp( "Login", key ) )
				{
					player = add_player( server, server->n_player_info );
					server->num_players++;
				}
				else if ( NULL != player )
				{
					if ( 0 == strcmp( "NickName", key ) )
					{
						player->name = strdup( value );
					}
					else if ( 0 == strcmp( "PlayerId", key ) )
					{
						//player->number = atoi( value );
					}
					else if ( 0 == strcmp( "TeamId", key ) )
					{
						player->team = atoi( value );
					}
					else if ( 0 == strcmp( "IsSpectator", key ) )
					{
						player->flags = player->flags & 1;
					}
					else if ( 0 == strcmp( "IsInOfficialMode", key ) )
					{
						player->flags = player->flags & 2;
					}
					else if ( 0 == strcmp( "LadderRanking", key ) )
					{
						player->score = atoi( value );
					}
				}
				break;
			}
			value = NULL;
		}
		s = strtok( NULL, "\015\012" );
	}

	free( pkt );

	if ( 0 == strncmp( rawpkt + pktlen - 19, "</methodResponse>", 17 ) )
	{
		// last packet seen
		return DONE_FORCE;
	}

	return INPROGRESS;
}
