#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "led.h"
#include "board_config.h"
#include "evse_error.h"
#include "evse_api.h"

#define LED_UPDATE_INTERVAL_MS 100
#define BLOCK_TIME pdMS_TO_TICKS(10)

static const char *TAG = "led";

typedef struct {
    gpio_num_t gpio;
    bool on : 1;
    uint16_t ontime;
    uint16_t offtime;
    TimerHandle_t timer;
    led_pattern_t pattern;
    uint8_t blink_count;
} led_t;

static led_t leds[LED_ID_MAX] = {0};
static TimerHandle_t led_update_timer = NULL;
static evse_state_t led_state = -1;

// ----------------------------
// Funções Internas
// ----------------------------

static void led_update_timer_callback(TimerHandle_t xTimer);
static void led_update(void);
static void led_apply_by_state(evse_state_t state);

static inline void led_gpio_write(gpio_num_t gpio, bool level) {
    if (gpio != GPIO_NUM_NC)
        gpio_set_level(gpio, level);
}

static void led_timer_callback(TimerHandle_t xTimer)
{
    led_t *led = (led_t *)pvTimerGetTimerID(xTimer);
    led->on = !led->on;
    led_gpio_write(led->gpio, led->on);
    uint32_t next_time = led->on ? led->ontime : led->offtime;

    xTimerChangePeriod(led->timer, pdMS_TO_TICKS(next_time), BLOCK_TIME);
}

// ----------------------------
// Inicialização
// ----------------------------

void led_init(void)
{
    gpio_config_t io_conf = {
        .mode = GPIO_MODE_OUTPUT,
        .intr_type = GPIO_INTR_DISABLE,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_ENABLE,
        .pin_bit_mask = 0
    };

    for (int i = 0; i < LED_ID_MAX; i++) {
        leds[i].gpio = GPIO_NUM_NC;
    }

    if (board_config.led_stop) {
        leds[LED_ID_STOP].gpio = board_config.led_stop_gpio;
        io_conf.pin_bit_mask |= BIT64(board_config.led_stop_gpio);
    }

    if (board_config.led_charging) {
        leds[LED_ID_CHARGING].gpio = board_config.led_charging_gpio;
        io_conf.pin_bit_mask |= BIT64(board_config.led_charging_gpio);
    }

    if (board_config.led_error) {
        leds[LED_ID_ERROR].gpio = board_config.led_error_gpio;
        io_conf.pin_bit_mask |= BIT64(board_config.led_error_gpio);
    }

    if (io_conf.pin_bit_mask != 0) {
        ESP_ERROR_CHECK(gpio_config(&io_conf));
    }

    if (!led_update_timer) {
        led_update_timer = xTimerCreate("led_update_timer",
                                        pdMS_TO_TICKS(LED_UPDATE_INTERVAL_MS),
                                        pdTRUE, NULL,
                                        led_update_timer_callback);
        if (led_update_timer) {
            xTimerStart(led_update_timer, BLOCK_TIME);
        } else {
            ESP_LOGE(TAG, "Failed to create LED update timer");
        }
    }
}

// ----------------------------
// API Pública
// ----------------------------

void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime)
{
    if (led_id >= LED_ID_MAX) return;

    led_t *led = &leds[led_id];
    if (led->gpio == GPIO_NUM_NC) return;

    // Evita reconfiguração idêntica
    if (led->ontime == ontime && led->offtime == offtime)
        return;

    if (led->timer) {
        xTimerStop(led->timer, BLOCK_TIME);
    }

    led->ontime = ontime;
    led->offtime = offtime;

    if (ontime == 0) {
        led->on = false;
        led_gpio_write(led->gpio, 0);
    } else if (offtime == 0) {
        led->on = true;
        led_gpio_write(led->gpio, 1);
    } else {
        led->on = true;
        led_gpio_write(led->gpio, 1);

        if (!led->timer) {
            led->timer = xTimerCreate("led_timer", pdMS_TO_TICKS(ontime),
                                      pdFALSE, (void *)led, led_timer_callback);
        }

        if (led->timer) {
            xTimerStart(led->timer, BLOCK_TIME);
        }
    }
}

void led_apply_pattern(led_id_t id, led_pattern_t pattern)
{
    if (id >= LED_ID_MAX) return;

    led_t *led = &leds[id];
    if (led->gpio == GPIO_NUM_NC) return;

    if (led->pattern == pattern) return;

    if (led->timer) {
        xTimerStop(led->timer, BLOCK_TIME);
    }

    led->pattern = pattern;
    led->blink_count = 0;

    switch (pattern) {
        case LED_PATTERN_OFF:
            led_set_state(id, 0, 0);
            break;
        case LED_PATTERN_ON:
            led_set_state(id, 1, 0);
            break;
        case LED_PATTERN_BLINK:
            led_set_state(id, 500, 500);
            break;
        case LED_PATTERN_BLINK_FAST:
            led_set_state(id, 200, 200);
            break;
        case LED_PATTERN_BLINK_SLOW:
            led_set_state(id, 300, 1700);
            break;
        case LED_PATTERN_CHARGING_EFFECT:
            led_set_state(id, 2000, 1000);
            break;
    }
}

// ----------------------------
// Controle por Estado
// ----------------------------

static void led_apply_by_state(evse_state_t state)
{
    // Reset todos
    led_apply_pattern(LED_ID_STOP, LED_PATTERN_OFF);
    led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_OFF);
    led_apply_pattern(LED_ID_ERROR, LED_PATTERN_OFF);

    switch (state) {
        case EVSE_STATE_A:
            led_apply_pattern(LED_ID_STOP, LED_PATTERN_ON);
            break;
        case EVSE_STATE_B1:
        case EVSE_STATE_B2:
        case EVSE_STATE_C1:
            led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_ON);
            break;
        case EVSE_STATE_C2:
            led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_CHARGING_EFFECT);
            break;
        case EVSE_STATE_D1:
        case EVSE_STATE_D2:
            led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_BLINK_FAST);
            break;
        case EVSE_STATE_E:
        case EVSE_STATE_F:
            led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST);
            break;
        default:
            break;
    }
}

// ----------------------------
// Timer Update
// ----------------------------

static void led_update(void)
{
    if (evse_error_is_active()) {
        led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST);
        led_apply_pattern(LED_ID_STOP, LED_PATTERN_OFF);
        led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_OFF);
        return;
    }

    evse_state_t current = evse_get_state();

    if (current != led_state) {
        led_state = current;
        led_apply_by_state(current);
    }
}

static void led_update_timer_callback(TimerHandle_t xTimer)
{
    (void)xTimer;
    led_update();
}
