// Librerias
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Arduino.h>
#include <LittleFS.h>
#include <HTTPClient.h>
#include <Wiegand.h>

//Definimos pines ESP32
#define PIN_D0 23
#define PIN_D1 22
#define PIN_RELAY 16
#define LED_OK 2


//Definimos LITTLEFS
#define FORMAT_LITTLEFS_IF_FAILED true

// Replace with your network credentials
// const char* ssid = "AkeppaSI";
// const char* password = "9@ArKoT#zEIR@vDz";


//Variables que se toman desde el HTML
const char* PARAM_INPUT_1 = "ssid";
const char* PARAM_INPUT_2 = "password";
const char* PARAM_INPUT_3 = "ip";


//Variables globales
String ssid;
String password;
String ip;
String cardData = "";
String lastCardData;

//Ruta de la api
//Dev
const char* serverName = "http://192.168.5.50/api/card/entrada";

// Objetos
Wiegand wiegand;
WiFiClient client;
HTTPClient http;

//Rutas del path
const char* ssidPath = "/ssid.txt";
const char* passwordPath = "/password.txt";
const char* ipPath = "/ip.txt";

// Creamos AsyncWebServer en puerto 80
AsyncWebServer asyncServer(80);

//Timers ESP32
unsigned long previousMillis = 0;
const long interval = 10000;
unsigned long lastTime = 0;
unsigned long timerDelay = 1000;

// Codigo HTML & CSS
const char index_html[] PROGMEM = R"rawliteral(
  <!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
    body {
    background-color: #f0f5ff;
    margin: 0;
    padding: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}

.container {
    max-width: 400px;
    margin: 0 auto;
    padding: 20px;
}

.form-group {
    margin-bottom: 20px;
}

.btn-container {
    text-align: center;
}

@media (max-width: 480px) {
    .container {
        padding: 10px;
    }
}

h2 {
    color: #1aa3d9;
    margin-bottom: 20px;
}

.form-group {
    margin-bottom: 20px;
}

label {
    display: block;
    color: #1aa3d9;
    font-weight: bold;
    margin-bottom: 5px;
}

input[type="text"],
input[type="password"] {
    width: 100%;
    padding: 10px;
    border: 1px solid #1aa3d9;
    border-radius: 4px;
}

.btn {
    background-color: #1aa3d9;
    color: #fff;
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.btn:hover {
    background-color: #1aa3d9;
}

.btn-container {
    text-align: center;
}

@media (max-width: 480px) {
    .container {
        padding: 20px;
    }
}
    </style>
</head>
<body>
<div class="view">
    <h2>Ajustes ESP32</h2>
    <form action="/" method="POST">
        <div class="form-group">
            <label for="ssid">SSID:</label>
            <input type="text" id="ssid" name="ssid" required>
        </div>
        <div class="form-group">
            <label for="password">Contrase&ntilde;a:</label>
            <input type="password" id="password" name="password" required>
        </div>
        <div class="form-group">
            <label for="ip">IP:</label>
            <input type="text" id="ip" name="ip" required>
        </div>
        <div class="form-group btn-container">
            <button type="submit" class="btn" value="Submit">Guardar</button>
        </div>
    </form>
</div>
</body>
</html>
)rawliteral";


//Metodo Setup
void setup() {


  //Iniciamnos los pines e indicamos su modo
  pinMode(LED_OK, OUTPUT);
  pinMode(PIN_D0, INPUT);
  pinMode(PIN_D1, INPUT);
  pinMode(PIN_RELAY, OUTPUT);
  
  digitalWrite(PIN_RELAY, LOW);
  //Iniciamnos Serial Port
  Serial.begin(115200);

  //Instalamos oyentes e inicializar la lectora Wiegand
  wiegand.onReceive(receivedData, "Card readed: ");
  
  wiegand.onReceiveError(receivedDataError, "Card read error: ");
  wiegand.onStateChange(stateChanged, "State changed: ");
  wiegand.begin(Wiegand::LENGTH_ANY, true);

  //Validamos  sistema de almacenamiento
  if (!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)) {
    Serial.println("LittleFS Mount Failed");
    return;
  }

  // deleteFile(LittleFS,ssidPath);
  // deleteFile(LittleFS,passwordPath);
  // deleteFile(LittleFS,ipPath);

  // Cargamos los valores LittleFS
  ssid = readFile(LittleFS, ssidPath);
  password = readFile(LittleFS, passwordPath);
  ip = readFile(LittleFS, ipPath);

  //Imprimimos variables
  Serial.println(ssid);
  Serial.println(password);
  Serial.println(ip);

  //Validamos si se puede realizar la conexion wifi
  if (initWiFi()) {
    // Código para cuando tenga conexión
    Serial.println("Conectado a la red WiFi");
    // Mostramos la dirección IP del ESP32
    Serial.println(WiFi.localIP());
  } else {
    // Se imprime que el ESP entrará en modo configuración
    Serial.println("Modo configuración AP (Access Point)");
    // Inicializamos el AP
    WiFi.softAP("ESP-WIFI-CONFIG-ENTRADA-PEATON", NULL);
    Serial.print("Dirección del AP: ");
    Serial.println(WiFi.softAPIP());

    // Ruta donde se accede al a configuarción
    asyncServer.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
      request->send_P(200, "text/html", index_html);
    });
    // Iniciamos el asyncServer
    asyncServer.begin();

    //En este apartado obtnemos los datos del modo configuracion
    asyncServer.on("/", HTTP_POST, [](AsyncWebServerRequest* request) {
      int params = request->params();
      for (int i = 0; i < params; i++) {
        AsyncWebParameter* p = request->getParam(i);
        if (p->isPost()) {
          // HTTP POST ssid valor
          if (p->name() == PARAM_INPUT_1) {
            ssid = p->value().c_str();
            Serial.print("SSID set to: ");
            Serial.println(ssid);
            // Escribimos los datos en LittleFS
            writeFile(LittleFS, ssidPath, ssid.c_str());
          }
          // HTTP POST pass valor
          if (p->name() == PARAM_INPUT_2) {
            password = p->value().c_str();
            Serial.print("Password set to: ");
            Serial.println(password);
            // Escribimos los datos en LittleFS
            writeFile(LittleFS, passwordPath, password.c_str());
          }
          // HTTP POST idSensor valor
          if (p->name() == PARAM_INPUT_3) {
            ip = p->value().c_str();
            Serial.print("Ip: ");
            Serial.println(ip);
            // Escribimos los datos en LittleFS
            writeFile(LittleFS, ipPath, ip.c_str());
          }
        }
      }
      //Una vez ingresados los datos mostramos mensaje y reiniciamos la ESP32
      request->send(200, "text/plain", "ESP configurada. Reiniciando dispositivo.........");
      delay(3000);
      ESP.restart();
    });

    //Iniciamos el asyncServer
    asyncServer.begin();
  }
}

void loop() {
  // Checks for pending messages
  wiegand.flush();

  // Check for changes on the the wiegand input pins
  wiegand.setPin0State(digitalRead(PIN_D0));
  wiegand.setPin1State(digitalRead(PIN_D1));

  //Send an HTTP POST request every 10 minutes
  if ((millis() - lastTime) > timerDelay) {

    //Check WiFi connection status
    if (WiFi.status() == WL_CONNECTED) {



      // Your Domain name with URL path or IP address with path
      http.begin(client, serverName);

      // If you need an HTTP request with a content type: application/json, use the following:
      http.addHeader("Content-Type", "application/json");


      if (!cardData.isEmpty()) {

        String json = "{\"uid\":\"" + strToDecimal(cardData) + "\"}";

        int httpResponseCode = http.POST(json);

        cardData = "";

        Serial.println(json);
        Serial.print("HTTP Response code: ");
        Serial.println(httpResponseCode);

        if (httpResponseCode == 201) {
           // Activa el relé
          digitalWrite(PIN_RELAY, HIGH); 
          digitalWrite(LED_OK, HIGH);
          //Ajsute de tempo
          delay(15000);  
          digitalWrite(PIN_RELAY, LOW);
          digitalWrite(LED_OK, LOW);  
        }
      }

      // Free resources
      http.end();
    }

    lastTime = millis();
  }
}

void stateChanged(bool plugged, const char* message) {
  Serial.print(message);
  Serial.println(plugged ? "CONNECTED" : "DISCONNECTED");
}

void receivedData(uint8_t* data, uint8_t bits, const char* message) {

  // Recorre el arreglo data y convierte los bytes en caracteres hexadecimales
  for (int i = 0; i < (bits + 7) / 8; i++) {
    // Convierte cada byte en un par de caracteres hexadecimales y añádelos al String
    cardData += String(data[i] >> 4, HEX);
    cardData += String(data[i] & 0xF, HEX);
  }
  Serial.println(cardData);
  delay(5000);
}


String strToDecimal(String cadena) {
  unsigned long SumaTotal = 0;
  unsigned long Pot = 0;
  unsigned long multiplo = 0;
  int z;

  cadena.toUpperCase();

  for (int x = cadena.length() - 1; x >= 0; x--) {

    z = (cadena.length() - x - 1);
    Pot = 1;
    for (int y = 0; y < z; y++) {
      Pot *= 16;
    }

    if (isDigit(cadena[x]))
      multiplo = int(cadena[x]) - 48;
    else  // no es digito
      multiplo = int(cadena[x]) - 55;

    SumaTotal += (Pot * multiplo);
  }

  return String(SumaTotal);
}

// Notifies when an invalid transmission is detected
void receivedDataError(Wiegand::DataError error, uint8_t* rawData, uint8_t rawBits, const char* message) {
  Serial.print(message);
  Serial.print(Wiegand::DataErrorStr(error));
  Serial.print(" - Raw data: ");
  Serial.print(rawBits);
  Serial.print("bits / ");

  //Print value in HEX
  uint8_t bytes = (rawBits + 7) / 8;
  for (int i = 0; i < bytes; i++) {
    Serial.print(rawData[i] >> 4, 16);
    Serial.print(rawData[i] & 0xF, 16);
  }
}



String readFile(fs::FS& fs, const char* path) {
  Serial.printf("Reading file: %s\r\n", path);

  File file = fs.open(path);
  if (!file || file.isDirectory()) {
    Serial.println("- failed to open file for reading");
    return String();
  }

  String fileContent;
  while (file.available()) {
    fileContent = file.readStringUntil('\n');
    break;
  }
  return fileContent;
}

void writeFile(fs::FS& fs, const char* path, const char* message) {
  Serial.printf("Writing file: %s\r\n", path);

  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("- failed to open file for writing");
    return;
  }
  if (file.print(message)) {
    Serial.println("- file written");
  } else {
    Serial.println("- write failed");
  }
  file.close();
}

void deleteFile(fs::FS& fs, const char* path) {
  Serial.printf("Deleting file: %s\r\n", path);
  if (fs.remove(path)) {
    Serial.println("- file deleted");
  } else {
    Serial.println("- delete failed");
  }
}

void listDir(fs::FS& fs, const char* dirname, uint8_t levels) {
  Serial.printf("Listing directory: %s\n", dirname);

  File root = fs.open(dirname);
  if (!root) {
    Serial.println("Failed to open directory");
    return;
  }
  if (!root.isDirectory()) {
    Serial.println("Not a directory");
    return;
  }

  File file = root.openNextFile();
  while (file) {
    if (file.isDirectory()) {
      Serial.print("  DIR : ");
      Serial.println(file.name());
      if (levels) {
        listDir(fs, file.name(), levels - 1);
      }
    } else {
      Serial.print("  FILE: ");
      Serial.print(file.name());
      Serial.print("  SIZE: ");
      Serial.println(file.size());
    }
    file = root.openNextFile();
  }
}

bool initWiFi() {
  Serial.println("Metodo initWIFI");
  WiFi.begin(ssid.c_str(), password.c_str());
  Serial.println("Conectando al Wifi...");
  unsigned long currentMillis = millis();
  previousMillis = currentMillis;

  while (WiFi.status() != WL_CONNECTED) {
    currentMillis = millis();
    if (currentMillis - previousMillis >= interval) {
      Serial.println("Error al conectar.");
      return false;
    }
  }
  Serial.println(WiFi.localIP());
  return true;
}
