//----------------------------------------------------------------------------------------
//
//   Status LED Controller
//
//   (C) 2026 colrhon.org
//   This program is released under the Creative Commons Public License, CC BY-NC-SA 4.0
//
//   tbd:
//   - maybe move the selftest to different place

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "driver/gptimer.h"
#include <esp_system.h>
#include "esp_log.h"
#include "esp_check.h"
#include "soc/gpio_struct.h"
#include "rom/ets_sys.h"

// adjust
//#define DEBUG
#include "platform.h"
#include "pgpio.h"
#include "pwav.h"

// Status task for ESP32 S3 only
// *****************************
#ifdef ESP32_S3

#define LED_WS2812 1
#define LED_SINGLE 2
//#define LED LED_SINGLE // compile for single led
#define LED LED_WS2812 // compile for RGB led

extern void ws2812b_init(void);
extern void ws2812b_set_pixel_color(uint8_t index, uint32_t color);
extern void ws2812b_clear(void);
extern void ws2812b_show(void);

#define ESP_INTR_FLAG_DEFAULT 0
extern StreamBufferHandle_t xpinevt;

// interprocess communication to assign job to LED controller
StreamBufferHandle_t xledevt;

static gptimer_handle_t sftimer = NULL;

typedef struct {
    uint16_t state;
    uint32_t color;
    uint32_t delay;
    uint32_t ontime;
} LedCtrl;

static inline void LED_plan(LedCtrl *lc, uint32_t color, uint32_t delay, uint32_t ontime) {
    lc->state = 0;
    lc->color = color;
    lc->delay = delay;
    lc->ontime = ontime+1;
}
                      
static void LED_on(uint32_t color) {
#if LED==LED_SINGLE
    gpio_set_level(LEDPIN,0);    
#elif LED==LED_WS2812
    ws2812b_set_pixel_color(0,color);
    ws2812b_show();
#endif
}

static void LED_off(void) {
#if LED==LED_SINGLE
    gpio_set_level(LEDPIN,1);
#elif LED==LED_WS2812
    ws2812b_set_pixel_color(0,0);
    ws2812b_show();
#endif
    }

// selftest timer expiry
static bool ST_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx) {
    gptimer_stop(timer);
    gpio_intr_enable(SELFTEST);
    return false;
}

static void IsrSelftest(void* arg) {
    for (int i = 0; i < 10; i++) if (gpio_get_level(SELFTEST) == 1) return;
    gpio_intr_disable(SELFTEST);
    gptimer_set_raw_count(sftimer,3000000); // 3s
    gptimer_start(sftimer);

    Rxcmd xcmd;
    // sound
    xcmd.cmd = 'p'; xcmd.arg = 0x32;
    xStreamBufferSend(xpinevt,&xcmd,sizeof(Rxcmd),1);
    // led
    xcmd.cmd = 'a'; xcmd.arg = 0;
    xStreamBufferSend(xledevt,&xcmd,sizeof(Rxcmd),1);
}

//
// Status report
//
void RepStatus(void *pvParameters) {

    // Selftest
    gpio_config_t io_conf = {0};
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.intr_type = GPIO_INTR_NEGEDGE;
    io_conf.pin_bit_mask =
        (1ULL << SELFTEST);
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    ESP_ERROR_CHECK(gpio_config(&io_conf));
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    gpio_isr_handler_add(SELFTEST, IsrSelftest, (void*)SELFTEST);

    // configure GPIO for LED
    gpio_config_t io_conf1 = {0};
    io_conf1.intr_type = GPIO_INTR_DISABLE;
    io_conf1.mode = GPIO_MODE_OUTPUT;
    io_conf1.pin_bit_mask = (1ULL << LEDPIN);
    io_conf1.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf1.pull_up_en = GPIO_PULLUP_DISABLE;
    ESP_ERROR_CHECK(gpio_config(&io_conf1));
    gpio_set_level(LEDPIN,1);

    // selftest timer
    gptimer_config_t timer_config = {
        .clk_src = GPTIMER_CLK_SRC_DEFAULT,
        .direction = GPTIMER_COUNT_DOWN,
        .resolution_hz = 1 * 1000 * 1000,   // 1 tick equals 1 microsecond
    };
    ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &sftimer));
    gptimer_alarm_config_t alarm_config = {
        .alarm_count = 10,
        .flags.auto_reload_on_alarm = false,
    };
    ESP_ERROR_CHECK(gptimer_set_alarm_action(sftimer, &alarm_config));
    gptimer_event_callbacks_t cbs = {
        .on_alarm = ST_alarm_cb,
    };
    ESP_ERROR_CHECK(gptimer_register_event_callbacks(sftimer, &cbs, NULL));
    ESP_ERROR_CHECK(gptimer_enable(sftimer));

#if LED==LED_WS2812
    // Initialize WS2812 LEDs
    ws2812b_init();
    ws2812b_clear();
    ws2812b_show();
#endif
    
    gpio_intr_enable(SELFTEST);
    LedCtrl ledc = {0}; 
    while(1) {
        Rxcmd xcmd;
        if ((xStreamBufferReceive(xledevt,&xcmd,sizeof(Rxcmd),0) == sizeof(Rxcmd))
            &&  (gpio_get_level(DIP_DSA) == 0)) {
            switch (xcmd.cmd) {
            case 'a': // assure: green short
                LED_plan(&ledc,0x550000,0,1); // green
                break;
            case 'b': // assure: blue short
                LED_plan(&ledc,0x0000ff,0,1); // blue
                break;
            case 'e': // warning: red short
                LED_plan(&ledc,0x00ff00,0,1); // red
                break;
            case 'E': // warning: red long
                LED_plan(&ledc,0x00ff00,0,10); // red
                break;
            case 'f': // failed: red forever
                LED_plan(&ledc,0x00ff00,0,1000000); // red
                break;
            case 'F': // failed if not cleared whitin 10 ticks: red forever, delayed
                LED_plan(&ledc,0x00ff00,10,1000000); // red
                break;
            case 'c': // clear
                ledc.delay = 0;
                ledc.ontime = 0;
                LED_off();
                break;
            }
        }
        vTaskDelay(4);
        // switch on/off led
        if (ledc.ontime > 0) {
            if (ledc.delay > 0) ledc.delay--;
            else {
                if (ledc.ontime == 1) LED_off();
                else LED_on(ledc.color); // this switches led on repeatedly
                ledc.ontime--;
            }
        }        
    }
}

#endif // ESP32_S3
