/*
 * 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 "gui.h"
#include "comport.h"
#include "config.h"
#include "timer.h"
#include "runtime.h"

#include "network.h"


typedef struct NetworkChannelsS {
	uint8_t          key;
	netPacketHandler value;
} NetworkChannelsT;


static netPacketHandler *_systemHandlers = NULL;
static NetworkChannelsT *_channels       = NULL;
static uint8_t           _netRunning     = 0;
static uint8_t           _netStarted     = 0;


uint8_t netChannelGet(netPacketHandler handler) {
	uint8_t  channel = 0;
	uint16_t x       = 0;

	// We reserve 0 for system stuff.
	// Returning 0 means no channel was found.

	// Find first unused channel.
	for (x=1; x<255; x++) {
		if (hmgeti(_channels, x) < 0) {
			channel = x;
			hmput(_channels, x, handler);
			break;
		}
	}

	return channel;
}


void netChannelRelease(uint8_t channel) {
	hmdel(_channels, channel);
}


void netChannelSystemGet(netPacketHandler handler) {
	arrput(_systemHandlers, handler);
}


void netChannelSystemRelease(netPacketHandler handler) {
	uint16_t x;

	for (x=0; x<arrlen(_systemHandlers); /* no increment */) {
		if (_systemHandlers[x] == handler) {
			arrdel(_systemHandlers, x);
		} else {
			x++;
		}
	}
}


void netPacketHandlerStart(void) {
	_netRunning = 1;
}


void netPacketHandlerStop(void) {
	if (_netRunning) {
		_netRunning = 0;

		// Return all channels.
		while (hmlen(_channels)) hmdel(_channels, _channels[0].key);
		hmfree(_channels);

		// Return all system channels.
		while (arrlen(_systemHandlers)) arrdel(_systemHandlers, 0);
		arrfree(_systemHandlers);
	}
}


void netProcess(void) {
	int32_t             r;
	char                buffer[1024];
	PacketDecodeDataT  *packet;
	PacketDecodeDataT   decoded;
	PacketEncodeDataT   encoded;

	// ***TODO*** Detect disconnection.  Maybe have callbacks registered that can notify tasks?

	if (_netRunning) {

		// Read pending bytes.
		r = comRead(__configData.serialCom - 1, buffer, 1024);
		// New data or not, process anything in the queue.
		if (packetDecode(__packetThreadData, &decoded, buffer, r)) {
			switch (decoded.packetType) {
				case PACKET_TYPE_PING:
					// Reply with PONG
					encoded.control    = PACKET_CONTROL_DAT;
					encoded.packetType = PACKET_TYPE_PONG;
					encoded.channel    = 0;
					encoded.encrypt    = 0;
					packetEncode(__packetThreadData, &encoded, NULL, 0);
					packetSend(__packetThreadData, &encoded);
					packetDecodeDataStaticDestroy(&decoded);
					break;

				default:
					// Is this the system channel?
					if (decoded.channel == 0) {
						// Send it to all registered handlers.
						for (r=0; r<arrlen(_systemHandlers); r++) {
							// Copy the packet out.
							NEW(PacketDecodeDataT, packet);
							packet->channel    = decoded.channel;
							packet->length     = decoded.length;
							packet->packetType = decoded.packetType;
							packet->data = (char *)malloc(decoded.length);
							memcpy(packet->data, decoded.data, decoded.length);
							// Send it to the subscriber.
							_systemHandlers[r](packet);
						}
					} else {
						// Does someone want this channel?
						r = hmgeti(_channels, decoded.channel);
						if (r >= 0) {
							// Copy the packet out.
							NEW(PacketDecodeDataT, packet);
							packet->channel    = decoded.channel;
							packet->length     = decoded.length;
							packet->packetType = decoded.packetType;
							packet->data = (char *)malloc(decoded.length);
							memcpy(packet->data, decoded.data, decoded.length);
							// Send it to the subscriber.
							_channels[r].value(packet);
						}
					}
					// Destroy our copy.
					packetDecodeDataStaticDestroy(&decoded);
					break;
			}
		}

	}  // _netRunning
}


void netShutdown(void) {
	netPacketHandlerStop();
	if (_netStarted) {
		_netStarted = 0;
		packetThreadDataDestroy(&__packetThreadData);
	}
}


void netStartup(void) {
	if (!_netStarted) {
		_netStarted = 1;
		__packetThreadData = packetThreadDataCreate(NULL);
		packetSenderRegister(comPacketSender);
	}
}
