/*
 * qstat 2.14
 *
 * opentTTD protocol
 * Copyright 2007 Ludwig Nussel
 *
 * Licensed under the Artistic License, see LICENSE.txt for license terms
 */

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

#include "qstat.h"
#include "qserver.h"
#include "debug.h"

enum { MAX_VEHICLE_TYPES = 5, MAX_STATION_TYPES = 5 };

static const char* vehicle_types[] = {
	"num_trains",
	"num_trucks",
	"num_busses",
	"num_aircrafts",
	"num_ships",
};

static const char* station_types[] = {
	"num_stations",
	"num_truckbays",
	"num_busstations",
	"num_airports",
	"num_docks",
};

query_status_t deal_with_ottdmaster_packet(struct qserver *server, char *rawpkt, int pktlen)
{
	unsigned num;
	server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
	server->server_name = MASTER;

	if(swap_short_from_little(rawpkt) != pktlen)
	{
		malformed_packet( server, "invalid packet length" );
		return PKT_ERROR;
	}
	if(rawpkt[2] != 7)
	{
		malformed_packet( server, "invalid packet type" );
		return PKT_ERROR;
	}

	if(rawpkt[3] != 1)
	{
		malformed_packet( server, "invalid packet version" );
		return PKT_ERROR;
	}

	num = swap_short_from_little(&rawpkt[4]);
	rawpkt += 6;
	pktlen -= 6;
	if( num && num*6 <= pktlen )
	{
		unsigned i;
		server->master_pkt = (char*)realloc(server->master_pkt, server->master_pkt_len + pktlen );
		memset(server->master_pkt + server->master_pkt_len, 0, pktlen );
		server->master_pkt_len += pktlen;
		for( i = 0; i < num * 6; i += 6 )
		{
			memcpy(&server->master_pkt[i], &rawpkt[i], 4);
			server->master_pkt[i+4] = rawpkt[i+5];
			server->master_pkt[i+5] = rawpkt[i+4];
		}
		server->n_servers += num;
	}
	else
	{
		malformed_packet( server, "invalid packet" );
		return PKT_ERROR;
	}

	bind_sockets();

	return DONE_AUTO;
}

#define xstr(s) str(s)
#define str(s) #s

#define GET_STRING do { \
		str = (char*)ptr; \
		ptr = memchr(ptr, '\0', end-ptr); \
		if ( !ptr ) \
		{ \
			malformed_packet( server, "%s:%s invalid packet", __FILE__, xstr(__LINE__) ); \
			return PKT_ERROR; \
		} \
		++ptr; \
	} while(0)

#define FAIL_IF(cond, msg) \
	if((cond)) { \
		malformed_packet( server, "%s:%s %s", __FILE__, xstr(__LINE__), msg ); \
		return PKT_ERROR; \
	}

#define INVALID_IF(cond) \
	FAIL_IF(cond, "invalid packet")

query_status_t deal_with_ottd_packet(struct qserver *server, char *rawpkt, int pktlen)
{
	unsigned char *ptr = (unsigned char*)rawpkt;
	unsigned char *end = (unsigned char*)(rawpkt + pktlen);
	unsigned char type;
	char* str;
	char buf[32];
	unsigned ver;

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

	FAIL_IF(pktlen < 4 || swap_short_from_little(rawpkt) > pktlen, "invalid packet");

	type = ptr[2];
	ver = ptr[3];
	ptr += 4;

	debug(3, "len %hu type %hhu ver %hhu", swap_short_from_little(rawpkt), type, ver);

	FAIL_IF(ver != 4 && ver != 5, "only version 4 and 5 servers are supported");

	if(type == 1) // info packet
	{
		unsigned numgrf = *ptr;
		FAIL_IF(ptr + numgrf * 20 + 1 > end, "invalid newgrf number");
		ptr += numgrf * 20 + 1;

		snprintf(buf, sizeof(buf), "%u", swap_long_from_little(ptr));
		add_rule(server, "date_days", buf, NO_FLAGS);
		ptr += 4;

		snprintf(buf, sizeof(buf), "%u", swap_long_from_little(ptr));
		add_rule(server, "startdate_days", buf, NO_FLAGS);
		ptr += 4;

		FAIL_IF(ptr + 3 > end, "invalid packet");

		snprintf(buf, sizeof(buf), "%hhu", ptr[0]);
		add_rule(server, "maxcompanies", buf, NO_FLAGS);
		snprintf(buf, sizeof(buf), "%hhu", ptr[1]);
		add_rule(server, "numcompanies", buf, NO_FLAGS);
		server->max_spectators = ptr[2];
		ptr += 3;

		GET_STRING;
		server->server_name = strdup(str);

		GET_STRING;
		add_rule(server, "version", str, NO_FLAGS);

		FAIL_IF(ptr + 7 > end, "invalid packet");

		{
			static const char* langs[] = {
				"any",
				"English",
				"German",
				"French"
			};
			unsigned i = *ptr++;
			if(i > 3) i = 0;
			add_rule(server, "language", (char*)langs[i], NO_FLAGS);
		}

		add_rule(server, "password", *ptr++ ? "1" : "0", NO_FLAGS);

		server->max_players = *ptr++;
		server->num_players = *ptr++;
		server->num_spectators = *ptr++;

		GET_STRING;

		server->map_name = strdup(str);

		snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr));
		add_rule(server, "map_width", buf, NO_FLAGS);
		snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr));
		add_rule(server, "map_height", buf, NO_FLAGS);

		{
			static const char* sets[] = {
				"temperate",
				"arctic",
				"desert",
				"toyland"
			};
			unsigned i = *ptr++;
			if(i > 3) i = 0;
			add_rule(server, "map_set", (char*)sets[i], NO_FLAGS);
		}

		add_rule(server, "dedicated", *ptr++ ? "1" : "0", NO_FLAGS);
	}
	else if(type == 3) // player packet
	{
		unsigned i, j;
		INVALID_IF(ptr + 2 > end);

		server->num_players = *ptr++;

		for(i = 0; i < server->num_players; ++i)
		{
			unsigned long long lli;
			struct player* player;
			unsigned char nr;

			nr = *ptr++;

			debug(3, "player number %d", nr);
			player = add_player(server, i);
			FAIL_IF(!player, "can't allocate player");

			GET_STRING;
			player->name = strdup(str);
			debug(3, "name %s", str);
			player->frags = 0;

			INVALID_IF(ptr + 4 + 3*8 + 2 + 1 + 2*MAX_VEHICLE_TYPES + 2*MAX_STATION_TYPES > end);

			snprintf(buf, sizeof(buf), "%u", swap_long_from_little(ptr));
			player_add_info(player, "startdate", buf, 0);
			ptr += 4;

			lli = swap_long_from_little(ptr+4);
			lli <<= 32;
			lli += swap_long_from_little(ptr);
			snprintf(buf, sizeof(buf), "%lld", lli);
			player_add_info(player, "value", buf, 0);
			ptr += 8;

			lli = swap_long_from_little(ptr+4);
			lli <<= 32;
			lli = swap_long_from_little(ptr);
			snprintf(buf, sizeof(buf), "%lld", lli);
			player_add_info(player, "money", buf, 0);
			ptr += 8;

			lli = swap_long_from_little(ptr+4);
			lli <<= 32;
			lli += swap_long_from_little(ptr);
			snprintf(buf, sizeof(buf), "%lld", lli);
			player_add_info(player, "income", buf, 0);
			ptr += 8;

			snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr));
			player_add_info(player, "performance", buf, 0);
			ptr += 2;

			player_add_info(player, "password", *ptr?"1":"0", 0);
			++ptr;

			for (j = 0; j < MAX_VEHICLE_TYPES; ++j)
			{
				snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr));
				player_add_info(player, (char*)vehicle_types[j], buf, 0);
				ptr += 2;
			}
			for (j = 0; j < MAX_STATION_TYPES; ++j)
			{
				snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr));
				player_add_info(player, (char*)station_types[j], buf, 0);
				ptr += 2;
			}
			
			if (ver != 5)
			{
				// connections
				while(ptr + 1 < end && *ptr)
				{
					++ptr;
					GET_STRING; // client name
					debug(3, "%s played by %s", str, player->name);
					GET_STRING; // id
					INVALID_IF(ptr + 4 > end);
					ptr += 4;
				}

				++ptr; // record terminated by zero byte
			}
		}

		// spectators
		while(ptr + 1 < end && *ptr)
		{
			++ptr;
			GET_STRING; // client name
			debug(3, "spectator %s", str);
			GET_STRING; // id
			INVALID_IF(ptr + 4 > end);
			ptr += 4;
		}
		++ptr; // record terminated by zero byte

		server->next_rule = NO_SERVER_RULES; // we're done
		server->next_player_info = server->num_players; // we're done
	}
	else
	{
		malformed_packet( server, "invalid type" );
		return PKT_ERROR;
	}

	server->retry1 = n_retries; // we're done with this packet, reset retry counter

	return DONE_AUTO;
}

query_status_t send_ottdmaster_request_packet(struct qserver *server)
{
	return qserver_send_initial(server, server->type->master_packet, server->type->master_len);
}

query_status_t send_ottd_request_packet(struct qserver *server)
{
	qserver_send_initial(server, server->type->status_packet, server->type->status_len);

	if(get_server_rules || get_player_info)
	{
		server->next_rule = ""; // trigger calling send_a2s_rule_request_packet
	}

	return INPROGRESS;
}
