/*
 * 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 "database.h"
#include "settings.h"

#include "file.h"


static void clientApiFileFailed(ClientThreadT *client, PacketDecodeDataT *data);
static void clientApiFileRequestCheck(ClientThreadT *client, PacketDecodeDataT *data);
static void clientApiFileRequestNext(ClientThreadT *client, PacketDecodeDataT *data);


static void clientApiFileFailed(ClientThreadT *client, PacketDecodeDataT *data) {
	PacketEncodeDataT  encoded      = { 0 };
	uint8_t           *packetData   = NULL;
	uint16_t           length       = 0;

	packetData = packetContentPack(&length, "i", FILE_RESPONSE_UNKNOWN);
	// Build packet.
	encoded.control    = PACKET_CONTROL_DAT;
	encoded.packetType = PACKET_TYPE_FILE_RESPONSE;
	encoded.channel    = data->channel;
	encoded.encrypt    = 0;
	packetEncode(client->packetThreadData, &encoded, packetData, length);
	// Send it.
	packetSend(client->packetThreadData, &encoded);
	DEL(packetData);
}


void clientApiFileRequest(ClientThreadT *client, PacketDecodeDataT *data) {
	FileRequestsT      request      = 0;

	BEGIN

	// Extract the request type.  We get more data later.
	packetContentUnpack(data->data, "i", &request);

	switch (request) {
		case FILE_REQUEST_CHECK:
			clientApiFileRequestCheck(client, data);
			break;

		case FILE_REQUEST_NEXT:
			clientApiFileRequestNext(client, data);
			break;

		default:
			// No idea what they want.
			clientApiFileFailed(client, data);
			break;
	}

	END
}


static void clientApiFileRequestCheck(ClientThreadT *client, PacketDecodeDataT *data) {
	FileRequestsT      request       = 0;
	char              *path          = NULL;
	char              *sha256        = NULL;
	PacketEncodeDataT  encoded       = { 0 };
	uint8_t           *packetData    = NULL;
	uint16_t           length        = 0;
	char               shaInDB[128]  = { 0 };
	char               buffer[1024]  = { 0 };
	char               buffer2[2064] = { 0 };
	uint32_t           temp          = 0;
	DbFileInfoT       *info          = NULL;
	uint8_t            mustAuth      = 1;

	BEGIN

	// Is something still open?
	if (client->handle) {
		fclose(client->handle);
		client->handle = NULL;
	}

	// Extract the request.
	packetContentUnpack(data->data, "iss", &request, &sha256, &path);

	// Get file info we need.
	info = dbFileInfoGet(path);
	strncpy(shaInDB, info->sha, 128);
	mustAuth = info->authenticated;
	dbFileInfoRelease(&info);

	// Are they allowed to check this file while un-authenticated?
	if (!client->authenticated && mustAuth) {
		logWrite("Unauthenticated user requested file:  %s\n", path);
		clientApiFileFailed(client, data);
		return;
	}

	// Look up the entry and compare SHA256.
	if (strcasecmp(sha256, shaInDB) == 0) {
		logWrite("SHAs match for %s\n", path);
		// File is already current on client.
		temp               = FILE_RESPONSE_OKAY;
		packetData         = packetContentPack(&length, "i", temp);
		encoded.control    = PACKET_CONTROL_DAT;
		encoded.packetType = PACKET_TYPE_FILE_RESPONSE;
		encoded.channel    = data->channel;
		encoded.encrypt    = 0;
		packetEncode(client->packetThreadData, &encoded, packetData, length);
		packetSend(client->packetThreadData, &encoded);
		DEL(packetData);
	} else {
		logWrite("SHAs do NOT match for %s\n", path);
		// Get real path by flipping colons to slashes.
		temp = 0;
		while (path[temp] != 0) {
			if (path[temp] == ':') {
				buffer[temp] = '/';
			} else {
				buffer[temp] = path[temp];
			}
			temp++;
		}
		buffer[temp] = 0;
		snprintf(buffer2, 2064, "%s/files/%s", __settingsFile, buffer);
		// Open file & get file size.
		client->handle = fopen(buffer2, "rb");
		if (!client->handle) {
			logWrite("Unable to open file:  %s\n", buffer2);
			DEL(sha256);
			DEL(path);
			// ***TODO*** Handle error
			return;
		}
		fseek(client->handle, 0, SEEK_END);
		client->fileSize = ftell(client->handle);
		fseek(client->handle, 0, SEEK_SET);

		logWrite("Sending %s - %d bytes\n", buffer2, client->fileSize);

		// Send file metadata to start transfer
		temp               = FILE_RESPONSE_SEND;
		packetData         = packetContentPack(&length, "iis", temp, client->fileSize, shaInDB);
		encoded.control    = PACKET_CONTROL_DAT;
		encoded.packetType = PACKET_TYPE_FILE_RESPONSE;
		encoded.channel    = data->channel;
		encoded.encrypt    = 0;
		packetEncode(client->packetThreadData, &encoded, packetData, length);
		packetSend(client->packetThreadData, &encoded);
		DEL(packetData);

		DEL(sha256);
		DEL(path);

		// Start sending actual file data.
		clientApiFileRequestNext(client, data);
	}

	END
}


static void clientApiFileRequestNext(ClientThreadT *client, PacketDecodeDataT *data) {
	uint32_t           temp         = 0;
	PacketEncodeDataT  encoded      = { 0 };
	uint16_t           length       = 0;
	uint8_t            packetData[PACKET_MAX];

	BEGIN

	// Do we have an open file?
	if (client->handle) {
		// Add response type.
		temp = FILE_RESPONSE_DATA;
		memcpy(&packetData[0], &temp, sizeof(int32_t));

		// Add file data.
		// ***TODO*** We can't send quite a full packet for some reason.
		length = client->fileSize - ftell(client->handle);
		if (length > PACKET_MAX - (10 + sizeof(int32_t))) {
			// File is larger than a packet size.
			logWrite("Sending file fraction %d bytes\n", length);
			length = PACKET_MAX - (10 + sizeof(int32_t)); // 4 for integer of response type.  10 more because a full PACKET_MAX causes issues.
			fread(&packetData[sizeof(int32_t)], length, 1, client->handle);
		} else {
			// File remains will fit in this packet.
			logWrite("Sending file end %d bytes\n", length);
			fread(&packetData[sizeof(int32_t)], length, 1, client->handle);
			fclose(client->handle);
			client->handle = NULL;
		}

		// Build packet.
		encoded.control    = PACKET_CONTROL_DAT;
		encoded.packetType = PACKET_TYPE_FILE_RESPONSE;
		encoded.channel    = data->channel;
		encoded.encrypt    = 0;
		packetEncode(client->packetThreadData, &encoded, packetData, length + sizeof(int32_t));
		// Send it.
		packetSend(client->packetThreadData, &encoded);
	}

	END
}
