/*
 * 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 "server.h"
#include "console.h"
#include "network.h"
#include "client.h"
#include "packet.h"


#define CLIENT_OVERFLOW   8


static ENetHost        *_server             = NULL;
static pthread_t        _serverThreadHandle = { 0 };
static void            *_serverThreadStatus = NULL;
static uint32_t         _serverMaxClients   = 0;
static uint32_t         _serverClients      = 0;
static pthread_mutex_t  _serverMutex        = { 0 };


static void serverPacketSender(char *data, uint32_t length, void *userData);


void serverDisconnectClient(ClientThreadT *client) {
	ENetPeer *peer         = (ENetPeer *)client->peer;
	void     *status       = NULL;
	char      buffer[2048] = { 0 };

	// Tell the console.
	enet_address_get_host_ip(&peer->address, buffer, 2047);
	consoleMessageQueue("%ld: [%s] disconnected.\n", client->threadIndex, buffer);
	// Stop client processing.
	client->running = 0;
	pthread_join(client->threadHandle, &status);
	// Hang up.
	enet_peer_reset(peer);
	// Adjust client count.
	pthread_mutex_lock(&_serverMutex);
	_serverClients--;
	pthread_mutex_unlock(&_serverMutex);
}


static void serverPacketSender(char *data, uint32_t length, void *userData) {
	ENetPeer   *peer   = (ENetPeer *)userData;
	ENetPacket *packet = NULL;

	// Send packet.
	packet = enet_packet_create(data, length, ENET_PACKET_FLAG_RELIABLE);
	enet_peer_send(peer, 0, packet);
}


void serverShutdown(void) {
	pthread_join(_serverThreadHandle, &_serverThreadStatus);
	pthread_mutex_destroy(&_serverMutex);
	enet_host_destroy(_server);
}


void serverStartup(uint32_t port, uint32_t maxClients) {
	ENetAddress     address                = { 0 };
	pthread_attr_t  serverThreadAttributes = { 0 };

	_serverMaxClients = maxClients;

	// Tell the packet code how to send packets.
	packetSenderRegister(serverPacketSender);

	// Set up listening socket.
	address.host = ENET_HOST_ANY;
	address.port = port;
	_server = enet_host_create(&address,                       /* the address to bind the server host to */
								maxClients + CLIENT_OVERFLOW,  /* allow up to XX clients and/or outgoing connections (some extra so we can send "server full" messages) */
								1,                             /* allow up to 2 channels to be used, 0 and 1 */
								0,                             /* assume any amount of incoming bandwidth */
								0                              /* assume any amount of outgoing bandwidth */
	);
	if (_server == NULL) utilDie("Unable to open server listening port.\n");

	// Start server thread.
	memset(&_serverMutex, 0, sizeof(pthread_mutex_t));
	pthread_mutex_init(&_serverMutex, NULL);
	if (pthread_attr_init(&serverThreadAttributes) != 0) utilDie("Unable to create server thread attributes.\n");
	pthread_attr_setdetachstate(&serverThreadAttributes, PTHREAD_CREATE_JOINABLE);
	if (pthread_create(&_serverThreadHandle, &serverThreadAttributes, serverThread, (void *)_server) != 0) utilDie("Unable to start server thread.\n");
}


void *serverThread(void *data) {
	ENetHost         *server                 = (ENetHost *)data;
	ENetEvent         event                  = { 0 };
	ENetPacket       *packet                 = NULL;
	ClientThreadT    *client                 = NULL;
	uint64_t          nextIndex              = 0;
	size_t            i                      = 0;
	char              buffer[2048]           = { 0 };

	while (__running) {
		while (enet_host_service(server, &event, 1) > 0) {
			switch (event.type) {
				case ENET_EVENT_TYPE_NONE:
					break;

				case ENET_EVENT_TYPE_CONNECT:
					// Is the server full?
					if (_serverClients >= _serverMaxClients) {
						// Send full banner to client.
						packet = enet_packet_create("KPMPGSMKII\rFULL\r", 16, ENET_PACKET_FLAG_RELIABLE);
						enet_peer_send(event.peer, 0, packet);
						break;
					}
					// Create new client.
					pthread_mutex_lock(&_serverMutex);
					_serverClients++;
					pthread_mutex_unlock(&_serverMutex);
					NEW(ClientThreadT, client);
					if (!client) utilDie("Unable to allocate new client.\n");
					// System Stuff.
					client->packetThreadData = packetThreadDataCreate(event.peer);
					if (!client->packetThreadData) {
						utilDie("Unable to allocate packetThreadData for new client.\n");
						break;  // This break silences an invalid clang warning.
					}
					for (i=0; i<PING_STATS_SIZE; i++) client->pingStats[i] = 0;
					memset(&client->packetQueueMutex, 0, sizeof(pthread_mutex_t));
					pthread_mutex_init(&client->packetQueueMutex, NULL);
					client->threadIndex      = nextIndex++;
					client->running          = 1;
					client->packetQueue      = NULL;
					client->peer             = event.peer;
					client->pingHead         = 0;
					client->pingAverage      = 0;
					client->pingHigh         = 0;
					client->pingLow          = 9999;
					// User State Stuff.
					client->authenticated    = 0;
					client->handle           = NULL;
					client->fileSize         = 0;
					// Keep our client in the peer data for later.
					event.peer->data = (void *)client;
					// Make new thread for this client.
					if (pthread_attr_init(&client->threadAttributes) != 0) utilDie("Unable to create client thread attributes.\n");
					pthread_attr_setdetachstate(&client->threadAttributes, PTHREAD_CREATE_JOINABLE);
					if (pthread_create(&client->threadHandle, &client->threadAttributes, clientThread, (void *)event.peer) != 0) utilDie("Unable to start client thread.\n");
					// Tell the console.
					enet_address_get_host_ip(&event.peer->address, buffer, 2047);
					consoleMessageQueue("%ld: [%s] connected.\n", client->threadIndex, buffer);
					// ***TODO*** Should probably cleanly handle a full server here with some kind of SERVER_FULL packet.
					// Send banner to client.
					packet = enet_packet_create("KPMPGSMKII\rOKAY\r", 16, ENET_PACKET_FLAG_RELIABLE);
					enet_peer_send(event.peer, 0, packet);
					break;

				case ENET_EVENT_TYPE_RECEIVE:
					// Send the raw packet to the client thread for processing.
					clientQueuePacket((ClientThreadT *)event.peer->data, event.packet->data, event.packet->dataLength);
					enet_packet_destroy(event.packet);
					break;

				case ENET_EVENT_TYPE_DISCONNECT:
				case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
					serverDisconnectClient((ClientThreadT *)event.peer->data);
					break;
			}
		}
	}

	// Anyone still running, be rude.  Nuke 'em.
	for (i=0; i<server->peerCount; i++) {
		client = (ClientThreadT *)server->peers[i].data;
		if (client) {
			serverDisconnectClient(client);
		}
	}

	pthread_exit(NULL);
}
