/*
 * qstat
 * 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 <sys/socket.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

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

int
gps_max_players(struct qserver *server)
{
	struct player *player;
	int no_players = 0;

	if (0 == server->num_players) {
		return (0);
	}

	for (player = server->players; player; player = player->next) {
		no_players++;
	}

	return ((no_players < server->num_players) ? 1 : 0);
}


int
gps_player_info_key(char *s, char *end)
{
	static char *keys[] =
	{
		"frags_",      "team_",	   "ping_",	"species_",
		"race_",       "deaths_",  "score_",	"enemy_",
		"player_",     "keyhash_", "teamname_",
		"playername_", "keyhash_", "kills_",	"queryid"
	};
	int i;

	for (i = 0; i < sizeof(keys) / sizeof(char *); i++) {
		int len = strlen(keys[i]);
		if ((s + len < end) && (strncmp(s, keys[i], len) == 0)) {
			return (len);
		}
	}
	return (0);
}


query_status_t
send_gps_request_packet(struct qserver *server)
{
	return (send_packet(server, server->type->status_packet, server->type->status_len));
}


query_status_t
deal_with_gps_packet(struct qserver *server, char *rawpkt, int pktlen)
{
	char *s, *key, *value, *end;
	struct player *player = NULL;
	int id_major = 0, id_minor = 0, final = 0, player_num;
	char tmp[256];

	debug(2, "processing...");

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

	/*
	 * // We're using the saved_data a bit differently here to track received
	 * // packets.
	 * // pkt_id is the id_major from the \queryid\
	 * // pkt_max is the total number of packets expected
	 * // pkt_index is a bit mask of the packets received.  The id_minor of
	 * // \queryid\ provides packet numbers (1 through pkt_max).
	 */
	if (server->saved_data.pkt_index == -1) {
		server->saved_data.pkt_index = 0;
	}

	rawpkt[pktlen] = '\0';
	end = &rawpkt[pktlen];

	s = rawpkt;
	while (*s) {
		// find the '\'
		while (*s && *s == '\\') {
			s++;
		}

		if (!*s) {
			// out of packet
			break;
		}
		// Start of key
		key = s;

		// while we still have data and its not a '\'
		while (*s && *s != '\\') {
			s++;
		}

		if (!*s) {
			// out of packet
			break;
		}

		// Terminate the key
		*s++ = '\0';

		// Now for the value
		value = s;

		// while we still have data and its not a '\'
		while (*s && *s != '\\') {
			s++;
		}

		if (s[0] && s[1]) {
			//fprintf( stderr, "%s = %s\n", key, value );
			if (!isalpha((unsigned char)s[1])) {
				// escape char?
				s++;
				// while we still have data and its not a '\'
				while (*s && *s != '\\') {
					s++;
				}
			} else if (
				isalpha((unsigned char)s[1]) &&
				(0 == strncmp(key, "player_", 7)) &&
				(0 != strcmp(key, "player_flags"))
				) {
				// possible '\' in player name
				if (!gps_player_info_key(s + 1, end)) {
					// yep there was an escape in the player name
					s++;
					// while we still have data and its not a '\'
					while (*s && *s != '\\') {
						s++;
					}
				}
			}
		}

		if (*s) {
			*s++ = '\0';
		}

		//fprintf( stderr, "%s = %s\n", key, value );
		if (*value == '\0') {
			if (strcmp(key, "final") == 0) {
				final = 1;
				if (id_minor > server->saved_data.pkt_max) {
					server->saved_data.pkt_max = id_minor;
				}
				continue;
			}
		}

		/* This must be done before looking for player info because
		 * "queryid" is a valid according to gps_player_info_key().
		 */
		if (strcmp(key, "queryid") == 0) {
			sscanf(value, "%d.%d", &id_major, &id_minor);
			if (server->saved_data.pkt_id == 0) {
				server->saved_data.pkt_id = id_major;
			}
			if (id_major == server->saved_data.pkt_id) {
				if (id_minor > 0) {
					// pkt_index is bitmask of packets recieved
					server->saved_data.pkt_index |= 1 << (id_minor - 1);
				}
				if (final && (id_minor > server->saved_data.pkt_max)) {
					server->saved_data.pkt_max = id_minor;
				}
			}
			continue;
		}

		if (player == NULL) {
			int len = gps_player_info_key(key, end);
			if (len) {
				// We have player info
				int player_number = atoi(key + len);
				player = get_player_by_number(server, player_number);

				// && gps_max_players( server ) due to bf1942 issue
				// where the actual no players is correct but more player
				// details are returned
				if ((player == NULL) && gps_max_players(server)) {
					player = add_player(server, player_number);
					if (player) {
						// init to -1 so we can tell if
						// we have team info
						player->team = -1;
						player->deaths = -999;
					}
				}
			}
		}

		if ((strcmp(key, "mapname") == 0) && !server->map_name) {
			server->map_name = strdup(value);
		} else if ((strcmp(key, "hostname") == 0) && !server->server_name) {
			server->server_name = strdup(value);
		} else if (strcmp(key, "hostport") == 0) {
			change_server_port(server, atoi(value), 0);
		} else if (strcmp(key, "maxplayers") == 0) {
			server->max_players = atoi(value);
		} else if (strcmp(key, "numplayers") == 0) {
			server->num_players = atoi(value);
		} else if ((strcmp(key, server->type->game_rule) == 0) && !server->game) {
			server->game = strdup(value);
			add_rule(server, key, value, NO_FLAGS);
		} else if (strcmp(key, "final") == 0) {
			final = 1;
			if (id_minor > server->saved_data.pkt_max) {
				server->saved_data.pkt_max = id_minor;
			}
			continue;
		} else if ((strncmp(key, "player_", 7) == 0) || (strncmp(key, "playername_", 11) == 0)) {
			int no;
			if (strncmp(key, "player_", 7) == 0) {
				no = atoi(key + 7);
			} else {
				no = atoi(key + 11);
			}

			if (player && (player->number == no)) {
				player->name = strdup(value);
				player = NULL;
			} else if (NULL != (player = get_player_by_number(server, no))) {
				player->name = strdup(value);
				player = NULL;
			} else if (gps_max_players(server)) {
				// gps_max_players( server ) due to bf1942 issue
				// where the actual no players is correct but more player
				// details are returned
				player = add_player(server, no);
				if (player) {
					player->name = strdup(value);
					// init to -1 so we can tell if
					// we have team info
					player->team = -1;
					player->deaths = -999;
				}
			}
		} else if (strncmp(key, "teamname_", 9) == 0) {
			// Yes plus 1 BF1942 is a silly
			players_set_teamname(server, atoi(key + 9) + 1, value);
		} else if (strncmp(key, "team_t", 6) == 0) {
			players_set_teamname(server, atoi(key + 6), value);
		} else if (strncmp(key, "frags_", 6) == 0) {
			player = get_player_by_number(server, atoi(key + 6));
			if (NULL != player) {
				player->frags = atoi(value);
			}
		} else if (strncmp(key, "kills_", 6) == 0) {
			player = get_player_by_number(server, atoi(key + 6));
			if (NULL != player) {
				player->frags = atoi(value);
			}
		} else if (strncmp(key, "team_", 5) == 0) {
			player = get_player_by_number(server, atoi(key + 5));
			if (NULL != player) {
				if (!isdigit((unsigned char)*value)) {
					player->team_name = strdup(value);
				} else {
					player->team = atoi(value);
				}
				server->flags |= FLAG_PLAYER_TEAMS;
			}
		} else if (strncmp(key, "skin_", 5) == 0) {
			player = get_player_by_number(server, atoi(key + 5));
			if (NULL != player) {
				player->skin = strdup(value);
			}
		} else if (strncmp(key, "mesh_", 5) == 0) {
			player = get_player_by_number(server, atoi(key + 5));
			if (NULL != player) {
				player->mesh = strdup(value);
			}
		} else if (strncmp(key, "ping_", 5) == 0) {
			player = get_player_by_number(server, atoi(key + 5));
			if (NULL != player) {
				player->ping = atoi(value);
			}
		} else if (strncmp(key, "face_", 5) == 0) {
			player = get_player_by_number(server, atoi(key + 5));
			if (NULL != player) {
				player->face = strdup(value);
			}
		} else if (strncmp(key, "deaths_", 7) == 0) {
			player = get_player_by_number(server, atoi(key + 7));
			if (NULL != player) {
				player->deaths = atoi(value);
			}
		}
		// isnum( key[6] ) as halo uses score_tX for team scores
		else if ((strncmp(key, "score_", 6) == 0) && isdigit((unsigned char)key[6])) {
			player = get_player_by_number(server, atoi(key + 6));
			if (NULL != player) {
				player->score = atoi(value);
			}
		} else if (player && (strncmp(key, "playertype", 10) == 0)) {
			player->team_name = strdup(value);
		} else if (player && (strncmp(key, "charactername", 13) == 0)) {
			player->face = strdup(value);
		} else if (player && (strncmp(key, "characterlevel", 14) == 0)) {
			player->ship = atoi(value);
		} else if (strncmp(key, "keyhash_", 8) == 0) {
			// Ensure these dont make it into the rules
		} else if (2 == sscanf(key, "%255[^_]_%d", tmp, &player_num)) {
			// arbitary player info
			player = get_player_by_number(server, player_num);
			if (NULL != player) {
				player_add_info(player, tmp, value, NO_FLAGS);
			} else if (gps_max_players(server)) {
				// gps_max_players( server ) due to bf1942 issue
				// where the actual no players is correct but more player
				// details are returned
				player = add_player(server, player_num);

				if (player) {
					player->name = NULL;
					// init to -1 so we can tell if
					// we have team info
					player->team = -1;
					player->deaths = -999;
				}
				player_add_info(player, tmp, value, NO_FLAGS);
			}
		} else {
			player = NULL;
			add_rule(server, key, value, NO_FLAGS);
		}
	}

	debug(2, "final %d\n", final);
	debug(2, "pkt_id %d\n", server->saved_data.pkt_id);
	debug(2, "pkt_max %d\n", server->saved_data.pkt_max);
	debug(2, "pkt_index %x\n", server->saved_data.pkt_index);

	if (
		(final && (server->saved_data.pkt_id == 0)) ||
		(server->saved_data.pkt_max && (server->saved_data.pkt_index >= ((1 << (server->saved_data.pkt_max)) - 1))) ||
		((server->num_players < 0) && (id_minor >= 3))
		) {
		return (DONE_FORCE);
	}

	return (INPROGRESS);
}
