/*
 * 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 <termios.h>
#include <sys/select.h>

#include "console.h"
#include "array.h"
#include "util.h"


static struct termios           _termios             = { 0 };
static        char            **_consoleMessageQueue = NULL;
static        pthread_mutex_t   _messageQueueMutex   = PTHREAD_MUTEX_INITIALIZER;

static uint8_t  getch(void);
static uint8_t  kbhit(void);
static void     sendToConsole(const char *message, ...);
static void     terminalModeConioSet(void);
static void     terminalModeOriginalSet(void);


void consoleMessageQueue(const char *message, ...) {
	va_list args         = { 0 };
	char    buffer[2048] = { 0 };
	char   *newMessage   = NULL;

	va_start(args, message);
	vsprintf(buffer, message, args);
	va_end(args);

	newMessage = strdup(buffer);

	pthread_mutex_lock(&_messageQueueMutex);
	arrput(_consoleMessageQueue, newMessage);
	pthread_mutex_unlock(&_messageQueueMutex);

	// Do not free newMessage - we do it when we dequeue the message.
}


void consoleRun(void) {
	uint8_t         commandEntering        = 0;
	char            command[256]           = { 0 };
	uint16_t        commandIndex           = 0;
	char            c                      = 0;
	uint8_t         commandOk              = 0;
	struct timespec remaining              = { 0, 0 };
	struct timespec sleepTime              = { 0, 1000000000/4 };  // 1/4th second

	if (pthread_mutex_init(&_messageQueueMutex, NULL)) utilDie("Unable to create console message queue mutex.\n");

	terminalModeConioSet();

	while (__running) {

		if (kbhit()) {

			// Is this a new command?
			if (!commandEntering) {
				// Show prompt.
				printf(">");
				// Command is being entered.
				commandEntering = 1;
			}

			// Read key.
			c = getch();

			switch (c) {
				// Backspace
				case 8:
				case 127:
					if (commandIndex > 0) {
						commandIndex--;
						command[commandIndex] = 0;
						printf("%c %c", 8, 8);
						if (commandIndex == 0) {
							commandEntering = 0;
							// Erase prompt, too.
							printf("%c %c", 8, 8);
						}
					}
					break;

				// ESC
				case 27:
					// Abort command entry.
					printf("<CANCEL>\n\r");
					commandIndex    = 0;
					command[0]      = 0;
					commandEntering = 0;
					break;

				// ENTER
				case 13:
					printf("\n\r");
					logWriteToFileOnly(">%s\n", command);
					commandOk = 0;
					if (!strcasecmp(command, "HELP") || !strcasecmp(command, "?")) {
						sendToConsole("HELP or ? - This message.\n");
						sendToConsole("SHUTDOWN  - Stop the server.\n");
						commandOk = 1;
					}
					if (!strcasecmp(command, "SHUTDOWN")) {
						__running  = 0;
						commandOk = 1;
					}
					// Did we grok it?
					if (!commandOk) {
						sendToConsole("Unknown command!  Type HELP (or ?) for help.\n\r");
					}
					commandIndex    = 0;
					command[0]      = 0;
					commandEntering = 0;
					break;

				default:
					// Add to command.
					command[commandIndex++] = c;
					command[commandIndex]   = 0;
					printf("%c", c);
					// Overflow?
					if (commandIndex == 255) {
						printf("<OVERFLOW>\n\r");
						commandIndex    = 0;
						command[0]      = 0;
						commandEntering = 0;
					}
					break;
			}

			fflush(stdout);
		}

		// Only do this if we're not typing on the console.
		if (!commandEntering) {
			// Don't eat all the CPU.
			nanosleep(&remaining, &sleepTime);

			// Messages to display?
			pthread_mutex_lock(&_messageQueueMutex);
			if (arrlenu(_consoleMessageQueue) > 0) {
				sendToConsole("%s", _consoleMessageQueue[0]);
				free(_consoleMessageQueue[0]);
				arrdel(_consoleMessageQueue, 0);
			}
			pthread_mutex_unlock(&_messageQueueMutex);
		}
	}

	// Anything left in the message queue?
	while (arrlen(_consoleMessageQueue) > 0) {
		DEL(_consoleMessageQueue[0]);
		arrdel(_consoleMessageQueue, 0);
	}
	arrfree(_consoleMessageQueue);

	terminalModeOriginalSet();
	pthread_mutex_destroy(&_messageQueueMutex);
}


static uint8_t getch(void) {
	int     r = 0;
	uint8_t c = 0;

	if ((r = read(0, &c, sizeof(c))) < 0) {
		return r;
	} else {
		return c;
	}
}


static uint8_t kbhit(void) {
	struct timeval tv  = { 0L, 0L };
	fd_set         fds = { 0 };

	FD_ZERO(&fds);
	FD_SET(0, &fds);

	return select(1, &fds, NULL, NULL, &tv) > 0;
}


static void sendToConsole(const char *message, ...) {
	va_list args         = { 0 };
	char    buffer[2048] = { 0 };

	va_start(args, message);

	vsprintf(buffer, message, args);
	printf("%s", buffer);
	if (buffer[strlen(buffer) - 1] == '\n') {
		// Console needs a CR with its LF.
		printf("\r");
	}
	fflush(stdout);

	logWriteToFileOnly("%s", buffer);

	va_end(args);
}


static void terminalModeConioSet(void) {
	struct termios new_termios = { 0 };

	// https://stackoverflow.com/questions/448944/c-non-blocking-keyboard-input

	// Take two copies - one for now, one for later.
	tcgetattr(0, &_termios);
	memcpy(&new_termios, &_termios, sizeof(new_termios));
	cfmakeraw(&new_termios);
	tcsetattr(0, TCSANOW, &new_termios);
}


static void terminalModeOriginalSet(void) {
	tcsetattr(0, TCSANOW, &_termios);
}


