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

#include "taglist.h"
#include "config.h"
#include "comport.h"
#include "network.h"
#include "runtime.h"

#include "window.h"
#include "picture.h"
#include "button.h"
#include "msgbox.h"
#include "timer.h"


typedef enum WelcomeStateE {
	S_START_CONNECT = 0,
	S_START_INIT_MODEM,
	S_INIT_MODEM,
	S_INIT_RESULT,
	S_DIAL,
	S_WAIT_FOR_BANNER,
	S_WAIT_FOR_ENCRYPTION,
	S_WAIT_FOR_DATA_TIMEOUT,
	S_WAIT_FOR_ICON
} WelcomeStateT;


static WindowT       *_winWelcome     = NULL;
static PictureT      *_picLogo        = NULL;
static PictureT      *_picInit        = NULL;
static PictureT      *_picDialing     = NULL;
static PictureT      *_picConnect     = NULL;
static ButtonT       *_btnQuit        = NULL;
static ButtonT       *_btnSettings    = NULL;
static ButtonT       *_btnConnect     = NULL;
static TimerT        *_timProgress    = NULL;
static WelcomeStateT  _state          = S_START_CONNECT;
static int16_t        _timeoutCounter = 0;


static void btnConnectClick(WidgetT *widget);
static void btnMsgBox(MsgBoxButtonT button);
static void btnMsgBoxQuit(MsgBoxButtonT button);
static void btnQuitClick(WidgetT *widget);
static void btnSettingsClick(WidgetT *widget);
static void packetHandler(PacketDecodeDataT *packet);
static void setButtons(uint8_t enabled);
static void settingsFinished(WidgetT *widget);
static void timWelcomeProgress(WidgetT *widget);


static void btnConnectClick(WidgetT *widget) {
	(void)widget;

	_state = S_START_CONNECT;
	timerQuarterSecondsSet(_timProgress, 0);
	timerStart(_timProgress);
}


static void btnMsgBox(MsgBoxButtonT button) {
	(void)button;

	// Re-enable buttons.
	setButtons(1);

	// Reset to Welcome image.
	widgetVisibleSet(W(_picLogo), 1);
	widgetVisibleSet(W(_picInit), 0);
	widgetVisibleSet(W(_picDialing), 0);
	widgetVisibleSet(W(_picConnect), 0);
}


static void btnMsgBoxQuit(MsgBoxButtonT button) {
	if (button == MSGBOX_BUTTON_ONE) {
		guiStop();
	} else {
		setButtons(1);
	}
}


static void btnQuitClick(WidgetT *widget) {
	(void)widget;

	setButtons(0);
	msgBoxTwo("Quit?", MSGBOX_ICON_QUESTION, "Exit to DOS?", "Okay", btnMsgBoxQuit, "Cancel", btnMsgBoxQuit);
}


static void btnSettingsClick(WidgetT *widget) {
	(void)widget;

	setButtons(0);
	settingsShow(settingsFinished);
}


static void packetHandler(PacketDecodeDataT *packet) {
	PacketEncodeDataT  encoded;
	char              *temp;

	// Reset timeout.
	timerReset(_timProgress);

	switch (packet->packetType) {
		case PACKET_TYPE_NUMBER:
			// Store in number table.
			shput(__runtimeData.integers, &packet->data[4], (int32_t)packet->data[0]);
			break;

		case PACKET_TYPE_PROCEED:
			// Connected!  Show icon.
			widgetVisibleSet(W(_picConnect), 1);
			// Leave it up about 1.5 seconds.
			timerQuarterSecondsSet(_timProgress, 6);
			_state = S_WAIT_FOR_ICON;
			break;

		case PACKET_TYPE_STRING:
			// Do we have this string already?
			temp = shget(__runtimeData.strings, packet->data);
			if (temp) {
				DEL(temp);
				shdel(__runtimeData.strings, packet->data);
			}
			// Store in string table.
			shput(__runtimeData.strings, packet->data, strdup(&packet->data[strlen(packet->data) + 1]));
			break;

		case PACKET_TYPE_VERSION:
			packetContentUnpack(packet->data, "i", &__runtimeData.protocolVersion);
			// Do we need to update?
			if (PACKET_PROTOCOL_VERSION == __runtimeData.protocolVersion) {
				// Nope, we're good.
				encoded.packetType = PACKET_TYPE_VERSION_OKAY;
			} else {
				// Version mismatch - upgrade time!
				encoded.packetType = PACKET_TYPE_VERSION_BAD;
				// ***TODO***
			}
			encoded.control = PACKET_CONTROL_DAT;
			encoded.channel = 0;
			encoded.encrypt = 0;
			packetEncode(__packetThreadData, &encoded, NULL, 0);
			// Send GOOD or BAD.
			packetSend(__packetThreadData, &encoded);
			break;

		default:
			logWrite("Unexpected packet received:  %d\n", packet->packetType);
			break;
	}

	packetDecodeDataDestroy(&packet);
}


static void setButtons(uint8_t enabled) {
	widgetEnableSet(W(_btnConnect), (__configData.serialCom > 0 && strlen(__configData.serverHost) > 2) ? enabled : 0);
	widgetEnableSet(W(_btnSettings), enabled);
	widgetEnableSet(W(_btnQuit), enabled);
}


static void settingsFinished(WidgetT *widget) {
	(void)widget;
	setButtons(1);
}


void welcomeShow(void) {

	char *logo = strdup(cacheFilenameGet("system:logo"));
	char *init = strdup(cacheFilenameGet("system:init"));
	char *dial = strdup(cacheFilenameGet("system:dialing"));
	char *conn = strdup(cacheFilenameGet("system:connect"));

	// 450x128 logo

	TagItemT uiWelcome[] = {
		T_START,
			T_WINDOW, O(_winWelcome),
				T_TITLE, P("Welcome to KangaWorld!"),
				T_WIDTH, 500, T_HEIGHT, 225,

				T_PICTURE, O(_picLogo),
					T_FILENAME, P(logo),
					T_X, 18, T_Y, 18,
				T_PICTURE, T_DONE,

				T_PICTURE, O(_picInit),
					T_FILENAME, P(init),
					T_X, 18, T_Y, 18,
					T_VISIBLE, T_FALSE,
				T_PICTURE, T_DONE,
				T_PICTURE, O(_picDialing),
					T_FILENAME, P(dial),
					T_X, 179, T_Y, 18,
					T_VISIBLE, T_FALSE,
				T_PICTURE, T_DONE,
				T_PICTURE, O(_picConnect),
					T_FILENAME, P(conn),
					T_X, 339, T_Y, 18,
					T_VISIBLE, T_FALSE,
				T_PICTURE, T_DONE,

				T_BUTTON, O(_btnQuit),
					T_TITLE, P("Quit"),
					T_X, 30, T_Y, 157,
					T_CLICK, P(btnQuitClick),
				T_BUTTON, T_DONE,
				T_BUTTON, O(_btnSettings),
					T_TITLE, P("Settings"),
					T_X, 201, T_Y, 157,
					T_CLICK, P(btnSettingsClick),
				T_BUTTON, T_DONE,
				T_BUTTON, O(_btnConnect),
					T_TITLE, P("Connect"),
					T_X, 379, T_Y, 157,
					T_CLICK, P(btnConnectClick),
					T_ENABLED, (__configData.serialCom > 0 && strlen(__configData.serverHost) > 2) ? T_TRUE : T_FALSE,
				T_BUTTON, T_DONE,
				T_TIMER, O(_timProgress),
					T_EVENT, P(timWelcomeProgress),
					T_VALUE, 0,
					T_ENABLED, 0,
				T_TIMER, T_DONE,

			T_WINDOW, T_DONE,
		T_END
	};

	tagListRun(uiWelcome);

	DEL(conn);
	DEL(dial);
	DEL(init);
	DEL(logo);
}


static void timWelcomeProgress(WidgetT *widget) {
	TimerT          *t            = (TimerT *)widget;
	int32_t          r            = 0;
	uint32_t         len          = 0;
	static char      buffer[1024] = { 0 };
	static uint16_t  offset       = 0;

	switch (_state) {
		case S_START_CONNECT:
			// Ghost all buttons.
			widgetEnableSet(W(_btnConnect), 0);
			widgetEnableSet(W(_btnSettings), 0);
			widgetEnableSet(W(_btnQuit), 0);
			// Hide welcome banner, show init.
			widgetVisibleSet(W(_picLogo), 0);
			widgetVisibleSet(W(_picInit), 1);
			_state = S_START_INIT_MODEM;
			break;

		case S_START_INIT_MODEM:
			// Open COM port.
			r = comOpen(__configData.serialCom - 1, 57600L, 8, 'n', 1, SER_HANDSHAKING_RTSCTS);
			if (r != SER_SUCCESS) {
				timerStop(t);
				msgBoxOne("COM Problem", MSGBOX_ICON_ERROR, "Unable to open COM port!\nPlease check settings.", "Okay", btnMsgBox);
				break;
			}
			// Send a CR to clear anything in the modem.
			snprintf(buffer, 1023, "%c", 13);
			comWrite(__configData.serialCom - 1, buffer, strlen(buffer));
			timerQuarterSecondsSet(t, 4);
			_state = S_INIT_MODEM;
			break;

		case S_INIT_MODEM:
			// Just read anything to clear the buffer.
			len = comRead(__configData.serialCom - 1, buffer, 1023);
			// Send actual init
			snprintf(buffer, 1023, "%s%c", "AT+SOCK1", 13);
			comWrite(__configData.serialCom - 1, buffer, strlen(buffer));
			_state = S_INIT_RESULT;
			break;

		case S_INIT_RESULT:
			len = comRead(__configData.serialCom - 1, buffer, 1023);
			buffer[len] = 0;
			if (strstr(buffer, "OK") == NULL) {
				comClose(__configData.serialCom - 1);
				timerStop(t);
				msgBoxOne("Modem Problem", MSGBOX_ICON_ERROR, "Modem does not support ENET!\nPlease check settings.", "Okay", btnMsgBox);
				break;
			}
			// Show dialing, dial service.
			widgetVisibleSet(W(_picDialing), 1);
			timerQuarterSecondsSet(t, 0);
			_state = S_DIAL;
			break;

		case S_DIAL:
			snprintf(buffer, 1023, "ATDT%s:%d%c", __configData.serverHost, __configData.serverPort, 13);
			comWrite(__configData.serialCom - 1, buffer, strlen(buffer));
			timerQuarterSecondsSet(t, 0);  // Run as fast as we can so we don't miss any data.
			offset  = 0;
			_timeoutCounter = 7 * 4;   // Seven seconds.
			_state = S_WAIT_FOR_BANNER;
			break;

		case S_WAIT_FOR_BANNER:
			// Process incoming bytes one at a time so we don't accidentally eat the first packet after the banner.
			len = comRead(__configData.serialCom - 1, &buffer[offset], 1);
			offset += len;
			buffer[offset] = 0;
			// ***TODO*** Should probably cleanly handle a full server here with some kind of SERVER_FULL packet.
			if (strstr(buffer, "KPMPGSMKII\rOKAY\r") != NULL) {
				// Connect!  Start packet handler and negotiate encryption.
				netPacketHandlerStart();
				packetEncryptionSetup(__packetThreadData);
				// Watch system channel for packets.
				netChannelSystemGet(packetHandler);
				timerQuarterSecondsSet(t, 1);
				_timeoutCounter = 5 * 4;   // Five seconds.
				_state = S_WAIT_FOR_ENCRYPTION;
				break;
			}
			// Did we time out?
			if (guiTimerQuarterSecondTick()) _timeoutCounter--;
			if (_timeoutCounter == 0) {
				comClose(__configData.serialCom - 1);
				timerStop(t);
				msgBoxOne("No Connection", MSGBOX_ICON_INFORMATION, "Unable to connect to server!\nPlease check settings or try later.", "Okay", btnMsgBox);
			}
			break;

		case S_WAIT_FOR_ENCRYPTION:
			if (packetEncryptionReady()) {
				timerQuarterSecondsSet(t, 5 * 4);
				_state = S_WAIT_FOR_DATA_TIMEOUT;
				break;
			}
			// Did we time out?
			_timeoutCounter--;
			if (_timeoutCounter == 0) {
				netPacketHandlerStop();
				comClose(__configData.serialCom - 1);
				timerStop(t);
				msgBoxOne("Negotiation Error", MSGBOX_ICON_INFORMATION, "Unable to negotiate encryption settings!", "Okay", btnMsgBox);
			}
			break;

		case S_WAIT_FOR_DATA_TIMEOUT:
			netPacketHandlerStop();
			comClose(__configData.serialCom - 1);
			timerStop(t);
			msgBoxOne("Negotiation Error", MSGBOX_ICON_INFORMATION, "Unable to fetch client settings!", "Okay", btnMsgBox);
			break;

		case S_WAIT_FOR_ICON:
			// Switch to Login window.
			timerStop(t);
			netChannelSystemRelease(packetHandler);
			guiDelete(D(_winWelcome));
			loginShow();
			break;
	}
}
