#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "driver/gpio.h"
#include "esp_heap_caps.h" 

#define GPIO_SIGNAL_PIN     23
#define GPIO_SIGNAL_INTR    ESP_INTR_FLAG_LEVEL1
#define GPIO_SIGNAL_BIT     (1 << 0)
#define DEBOUNCE_TIME_MS    500 // Tempo de debounce de 500 ms

// Estados da máquina de estados
typedef enum {
    WAITING_SIGNAL,
    FLASHING_FIRMWARE,
    FINISHED_FLASHING
} state_t;

// Variáveis globais
static state_t current_state = WAITING_SIGNAL;
static EventGroupHandle_t gpio_evt_group;

// Callback para interrupção
static void IRAM_ATTR gpio_isr_handler(void* arg) {
    // Define o bit de evento do ISR
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xEventGroupSetBitsFromISR(gpio_evt_group, GPIO_SIGNAL_BIT, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

// Função da tarefa de Espera de Sinal
void wait_signal_task(void *pvParameters) {
    while (1) {
        if (current_state == WAITING_SIGNAL) {
            // Aguarda o sinal
            printf("Aguardando sinal...\n");
            xEventGroupWaitBits(
                gpio_evt_group,
                GPIO_SIGNAL_BIT,
                pdTRUE,
                pdTRUE,
                portMAX_DELAY
            );
            printf("Sinal detectado! Iniciando gravação...\n");
            current_state = FLASHING_FIRMWARE;
        }
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
}

// Função da tarefa de Gravação de Firmware
void flash_firmware_task(void *pvParameters) {
    const int progress_max = 37; // Comprimento da barra de progresso
    int progress;

    while (1) {
        if (current_state == FLASHING_FIRMWARE) {
            printf("Gravando firmware...\n");
            for (progress = 0; progress <= progress_max; progress++) {
                // Imprime o indicador de progresso
                printf("\r["); // Retorna ao início da linha
                for (int i = 0; i < progress_max; i++) {
                    if (i < progress) {
                        printf("#"); // Caractere de progresso
                    } else {
                        printf(" "); // Espaço representa a parte não completada
                    }
                }
                printf("] %d%%", (progress * 100) / progress_max);
                fflush(stdout); // Força a atualização da saída do console
                vTaskDelay(100 / portTICK_PERIOD_MS); // Atraso entre atualizações
            }
            printf("\nFirmware gravado com sucesso!\n");
            // Exibir informações de memória
            printf("Memória total livre: %lu bytes\n", esp_get_free_heap_size());
            printf("Menor tamanho de memória livre: %d bytes\n",
                   heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT));
            current_state = FINISHED_FLASHING;
        } else if (current_state == FINISHED_FLASHING) {
            printf("Gravação concluída. Voltando para a espera de sinal.\n");
            xEventGroupClearBits(gpio_evt_group, GPIO_SIGNAL_BIT);
            current_state = WAITING_SIGNAL; // Volta para aguardar o sinal
        }
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
}

// Função para inicializar a GPIO para detecção de sinal
void init_gpio() {
    gpio_config_t io_conf;
    // Configura para interrupção na borda de subida
    io_conf.intr_type = GPIO_INTR_POSEDGE;
    io_conf.pin_bit_mask = (1ULL << GPIO_SIGNAL_PIN);
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    gpio_config(&io_conf);

    // Instala o serviço de interrupção GPIO e configura a callback para o pino
    gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1);
    gpio_isr_handler_add(GPIO_SIGNAL_PIN, gpio_isr_handler, NULL);
}

void app_main() {
    // Inicializa a GPIO
    init_gpio();

    // Inicializa o grupo de eventos
    gpio_evt_group = xEventGroupCreate();

    // Cria a tarefa de Espera de Sinal
    xTaskCreate(wait_signal_task, "wait_signal_task", 2048, NULL, 10, NULL);

    // Cria a tarefa de Gravação de Firmware
    xTaskCreate(flash_firmware_task, "flash_firmware_task", 2048, NULL, 5, NULL);
}