/** 
 * @file    led_ring.c
 * @brief   LED ring module
 * @author  Kenny Tran
 * @date    Feb 29 2024
 * @version 0.0.1
 *
 * @section LICENSES
 * Copyright (C) Morgan Advanced Programmable Systems, Inc.
 */


/*--------------------------------------------------------------------------------------------------
 * INCLUDE FILES
 *------------------------------------------------------------------------------------------------*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include <zephyr/kernel.h>
#include <zephyr/drivers/led_strip.h>

#include "../lib/errno.h"
#include "../lib/print.h"
#include "../lib/rgb.h"

#include "../wrappers/gpio.h"

#include "led_ring.h"


/*--------------------------------------------------------------------------------------------------
 * TYPE DEFINITIONS & ENUMERATIONS
 *------------------------------------------------------------------------------------------------*/
typedef struct led_rgb _led_ring_rgb_s;


/*--------------------------------------------------------------------------------------------------
 * MACROS & CONSTANTS
 *------------------------------------------------------------------------------------------------*/


/*--------------------------------------------------------------------------------------------------
 * PRIVATE CONSTANTS & VARIABLES
 *------------------------------------------------------------------------------------------------*/
static const struct device *const _led_ring                = DEVICE_DT_GET(DT_ALIAS(led_ring));
static       _led_ring_rgb_s      _led_ring_rgb_arr[LED_RING_PIXELS];
static       _led_ring_rgb_s      _led_ring_rgb_arr_prev[LED_RING_PIXELS];
static       bool                 _led_ring_init           = false;
static       bool                 _led_ring_din            = false;
static       uint16_t             _led_ring_rainbow_bounds = LED_RING_PIXELS - 1;


/*--------------------------------------------------------------------------------------------------
 * PRIVATE FUNCTION PROTOTYPES
 *------------------------------------------------------------------------------------------------*/
static RGB_s _led_ring_hsv (uint16_t hue, uint8_t sat, uint8_t val);
static void _led_ring_din_isr (void);


/*--------------------------------------------------------------------------------------------------
 * PUBLIC FUNCTION DEFINITIONS
 *------------------------------------------------------------------------------------------------*/
ERRNO_e LED_RING_init (void)
{
    if (_led_ring_init)
    {
        return ERRNO_ALREADY_INITIALIZED;
    }

    if (!device_is_ready (_led_ring))
    {
        return ERRNO_NOT_READY;
	}

    /* Initialize GPIO used to control FET (power to LED ring). */
    if (GPIO_init () < ERRNO_SUCCESS)
    {
        return ERRNO_GPIO_FAILURE;
    }

    /* Configure GPIO as output and initialize to low. */
    if (GPIO_cfg (GPIO_PIN_LED_RING_PWR, GPIO_DIRECTION_OUT, GPIO_PULLNONE, GPIO_INIT_LOW, GPIO_INT_STATE_CHANGE_LL_HIGH, NULL))
    {
        return ERRNO_GPIO_FAILURE;
    }

    /* Configure GPIO as output to enable / disable level shifter. */
    if (GPIO_cfg (GPIO_PIN_LED_RING_LEVEL_SHIFT_ENABLE, GPIO_DIRECTION_OUT, GPIO_PULLNONE, GPIO_INIT_LOW, GPIO_INT_STATE_CHANGE_LL_HIGH, NULL))
    {
        return ERRNO_GPIO_FAILURE;
    }

    /* Configure GPIO for data in from LED ring. */
    if (GPIO_cfg (GPIO_PIN_LED_RING_DIN, GPIO_DIRECTION_IN, GPIO_PULLNONE, GPIO_INIT_LOW, GPIO_INT_STATE_CHANGE_LL_HIGH, _led_ring_din_isr))
    {
        return ERRNO_GPIO_FAILURE;
    }

    /* Set initialized flag. */
    _led_ring_init = true;

    return ERRNO_SUCCESS;
}

ERRNO_e LED_RING_clear (void)
{
    if (!_led_ring_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    /* Zero out RGB array. */
    memset (_led_ring_rgb_arr, 0, sizeof (_led_ring_rgb_arr));

    return ERRNO_SUCCESS;
}

ERRNO_e LED_RING_set (uint16_t idx, uint8_t r, uint8_t g, uint8_t b)
{
    if (!_led_ring_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }
    else if (idx >= LED_RING_PIXELS)
    {
        return ERRNO_INVALID_PARAM;
    }

    /* Update RGB array. */
    _led_ring_rgb_arr[idx].r = r;
    _led_ring_rgb_arr[idx].g = g;
    _led_ring_rgb_arr[idx].b = b;

    return ERRNO_SUCCESS;
}

ERRNO_e LED_RING_bounds_set (uint16_t idx)
{
    if (!_led_ring_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    _led_ring_rainbow_bounds = idx;

    return ERRNO_SUCCESS;
}

/*
  @param   first_hue   Hue of first pixel, 0-65535, representing one full
                       cycle of the color wheel. Each subsequent pixel will
                       be offset to complete one or more cycles over the
                       length of the strip.
  @param   reps        Number of cycles of the color wheel over the length
                       of the strip. Default is 1. Negative values can be
                       used to reverse the hue order.
  @param   saturation  Saturation (optional), 0-255 = gray to pure hue,
                       default = 255.
  @param   brightness  Brightness/value (optional), 0-255 = off to max,
                       default = 255. This is distinct and in combination
                       with any configured global strip brightness.
  @param   gammify     If true (default), apply gamma correction to colors
                       for better appearance.
*/
ERRNO_e LED_RING_rainbow (uint16_t first_hue, int8_t reps, uint8_t sat, uint8_t brightness, bool gammify)
{
                 ERRNO_e err = ERRNO_SUCCESS;
                 uint8_t i;
    static const uint8_t rgb_gamma_table[] =
    {
        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   1,   1,   1,   1,
        1,   1,   1,   1,   1,   1,   1,   1,   1,   2,   2,   2,   2,   2,   2,   2,
        2,   3,   3,   3,   3,   3,   3,   3,   4,   4,   4,   4,   4,   5,   5,   5,
        5,   6,   6,   6,   6,   7,   7,   7,   7,   8,   8,   8,   9,   9,   9,  10,
        10,  10,  11,  11,  11,  12,  12,  13,  13,  13,  14,  14,  15,  15,  16,  16,
        17,  17,  18,  18,  19,  19,  20,  20,  21,  21,  22,  22,  23,  24,  24,  25,
        25,  26,  27,  27,  28,  29,  29,  30,  31,  32,  32,  33,  34,  35,  35,  36,
        37,  38,  39,  39,  40,  41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  50,
        51,  52,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,  64,  66,  67,  68,
        69,  70,  72,  73,  74,  75,  77,  78,  79,  81,  82,  83,  85,  86,  87,  89,
        90,  92,  93,  95,  96,  98,  99, 101, 102, 104, 105, 107, 109, 110, 112, 114,
        115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137, 138, 140, 142,
        144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175,
        177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213,
        215, 218, 220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255
    };

    if (!_led_ring_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    for (i = 0; i <= _led_ring_rainbow_bounds; i++)
    {
        uint16_t hue   = first_hue + (i * reps * 65536) / LED_RING_PIXELS;
        RGB_s    color = _led_ring_hsv (hue, sat, brightness);

        if (gammify)
        {
            color.r = rgb_gamma_table[color.r];
            color.g = rgb_gamma_table[color.g];
            color.b = rgb_gamma_table[color.b];
        }

        err |= LED_RING_set (i, color.r, color.g, color.b);
    }
    
    return err;
}

ERRNO_e LED_RING_update (void)
{
    if (!_led_ring_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    if (!memcmp (_led_ring_rgb_arr, _led_ring_rgb_arr_prev, sizeof (_led_ring_rgb_arr)))
    {
        return ERRNO_SUCCESS;
    }

    memcpy (_led_ring_rgb_arr_prev, _led_ring_rgb_arr, sizeof (_led_ring_rgb_arr));

    if (led_strip_update_rgb (_led_ring, _led_ring_rgb_arr, LED_RING_PIXELS))
    {
        return ERRNO_FAILURE;
    }

    return ERRNO_SUCCESS;
}

ERRNO_e LED_RING_enable (bool en)
{
    if (!_led_ring_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    /* Enable / disable level shifter for LED ring. Active-high. */
    if (GPIO_level_set (GPIO_PIN_LED_RING_LEVEL_SHIFT_ENABLE, en))
    {
        return ERRNO_GPIO_FAILURE;
    }

    /* Enable / disable power to LED ring. Active-high. */
    if (GPIO_level_set (GPIO_PIN_LED_RING_PWR, en))
    {
        return ERRNO_GPIO_FAILURE;
    }

    return ERRNO_SUCCESS;
}

ERRNO_e LED_RING_din_received (bool *p_rx)
{
    if (!_led_ring_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    *p_rx = _led_ring_din;

    _led_ring_din = false;

    return ERRNO_SUCCESS;
}

void LED_RING_cli_cmd (int32_t argc, char **argv)
{
    if (argc == 1)
    {
        if (!strcmp (argv[0], "--init") || !strcmp (argv[0], "-i"))
        {
            PRINT (INFO, "Ret code : %d\r\n", LED_RING_init ());
        }
        else if (!strcmp (argv[0], "--update") || !strcmp (argv[0], "-u"))
        {
            PRINT (INFO, "Ret code : %d\r\n", LED_RING_update ());
        }
        else if (!strcmp (argv[0], "--din"))
        {
            bool din;

            PRINT (INFO, "Ret code : %d\r\n", LED_RING_din_received (&din));
            PRINT (INFO, "DIN : %s\r\n", (din ? "true" : "false"));
        }
        else
        {
            goto USAGE;
        }
    }
    else if (argc == 2)
    {
        if (!strcmp (argv[0], "--enable") || !strcmp (argv[0], "-e"))
        {
            if (!strcmp (argv[1], "true"))
            {
                PRINT (INFO, "Ret code : %d\r\n", LED_RING_enable (true));
            }
            else if (!strcmp (argv[1], "false"))
            {
                PRINT (INFO, "Ret code : %d\r\n", LED_RING_enable (false));
            }
            else
            {
                goto USAGE;
            }
            
        }
        else
        {
            goto USAGE;
        }
    }
    else if (argc == 5)
    {
        if (!strcmp (argv[0], "--set") || !strcmp (argv[0], "-s"))
        {
            uint16_t idx = (uint16_t) strtoul (argv[1], NULL, 0);
            uint8_t    r = (uint8_t) strtoul (argv[2], NULL, 0);
            uint8_t    g = (uint8_t) strtoul (argv[3], NULL, 0);
            uint8_t    b = (uint8_t) strtoul (argv[4], NULL, 0);

            PRINT (INFO, "Ret code : %d\r\n", LED_RING_set (idx, r, g, b));
        }
        else
        {
            goto USAGE;
        }
    }
    else
    {
USAGE:
        PRINT (INFO, "Usage: led_ring [OPTION...]\r\n");
        PRINT (INFO, "  -i | --init          Initializes LED ring module\r\n");
        PRINT (INFO, "  -e | --enable        Enables power to the LED ring\r\n");
        PRINT (INFO, "    <%%b>               true  = enable\r\n");
        PRINT (INFO, "                       false = disable\r\n");
        PRINT (INFO, "  -s | --set           Sets addressed pixel to specified RGB value\r\n");
        PRINT (INFO, "    <%%d>               LED index\r\n");
        PRINT (INFO, "    <%%d>               Red   [0-255]\r\n");
        PRINT (INFO, "    <%%d>               Green [0-255]\r\n");
        PRINT (INFO, "    <%%d>               Blue  [0-255]\r\n");
        PRINT (INFO, "  -u | --update        Updates / refreshes LED ring to newest setting\r\n");
        PRINT (INFO, "       --din           Checks if data out from LED ring is going back to MCU\r\n");
    }
}


/*--------------------------------------------------------------------------------------------------
 * PRIVATE FUNCTION DEFINITIONS
 *------------------------------------------------------------------------------------------------*/
/*!
  @brief   Convert hue, saturation and value into a packed 32-bit RGB color
           that can be passed to setPixelColor() or other RGB-compatible
           functions.
  @param   hue  An unsigned 16-bit value, 0 to 65535, representing one full
                loop of the color wheel, which allows 16-bit hues to "roll
                over" while still doing the expected thing (and allowing
                more precision than the wheel() function that was common to
                prior NeoPixel examples).
  @param   sat  Saturation, 8-bit value, 0 (min or pure grayscale) to 255
                (max or pure hue). Default of 255 if unspecified.
  @param   val  Value (brightness), 8-bit value, 0 (min / black / off) to
                255 (max or full brightness). Default of 255 if unspecified.
  @return  Packed 32-bit RGB with the most significant byte set to 0 -- the
           white element of WRGB pixels is NOT utilized. Result is linearly
           but not perceptually correct, so you may want to pass the result
           through the gamma32() function (or your own gamma-correction
           operation) else colors may appear washed out. This is not done
           automatically by this function because coders may desire a more
           refined gamma-correction function than the simplified
           one-size-fits-all operation of gamma32(). Diffusing the LEDs also
           really seems to help when using low-saturation colors.
*/
static RGB_s _led_ring_hsv (uint16_t hue, uint8_t sat, uint8_t val)
{
    RGB_s    color;
    uint32_t packed;
    /* Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover;
     * 0 is not the start of pure red, but the midpoint...a few values above
     * zero and a few below 65536 all yield pure red (similarly, 32768 is the
     * midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values
     * each for red, green, blue) really only allows for 1530 distinct hues
     * (not 1536, more on that below), but the full unsigned 16-bit type was
     * chosen for hue so that one's code can easily handle a contiguous color
     * wheel by allowing hue to roll over in either direction.
     */
    hue = (hue * 1530L + 32768) / 65536;
    /* Because red is centered on the rollover point (the +32768 above,
     * essentially a fixed-point + 0.5), the above actually yields 0 to 1530,
     * where 0 and 1530 would yield the same thing. Rather than apply a
     * costly modulo operator, 1530 is handled as a special case below.
     */

    /* So you'd think that the color "hexcone" (the thing that ramps from
     * pure red, to pure yellow, to pure green and so forth back to red,
     * yielding six slices), and with each color component having 256
     * possible values (0-255), might have 1536 possible items (6*256),
     * but in reality there's 1530. This is because the last element in
     * each 256-element slice is equal to the first element of the next
     * slice, and keeping those in there this would create small
     * discontinuities in the color wheel. So the last element of each
     * slice is dropped...we regard only elements 0-254, with item 255
     * being picked up as element 0 of the next slice. Like this:
     * Red to not-quite-pure-yellow is:        255,   0, 0 to 255, 254,   0
     * Pure yellow to not-quite-pure-green is: 255, 255, 0 to   1, 255,   0
     * Pure green to not-quite-pure-cyan is:     0, 255, 0 to   0, 255, 254
     * and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why
     * the constants below are not the multiples of 256 you might expect.
     */

    /* Convert hue to R,G,B (nested ifs faster than divide+mod+switch) */
    if (hue < 510)
    {
        /* Red to Green-1 */
        color.b = 0;
        if (hue < 255)
        {
            /* Red to Yellow-1 */
            color.r = 255;
            /* color.g = 0 to 254 */
            color.g = hue;
        }
        else
        {
            /* Yellow to Green-1 */
            /* color.r = 255 to 1 */
            color.r = 510 - hue;
            color.g = 255;
        }
    }
    else if (hue < 1020)
    {
        /* Green to Blue-1 */
        color.r = 0;
        if (hue < 765)
        {
            /* Green to Cyan-1 */
            color.g = 255;
            /* color.b = 0 to 254 */
            color.b = hue - 510;
        }
        else
        {
            /* Cyan to Blue-1 */
            /* color.g = 255 to 1 */
            color.g = 1020 - hue;
            color.b = 255;
        }
    }
    else if (hue < 1530)
    {
        /* Blue to Red-1 */
        color.g = 0;
        if (hue < 1275)
        {
            /* Blue to Magenta-1 */
            /* color.r = 0 to 254 */
            color.r = hue - 1020;
            color.b = 255;
        }
        else
        {
            /* Magenta to Red-1 */
            color.r = 255;
            /* color.b = 255 to 1 */
            color.b = 1530 - hue;
        }
    }
    else
    {
        /* Last 0.5 Red (quicker than % operator) */
        color.r = 255;
        color.g = color.b = 0;
    }

    /* Apply saturation and value to R,G,B, pack into 32-bit result. */
    uint32_t v1 = 1 + val;      /* 1 to 256; allows >> 8 instead of / 255. */
    uint16_t s1 = 1 + sat;      /* 1 to 256; allows >> 8 instead of / 255. */
    uint8_t  s2 = 255 - sat;    /* 255 to 0 */
    
    packed = ((((((color.r * s1) >> 8) + s2) * v1) & 0xff00) << 8) |
              (((((color.g * s1) >> 8) + s2) * v1) & 0xff00) |
              (((((color.b * s1) >> 8) + s2) * v1) >> 8);

    return (RGB_s) { .r = ((packed >> 16) & 0xFF),
                     .g = ((packed >>  8) & 0xFF),
                     .b = ((packed >>  0) & 0xFF) };
}

static void _led_ring_din_isr (void)
{
    _led_ring_din = true;
}


