//----------------------------------------------------------------------------------------
//
//    LED WS2812 control
//
//    Code copied from https://github.com/grzegorzwozny/ESP32-WS2812B
//    MIT License, Copyright (c) 2025 Grzegorz Woźny
//
//    Note: Code is not interrupt safe, invoking ws2812b_show() from inside an ISR fails 
//    2026 colrhon.org
//

#include  <string.h>
#include "freertos/FreeRTOS.h"
#include "driver/rmt_tx.h"
#include "esp_err.h"

#include "platform.h"
#include "pgpio.h"

// WS2812 support for ESP32 S3 only
// ********************************
#ifdef ESP32_S3

// Configuration
#define RMT_LED_STRIP_RESOLUTION_HZ 10000000 // 10MHz resolution, 1 tick = 0.1us (led strip needs a high resolution)
#define RMT_LED_STRIP_GPIO_NUM      LEDPIN   // see pgpio.h
#define LED_NUMBERS_MAX             1
#define FRAME_DURATION_MS           10

static uint8_t led_strip_pixels[LED_NUMBERS_MAX * 3];

static rmt_channel_handle_t led_chan = NULL;
static rmt_encoder_handle_t simple_encoder = NULL;

// Start LED rainbow chase
static rmt_transmit_config_t tx_config = {
    .loop_count = 0, // no transfer loop
};

static const rmt_symbol_word_t ws2812_zero = {
    .level0 = 1,
    .duration0 = 0.3 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000, // T0H=0.3us
    .level1 = 0,
    .duration1 = 0.9 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000, // T0L=0.9us
};

static const rmt_symbol_word_t ws2812_one = {
    .level0 = 1,
    .duration0 = 0.9 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000, // T1H=0.9us
    .level1 = 0,
    .duration1 = 0.3 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000, // T1L=0.3us
};

// Reset defaults to 50uS
static const rmt_symbol_word_t ws2812_reset = {
    .level0 = 1,
    .duration0 = RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 50 / 2,
    .level1 = 0,
    .duration1 = RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 50 / 2,
};

static size_t encoder_callback(const void *data, size_t data_size,
                               size_t symbols_written, size_t symbols_free,
                               rmt_symbol_word_t *symbols, bool *done, void *arg)
{
    // We need a minimum of 8 symbol spaces to encode a byte. We only
    // need one to encode a reset, but it's simpler to simply demand that
    // there are 8 symbol spaces free to write anything.
    if (symbols_free < 8) {
        return 0;
    }

    // We can calculate where in the data we are from the symbol pos.
    // Alternatively, we could use some counter referenced by the arg
    // parameter to keep track of this.
    size_t data_pos = symbols_written / 8;
    uint8_t *data_bytes = (uint8_t*)data;
    if (data_pos < data_size) {
        // Encode a byte
        size_t symbol_pos = 0;
        for (int bitmask = 0x80; bitmask != 0; bitmask >>= 1) {
            if (data_bytes[data_pos]&bitmask) {
                symbols[symbol_pos++] = ws2812_one;
            } else {
                symbols[symbol_pos++] = ws2812_zero;
            }
        }
        // We're done; we should have written 8 symbols.
        return symbol_pos;
    } else {
        //All bytes already are encoded.
        //Encode the reset, and we're done.
        symbols[0] = ws2812_reset;
        *done = 1; //Indicate end of the transaction.
        return 1; //we only wrote one symbol
    }
}

void ws2812b_init(void)
{
    // Reset buffer
    memset(led_strip_pixels, 0, sizeof(led_strip_pixels));

    // Create RMT TX channel
    rmt_tx_channel_config_t tx_chan_config = {
        .clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
        .gpio_num = RMT_LED_STRIP_GPIO_NUM,
        .mem_block_symbols = 64, // increase the block size can make the LED less flickering
        .resolution_hz = RMT_LED_STRIP_RESOLUTION_HZ,
        .trans_queue_depth = 4, // set the number of transactions that can be pending in the background
    };
    rmt_new_tx_channel(&tx_chan_config, &led_chan);

    // Create simple callback-based encoder
    const rmt_simple_encoder_config_t simple_encoder_cfg = {
        .callback = encoder_callback
        //Note we don't set min_chunk_size here as the default of 64 is good enough.
    };
    rmt_new_simple_encoder(&simple_encoder_cfg, &simple_encoder);

    // Enable RMT TX channel
    rmt_enable(led_chan);
}

void ws2812b_set_pixel_color(uint8_t index, uint32_t color)
{
    if (index < LED_NUMBERS_MAX) {
        led_strip_pixels[index * 3 + 0] = (color >> 16) & 0xFF; // G
        led_strip_pixels[index * 3 + 1] = (color >> 8) & 0xFF;  // R
        led_strip_pixels[index * 3 + 2] = color & 0xFF;         // B
    }
}

uint32_t ws2812b_color(uint8_t red, uint8_t green, uint8_t blue)
{
    return ((uint32_t)green << 16) | ((uint32_t)red << 8) | blue;
}

void ws2812b_clear(void)
{
    memset(led_strip_pixels, 0, sizeof(led_strip_pixels));
}

void ws2812b_show(void)
{
    // Flush RGB values to LEDs
    rmt_transmit(led_chan, simple_encoder, led_strip_pixels, sizeof(led_strip_pixels), &tx_config);
    rmt_tx_wait_all_done(led_chan, portMAX_DELAY);
}

#endif // ESP32_S3
