#include <Arduino.h>
#include <TM1637.h>
#include <Ticker.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESP8266mDNS.h>
#include <ArduinoOTA.h>
#include <PCF8574.h>
#include <Wire.h>
#include <Adafruit_INA219.h>
#include <ArduinoJson.h>

// local includes
#include "common.h"
#include "sanitycheck.h"
#include "defaults.h"
#include "webui.h"
#include "config.h"
#include "globals.h"
#include "dtc.h"
#include "debugger.h"
#if defined(FEATURE_ENABLE_LORA) || defined(FEATURE_ENABLE_UARTLORA)
#include "lora_net.h"
#endif

#ifdef FEATURE_ENABLE_WIFI_CLIENT
#include <ESP8266WiFiMulti.h>

const char *ssid = QUOTE(WIFI_SSID_CLIENT);
const char *password = QUOTE(WIFI_PASSWORD_CLIENT);
const uint32_t connectTimeoutMs = 5000;

ESP8266WiFiMulti wifiMulti;
#endif

PCF8574 i2c_io(I2C_IO_ADDRESS);
Adafruit_INA219 ina219;

TM1637 disp_FAC_1(GPIO_7SEG_CLK, GPIO_7SEG_EN_FAC1);
TM1637 disp_FAC_2(GPIO_7SEG_CLK, GPIO_7SEG_EN_FAC2);
TM1637 disp_FAC_3(GPIO_7SEG_CLK, GPIO_7SEG_EN_FAC3);

void SevenSeg_Output();
void toggleWiFiAP(boolean shutdown = false);
void SystemShutdown(bool restart = false);
void SetBatteryType(batteryType_t type);
void ProcessKeyCombos(bool *btnState);
void OverrideDisplay(uint32_t time, const char *message1, const char *message2, const char *message3);
void EEPROMCyclicPDS_callback();

#if defined(FEATURE_ENABLE_UARTLORA) || defined(FEATURE_ENABLE_LORA)
void setMPins_Helper(int pin, int status);
void tmrCallback_StatusSender();
Ticker tmrStatusSender(tmrCallback_StatusSender, 30000, 0, MILLIS);
#endif

void tmrCallback_PowerMonitor();
Ticker tmrPowerMonitor(tmrCallback_PowerMonitor, 10000, 0, MILLIS);
void tmrCallback_FactionTicker();
Ticker tmrFactionTicker(tmrCallback_FactionTicker, 1000, 0, MILLIS);
void tmrCallback_InputGetter();
Ticker tmrInputGetter(tmrCallback_InputGetter, 250, 0, MILLIS);
void EEPROMCyclicPDS_callback();
Ticker tmrEEPROMCyclicPDS(EEPROMCyclicPDS_callback, 60000, 0, MILLIS);

#ifdef FEATURE_ENABLE_WIFI_CLIENT
void wifiMaintainConnectionTicker_callback();
Ticker tmrWiFiMaintainConnection(wifiMaintainConnectionTicker_callback, 1000, 0, MILLIS);
#endif

uint32_t DisplayOverrideFlag = 0;
char DisplayOverrideValue[3][5] = {0};

#if defined(FEATURE_ENABLE_UARTLORA) || defined(FEATURE_ENABLE_LORA)
void setMPins_Helper(int pin, int status)
{
	i2c_io.write(pin, status);
}
#endif

void setup()
{
	// Set CPU frequency to 80MHz
	system_update_cpu_freq(SYS_CPU_80MHZ);

	// Generate a unique device name based on ESP chip ID
	strncpy(globals.DeviceName, HOST_NAME, sizeof(globals.DeviceName));
	snprintf(globals.DeviceNameId, sizeof(globals.DeviceNameId), "%s_%08X", globals.DeviceName, ESP.getChipId());

	// Disable WiFi persistent storage
	WiFi.persistent(false);

	// Initialize and clear Diagnostic Trouble Code (DTC) storage
	ClearAllDTC();

#ifdef FEATURE_ENABLE_WIFI_CLIENT
	// Configure WiFi settings for client mode if enabled
	WiFi.mode(WIFI_STA);
	WiFi.setHostname(globals.DeviceNameId);
	wifiMulti.addAP(QUOTE(WIFI_SSID_CLIENT), QUOTE(WIFI_PASSWORD_CLIENT));
	tmrWiFiMaintainConnection.start();
#else
	// Disable WiFi if WiFi client feature is not enabled
	WiFi.mode(WIFI_OFF);
#endif

	// Initialize Serial communication
	Serial.begin(115200);
	Serial.setDebugOutput(false);

	Serial.print("\n\n-------------------START-------------------\n");
	Serial.print(globals.DeviceNameId);
	Serial.print("\nby Hiabuto Defense\n\n");

	// Initialize EEPROM, load configuration, and persistence data from EEPROM
	if (InitEEPROM())
	{
		GetConfig_EEPROM();
		GetPersistence_EEPROM();
		Serial.printf("Initialized EEPROM at Address 0x%02X\n", I2C_EE_ADDRESS);
	}
	else
	{
		Serial.print("EEPROM not Initialized\n");
	}

	if (i2c_io.begin())
	{
		Serial.printf("Initialized PCF8574-GPIO at Address 0x%02X\n", I2C_IO_ADDRESS);
	}
	else
	{
		Serial.print("PCF8574-GPIO not Initialized\n");
	}

	if (ina219.begin())
	{
		Serial.printf("Initialized INA219-Powermonitor at Address 0x%02X\n", I2C_POWER_ADDRESS);
		tmrPowerMonitor.start();
	}
	else
	{
		Serial.print("INA219 not Initialized\n");
	}

#if defined(FEATURE_ENABLE_UARTLORA) || defined(FEATURE_ENABLE_LORA)
	if (InitLoRa(&setMPins_Helper))
	{
		Serial.print("Initialized LoRa_Transceiver\n");
		tmrStatusSender.start();
	}
	else
	{
		Serial.print("LoRa not Initialized\n");
	}
#endif

	// Set up OTA updates
	ArduinoOTA.setPort(8266);
	ArduinoOTA.setHostname(globals.DeviceNameId);
	ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD));

	ArduinoOTA.onStart([]()
					   {
                       disp_FAC_1.setBrightness(5);
                       disp_FAC_2.setBrightness(5);
                       disp_FAC_3.setBrightness(5);
                       disp_FAC_1.display("OTA ");
                       disp_FAC_3.clearScreen();
                       
                       if (ArduinoOTA.getCommand() == U_FLASH)
                       {
                         disp_FAC_2.display("FLSH");
                       }
                       else
                       {
                         disp_FAC_2.display("FILE");
                         LittleFS.end();
                       } });

	ArduinoOTA.onEnd([]()
					 { 
                       disp_FAC_1.display("DONE");
                       disp_FAC_2.clearScreen();
                       disp_FAC_3.clearScreen(); });

	ArduinoOTA.onProgress([](unsigned int progress, unsigned int total)
						  { disp_FAC_3.display((progress / (total / 100))); });

	ArduinoOTA.onError([](ota_error_t error)
					   {
                       Serial.printf("Error[%u]: ", error);
                       if (error == OTA_AUTH_ERROR)
                         Serial.println("Auth Failed");
                       else if (error == OTA_BEGIN_ERROR)
                         Serial.println("Begin Failed");
                       else if (error == OTA_CONNECT_ERROR)
                         Serial.println("Connect Failed");
                       else if (error == OTA_RECEIVE_ERROR)
                         Serial.println("Receive Failed");
                       else if (error == OTA_END_ERROR)
                         Serial.println("End Failed"); });

	// Begin OTA updates
	ArduinoOTA.begin();
	Serial.print("\nOTA-Init done");

	// Initialize the web user interface
	initWebUI();
	Serial.print("\nWebUI-Init done");

	// Initialize global variables
	initGlobals();
	Serial.print("\nglobals-Init done");

	// Start cyclic EEPROM updates for Persistence Data Structure (PDS)
	tmrEEPROMCyclicPDS.start();

	disp_FAC_1.init();
	disp_FAC_1.setBrightness(5);
	disp_FAC_2.init();
	disp_FAC_2.setBrightness(5);
	disp_FAC_3.init();
	disp_FAC_3.setBrightness(5);

	tmrFactionTicker.start();
	tmrInputGetter.start();

	if (ConfigData.active_faction_on_reboot == false)
		PersistenceData.activeFaction = NONE;

	Serial.print("\nSetup Done\n");
}

void loop()
{
	tmrEEPROMCyclicPDS.update();
	tmrFactionTicker.update();
	tmrInputGetter.update();
	tmrPowerMonitor.update();

	ArduinoOTA.handle();
	SevenSeg_Output();
	EEPROM_Process();
	Webserver_Process();
	DTC_Process();
	Debug_Process();

#if defined(FEATURE_ENABLE_LORA) || defined(FEATURE_ENABLE_UARTLORA)
	LoRa_Process();
	tmrStatusSender.update();
#endif

#ifdef FEATURE_ENABLE_WIFI_CLIENT
	// Update WiFi connection maintenance ticker if WiFi client feature is enabled
	tmrWiFiMaintainConnection.update();
#endif

	static tSystem_Status lastStatus = sysStat_Init;

	// Handle different system statuses
	switch (globals.systemStatus)
	{
	case sysStat_Startup:
		if (lastStatus != globals.systemStatus)
		{
			strcpy_P(globals.systemStatustxt, PSTR("Startup"));
			lastStatus = globals.systemStatus;
		}
		// Transition to Normal status after startup delay
		if (millis() > STARTUP_DELAY)
		{
			globals.systemStatus = sysStat_Normal;
			globals.resumeStatus = sysStat_Normal;
		}
		break;

	case sysStat_Normal:
		if (lastStatus != globals.systemStatus)
		{
			strcpy_P(globals.systemStatustxt, PSTR("Normal"));
			lastStatus = globals.systemStatus;
		}
		break;

		if (lastStatus != globals.systemStatus)
		{
			strcpy_P(globals.systemStatustxt, PSTR("Error"));
			lastStatus = globals.systemStatus;
		}
		break;

	case sysStat_Shutdown:
		if (lastStatus != globals.systemStatus)
		{
			strcpy_P(globals.systemStatustxt, PSTR("Shutdown"));
			lastStatus = globals.systemStatus;
		}
		SystemShutdown(false);
		break;
	case sysStat_Error:
		if (lastStatus != globals.systemStatus)
		{
			strcpy_P(globals.systemStatustxt, PSTR("Error"));
			lastStatus = globals.systemStatus;
		}
	default:
		break;
	}

	// Yield to allow other tasks to run
	yield();
}

String macToString(const unsigned char *mac)
{
	char buf[20];
	snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
			 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
	return String(buf);
}

void SevenSeg_Output()
{
	char sevenSegBuff[9] = "";

	if (DisplayOverrideFlag > millis())
	{
		disp_FAC_1.setBrightness(5);
		disp_FAC_2.setBrightness(5);
		disp_FAC_3.setBrightness(5);
		disp_FAC_1.display(String(DisplayOverrideValue[0]));
		disp_FAC_2.display(String(DisplayOverrideValue[1]));
		disp_FAC_3.display(String(DisplayOverrideValue[2]));
	}
	else
	{
		if (globals.battery_level < BAT_LOW_PERCENT && millis() % 10000 > 7000)
		{
			if (millis() % 3000 < 1500)
				snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4u", globals.battery_level);
			else
				snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%3u.%1u", (globals.loadvoltage_mV / 1000), ((globals.loadvoltage_mV % 1000) / 100));

			sevenSegBuff[5] = '\0'; // truncate here if for any reason something bigger is in buffer than ecpected

			disp_FAC_1.setBrightness(1);
			disp_FAC_1.display(" Bat");

			disp_FAC_2.setBrightness(1);
			disp_FAC_2.display("low ");

			disp_FAC_3.setBrightness(1);
			disp_FAC_3.display(String(sevenSegBuff));
		}
		else
		{
			disp_FAC_1.setBrightness(PersistenceData.activeFaction == FACTION_1 ? 5 : 1);
			disp_FAC_1.refresh();
			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_1_timer / 60);
			sevenSegBuff[5] = '\0'; // truncate here if for any reason something bigger is in buffer than ecpected
			disp_FAC_1.display(String(sevenSegBuff), false, false);
			disp_FAC_1.setDp((PersistenceData.activeFaction == FACTION_1) && (millis() % 1000 > 500) ? 0x08 : 0x00);

			disp_FAC_2.setBrightness(PersistenceData.activeFaction == FACTION_2 ? 5 : 1);
			disp_FAC_2.refresh();
			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_2_timer / 60);
			sevenSegBuff[5] = '\0'; // truncate here if for any reason something bigger is in buffer than ecpected
			disp_FAC_2.display(String(sevenSegBuff), false, false);
			disp_FAC_2.setDp((PersistenceData.activeFaction == FACTION_2) && (millis() % 1000 > 500) ? 0x08 : 0x00);

			disp_FAC_3.setBrightness(PersistenceData.activeFaction == FACTION_3 ? 5 : 1);
			disp_FAC_3.refresh();
			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_3_timer / 60);
			sevenSegBuff[5] = '\0'; // truncate here if for any reason something bigger is in buffer than ecpected
			disp_FAC_3.display(String(sevenSegBuff), false, false);
			disp_FAC_3.setDp((PersistenceData.activeFaction == FACTION_3) && (millis() % 1000 > 500) ? 0x08 : 0x00);
		}
	}
}

void tmrCallback_FactionTicker()
{
	if (globals.timer_disabled == true)
		PersistenceData.activeFaction = NONE;

	switch (PersistenceData.activeFaction)
	{
	case FACTION_1:
		PersistenceData.faction_1_timer++;
		break;

	case FACTION_2:
		PersistenceData.faction_2_timer++;
		break;

	case FACTION_3:
		PersistenceData.faction_3_timer++;
		break;

	default:
		break;
	}
}

void tmrCallback_InputGetter()
{
	static bool btnState[3];
	uint8_t keysPressed = 0;

	btnState[0] = i2c_io.readButton(I2C_IO_BTN_FAC1);
	btnState[1] = i2c_io.readButton(I2C_IO_BTN_FAC2);
	btnState[2] = i2c_io.readButton(I2C_IO_BTN_FAC3);

	ProcessKeyCombos(btnState);

	keysPressed = keysPressed + (btnState[0] == FAC_1_TRG_PRESSED ? 1 : 0);
	keysPressed = keysPressed + (btnState[1] == FAC_2_TRG_PRESSED ? 1 : 0);
	keysPressed = keysPressed + (btnState[2] == FAC_3_TRG_PRESSED ? 1 : 0);

	if (keysPressed > 1)
	{
		Debug_pushMessage("ERROR: More than one Flag active - setting no Faction active");
		PersistenceData.activeFaction = NONE;
		return;
	}

	if (btnState[0] == FAC_1_TRG_PRESSED && globals.timer_disabled != true)
	{
		if (PersistenceData.activeFaction != FACTION_1)
		{
			Debug_pushMessage("Faction 1 captured !");
			globals.requestEEAction = EE_PDS_SAVE;
		}
		PersistenceData.activeFaction = FACTION_1;
	}

	if (btnState[1] == FAC_2_TRG_PRESSED && globals.timer_disabled != true)
	{
		if (PersistenceData.activeFaction != FACTION_2)
		{
			Debug_pushMessage("Faction 2 captured !");
			globals.requestEEAction = EE_PDS_SAVE;
		}
		PersistenceData.activeFaction = FACTION_2;
	}

	if (btnState[2] == FAC_3_TRG_PRESSED && globals.timer_disabled != true)
	{
		if (PersistenceData.activeFaction != FACTION_3)
		{
			Debug_pushMessage("Faction 3 captured !");
			globals.requestEEAction = EE_PDS_SAVE;
		}
		PersistenceData.activeFaction = FACTION_3;
	}
}

#if defined(FEATURE_ENABLE_LORA) || defined(FEATURE_ENABLE_UARTLORA)
void tmrCallback_StatusSender()
{
	sendStatus_LoRa();
}
#endif

void tmrCallback_PowerMonitor()
{
	// loadvoltage and percentage is global, because of battery Monitoring

	const int bat_min_2s = 680;
	const int bat_max_2s = 840;
	const int bat_min_3s = 1020;
	const int bat_max_3s = 1260;

	float shuntvoltage = 0;
	// float current_mA = 0;
	float busvoltage = 0;
	// float power_mW = 0;
	int battery_level = 0;

	shuntvoltage = ina219.getShuntVoltage_mV();
	busvoltage = ina219.getBusVoltage_V();
	// current_mA = ina219.getCurrent_mA();
	// power_mW = ina219.getPower_mW();
	globals.loadvoltage_mV = (busvoltage * 1000) + shuntvoltage;
	switch (ConfigData.batteryType)
	{
	case BATTERY_LIPO_2S:
		battery_level = map(globals.loadvoltage_mV / 10, bat_min_2s, bat_max_2s, 0, 100);
		globals.battery_level = battery_level < 0 ? 0 : battery_level;
		break;
	case BATTERY_LIPO_3S:
		battery_level = map(globals.loadvoltage_mV / 10, bat_min_3s, bat_max_3s, 0, 100);
		globals.battery_level = battery_level < 0 ? 0 : battery_level;
		break;
	default:
		globals.battery_level = -1;
		break;
	}

	MaintainDTC(DTC_BAT_LOW, (battery_level < 15 ? true : false), battery_level);
	MaintainDTC(DTC_BAT_CRITICAL, (battery_level < 5 ? true : false), battery_level);

	// Serial.printf("Battery Level: %d %%\n", globals.battery_level);
	// Serial.printf("Bus Voltage: %f V\n", busvoltage);
	// Serial.printf("Shunt Voltage: %f mV\n", shuntvoltage);
	// Serial.printf("Load Voltage: %d mV\n", globals.loadvoltage_mV;
	// Serial.printf("Current: %f mA\n", current_mA);
	// Serial.printf("Power: %f mW\n", power_mW);
}

/**
 * @brief Callback function for cyclically storing Persistence Data Structure (PDS) to EEPROM.
 *
 * This callback function is invoked periodically to store the Persistence Data Structure (PDS)
 * to the EEPROM. It ensures that essential data is saved persistently, allowing the system to
 * recover its state after power cycles or resets.
 */
void EEPROMCyclicPDS_callback()
{
	StorePersistence_EEPROM();
}

#ifdef FEATURE_ENABLE_WIFI_CLIENT
/**
 * @brief Callback function for maintaining WiFi connection and handling connection failures.
 *
 * This callback function is used by a ticker to periodically check the WiFi connection status.
 * If the device is not connected to WiFi, it counts connection failures. If the number of failures
 * exceeds a defined threshold, the function triggers the initiation of an Access Point (AP) mode
 * using the `toggleWiFiAP` function.
 */
void wifiMaintainConnectionTicker_callback()
{
	// Static variables to track WiFi connection failure count and maximum allowed failures
	static uint32_t WiFiFailCount = 0;
	const uint32_t WiFiFailMax = 20;

	// Check if the device is connected to WiFi
	if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED)
	{
		return; // Exit if connected
	}
	else
	{
		// Increment WiFi connection failure count
		if (WiFiFailCount < WiFiFailMax)
		{
			WiFiFailCount++;
		}
		else
		{
			// Trigger AP mode if the maximum failures are reached
			Debug_pushMessage("WiFi not connected! - Start AP\n");
			toggleWiFiAP();
		}
	}
}

#endif

/**
 * @brief Toggles the WiFi functionality based on the current status.
 *
 * This function manages the WiFi state, either turning it off or starting it as an Access Point (AP),
 * depending on the current mode. If the WiFi is turned off, it can be started in AP mode with the
 * device name and password configured. Additionally, it may stop certain operations related to WiFi
 * maintenance or display debug messages based on the defined features.
 *
 * @param shutdown Flag indicating whether the system is in a shutdown state.
 */
void toggleWiFiAP(bool shutdown)
{
	// Check if WiFi is currently active
	if (WiFi.getMode() != WIFI_OFF)
	{
		// Turn off WiFi
		WiFi.mode(WIFI_OFF);
		Debug_pushMessage("WiFi turned off\n");

		// Stop WiFi maintenance connection ticker if enabled
#ifdef FEATURE_ENABLE_WIFI_CLIENT
		tmrWiFiMaintainConnection.stop();
#endif
	}
	else
	{
		// Start WiFi in Access Point (AP) mode
		WiFi.mode(WIFI_AP);
		WiFi.softAPConfig(IPAddress(WIFI_AP_IP_GW), IPAddress(WIFI_AP_IP_GW), IPAddress(255, 255, 255, 0));
		WiFi.softAP(globals.DeviceNameId, QUOTE(WIFI_AP_PASSWORD));

		// Stop WiFi maintenance connection ticker if enabled and display debug messages
#ifdef FEATURE_ENABLE_WIFI_CLIENT
		tmrWiFiMaintainConnection.stop();
		Debug_pushMessage("WiFi AP started, stopped Maintain-Timer\n");
#else
		Debug_pushMessage("WiFi AP started\n");
#endif
	}
}

/**
 * @brief Performs necessary tasks before shutting down and optionally restarts the ESP.
 *
 * This function initiates a system shutdown, performing tasks such as storing configuration
 * and persistence data to EEPROM before shutting down. If a restart is requested, the ESP
 * will be restarted; otherwise, the system will enter an indefinite loop.
 *
 * @param restart Flag indicating whether to restart the ESP after shutdown (default: false).
 */
void SystemShutdown(bool restart)
{
	static uint32_t shutdown_delay = 0;

	// Initialize shutdown delay on the first call
	if (shutdown_delay == 0)
	{
		shutdown_delay = millis() + SHUTDOWN_DELAY_MS;
		Serial.printf("Shutdown requested - Restarting in %d seconds\n", SHUTDOWN_DELAY_MS / 1000);
	}

	// Check if the shutdown delay has elapsed
	if (shutdown_delay < millis())
	{
		Webserver_Shutdown();

		// Store persistence data to EEPROM
		StorePersistence_EEPROM();

		// Perform restart if requested, otherwise enter an indefinite loop
		if (restart)
			ESP.restart();
		else
			while (1)
				;
	}
}

void SetBatteryType(batteryType_t type)
{
	if (ConfigData.batteryType != type)
	{
		ConfigData.batteryType = type;
		globals.requestEEAction = EE_CFG_SAVE;
		Serial.printf("Set Batterytype to %s\n", type == BATTERY_LIPO_2S ? "2s Lipo" : "3s LiPo");
	}
}

void OverrideDisplay(uint32_t time, const char *message1, const char *message2, const char *message3)
{
	DisplayOverrideFlag = millis() + time;
	strcpy(DisplayOverrideValue[0], message1);
	strcpy(DisplayOverrideValue[1], message2);
	strcpy(DisplayOverrideValue[2], message3);
}

void ProcessKeyCombos(bool *btnState)
{
	typedef enum
	{
		KEY_PRESSED,
		KEY_RELEASED
	} keyStatus_t;

	static keyStatus_t keyStatus_Fac1 = KEY_RELEASED;
	static keyStatus_t keyStatus_Fac2 = KEY_RELEASED;
	static uint8_t keyCount_Fac2 = 0;
	static keyStatus_t keyStatus_Fac3 = KEY_RELEASED;
	static uint8_t keyCount_Fac3 = 0;

	if (btnState[0] == FAC_1_TRG_PRESSED)
	{
		keyStatus_Fac1 = KEY_PRESSED;

		// Process FactionKey 2 ComboCounter
		if (btnState[1] == FAC_2_TRG_PRESSED && keyStatus_Fac2 == KEY_RELEASED)
		{
			keyStatus_Fac2 = KEY_PRESSED;
			keyCount_Fac2++;
		}

		if (btnState[1] != FAC_2_TRG_PRESSED)
			keyStatus_Fac2 = KEY_RELEASED;

		// Process FactionKey 3 ComboCounter
		if (btnState[2] == FAC_3_TRG_PRESSED && keyStatus_Fac3 == KEY_RELEASED)
		{
			keyStatus_Fac3 = KEY_PRESSED;
			keyCount_Fac3++;
		}

		if (btnState[2] != FAC_3_TRG_PRESSED)
			keyStatus_Fac3 = KEY_RELEASED;
	}

	if (btnState[0] != FAC_1_TRG_PRESSED && keyStatus_Fac1 == KEY_PRESSED)
	{
		if (keyCount_Fac2 > 0 || keyCount_Fac3 > 0)
			Debug_pushMessage("KeyCombo 2: %d | 3: %d\n", keyCount_Fac2, keyCount_Fac3);

		if (keyCount_Fac2 == 2 && keyCount_Fac3 == 0)
		{
			Debug_pushMessage("KeyCombo: WiFi AP ON");
			OverrideDisplay(5000, "NET ", "    ", "    ");
			toggleWiFiAP(false);
		}
		else if (keyCount_Fac2 == 4 && keyCount_Fac3 == 0)
		{
			Debug_pushMessage("KeyCombo: Reset Timer\n");
			if (globals.systemStatus == sysStat_Startup)
			{
				OverrideDisplay(5000, "RST ", "    ", "    ");
				PersistenceData.faction_1_timer = 0;
				PersistenceData.faction_2_timer = 0;
				PersistenceData.faction_3_timer = 0;
				PersistenceData.activeFaction = NONE;
				globals.requestEEAction = EE_PDS_SAVE;
			}
			else
			{
				OverrideDisplay(5000, "ERR ", "    ", "    ");
				Debug_pushMessage("ERROR: only %d seconds after Startup!\n", STARTUP_DELAY_MS / 1000);
			}
		}

		keyCount_Fac2 = 0;
		keyCount_Fac3 = 0;
		keyStatus_Fac1 = KEY_RELEASED;
		keyStatus_Fac2 = KEY_RELEASED;
		keyStatus_Fac3 = KEY_RELEASED;
	}
}

void maintainSysStat()
{

	static tSystem_Status lastStat = sysStat_Startup;

	// system Status Transistions
	switch (globals.systemStatus)
	{
	case sysStat_Shutdown:
		SystemShutdown();
		break;

	case sysStat_Startup:
		if (millis() > STARTUP_DELAY_MS)
			globals.systemStatus = sysStat_Normal;
		break;

	case sysStat_Error:
	case sysStat_Normal:
	default:
		break;
	}

	// system Status Changed Actions
	if (lastStat != globals.systemStatus)
	{
		switch (globals.systemStatus)
		{
		case sysStat_Shutdown:
			OverrideDisplay(SHUTDOWN_DELAY_MS, "  re", "boot", "    ");
			break;

		case sysStat_Startup:
			OverrideDisplay(STARTUP_DELAY_MS, "star", "t up", "    ");
			break;

		case sysStat_Error:
		case sysStat_Normal:
		default:
			break;
		}

		lastStat = globals.systemStatus;
	}
}