/*
 * Kangaroo Punch MultiPlayer Game Server Mark II
 * Copyright (C) 2020-2021  Scott Duensing
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */


#include "client.h"
#include "array.h"
#include "network.h"
#include "console.h"
#include "packet.h"
#include "server.h"

#include "client/login.h"
#include "client/signup.h"
#include "client/pong.h"
#include "client/version.h"
#include "client/shutdown.h"
#include "client/file.h"


typedef void (*clientApi)(ClientThreadT *client, PacketDecodeDataT *data);


// Also update enum in packets.h!
static clientApi _clientApiMethod[PACKET_TYPE_COUNT];


static uint8_t clientDequeuePacket(ClientThreadT *client);


static uint8_t clientDequeuePacket(ClientThreadT *client) {
	uint16_t           length  = 0;
	char              *data    = NULL;
	PacketDecodeDataT  decode  = { 0 };
	ClientRawPacketT  *packet  = NULL;

	// Is there new data to process?
	pthread_mutex_lock(&client->packetQueueMutex);
	if (arrlenu(client->packetQueue) > 0) {
		packet = client->packetQueue[0];
		length = packet->length;
		data   = packet->data;
		arrdel(client->packetQueue, 0);
	}
	pthread_mutex_unlock(&client->packetQueueMutex);

	// New data or not, process anything in the queue.
	if (packetDecode(client->packetThreadData, &decode, data, length)) {
		if (_clientApiMethod[decode.packetType]) {
			_clientApiMethod[decode.packetType](client, &decode);
		} else {
			consoleMessageQueue("%ld: Channel %d Unknown Packet %d\n", client->threadIndex, decode.channel, decode.packetType);
		}
		if (decode.data) DEL(decode.data);
		if (packet) {
			DEL(packet->data);
			DEL(packet);
		}
		return 1;
	}

	// No packet.
	return 0;
}


void clientQueuePacket(ClientThreadT *client, uint8_t *data, uint32_t length) {
	ClientRawPacketT *packet = NULL;

	packet = (ClientRawPacketT *)malloc(sizeof(ClientRawPacketT));
	if (packet) {
		packet->data = (char *)malloc(length);
		if (packet->data) {
			memcpy(packet->data, data, length);
			packet->length = length;
		}
		pthread_mutex_lock(&client->packetQueueMutex);
		arrput(client->packetQueue, packet);
		pthread_mutex_unlock(&client->packetQueueMutex);
	}
}


void clientShutdown(void) {
	// Nothing yet.
}


void clientStartup(void) {
	memset(_clientApiMethod, 0, sizeof(_clientApiMethod));

	_clientApiMethod[PACKET_TYPE_CLIENT_SHUTDOWN] = clientApiClientShutdown;
	_clientApiMethod[PACKET_TYPE_FILE_REQUEST]    = clientApiFileRequest;
	_clientApiMethod[PACKET_TYPE_LOGIN]           = clientApiLogin;
	_clientApiMethod[PACKET_TYPE_PONG]            = clientApiPong;
	_clientApiMethod[PACKET_TYPE_SIGNUP]          = clientApiSignup;
	_clientApiMethod[PACKET_TYPE_VERSION_BAD]     = clientApiVersionBad;
	_clientApiMethod[PACKET_TYPE_VERSION_OKAY]    = clientApiVersionOkay;
}


void *clientThread(void *data) {

	ENetPeer          *peer        = (ENetPeer *)data;
	ClientThreadT     *client      = (ClientThreadT *)peer->data;
	PacketEncodeDataT  encoded     = { 0 };
	struct timespec    remaining   = { 0, 0 };
	struct timespec    sleepTime   = { 0, 1000000000/100 };  // 1/100th second.
	uint8_t            versionSent = 0;
	time_t             ticks       = { 0 };
	time_t             lastTicks   = { 0 };
	int8_t             pingTimeout = 0;
	char              *packetData  = NULL;
	uint16_t           length      = 0;

	// Process packets until we're done.
	while (client->running) {
		if (!clientDequeuePacket(client)) {

			// Start communications with client as soon as encryption channel is ready.
			if (!versionSent) {
				if (packetEncryptionReady()) {
					packetData = packetContentPack(&length, "i", PACKET_PROTOCOL_VERSION);
					// Send required protocol version.
					encoded.control    = PACKET_CONTROL_DAT;
					encoded.packetType = PACKET_TYPE_VERSION;
					encoded.channel    = 0;
					encoded.encrypt    = 0;
					packetEncode(client->packetThreadData, &encoded, packetData, length);
					packetSend(client->packetThreadData, &encoded);
					DEL(packetData);
					versionSent = 1;
					consoleMessageQueue("PACKET_TYPE_VERSION sent.\n");
				}
			}

			// Ping the client every 5 seconds.
			ticks = time(NULL);
			if (ticks != lastTicks) {
				lastTicks = ticks;
				pingTimeout++;
				if (pingTimeout >= 5) {
					pingTimeout = 0;
					encoded.control    = PACKET_CONTROL_DAT;
					encoded.packetType = PACKET_TYPE_PING;
					encoded.channel    = 0;
					encoded.encrypt    = 0;
					packetEncode(client->packetThreadData, &encoded, NULL, 0);
					packetSend(client->packetThreadData, &encoded);
					clock_gettime(CLOCK_MONOTONIC_RAW, &client->pingStart);
				}
			}

			// Don't eat all the CPU.
			nanosleep(&remaining, &sleepTime);
		}
	}

	// ***TODO*** Write ping stats to database.

	// Clean up client data on the way out.
	while (arrlen(client->packetQueue) > 0) {
		if (client->packetQueue[0]->data) DEL(client->packetQueue[0]->data);
		arrdel(client->packetQueue, 0);
	}
	arrfree(client->packetQueue);

	pthread_mutex_destroy(&client->packetQueueMutex);
	packetThreadDataDestroy(&client->packetThreadData);

	if (client->handle) fclose(client->handle);

	DEL(client);

	pthread_exit(NULL);
}
