/*
 * qstat
 * 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);
}
