#include "webui.h"

AsyncWebServer webServer(80);

typedef enum
{
  RESPMSG_HIDE,
  RESPMSG_SUCCESS,
  RESPMSG_INFO,
  RESPMSG_WARNING,
  RESPMSG_DANGER
} statusResponseMessage_Type_t;
char StatusResponseMessage[64];
statusResponseMessage_Type_t StatusResponseMessage_Type = RESPMSG_INFO;

#ifdef CAPTIVE
DNSServer dnsServer;
#endif
AsyncWebServer server(80);

String processor(const String &var);
void WebserverPOST_Callback(AsyncWebServerRequest *request);
void WebserverNotFound_Callback(AsyncWebServerRequest *request);
void Webserver_Callback(AsyncWebServerRequest *request);
void WebserverCommands_Callback(String input);
void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final);
void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final);
void WebServerEEJSON_Callback(AsyncWebServerRequest *request);
void GetFlashVersion(char *buff, size_t buff_size);

void initWebUI()
{
  if (!LittleFS.begin())
  {
    Serial.println("An Error has occurred while mounting LittleFS");
    MaintainDTC(DTC_FLASHFS_ERROR, DTC_CRITICAL, true);
    return;
  }

  GetFlashVersion(globals.FlashVersion, sizeof(globals.FlashVersion));

  if (strcmp(globals.FlashVersion, QUOTE(FLASH_FS_VERSION)))
  {
    MaintainDTC(DTC_FLASHFS_VERSION_ERROR, DTC_WARN, true);
  }

  MDNS.begin(globals.DeviceName);
  MDNS.addService("telnet", "tcp", 23);
  MDNS.addService("http", "tcp", 80);

  webServer.serveStatic("/static/", LittleFS, "/static/").setCacheControl("max-age=360000");
  webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
               { request->redirect("/index.htm"); });
  webServer.onNotFound(WebserverNotFound_Callback);
  webServer.on("/index.htm", HTTP_GET, Webserver_Callback);
  webServer.on("/post.htm", HTTP_POST, WebserverPOST_Callback);
  webServer.on("/eejson", HTTP_GET, WebServerEEJSON_Callback);
  webServer.on(
      "/doUpdate", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverFirmwareUpdate_Callback);
  webServer.on(
      "/eeRestore", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverEERestore_Callback);

  webServer.begin();
}

String processor(const String &var)
{
  if (var == "SHOW_DTC_TABLE")
    return globals.systemStatus == sysStat_Error ? "" : "hidden";

  if (var == "SHOW_RESP_MESSAGE")
    return StatusResponseMessage_Type != RESPMSG_HIDE ? "" : "hidden";

  if (var == "RESP_MESSAGE_TYPE")
  {
    switch (StatusResponseMessage_Type)
    {
    case RESPMSG_SUCCESS:
      return "success";
    case RESPMSG_INFO:
      return "info";
    case RESPMSG_WARNING:
      return "warning";
    case RESPMSG_DANGER:
      return "danger";
    default:
      return "info";
    }
  }

  if (var == "RESP_MESSAGE")
    return String(StatusResponseMessage);

  if (var == "BAT_REMAIN_CAPACITY")
    return String(globals.battery_level);

  if (var == "DEVICE_NAME")
    return String(globals.DeviceName);

  if (var == "BAT_VOLTAGE")
    return String((float)globals.loadvoltage_mV / 1000.0);

  if (var == "DTC_TABLE")
  {
    String temp;
    char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx

    for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++)
    {
      if (DTCStorage[i].Number > 0)
      {
        sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d",
                DTCStorage[i].timestamp / 86400000,    // Days
                DTCStorage[i].timestamp / 360000 % 24, // Hours
                DTCStorage[i].timestamp / 60000 % 60,  // Minutes
                DTCStorage[i].timestamp / 1000 % 60,   // Seconds
                DTCStorage[i].timestamp % 1000);       // milliseconds

        temp = "<tr><td>" + String(buff_timestamp);
        temp = temp + "</td><td>" + String(DTCStorage[i].Number) + "</td><td>";

        if (DTCStorage[i].active == DTC_ACTIVE)
          temp = temp + "active";
        else if (DTCStorage[i].active == DTC_PREVIOUS)
          temp = temp + "previous";
        else
          temp = temp + "none";

        temp = temp + "</td></tr>";
      }
    }
    return temp;
  }

  if (var == "PLACEHOLDER")
    return "placeholder";

  if (var == "POINTS_FAC_1")
  {
    char buff[12];
    snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_1_timer / 3600, (PersistenceData.faction_1_timer / 60) % 60, PersistenceData.faction_1_timer % 60);
    return String(buff);
  }

  if (var == "POINTS_FAC_2")
  {
    char buff[12];
    snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_2_timer / 3600, (PersistenceData.faction_2_timer / 60) % 60, PersistenceData.faction_2_timer % 60);
    return String(buff);
  }

  if (var == "POINTS_FAC_3")
  {
    char buff[12];
    snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_3_timer / 3600, (PersistenceData.faction_3_timer / 60) % 60, PersistenceData.faction_3_timer % 60);
    return String(buff);
  }

  if (var == "STATUS_FAC_1")
    return PersistenceData.activeFaction == FACTION_1 ? "ACTIVE" : "INACTIVE";

  if (var == "STATUS_FAC_2")
    return PersistenceData.activeFaction == FACTION_2 ? "ACTIVE" : "INACTIVE";

  if (var == "STATUS_FAC_3")
    return PersistenceData.activeFaction == FACTION_3 ? "ACTIVE" : "INACTIVE";

  if (var == "NAME_FAC_1")
    return FACTION_1_NAME;

  if (var == "NAME_FAC_2")
    return FACTION_2_NAME;

  if (var == "NAME_FAC_3")
    return FACTION_3_NAME;

  if (var == "TITLE")
    return DEVICE_NAME;

  if (var == "BATTERY_LEVEL")
  {
    return String(globals.battery_level);
  }
  if (var == "BATTERY_TYPE")
  {
    return String(BatteryString[ConfigData.batteryType]);
  }
  if (var == "BATTERY_VOLTAGE")
  {
    return String((float)globals.loadvoltage_mV / 1000.0);
  }

  return String();
}

void Webserver_Callback(AsyncWebServerRequest *request)
{
  request->send(LittleFS, "/index.htm", "text/html", false, processor);
  StatusResponseMessage_Type = RESPMSG_HIDE;
}

void WebserverPOST_Callback(AsyncWebServerRequest *request)
{
  int paramsNr = request->params();
  for (int i = 0; i < paramsNr; i++)
  {
    AsyncWebParameter *p = request->getParam(i);
    if (p->name() == "commandInput")
      WebserverCommands_Callback(p->value());
  }
  request->send(LittleFS, "/index.htm", "text/html", false, processor);
}

void WebserverNotFound_Callback(AsyncWebServerRequest *request)
{
  request->send(404, "text/html", "Not found");
}

void WebserverCommands_Callback(String input)
{
  String command = input.substring(0, input.indexOf(' '));
  command.toUpperCase();
  StatusResponseMessage_Type = RESPMSG_HIDE;

  if (command == "RESET")
  {
    strcpy(StatusResponseMessage, "Counter Reset done");
    StatusResponseMessage_Type = RESPMSG_SUCCESS;

    PersistenceData.faction_1_timer = 0;
    PersistenceData.faction_2_timer = 0;
    PersistenceData.faction_3_timer = 0;
    PersistenceData.activeFaction = NONE;
  }
  else if (command == "BAT3S")
  {
    strcpy(StatusResponseMessage, "Set BatteryType to LiPo 3S");
    StatusResponseMessage_Type = RESPMSG_SUCCESS;

    ConfigData.batteryType = BATTERY_LIPO_3S;
    globals.requestEEAction = EE_CFG_SAVE;
  }
  else if (command == "BAT2S")
  {
    strcpy(StatusResponseMessage, "Set BatteryType to LiPo 2S");
    StatusResponseMessage_Type = RESPMSG_SUCCESS;

    ConfigData.batteryType = BATTERY_LIPO_2S;
    globals.requestEEAction = EE_CFG_SAVE;
  }
}


void GetFlashVersion(char *buff, size_t buff_size)
{
  File this_file = LittleFS.open("version", "r");
  if (!this_file)
  { // failed to open the file, retrn empty result
    buff[0] = '\0';
    return;
  }
  if (this_file.available())
  {
    int bytes_read;
    bytes_read = this_file.readBytesUntil('\r', buff, buff_size - 1);
    buff[bytes_read] = '\0';
  }
  this_file.close();
}

void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final)
{

  if (!index)
  {
    Serial.println("Update");
    size_t content_len = request->contentLength();
    int cmd = (filename.indexOf(".fs") > -1) ? U_FS : U_FLASH;
    Update.runAsync(true);
    if (!Update.begin(content_len, cmd))
    {
      Update.printError(Serial);
    }
  }

  if (Update.write(data, len) != len)
  {
    Update.printError(Serial);
  }
  else
  {
    Serial.printf("Progress: %d%%\n", (Update.progress() * 100) / Update.size());
  }

  if (final)
  {
    AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots");
    response->addHeader("Refresh", "20");
    response->addHeader("Location", "/");
    request->send(response);
    if (!Update.end(true))
    {
      Update.printError(Serial);
    }
    else
    {
      Serial.println("Update complete");
      Serial.flush();
      globals.systemStatus = sysStat_Shutdown;
    }
  }
}

void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final)
{

  bool ee_done = false;
  bool validext = false;

  if (!index)
  {
    Serial.println("EEPROM restore");
    validext = (filename.indexOf(".ee.json") > -1);
  }

  if (validext)
  {
    Serial.println("Restoring EEPROM-Stuff");
  }

  if (final)
  {
    AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots");
    response->addHeader("Refresh", "20");
    response->addHeader("Location", "/");
    request->send(response);
    if (ee_done)
    {
      Serial.println("Update complete");
      Serial.flush();
      globals.systemStatus = sysStat_Shutdown;
    }
    else
    {
    }
  }
}

void WebServerEEJSON_Callback(AsyncWebServerRequest *request)
{
  AsyncResponseStream *response = request->beginResponseStream("application/json");
  DynamicJsonDocument json(1024);
  JsonObject fwinfo = json.createNestedObject("info");

  char buffer[16];

  fwinfo["DeviceName"] = globals.DeviceName;
  fwinfo["FW-Version"] = QUOTE(SW_VERSION);
  fwinfo["FS-Version"] = globals.FlashVersion;

  JsonObject config = json.createNestedObject("config");

  config["EEPROM_Version"] = ConfigData.EEPROM_Version;
 
  sprintf(buffer, "0x%08X", ConfigData.checksum);
  config["checksum"] = buffer;

  JsonObject eepart = json.createNestedObject("eepart");

  sprintf(buffer, "0x%04X", globals.eePersistanceAdress);
  eepart["PersistanceAddress"] = buffer;

  JsonObject persis = json.createNestedObject("persis");

  persis["writeCycleCounter"] = PersistenceData.writeCycleCounter;

  sprintf(buffer, "0x%08X", PersistenceData.checksum);
  persis["checksum"] = buffer;

  serializeJsonPretty(json, *response);

  response->addHeader("Content-disposition", "attachment; filename=backup.ee.json");

  request->send(response);
}