/**
 * @file    led_ancs.c
 *
 * @author  PerL13
 * @date    2025-09-16
 * @version 1.1
 *
 * @note    This module assumes LEDs are connected to GPIO16.
 *
 */

#include "led_ancs.h"
#include "led_strip.h"
#include "esp_timer.h"
#include "esp_log.h"

#define LED_ANCS_TAG  "LED_ANCS"

static led_state_t led_state;
static led_strip_handle_t strip;
static uint8_t led_brightness[LED_STRIP_COUNT] = {0};

// --- millis helper ---
static inline uint32_t millis(void) {
    return (uint32_t)(esp_timer_get_time() / 1000ULL);
}

// --- Internal helpers ---
static void leds_clear(void) {
    esp_err_t err = led_strip_clear(strip);
    if (err != ESP_OK) {
        ESP_LOGE(LED_ANCS_TAG, "Failed to clear leds (err=0x%x)", err);
    }
}

void led_init(void)
{
    esp_err_t err;

    led_strip_config_t strip_config = {
        .strip_gpio_num = LED_STRIP_PIN,    // WS2812 data pin
        .max_leds = LED_STRIP_COUNT,        // number of pixels
        .flags.invert_out = false,
    };

    led_strip_rmt_config_t rmt_config = {
        .clk_src = RMT_CLK_SRC_DEFAULT,
        .resolution_hz = 10 * 1000 * 1000, // 10 MHz
        .mem_block_symbols = 64,
        .flags.with_dma = false,
    };

    err = led_strip_new_rmt_device(&strip_config, &rmt_config, &strip);
    if (err != ESP_OK) {
        ESP_LOGE(LED_ANCS_TAG, "Failed to configure led strip (err=0x%x)", err);
    }else{
        ESP_LOGI(LED_ANCS_TAG, "Led strip configured");
    }

    leds_clear();
    led_state.mode = LED_MODE_OFF;
    led_state.r = led_state.g = led_state.b = 0;
    led_state.start_time = 0;
    led_state.chase_index = 0;
    led_state.left_position = 0;
    led_state.right_position = 0;
}

static void leds_solid(uint8_t r, uint8_t g, uint8_t b) {
    esp_err_t err;

    for (int i = 0; i < LED_STRIP_COUNT; i++) {
        err = led_strip_set_pixel(strip, i, r, g, b);
        if (err != ESP_OK) {
            ESP_LOGE(LED_ANCS_TAG, "Failed to set pixel %d (err=0x%x)", i, err);
        }
    }
    err = led_strip_refresh(strip);
    if (err != ESP_OK) {
        ESP_LOGE(LED_ANCS_TAG, "Failed to refresh strip (err=0x%x)", err);
    }
}

static void led_strobe_run(void) {
    bool should_on = false;

    uint32_t elapsed = millis() - led_state.start_time;

    uint32_t burst_period = (LED_STROBE_BURSTS * LED_STROBE_PERIOD_MS + LED_STROBE_PAUSE_MS);
    uint32_t cycle = elapsed % burst_period;
    if (cycle < LED_STROBE_BURSTS * LED_STROBE_PERIOD_MS) {
        uint32_t phase = cycle % LED_STROBE_PERIOD_MS;
        should_on = (phase < LED_STROBE_PERIOD_MS / 2); //50% duty cycle
    } else {
        should_on = false; // we are in pause
    }

    if (should_on != led_state.strobe_on) {
        led_state.strobe_on = should_on;
        if (led_state.strobe_on) leds_solid(led_state.r, led_state.g, led_state.b);
        else leds_clear();
    }
}

static void led_chase_run(void) {
    esp_err_t err;

    // fade all LEDs
    for (int i = 0; i < LED_STRIP_COUNT; i++) {
        if (led_brightness[i] > LED_DECAY_STEP)
            led_brightness[i] -= LED_DECAY_STEP;
        else
            led_brightness[i] = 0;
    }

    // light head LED
    led_brightness[led_state.chase_index] = LED_BRIGHTNESS_MAX;

    // update strip
    for (int i = 0; i < LED_STRIP_COUNT; i++) {
        err = led_strip_set_pixel(strip, i,
                                 (led_state.r * led_brightness[i]) / 255,
                                 (led_state.g * led_brightness[i]) / 255,
                                 (led_state.b * led_brightness[i]) / 255);
        if (err != ESP_OK) {
            ESP_LOGE(LED_ANCS_TAG, "Failed to set pixel %d (err=0x%x)", i, err);
        }
    }
    err = led_strip_refresh(strip);
    if (err != ESP_OK) {
        ESP_LOGE(LED_ANCS_TAG, "Failed to refresh strip (err=0x%x)", err);
    }
    // move head
    led_state.chase_index = (led_state.chase_index + 1) % LED_STRIP_COUNT;
}

void led_set_strobe(uint8_t r, uint8_t g, uint8_t b, uint8_t brightness, uint32_t duration_ms) {
    led_state.mode = LED_MODE_STROBE;
    led_state.r = (uint16_t)r * brightness / 255;
    led_state.g = (uint16_t)g * brightness / 255;
    led_state.b = (uint16_t)b * brightness / 255;
    led_state.duration_ms = duration_ms;
    led_state.start_time = millis();
    led_state.strobe_on = false;
}

void led_set_chase(uint8_t r, uint8_t g, uint8_t b, uint8_t brightness, uint32_t duration_ms) {
    led_state.mode = LED_MODE_CHASE;
    led_state.r = (uint16_t)r * brightness / 255;
    led_state.g = (uint16_t)g * brightness / 255;
    led_state.b = (uint16_t)b * brightness / 255;
    led_state.duration_ms = duration_ms;
    led_state.start_time = millis();
    led_state.chase_index = 0;
}

void led_set_status(uint8_t r, uint8_t g, uint8_t b, uint8_t brightness, uint32_t duration_ms) {
    led_state.mode = LED_MODE_STATUS;
    led_state.r = (uint16_t)r * brightness / 255;
    led_state.g = (uint16_t)g * brightness / 255;
    led_state.b = (uint16_t)b * brightness / 255;
    led_state.duration_ms = duration_ms;
    led_state.start_time = millis();
    led_state.left_position = 0;
    led_state.right_position = LED_STRIP_COUNT-1;
}

// --- Public API ---
void led_set_solid(uint8_t r, uint8_t g, uint8_t b) {
    led_state.mode = LED_MODE_SOLID;
    led_state.r = r;
    led_state.g = g;
    led_state.b = b;
    leds_solid(r, g, b);
}

void set_led_by_category(const uint8_t cid)
{
    const rgb_t *c;

    if (cid < sizeof(category_colors)/sizeof(category_colors[0])){
        c = &category_colors[cid];
    }else{
         c = &category_colors[12];
    }

    led_set_strobe(c->r, c->g, c->b, LED_BRIGHTNESS_MAX, LED_STROBE_TIME_MS);
    //led_set_chase(c->r, c->g, c->b, LED_BRIGHTNESS_MAX, LED_CHASE_TIME_MS);
}

// --- Non-blocking update (call frequently) ---
void led_update(void) {
    esp_err_t err;
    uint32_t now = millis();
    uint32_t elapsed;

    switch (led_state.mode) {
        case LED_MODE_STROBE: {
            elapsed = now - led_state.start_time;
            if (elapsed > led_state.duration_ms) {
                led_set_status(led_state.r, led_state.g, led_state.b, LED_BRIGHTNESS_MIN, LED_STATUS_TIME_MS); // go to status
                break;
            }
            led_strobe_run();
            break;
        }
        case LED_MODE_CHASE: {
            elapsed = now - led_state.start_time;
            if (elapsed > led_state.duration_ms) {
                led_set_status(led_state.r, led_state.g, led_state.b, LED_BRIGHTNESS_MIN, LED_STATUS_TIME_MS); // go to status
                break;
            }
            led_chase_run();
            break;
        }
        case LED_MODE_STATUS:
            elapsed = now - led_state.start_time;
            if (led_state.left_position < led_state.right_position) {
                leds_clear();
                err = led_strip_set_pixel(strip, led_state.left_position, led_state.r, led_state.g, led_state.b);
                if (err != ESP_OK) {
                    ESP_LOGE(LED_ANCS_TAG, "Failed to set pixel %d (err=0x%x)", led_state.left_position, err);
                }
                err = led_strip_set_pixel(strip, led_state.right_position, led_state.r, led_state.g, led_state.b);
                if (err != ESP_OK) {
                    ESP_LOGE(LED_ANCS_TAG, "Failed to set pixel %d (err=0x%x)", led_state.right_position, err);
                }
                err = led_strip_refresh(strip);
                if (err != ESP_OK) {
                    ESP_LOGE(LED_ANCS_TAG, "Failed to refresh strip (err=0x%x)", err);
                }
                led_state.left_position++;
                led_state.right_position--;
            }else if (elapsed > led_state.duration_ms) {
                led_set_status(0, 0, LED_BRIGHTNESS_MAX, LED_BRIGHTNESS_MIN, LED_STATUS_TIME_MS); // set blue and restart same state
            }
            break;
        default:
            break;
    }
}
