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

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

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

typedef enum {
	SM_INT = 0x01,
	SM_LONG,
	SM_FLOAT,
	SM_STRING,
	SM_BOOLEAN,
	SM_BYTE,
	SM_SHORT,
	SM_BYTE_ARRAY
} starmade_param_type;


query_status_t
send_starmade_request_packet(struct qserver *server)
{
	char buf[13] = { 0x00, 0x00, 0x00, 0x09, 0x2a, 0xff, 0xff, 0x01, 0x6f, 0x00, 0x00, 0x00, 0x00 };

	/*
	 * Header
	 * int(4)	- packet length
	 * byte		- packet type : 0x2a = packet, 0x20 = ping, test = 0x64, logout = 0x41
	 * short(2)	- packet id
	 * byte		- command id : 0x01 = status, 0x02 = exec
	 * byte		- command type : 0x6f = parameterised command, 0x84 = stream command
	 *
	 * Paramters
	 * int(4)	- paramter count
	 * byte		- paramter type : 1 = int, 2 = long, 3 = float, 4 = UTF8 string, 5 = boolean, 6 = byte, 7 = short, 8 = byte array
	 * ...
	 */

	debug(3, "send_starmade_request_packet: state = %ld", server->challenge);

	return send_packet(server, buf, 13);
}

static query_status_t
starmade_read_parameter(char **datap, int *datalen, void *val, int vlen, starmade_param_type type)
{
	int size;

	if (**datap != type) {
		/* unexpected type */
		debug(2, "Invalid type detected, expected 0x%02x got 0x%02x", type, **datap);
		return REQ_ERROR;
	}

	(*datap)++;
	(*datalen)--;

	switch(type) {
	case SM_INT:
		size = 4;
		if (size > vlen || size > *datalen) {
			return MEM_ERROR;
		}
		*(uint32_t*)val = ((uint32_t)(*datap)[3]) |
			((uint32_t)(*datap)[2] << 8) |
			((uint32_t)(*datap)[1] << 16) |
			((uint32_t)(*datap)[0] << 24);
		break;
	case SM_LONG:
		size = 8;
		if (size > vlen || size > *datalen) {
			return MEM_ERROR;
		}
		*(uint64_t*)val = ((uint64_t)(*datap)[7]) |
			((uint64_t)(*datap)[6] << 8) |
			((uint64_t)(*datap)[5] << 16) |
			((uint64_t)(*datap)[4] << 24) |
			((uint64_t)(*datap)[3] << 32) |
			((uint64_t)(*datap)[2] << 40) |
			((uint64_t)(*datap)[1] << 48) |
			((uint64_t)(*datap)[0] << 56);
		break;
	case SM_FLOAT:
		size = 4;
		if (size > vlen || size > *datalen) {
			return MEM_ERROR;
		}
		*(uint32_t*)val = (uint32_t)(*datap)[3] |
			((uint32_t)(*datap)[2] << 8) |
			((uint32_t)(*datap)[1] << 16) |
			((uint32_t)(*datap)[0] << 24);
		break;
	case SM_STRING:
		size = ((short)(*datap)[1]) | ((short)(*datap)[0] << 8);
		if (size > vlen || size > *datalen) {
			return MEM_ERROR;
		}
		(*datap) += 2;
		memcpy(val, *datap, size);
		((char*)val)[size] = 0x00;
		break;
	case SM_BOOLEAN:
		size = 1;
		if (size > vlen || size > *datalen) {
			return MEM_ERROR;
		}
		*(unsigned char*)val = **datap;
		break;
	case SM_BYTE:
		size = 1;
		if (size > vlen || size > *datalen) {
			return MEM_ERROR;
		}
		*(unsigned char*)val = **datap;
		break;
	case SM_SHORT:
		size = 2;
		if (size > vlen || size > *datalen) {
			return MEM_ERROR;
		}
		*(short*)val = (short)(*datap)[1] | ((short)(*datap)[0] << 8);
		break;
	case SM_BYTE_ARRAY:
		debug(2, "Unsupport type 0x%02x requested / received", type);
		return REQ_ERROR;
	default:
		debug(2, "Unknown type 0x%02x requested / received", type);
		return REQ_ERROR;
	}

	(*datap) += size;
	(*datalen) -= size;

	return INPROGRESS;
}

query_status_t
deal_with_starmade_packet(struct qserver *server, char *rawpkt, int pktlen)
{
	char *s, buf[256];
	int ret;

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

	if (pktlen < 54) {
		// Invalid password
		return REQ_ERROR;
	}

	s = rawpkt;

	// TODO: continue reading / combining packets until we've read enough.
	debug(3, "packet: combined = %d, challenge = %ld", server->combined, server->challenge);
	// Packet Header
	// int - response size excluding header (12 bytes)
	s += 4;
	// long - ts??
	s += 8;

	// Response Header
	// TODO: validate header details
	// byte - packet type
	s += 1;
	// short - packet id
	s += 2;
	// byte - command id
	s += 1;
	// byte - command type
	s += 1;

	// Parameters
	// int - paramater count
	s += 4;

	// byte - info version
	ret = starmade_read_parameter(&s, &pktlen, buf, sizeof(buf), SM_BYTE);
	if (ret != INPROGRESS)
		return ret;

	// float - version
	ret = starmade_read_parameter(&s, &pktlen, buf, sizeof(buf), SM_FLOAT);
	if (ret != INPROGRESS)
		return ret;

	// string - server name
	ret = starmade_read_parameter(&s, &pktlen, buf, sizeof(buf), SM_STRING);
	if (ret != INPROGRESS)
		return ret;
	server->server_name = strdup(buf);

	// string - description
	ret = starmade_read_parameter(&s, &pktlen, buf, sizeof(buf), SM_STRING);
	if (ret != INPROGRESS)
		return ret;

	// long - start time
	ret = starmade_read_parameter(&s, &pktlen, buf, sizeof(buf), SM_LONG);
	if (ret != INPROGRESS)
		return ret;

	// int - player count
	ret = starmade_read_parameter(&s, &pktlen, &server->num_players, sizeof(server->num_players), SM_INT);
	if (ret != INPROGRESS)
		return ret;

	// int - max players
	ret = starmade_read_parameter(&s, &pktlen, &server->max_players, sizeof(server->max_players), SM_INT);
	if (ret != INPROGRESS)
		return ret;

	gettimeofday(&server->packet_time1, NULL);

	server->map_name = strdup("default");

	return DONE_FORCE;
}

