#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"
#ifdef FEATURE_ENABLE_LORA
#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();
void SetBatteryType(batteryType_t type);
void ProcessKeyCombos(bool *btnState);
void OverrideDisplay(const uint8_t *message, uint32_t time);
void initGlobals();

#ifdef 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 tmrCallback_EEPROMCyclicPDS();
Ticker tmrEEPROMCyclicPDS(tmrCallback_EEPROMCyclicPDS, 60000, 0, MILLIS);

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

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

#ifdef FEATURE_ENABLE_LORA
void setMPins_Helper(int pin, int status)
{
	i2c_io.write(pin, status);
}
#endif

void setup()
{
	system_update_cpu_freq(SYS_CPU_80MHZ);
	strcpy(globals.DeviceName, DEVICE_NAME);
	snprintf(globals.DeviceName_ID, 42, "%s_%08X", globals.DeviceName, ESP.getChipId());
	WiFi.persistent(false);
	WiFi.disconnect();

	Serial.begin(115200);
	Serial.setDebugOutput(false);

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

	ClearAllDTC(); // Init DTC-Storage

	InitEEPROM();
	GetConfig_EEPROM();
	GetPersistence_EEPROM();
	Serial.print("EE-Init done\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");
	}

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

#ifdef FEATURE_ENABLE_WIFI_CLIENT
	WiFi.mode(WIFI_STA);
	WiFi.setHostname(globals.DeviceName);
	wifiMulti.addAP(QUOTE(WIFI_CLIENT_SSID), QUOTE(WIFI_CLIENT_PASSWORD));
	tmrWiFiMaintainConnection.start();
	Serial.print("WiFi-Client Initialized\n");
#else
	WiFi.mode(WIFI_OFF);
#endif

	ArduinoOTA.setPort(8266);
	ArduinoOTA.setHostname(globals.DeviceName_ID);
	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"); });

	ArduinoOTA.begin();
	Serial.print("\nOTA-Init done");
	initWebUI();
	Serial.print("\nWebUI-Init done");
	initGlobals();
	Serial.print("\nglobals-Init done");
#ifdef CAPTIVE
	dnsServer.start(53, "*", WiFi.softAPIP());
#endif

	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);

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

	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();

#ifdef FEATURE_ENABLE_LORA
	tmrStatusSender.update();
	LoRa_Process();
#endif

#ifdef CAPTIVE
	dnsServer.processNextRequest();
#endif
#ifdef FEATURE_ENABLE_WIFI_CLIENT
	tmrWiFiMaintainConnection.update();
#endif
	if (globals.systemStatus == sysStat_Shutdown)
		SystemShutdown();

	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[5] = "";

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

			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);
			disp_FAC_1.display(String(sevenSegBuff), false, false);

			disp_FAC_2.setBrightness(PersistenceData.activeFaction == FACTION_2 ? 5 : 1);
			disp_FAC_2.refresh();
			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_2_timer / 60);
			disp_FAC_2.display(String(sevenSegBuff), false, false);

			disp_FAC_3.setBrightness(PersistenceData.activeFaction == FACTION_3 ? 5 : 1);
			disp_FAC_3.refresh();
			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_3_timer / 60);
			disp_FAC_3.display(String(sevenSegBuff), false, false);
		}
	}
}

void tmrCallback_FactionTicker()
{
	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)
	{
		Serial.println("ERROR: More than one Flag active - setting no Faction active");
		PersistenceData.activeFaction = NONE;
		return;
	}

	if (btnState[0] == FAC_1_TRG_PRESSED)
	{
		if (PersistenceData.activeFaction != FACTION_1)
		{
			Serial.println("Faction 1 captured !");
		}
		PersistenceData.activeFaction = FACTION_1;
	}

	if (btnState[1] == FAC_2_TRG_PRESSED)
	{
		if (PersistenceData.activeFaction != FACTION_2)
		{
			Serial.println("Faction 2 captured !");
		}
		PersistenceData.activeFaction = FACTION_2;
	}

	if (btnState[2] == FAC_3_TRG_PRESSED)
	{
		if (PersistenceData.activeFaction != FACTION_3)
		{
			Serial.println("Faction 3 captured !");
		}
		PersistenceData.activeFaction = FACTION_3;
	}
}

#ifdef FEATURE_ENABLE_LORA
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, DTC_WARN, (battery_level < 15 ? true : false), battery_level);
	MaintainDTC(DTC_BAT_CRITICAL, DTC_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);
}

void tmrCallback_EEPROMCyclicPDS()
{
	StorePersistence_EEPROM();
}

#ifdef FEATURE_ENABLE_WIFI_CLIENT
void tmrCallback_WiFiMaintainConnection()
{
	static uint32_t WiFiFailCount = 0;
	const uint32_t WiFiFailMax = 20;

	if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED)
	{
		return;
	}
	else
	{
		if (WiFiFailCount < WiFiFailMax)
		{
			WiFiFailCount++;
		}
		else
		{
			Debug_pushMessage("WiFi not connected! - Start AP");
			toggleWiFiAP();
		}
	}
}
#endif

void toggleWiFiAP(boolean shutdown)
{
	if (WiFi.getMode() != WIFI_OFF)
	{
		WiFi.mode(WIFI_OFF);
		Serial.println("WiFi turned off");
#ifdef FEATURE_ENABLE_WIFI_CLIENT
		tmrWiFiMaintainConnection.stop();
#endif
	}
	else
	{
		WiFi.mode(WIFI_AP);
		WiFi.softAPConfig(IPAddress(WIFI_AP_IP_GW), IPAddress(WIFI_AP_IP_GW), IPAddress(255, 255, 255, 0));
		WiFi.softAP(QUOTE(WIFI_AP_SSID), QUOTE(WIFI_AP_PASSWORD));
#ifdef FEATURE_ENABLE_WIFI_CLIENT
		tmrWiFiMaintainConnection.stop();
		Serial.println("WiFi AP started, stopped Maintain-Timer");
#else
		Serial.println("WiFi AP started");
#endif
	}
}

void SystemShutdown()
{
	static uint32_t shutdown_delay = 0;

	if (shutdown_delay == 0)
	{
		shutdown_delay = millis() + SHUTDOWN_DELAY_MS;
		Serial.printf("Shutdown requested - Restarting in %d seconds\n", SHUTDOWN_DELAY_MS / 1000);
	}
	if (shutdown_delay < millis())
	{
		StoreConfig_EEPROM();
		StorePersistence_EEPROM();
		ESP.restart();
	}
}

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(const char *message, uint32_t time)
{
	DisplayOverrideFlag = millis() + time;
	strcpy(DisplayOverrideValue, message);
}

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)
			Serial.printf("KeyCombo 2: %d | 3: %d\n", keyCount_Fac2, keyCount_Fac3);

		if (keyCount_Fac2 == 2 && keyCount_Fac3 == 0)
		{
			Serial.println("KeyCombo: WiFi AP ON");
			OverrideDisplay("NET ", 5000);
			toggleWiFiAP(false);
		}
		else if (keyCount_Fac2 == 4 && keyCount_Fac3 == 0)
		{
			Serial.printf("KeyCombo: Reset Timer\n");
			if (millis() < RESETABLE_AFTER_STARTUP_MS)
			{
				OverrideDisplay("RST ", 5000);
				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("ERR ", 5000);
				Serial.printf("ERROR: only %d seconds after Startup!\n", RESETABLE_AFTER_STARTUP_MS / 1000);
			}
		}

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