#include <WiFi.h>
#include "ESPAsyncWebSrv.h" //Wokwi
//#include <ESPAsyncwebServer.h> //NodeMCU
#include <LittleFS.h>
#include <ArduinoJson.h>
#include <Wire.h>
#include <LiquidCrystal.h>
#include <Keypad.h>
// #include <Tone32.h>

#define FORMAT_LITTLEFS_IF_FAILED true
#define WEBSERVER_H
#define BUZZER_PIN 15 // Pino do buzzer

// Defina as credenciais de WiFi
const char *ssid = "AirBombAjotark";
const char *password = "ajotark";

// Crie uma instância do servidor
AsyncWebServer server(80);

// Crie uma instância do display 16x2 (endereço I2C 0x27)
LiquidCrystal lcd(19, 23, 18, 21, 5, 15);

// Variável global de configurações
StaticJsonDocument<256> configDoc;

// Variável para armazenar a senha gerada
String generatedPassword = "";

// Variável para armazenar se a senha já foi validada
bool isAuthenticated = false;

// Configuração do teclado 4x4
const byte ROWS = 4; // quatro linhas
const byte COLS = 4; // quatro colunas
char keys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

byte rowPins[ROWS] = {19, 18, 5, 17}; // Pines conectados às linhas do teclado
byte colPins[COLS] = {16, 4, 0, 2};   // Pines conectados às colunas do teclado

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);


// Variáveis do menu
enum GameMode
{
  PAGE_INIT,
  SABOTAGE,
  DOMINATION,
  SEARCH_DESTROY
};
GameMode currentMode = PAGE_INIT;

// Estrutura para armazenar as mensagens carregadas do arquivo JSON
struct Messages
{
  String page_init;
  String sabotage;
  String domination;
  String search_destroy;
  String starting_game;
  String canceled;
};

Messages messages;

// Função para carregar as mensagens de idioma
bool loadLanguage(String language)
{
  String filePath = "/data/language/";
  filePath += language;
  filePath += ".json";

  File file = LittleFS.open(filePath, "r");
  if (!file)
  {
    Serial.println("Falha ao abrir o arquivo de idioma.");
    return false;
  }

  // Carregar o conteúdo do arquivo JSON
  StaticJsonDocument<256> doc;
  DeserializationError error = deserializeJson(doc, file);

  if (error)
  {
    Serial.println("Erro ao carregar JSON de idioma.");
    file.close();
    return false;
  }

  // Armazenar as mensagens no struct
  messages.page_init = doc["page_init"].as<String>();
  messages.sabotage = doc["sabotage"].as<String>();
  messages.domination = doc["domination"].as<String>();
  messages.search_destroy = doc["search_destroy"].as<String>();
  messages.starting_game = doc["starting_game"].as<String>();
  messages.canceled = doc["canceled"].as<String>();

  file.close();
  return true;
}

// Função para exibir o menu com a seta e o jogo selecionado
void displayMenu()
{
  lcd.clear();

  // Exibe o jogo selecionado
  switch (currentMode)
  {
    case PAGE_INIT:
      lcd.setCursor(1, 0); // Desloca 1 caractere para deixar espaço para a seta
      lcd.print(messages.page_init);
      break;
    case SABOTAGE:
      lcd.setCursor(1, 0);
      lcd.print(messages.sabotage);
      break;
    case DOMINATION:
      lcd.setCursor(1, 0);
      lcd.print(messages.domination);
      break;
    case SEARCH_DESTROY:
      lcd.setCursor(1, 0);
      lcd.print(messages.search_destroy);
      break;
  }

  // Exibe as setas de navegação
  lcd.setCursor(16, 0); // A posição 0,0 é para a seta na esquerda da linha
  if (currentMode > PAGE_INIT)
  {
    lcd.write(0x7E); // Exibe a seta para cima (código do caractere `↑`)
  }

  lcd.setCursor(16, 1); // A posição 0,1 é para a seta para baixo
  if (currentMode < SEARCH_DESTROY)
  {
    lcd.write(0x7F); // Exibe a seta para baixo (código do caractere `↓`)
  }
}

void handleKeypadForMenu()
{
  char key = keypad.getKey(); // Verifica se uma tecla foi pressionada

  if (key)
  {
    tone(BUZZER_PIN, 1000, 100); // Toca o buzzer

    switch (key)
    {
      case 'A':                                                     // Mover para cima
        currentMode = (GameMode)(((int)currentMode - 1 + 4) % 4); // Loop circular
        displayMenu();
        break;
      case 'B':                                                 // Mover para baixo
        currentMode = (GameMode)(((int)currentMode + 1) % 4); // Loop circular
        displayMenu();
        break;
      case 'C': // Cancelar
        lcd.clear();
        lcd.print(messages.canceled);
        delay(1000);
        break;
      case 'D': // Confirmar seleção
        lcd.clear();
        lcd.print(messages.starting_game);

        switch (currentMode)
        {
          case SABOTAGE:
            lcd.print(messages.sabotage);
            startSabotageGame();
            break;
          case DOMINATION:
            lcd.print(messages.domination);
            startDominationGame();
            break;
          case SEARCH_DESTROY:
            lcd.print(messages.search_destroy);
            startSearchDestroyGame();
            break;
          default:
            lcd.print(messages.page_init);
            break;
        }
        break;
    }
  }
}

// Funções para iniciar os modos de jogo (exemplo)
void startSabotageGame()
{
  // Lógica para iniciar o modo Sabotage
}

void startDominationGame()
{
  // Lógica para iniciar o modo Domination
}

void startSearchDestroyGame()
{
  // Lógica para iniciar o modo Search & Destroy
}

// Função para carregar o config.json para a variável global
void loadConfig()
{
  File file = LittleFS.open("/config.json", "r");
  if (!file)
  {
    Serial.println("Erro ao abrir o arquivo config.json");
    return;
  }

  // Desserializa o arquivo JSON
  DeserializationError error = deserializeJson(configDoc, file);
  if (error)
  {
    Serial.println("Erro ao desserializar o arquivo JSON");
  }

  String language = configDoc["language"].as<String>();
  loadLanguage(language);

  file.close();
}

// Função para salvar o config.json a partir da variável global
void saveConfig()
{
  File file = LittleFS.open("/config.json", "w");
  if (!file)
  {
    Serial.println("Erro ao abrir o arquivo config.json para escrita");
    return;
  }

  // Serializa o documento JSON e grava no arquivo
  serializeJson(configDoc, file);
  file.close();
}

// Função para exibir informações no display
void updateDisplay(const char *line1, const char *line2, bool center1 = false, bool center2 = false)
{
  lcd.clear();

  // Exibe ou rola a primeira linha
  if (strlen(line1) > 16)
  {
    scrollText(line1, 0); // Rola o texto na primeira linha
  }
  else
  {
    if (center1)
    {
      int len = strlen(line1);
      int pad = (16 - len) / 2;
      lcd.setCursor(pad, 0); // Centraliza
    }
    else
    {
      lcd.setCursor(0, 0);
    }
    lcd.print(line1);
  }

  // Exibe ou rola a segunda linha
  if (strlen(line2) > 16)
  {
    scrollText(line2, 1); // Rola o texto na segunda linha
  }
  else
  {
    if (center2)
    {
      int len = strlen(line2);
      int pad = (16 - len) / 2;
      lcd.setCursor(pad, 1); // Centraliza
    }
    else
    {
      lcd.setCursor(0, 1);
    }
    lcd.print(line2);
  }
}

// Função para rolar o texto no display (marquee effect) com "loop" contínuo
void scrollText(const char *message, int row)
{
  int messageLen = strlen(message);

  // Rola indefinidamente
  while (true)
  {
    // Rola a janela de 16 caracteres pela mensagem
    for (int i = 0; i < messageLen - 16 + 1; i++)
    {
      lcd.clear();
      lcd.setCursor(0, row);
      for (int j = 0; j < 16; j++)
      {
        lcd.print(message[i + j]);
      }
      delay(300); // Atraso para o efeito de rolagem
    }

    // Quando chegar ao final, volta a rolar o texto desde o início
    for (int i = 0; i < 16; i++)
    {
      lcd.clear();
      lcd.setCursor(0, row);
      // Rola a parte final da mensagem e começa a repetir do início
      for (int j = 0; j < 16; j++)
      {
        lcd.print(message[(i + j) % messageLen]); // Usa o operador '%' para criar o loop
      }
      delay(300); // Atraso para o efeito de rolagem
    }
  }
}

// Função para exibir o IP e a senha gerada no display
void displayIPAndPassword()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("IP:");
  lcd.print(WiFi.localIP());
  lcd.setCursor(0, 1);
  lcd.print("Senha:");
  lcd.print(generatedPassword);
}

// Função para gerar uma senha aleatória de 4 dígitos
String generateRandomPassword()
{
  String password = "";
  for (int i = 0; i < 4; i++)
  {
    password += String(random(0, 10)); // Gera um número aleatório entre 0 e 9
  }
  return password;
}

// Função chamada ao salvar o config.json via requisição POST
void handleSaveConfig(AsyncWebServerRequest *request, uint8_t *data, size_t len)
{
  String jsonData = "";
  for (size_t i = 0; i < len; i++)
  {
    jsonData += (char)data[i];
  }

  DeserializationError error = deserializeJson(configDoc, jsonData);
  if (error)
  {
    request->send(400, "application/json", "{\"status\":\"Erro ao processar JSON\"}");
    return;
  }

  saveConfig();
  request->send(200, "application/json", "{\"status\":\"Configurações salvas com sucesso!\"}");
  updateDisplay("Config Atualizada!", "");
}

// Verifica a senha digitada pelo usuário
void handlePasswordCheck(AsyncWebServerRequest *request)
{
  if (request->hasParam("password"))
  {
    String password = request->getParam("password")->value();
    if (password == generatedPassword)
    {
      isAuthenticated = true;
      request->send(200, "application/json", "{\"status\":\"Autenticado!\"}");
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Acesso Liberado");
    }
    else
    {
      request->send(401, "application/json", "{\"status\":\"Senha incorreta!\"}");
    }
  }
  else
  {
    request->send(400, "application/json", "{\"status\":\"Senha não fornecida!\"}");
  }
}

// Função para tocar o buzzer
void playBuzzer()
{
  tone(BUZZER_PIN, 1000); // Frequência do som
  delay(100);             // Duração
  noTone(BUZZER_PIN);
}

void splashScreen()
{
  delay(5000);
}

void setup()
{
  Serial.begin(115200);

  if (!LittleFS.begin())
  {
    Serial.println("Erro ao montar o sistema de arquivos LittleFS!");
    return;
  }

  lcd.begin(16, 2);
  // lcd.init();
  // lcd.backlight();

  splashScreen();

  loadConfig();
  generatedPassword = generateRandomPassword();

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
    Serial.println("Conectando ao WiFi...");
    updateDisplay("Conectando WiFi...", "");
  }
  Serial.println("WiFi conectado!");
  displayIPAndPassword();

  // Configura o servidor web
  server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");

  server.on("/config.json", HTTP_GET, [](AsyncWebServerRequest * request)
  {
    if (isAuthenticated) {
      String jsonString;
      serializeJson(configDoc, jsonString);
      request->send(200, "application/json", jsonString);
    } else {
      request->send(403, "application/json", "{\"status\":\"Não autenticado\"}");
    }
  });

// server.on("/save-config", HTTP_POST, 
//   [](AsyncWebServerRequest *request) {
//     // Handler para enviar a resposta após processar os dados
//     request->send(200, "application/json", "{\"status\":\"Configurações recebidas!\"}");
//   }, 
//   NULL, 
//   [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
//     // Handler para processar o corpo da requisição
//     handleSaveConfig(request, data, len);
//   }
// );

  server.on("/check-password", HTTP_POST, handlePasswordCheck);

  server.begin();

  if (isAuthenticated)
  {
    displayMenu();
  }
}

void loop()
{
  handleKeypadForMenu();
}