/*
 * 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"


static uint8_t clientDequeuePacket(ClientThreadT *client);
static void    clientProcessPacket(ClientThreadT *client, PacketDecodeDataT *data);


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)) {
		clientProcessPacket(client, &decode);
		DEL(decode.data);
		if (packet) {
			DEL(packet->data);
			DEL(packet);
		}
		return 1;
	}

	// No packet.
	return 0;
}


static void clientProcessPacket(ClientThreadT *client, PacketDecodeDataT *data) {
	PacketEncodeDataT encoded = { 0 };

	switch (data->packetType) {
		case PACKET_TYPE_CLIENT_SHUTDOWN:
			serverDisconnectClient(client);
			break;

		case PACKET_TYPE_LOGIN:
			consoleMessageQueue("%ld: Channel %d %s %s\n", client->threadIndex, data->channel, ((PacketTypeLoginT *)data->data)->user, ((PacketTypeLoginT *)data->data)->pass);
			break;

		case PACKET_TYPE_PING:
			// Build PONG packet.
			encoded.control    = PACKET_CONTROL_DAT;
			encoded.packetType = PACKET_TYPE_PONG;
			encoded.channel    = 0;
			encoded.encrypt    = 0;
			packetEncode(client->packetThreadData, &encoded, NULL, 0);
			// Send it.
			packetSend(client->packetThreadData, &encoded);
			logWrite("Got PING, sent PONG\n\r");
			break;

		default:
			consoleMessageQueue("%ld: Channel %d Unknown Packet %d\n", client->threadIndex, data->channel, data->packetType);
			break;
	}
}


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

		//consoleMessageQueue("%ld: Channel %d Bytes in %d\n", client->threadIndex, length);
		/*
		logWrite("Bytes %d\n\r", length);
		for (size_t x=0; x<length; x++) {
			logWrite("[%x] '%c'\n\r", data[x], data[x]);
		}
		*/

		pthread_mutex_lock(&client->packetQueueMutex);
		arrput(client->packetQueue, packet);
		pthread_mutex_unlock(&client->packetQueueMutex);
	}
}


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.
	PacketTypeVersionT version     = { 0 };
	uint8_t            versionSent = 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()) {
					version.version    = PACKET_PROTOCOL_VERSION;
					encoded.control    = PACKET_CONTROL_DAT;
					encoded.packetType = PACKET_TYPE_VERSION;
					encoded.channel    = 0;
					encoded.encrypt    = 0;
					packetEncode(client->packetThreadData, &encoded, (char *)&version, sizeof(PacketTypeVersionT));
					packetSend(client->packetThreadData, &encoded);
					versionSent = 1;
				}
			}

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

	// Clean up client data on the way out.
	arrfree(client->packetQueue);
	pthread_mutex_destroy(&client->packetQueueMutex);

	pthread_exit(NULL);
}
