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


/*
 * To Do:
 *
 * - Replace any direct data manipulation from outside a class with methods to handle it
 * - More widget states:  Ghosted (underway)
 * - Methods that can change the width of a widget (such as setTitle) need to repaint the parent window as well
 * - Metrics, colors, etc. should be defined in each widget and not in GUI
 * - Widgets should support a "changed" callback that can cancel the change
 * - Use LabelT in all widgets that have a label
 * - Find a light grey to replace white widget data areas
 * - No thumb in listbox scrollbar
 * - Layout container widgets!
 * - Fix variable names.  something = local; _something = file global; __something = project global
 */


#include "stddclmr.h"
#include "os.h"
#include "vesa.h"
#include "surface.h"
#include "mouse.h"
#include "keyboard.h"
#include "task.h"
#include "image.h"
#include "font.h"
#include "timer.h"
#include "gui.h"
#include "config.h"
#include "runtime.h"

#include "welcome.h"


PacketThreadDataT *__packetThreadData = NULL;  // Exported in os.h
RuntimeDataT       __runtimeData;              // Exported in runtime.h


static void taskComDebugLoop(void *data);
static void taskGuiEventLoop(void *data);


#include "comport.h"
static void taskComDebugLoop(void *data) {
	int32_t   r;
	int32_t   x;
	char      buffer[1024];

	(void)data;

	// Open COM port.
	logWrite("Opening COM\n");
	r = comOpen(__configData.serialCom - 1, 57600L, 8, 'n', 1, SER_HANDSHAKING_RTSCTS);
	if (r != SER_SUCCESS) {
		logWrite("Unable to open COM port!  Please check settings.\n");
		guiStop();
		return;
	}
	// Send a CR to clear anything in the modem.
	logWrite("Clearing modem buffer\n");
	snprintf(buffer, 1023, "%c", 13);
	comWrite(__configData.serialCom - 1, buffer, strlen(buffer));
	// Wait roughly a second for anything.
	comWaitWithTimeout(__configData.serialCom - 1, buffer, 1023, 4, "weExpectNothing");
	// Send actual init
	logWrite("Init modem\n");
	snprintf(buffer, 1023, "%s%c", "AT+SOCK1", 13);
	comWrite(__configData.serialCom - 1, buffer, strlen(buffer));
	// Wait roughly a second for "OK".
	r = comWaitWithTimeout(__configData.serialCom - 1, buffer, 1023, 4, "OK");
	if (r <= 0) {
		comClose(__configData.serialCom - 1);
		logWrite("Modem does not support ENET!  Please check settings.\n");
		guiStop();
		return;
	}
	logWrite("Modem OK\n");
	// Flush COM port.
	timerQuarterSecondsWait(4);
	comReceiveBufferFlush(__configData.serialCom - 1);

	// Show dialing, dial service.
	logWrite("Dialing\n");
	snprintf(buffer, 1023, "ATDT%s:%d%c", __configData.serverHost, __configData.serverPort, 13);
	comWrite(__configData.serialCom - 1, buffer, strlen(buffer));
	// Wait 7 seconds for welcome banner.
	r = comWaitWithTimeout(__configData.serialCom - 1, buffer, 1023, 4 * 7, "KPMPGSMKII\r");
	if (r <= 0) {
		comClose(__configData.serialCom - 1);
		logWrite("Unable to connect to server!  Please check settings or try later.\n");
		guiStop();
		return;
	}

	// Connected!  Show icon and negotiate session.
	logWrite("Connected\n");

	packetEncryptionSetup(__packetThreadData);

	PacketEncodeDataT encoded = { 0 };
	PacketDecodeDataT decoded = { 0 };

	// Send it 10 times, waiting for a PONG between each.
	for (x=0; x<10; x++) {
		// Send PING.
		logWrite("Sending PING %d\n", x);
		encoded.control    = PACKET_CONTROL_DAT;
		encoded.packetType = PACKET_TYPE_PING;
		encoded.channel    = 1;
		encoded.encrypt    = 0;
		packetEncode(__packetThreadData, &encoded, NULL, 0);  // Must encode each packet - no reusing encoded data.
		packetSend(__packetThreadData, &encoded);
		// Wait for PONG.
		while (!guiHasStopped()) {
			r = comRead(__configData.serialCom - 1, buffer, 1);
			if (r == 1) {
				if (packetDecode(__packetThreadData, &decoded, buffer, 1)) {
					if (decoded.packetType == PACKET_TYPE_PONG) {
						logWrite("Received PONG\n");
						break;
					} else {
						logWrite("Unexpected packet type received %d\n", decoded.packetType);
					}
				}
			} else {
				taskYield();
			}
		}
	}

	if (guiHasStopped()) return;

	// Send LOGIN.
	logWrite("Sending LOGIN\n");
	PacketTypeLoginT loginData;
	strcpy(loginData.user, "Encryption");
	strcpy(loginData.pass, "Works!");
	encoded.control    = PACKET_CONTROL_DAT;
	encoded.packetType = PACKET_TYPE_LOGIN;
	encoded.channel    = 1;
	encoded.encrypt    = 1;
	packetEncode(__packetThreadData, &encoded, (char *)&loginData, sizeof(PacketTypeLoginT));  // Must encode each packet - no reusing encoded data.
	packetSend(__packetThreadData, &encoded);

	// Send CLIENT_SHUTDOWN.
	logWrite("Sending CLIENT_SHUTDOWN\n");
	encoded.control    = PACKET_CONTROL_DAT;
	encoded.packetType = PACKET_TYPE_CLIENT_SHUTDOWN;
	encoded.channel    = 1;
	encoded.encrypt    = 0;
	packetEncode(__packetThreadData, &encoded, NULL, 0);
	packetSend(__packetThreadData, &encoded);

	logWrite("Sleeping\n");
	timerQuarterSecondsWait(8);

	logWrite("COM closed\n");
	comClose(__configData.serialCom - 1);
	guiStop();
}


static void taskGuiEventLoop(void *data) {
	MouseT    *mouse       = NULL;
	ImageT    *pointer     = NULL;
	ColorT     alpha       = { 0 };

	(void)data;

	pointer = imageLoad("data/mouse.png");
	alpha   = imagePixelGet(pointer, 5, 0);

	do {
		timerUpdate();
		mouse = mouseRead();
		if (keyHit()) {
			guiKeyboardProcess(keyASCIIGet(), keyExtendedGet(), keyScanCodeGet(), keyShiftGet(), keyControlGet(), keyAltGet());
			//logWrite("Key '%d' Extended '%d' Scancode '%d' Shift '%d' Control '%d' Alt '%d'\n", keyASCIIGet(), keyExtended(), keyScanCode(), keyShift(), keyControl(), keyAlt());
			if (keyASCIIGet() == 27) guiStop();
		}
		guiMouseProcess(mouse);
		guiComposite();
		imageRenderWithAlpha(pointer, mouse->x, mouse->y, alpha);
		vbeVBlankWait();
		vbePresent();
		taskYield();
	} while (!guiHasStopped());

	imageUnload(&pointer);
}



int main(int argc, char *argv[]) {
#ifndef __linux__
	// On DOS, display the contets of the log now that we're back in text mode.
	char *logName = NULL;
	FILE *in      = NULL;
#endif

	memoryStartup(argv[0]);
	logOpenByHandle(memoryLogHandleGet());
	configStartup(argv[0]);

	//      0        1         2         3         4         5         6         7         8
	//      12345678901234567890123456789012345678901234567890123456789012345678901234567890
	logWrite("%s", "Kangaroo Punch MultiPlayer DOS Game Client Mark II\n");
	logWrite("%s", "Copyright (C) 2020-2021  Scott Duensing  scott@kangaroopunch.com\n\n");

	if (argc > 1) {
		if (strcmp(argv[1], "/?") == 0) {
			vbeInfoShow();
			configShutdown();
			logClose();
			memoryShutdown();
			return 0;
		}
	}

	// Do we have the video mode they asked for?
	if (vbeStartup(__configData.videoWidth, __configData.videoHeight, __configData.videoDepth)) {
		configShutdown();
		logClose();
		memoryShutdown();
		return 1;
	}

	surfaceStartup();
	mouseStartup();
	timerStartup();
	guiStartup();
	taskStartup();

	__packetThreadData = packetThreadDataCreate(NULL);
	packetSenderRegister(comPacketSender);

	//taskCreate(taskComDebugLoop, NULL);
	taskCreate(taskWelcome, NULL);
	taskCreate(taskGuiEventLoop, NULL);

	taskRun();

	packetThreadDataDestroy(&__packetThreadData);

	taskShutdown();
	guiShutdown();
	timerShutdown();
	mouseShutdown();
	surfaceShutdown();
	vbeShutdown();
	configShutdown();
	logClose();
	memoryShutdown();

#ifndef __linux__
	//***TODO*** Why the frack does this not work?!
	textmode(C80);
	// On DOS, display the contets of the log now that we're back in text mode.
	logName = utilAppNameWithNewExtensionGet(argv[0], "log");
	in = fopen(logName, "r");
	if (in) {
		while (!feof(in)) {
			putc(fgetc(in), stdout);
		}
		fclose(in);
		free(in);
	}
	free(logName);
	_Exit(0);
#endif

	return 0;
}
