#include "ws2812_status.h"

#include "dcdc_controller.h"
#include "led_strip.h"
#include "esp_check.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/semphr.h"
#include "sdkconfig.h"

#ifndef CONFIG_WATCH_WS2812_LED_COUNT
#define CONFIG_WATCH_WS2812_LED_COUNT 5
#endif
#ifndef CONFIG_WATCH_WS2812_GPIO
#define CONFIG_WATCH_WS2812_GPIO 8
#endif
#ifndef CONFIG_WATCH_WS2812_RMT_RESOLUTION
#define CONFIG_WATCH_WS2812_RMT_RESOLUTION (10 * 1000 * 1000)
#endif

#define WS2812_STATUS_GPIO ((gpio_num_t)CONFIG_WATCH_WS2812_GPIO)
#define WS2812_STATUS_RESOLUTION_HZ CONFIG_WATCH_WS2812_RMT_RESOLUTION
#define WS2812_ANIM_REFRESH pdMS_TO_TICKS(100)
#define WS2812_ALERT_BLINK_PERIOD pdMS_TO_TICKS(200)
#define WS2812_ALERT_GAP_PERIOD pdMS_TO_TICKS(2000)
#define WS2812_VPN_ALERT_BLINKS 2
#define WS2812_APP_ALERT_BLINKS 3
#define WS2812_BRIGHTNESS_PERCENT 5

#define WS2812_VPN_SECTION_TICKS (WS2812_ALERT_BLINK_PERIOD * 2U * WS2812_VPN_ALERT_BLINKS)
#define WS2812_APP_SECTION_TICKS (WS2812_ALERT_BLINK_PERIOD * 2U * WS2812_APP_ALERT_BLINKS)

static const char *TAG = "ws2812";

static led_strip_handle_t s_strip;
static bool s_channel_enabled[WS2812_STATUS_LED_COUNT];
static bool s_vpn_state[WS2812_STATUS_LED_COUNT];
static bool s_app_state[WS2812_STATUS_LED_COUNT];
static SemaphoreHandle_t s_ws_lock;
static TimerHandle_t s_animation_timer;
static bool s_error_state;
static bool s_startup_hold;
static TickType_t s_startup_expire_tick;
static TickType_t s_alert_cycle_epoch;
static size_t s_active_vpn_alerts;
static size_t s_active_app_alerts;

static esp_err_t ws2812_status_apply(void);
static void ws2812_animation_timer_cb(TimerHandle_t timer);
static void ws2812_status_compute_color(size_t index,
                                        TickType_t now_ticks,
                                        uint8_t *r,
                                        uint8_t *g,
                                        uint8_t *b);
static bool ws2812_recalculate_alert_counts(void);
static uint8_t ws2812_apply_brightness(uint8_t component);

static void ws2812_animation_timer_cb(TimerHandle_t timer)
{
    (void)timer;
    ws2812_status_apply();
}

static bool ws2812_startup_hold_active(TickType_t now_ticks)
{
    if (!s_startup_hold) {
        return false;
    }
    if (now_ticks >= s_startup_expire_tick) {
        s_startup_hold = false;
        return false;
    }
    return true;
}

static void ws2812_status_compute_color(size_t index,
                                        TickType_t now_ticks,
                                        uint8_t *r,
                                        uint8_t *g,
                                        uint8_t *b)
{
    if (!r || !g || !b || index >= WS2812_STATUS_LED_COUNT) {
        return;
    }

    *r = 0;
    *g = 0;
    *b = 0;

    if (s_error_state) {
        *r = 40;
        return;
    }

    if (ws2812_startup_hold_active(now_ticks)) {
        *g = 40;
        return;
    }

    if (!s_channel_enabled[index]) {
        return;
    }

    const bool vpn_alert = !s_vpn_state[index];
    const bool app_alert = !s_app_state[index];
    if (!vpn_alert && !app_alert) {
        return;
    }

    const bool vpn_global = (s_active_vpn_alerts > 0);
    const bool app_global = (s_active_app_alerts > 0);

    TickType_t cycle_ticks = 0;
    if (vpn_global) {
        cycle_ticks += WS2812_VPN_SECTION_TICKS + WS2812_ALERT_GAP_PERIOD;
    }
    if (app_global) {
        cycle_ticks += WS2812_APP_SECTION_TICKS + WS2812_ALERT_GAP_PERIOD;
    }
    if (cycle_ticks == 0) {
        return;
    }

    TickType_t blink_period = WS2812_ALERT_BLINK_PERIOD ? WS2812_ALERT_BLINK_PERIOD : 1;
    TickType_t cycle_pos = (now_ticks - s_alert_cycle_epoch) % cycle_ticks;

    if (vpn_global) {
        if (cycle_pos < WS2812_VPN_SECTION_TICKS) {
            if (vpn_alert) {
                const bool on = ((cycle_pos / blink_period) % 2U) == 0U;
                if (on) {
                    *r = 90;
                }
            }
            return;
        }
        cycle_pos -= WS2812_VPN_SECTION_TICKS;
        if (cycle_pos < WS2812_ALERT_GAP_PERIOD) {
            return;
        }
        cycle_pos -= WS2812_ALERT_GAP_PERIOD;
    }

    if (app_global) {
        if (cycle_pos < WS2812_APP_SECTION_TICKS) {
            if (app_alert) {
                const bool on = ((cycle_pos / blink_period) % 2U) == 0U;
                if (on) {
                    *r = 80;
                    *g = 80;
                }
            }
            return;
        }
        cycle_pos -= WS2812_APP_SECTION_TICKS;
        if (cycle_pos < WS2812_ALERT_GAP_PERIOD) {
            return;
        }
    }
}

static esp_err_t ws2812_status_apply(void)
{
    if (!s_strip) {
        return ESP_ERR_INVALID_STATE;
    }
    if (!s_ws_lock) {
        return ESP_ERR_INVALID_STATE;
    }
    if (xSemaphoreTake(s_ws_lock, portMAX_DELAY) != pdTRUE) {
        return ESP_ERR_TIMEOUT;
    }

    const TickType_t now_ticks = xTaskGetTickCount();
    esp_err_t err = ESP_OK;
    for (size_t i = 0; i < WS2812_STATUS_LED_COUNT && err == ESP_OK; ++i) {
        uint8_t r = 0, g = 0, b = 0;
        ws2812_status_compute_color(i, now_ticks, &r, &g, &b);
        err = led_strip_set_pixel(s_strip,
                                  i,
                                  ws2812_apply_brightness(g),
                                  ws2812_apply_brightness(r),
                                  ws2812_apply_brightness(b));
    }
    if (err == ESP_OK) {
        err = led_strip_refresh(s_strip);
    }

    xSemaphoreGive(s_ws_lock);
    return err;
}

esp_err_t ws2812_status_init(void)
{
    if (s_strip) {
        return ESP_OK;
    }

    led_strip_config_t strip_config = {
        .strip_gpio_num = WS2812_STATUS_GPIO,
        .max_leds = WS2812_STATUS_LED_COUNT,
        .led_model = LED_MODEL_WS2812,
        .flags.invert_out = false,
    };
    led_strip_rmt_config_t rmt_cfg = {
        .clk_src = RMT_CLK_SRC_DEFAULT,
        .resolution_hz = WS2812_STATUS_RESOLUTION_HZ,
        .flags.with_dma = false,
    };

    ESP_RETURN_ON_ERROR(
        led_strip_new_rmt_device(&strip_config, &rmt_cfg, &s_strip),
        TAG,
        "Не вдалося створити RMT пристрій для WS2812");
    ESP_RETURN_ON_ERROR(led_strip_clear(s_strip), TAG, "clear fail");

    s_ws_lock = xSemaphoreCreateMutex();
    ESP_RETURN_ON_FALSE(s_ws_lock, ESP_ERR_NO_MEM, TAG, "mutex alloc failed");

    const TickType_t now = xTaskGetTickCount();
    for (size_t i = 0; i < WS2812_STATUS_LED_COUNT; ++i) {
        s_channel_enabled[i] = false;
        s_vpn_state[i] = true;
        s_app_state[i] = true;
    }
    s_error_state = false;
    s_startup_hold = false;
    s_startup_expire_tick = 0;
    s_alert_cycle_epoch = now;
    (void)ws2812_recalculate_alert_counts();

    if (!s_animation_timer) {
        s_animation_timer = xTimerCreate("ws2812_anim",
                                         WS2812_ANIM_REFRESH,
                                         pdTRUE,
                                         NULL,
                                         ws2812_animation_timer_cb);
        ESP_RETURN_ON_FALSE(s_animation_timer, ESP_ERR_NO_MEM, TAG, "anim timer alloc failed");
        ESP_RETURN_ON_FALSE(xTimerStart(s_animation_timer, 0) == pdPASS,
                            ESP_FAIL,
                            TAG,
                            "anim timer start failed");
    }

    (void)ws2812_recalculate_alert_counts();
    return ws2812_status_apply();
}

esp_err_t ws2812_status_set_channel_state(size_t channel, bool enabled)
{
    if (channel >= WS2812_STATUS_LED_COUNT) {
        return ESP_ERR_INVALID_ARG;
    }
    if (!s_strip) {
        return ESP_ERR_INVALID_STATE;
    }
    s_channel_enabled[channel] = enabled;
    if (!enabled) {
        s_vpn_state[channel] = true;
        s_app_state[channel] = true;
    }
    return ws2812_status_apply();
}

esp_err_t ws2812_status_set_error(bool has_error)
{
    if (!s_strip) {
        return ESP_ERR_INVALID_STATE;
    }
    s_error_state = has_error;
    return ws2812_status_apply();
}

esp_err_t ws2812_status_set_service_state(size_t channel, bool vpn_ok, bool app_ok)
{
    if (channel >= WS2812_STATUS_LED_COUNT) {
        return ESP_ERR_INVALID_ARG;
    }
    if (!s_strip) {
        return ESP_ERR_INVALID_STATE;
    }
    bool changed = false;
    if (s_vpn_state[channel] != vpn_ok) {
        s_vpn_state[channel] = vpn_ok;
        changed = true;
    }
    if (s_app_state[channel] != app_ok) {
        s_app_state[channel] = app_ok;
        changed = true;
    }
    if (changed) {
        (void)ws2812_recalculate_alert_counts();
        s_alert_cycle_epoch = xTaskGetTickCount();
    }
    return ws2812_status_apply();
}

esp_err_t ws2812_status_refresh_from_dcdc(void)
{
    if (!s_strip) {
        return ESP_ERR_INVALID_STATE;
    }
    const size_t available_channels = dcdc_channel_count();
    const size_t count = available_channels < WS2812_STATUS_LED_COUNT
                             ? available_channels
                             : WS2812_STATUS_LED_COUNT;
    for (size_t i = 0; i < count; ++i) {
        s_channel_enabled[i] = dcdc_get_state(i);
    }
    for (size_t i = count; i < WS2812_STATUS_LED_COUNT; ++i) {
        s_channel_enabled[i] = false;
        s_vpn_state[i] = true;
        s_app_state[i] = true;
    }
    (void)ws2812_recalculate_alert_counts();
    s_alert_cycle_epoch = xTaskGetTickCount();
    return ws2812_status_apply();
}

esp_err_t ws2812_status_set_startup_hold(uint32_t duration_ms)
{
    if (!s_strip) {
        return ESP_ERR_INVALID_STATE;
    }
    if (duration_ms == 0) {
        s_startup_hold = false;
        s_startup_expire_tick = 0;
        return ws2812_status_apply();
    }

    TickType_t duration_ticks = pdMS_TO_TICKS(duration_ms);
    if (duration_ticks == 0) {
        duration_ticks = 1;
    }
    s_startup_hold = true;
    s_startup_expire_tick = xTaskGetTickCount() + duration_ticks;
    return ws2812_status_apply();
}
static bool ws2812_recalculate_alert_counts(void)
{
    size_t vpn = 0;
    size_t app = 0;
    for (size_t i = 0; i < WS2812_STATUS_LED_COUNT; ++i) {
        if (!s_channel_enabled[i]) {
            continue;
        }
        if (!s_vpn_state[i]) {
            ++vpn;
        }
        if (!s_app_state[i]) {
            ++app;
        }
    }
    const bool changed = (vpn != s_active_vpn_alerts) || (app != s_active_app_alerts);
    s_active_vpn_alerts = vpn;
    s_active_app_alerts = app;
    return changed;
}

static uint8_t ws2812_apply_brightness(uint8_t component)
{
    // Scale brightness down (component * 20%) with rounding to nearest.
    const uint16_t scaled = (component * WS2812_BRIGHTNESS_PERCENT + 50U) / 100U;
    return (uint8_t)scaled;
}
