#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

// 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(7, 6, 5, 4, 3, 2);

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

#define BUZZER_PIN 15 // Pino do buzzer

// 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(const char *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;
    }

    // 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) {}, NULL, handleSaveConfig);

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

    server.begin();

    if (isAuthenticated)
    {
        displayMenu();
    }
}

void loop()
{
    handleKeypadForMenu();
}