/*
 * led_rgbprog.c
 *
 *  Created on: Jul 27, 2024
 *      Author: true
 */
#include <stdint.h>

#include "config.h"
#include "hsv2rgb.h"
#include "led.h"
#include "rand.h"
#include "ui.h"


// led programs
void led_rgb_0_rainbow(uint8_t preview, uint8_t tick);
void led_rgb_1_lite_then_fade(uint8_t preview, uint8_t tick);
void led_rgb_2_twinkle(uint8_t preview, uint8_t tick);
void led_rgb_3_alternate(uint8_t preview, uint8_t tick);
void led_rgb_4_typing(uint8_t preview, uint8_t tick);

void ((*led_rgbprog[5])(uint8_t, uint8_t)) = {
    led_rgb_0_rainbow,
    led_rgb_1_lite_then_fade,
    led_rgb_2_twinkle,
    led_rgb_3_alternate,
    led_rgb_4_typing
};

static uint8_t prog_rgb[3];



/*
 * always have to have unicorn piss
 *
 * button: angle (45 degree shifts for each press)
 *   knob: spacing
 */
static uint16_t rainbow_hue = 0;

// probably some way to compute this but I can't think of it right now
// might take more codespace than this 40-byte LUT anyway
// encoding: LED1-LED2 LED3-LED4 LED5-MAX
//           LED6-LED7 LED8-LED9
static const uint8_t rainbow_angles[8][5] = {
    {0x12, 0x34, 0x55,
     0x01, 0x23},       // bottom left to top right
    {0x01, 0x23, 0x44,
     0x01, 0x23},       // left to right
    {0x01, 0x23, 0x44,
     0x12, 0x34},       // top left to bottom right
    {0x00, 0x00, 0x02,
     0x22, 0x22},       // top to bottom
    {0x43, 0x21, 0x04,
     0x32, 0x10},       // top right to bottom left
    {0x43, 0x21, 0x04,
     0x43, 0x21},       // right to left
    {0x43, 0x21, 0x04,
     0x32, 0x10},       // bottom right to top left
    {0x22, 0x22, 0x22,
     0x00, 0x00}        // bottom to top
};

void led_rgb_0_rainbow(uint8_t preview, uint8_t tick)
{
    uint8_t i, j;   // iterator
    uint8_t n;      // led index, remapped
    uint8_t idx;    // led index being updated, 0-5
    uint8_t max;    // max number of iterations for this angle
    uint8_t angle;  // fill direction from LUT

    uint8_t output = 1;

    uint16_t hoff;

    // run at half framerate
    if (tick & 1) {
        // no matter what, this puke is getting updated
        led_is_updated();

        // setup
        angle = userconf.ledprog_setting[0][0];
        angle &= 0x7;
        max = rainbow_angles[angle][2] & 0x7;

        hoff = userconf.ledprog_setting[0][1];
        hoff <<= 2;
        hoff = (256*6) - hoff;

        // process outputs
        for (i = 0; i <= max; i++) {
            // get the next puke
            hsv2rgb_8b((rainbow_hue + (hoff * i)) % (256*6), 255, 255,
                    &prog_rgb[0], &prog_rgb[1], &prog_rgb[2]);

            // apply to next set of LEDs
            n = 0;
            for (j = 0; j < 9; j++) {
                idx = rainbow_angles[angle][n >> 1];    // get mask byte
                if (!(n & 1)) idx >>= 4;                // shift even numbered LED index over
                idx &= 0x7;                             // mask lowest 3 bits

                if (i == idx) {                         // is this LED part of this pattern at this index?
                    if (preview) {                      // are we in preview mode?
                        output = 1;
                        if (j != 0) {                   // is the first letter not the one selected?
                            if ((preview & 0xf) == 0)  {// is this preview selected?
                                if (j < 5) output = 0;  // pass over any outputs that are not "TECH" letters
                            } else output = 0;
                        }
                    }

                    if (output) {
                        rgb[j][0] = prog_rgb[0];            // if so, copy the values
                        rgb[j][1] = prog_rgb[1];
                        rgb[j][2] = prog_rgb[2];
                    }
                }

                n++;                                    // nybble index
                if (n == 5) n++;                        // skip the 6th nybble; this indicates the max
            }
        }

        rainbow_hue += 2;
        rainbow_hue %= (256*6);
    }
}

/*
 * LEDs are lit from left to right, which fade out as it passes
 * then top word then bottom word light, then fade out,
 * and program repeats.
 * button: fade rate
 * knob:   hue
 */

#define LITE_INTER_DWELL            20          // time between characters
#define LITE_IDLE_DWELL             (256+64)    // time before changing direction
#define LITE_PERSIST                10          // how long to keep each LED at fullbrite

static uint8_t lite_direction = 0;
static uint8_t lite_idx = 0;
static uint16_t lite_timeout = 0;
static uint8_t lite_persist[9] = {0};

void led_rgb_1_lite_then_fade(uint8_t preview, uint8_t tick)
{
    uint16_t i, j;
    uint8_t start, end;

    uint16_t hue = userconf.ledprog_setting[1][1];
    uint8_t rate = userconf.ledprog_setting[1][0];

    // user sets the color
    i = 255;
    if (hue >= 254) {
        // force white
        i = 0;
    }

    hsv2rgb_8b(hue * 6, i, 255, &prog_rgb[0], &prog_rgb[1], &prog_rgb[2]);

    // make the rate adjustment a bit more intuitive
    if (rate >= 254) rate = 255;
    else rate >>= 2;
    rate++;

    // fade out anything that's already lit at user's rate
    for (i = 0; i < 9; i++) {
        if (preview) {
            if (i <= 4) {
                if (i != 1) {
                    continue;
                }
            } else if ((preview & 0xf) != 1) {
                continue;
            }
        }

        if (lite_persist[i]) lite_persist[i]--;
        else for (j = 0; j < 3; j++) {
            if (rgb[i][j] > rate) rgb[i][j] -= rate; else rgb[i][j] = 0;
        }
    }

    // light it up
    if (!lite_timeout) {
        if (!lite_direction) {
            // left to right
            if (!preview || (preview && (lite_idx == 1))) {
                if (lite_idx < 5) {
                    // light RETRO
                    rgb[lite_idx][0] = prog_rgb[0];
                    rgb[lite_idx][1] = prog_rgb[1];
                    rgb[lite_idx][2] = prog_rgb[2];
                    lite_persist[lite_idx] = LITE_PERSIST;
                }
            }

            if (!preview || ((preview & 0xf) == 1)) {
                // light TECH
                if (lite_idx < 4) {
                    rgb[lite_idx + 5][0] = prog_rgb[0];
                    rgb[lite_idx + 5][1] = prog_rgb[1];
                    rgb[lite_idx + 5][2] = prog_rgb[2];
                    lite_persist[lite_idx + 5] = LITE_PERSIST;
                }
            }
        } else {
            // up to down
            if (lite_idx < 2) {
                if (!lite_idx) {
                    if (preview) {
                        start = 1;
                        end = 2;
                    } else {
                        start = 0;
                        end = 5;
                    }
                } else {
                    if (preview && ((preview & 0xf) != 1)) {
                        start = 9;
                    } else {
                        start = 5;
                    }
                    end = 9;
                }

                for (i = start; i < end; i++) {
                    rgb[i][0] = prog_rgb[0];
                    rgb[i][1] = prog_rgb[1];
                    rgb[i][2] = prog_rgb[2];
                    lite_persist[i] = LITE_PERSIST;
                }
            }
        }

        // next light
        lite_idx++;

        end = lite_direction ? 2 : 5;

        // after idle phase, start over in next direction
        if (lite_idx > end) {
            lite_idx = 0;
            lite_direction++;
            lite_direction &= 1;
        }

        lite_timeout = lite_direction ? LITE_INTER_DWELL*2 : LITE_INTER_DWELL;

        // at idle phase?
        if (lite_idx == end) {
            lite_timeout = LITE_IDLE_DWELL;
        }

        led_is_updated();
    }

    lite_timeout--;
}

/*
 * LEDs are on at some idle level, and random letters will
 * twinkle by intensifying and shifting hue slightly by a
 * random small amount.
 * button: saturation
 * knob:   flashing intensity
 */

#define TWINKLE_PERSIST     3;
#define TWINKLE_RGB_VAL     255
uint8_t twinkle_set[9] = {0};

void led_rgb_2_twinkle(uint8_t preview, uint8_t tick)
{
    uint8_t i, j;
    uint32_t rnd;
    uint8_t sat;

    uint8_t show;

    // set
    if ((tick & 3) == 0) {
        rnd = prng_get8();
        if (preview && (preview & 0xf) != 2) {
            // possibly increase chances to flash for top icon
            rnd >>= 2;
        }

        if (rnd < userconf.ledprog_setting[2][1]) {
            // yup, we're doing it
            show = 1;

            rnd = prng_get16();
            rnd *= 1536;
            rnd >>= 16;

            sat = userconf.ledprog_setting[2][0];
            if (sat >= 128) sat = 128 - (sat - 128);
            sat <<= 1;

            hsv2rgb_8b(rnd, sat, TWINKLE_RGB_VAL, &prog_rgb[0], &prog_rgb[1], &prog_rgb[2]);

            rnd = prng_get16() & 0xfff;
            rnd /= 455;

            if (preview) {
                show = 0;
                if (rnd == 2) show = 1;
                else if (((preview & 0xf) == 2) && (rnd >= 5)) {
                    show = 1;
                }
            }

            if (show) {
                rgb[rnd][0] = prog_rgb[0];
                rgb[rnd][1] = prog_rgb[1];
                rgb[rnd][2] = prog_rgb[2];
                twinkle_set[rnd] = TWINKLE_PERSIST;
            }
        }


        for (i = 0; i < 9; i++) {
            show = 1;

            // kinda bad but no fucking time to finish this code
            if (preview) {
                show = 0;
                if (i == 2) show = 1;
                else if (((preview & 0xf) == 2) && (i >= 5)) {
                    show = 1;
                }
            }

            if (show) {
                if (twinkle_set[i]) twinkle_set[i]--;
                for (j = 0; j < 3; j++) {
                    if (twinkle_set[i])  rgb[i][j]--;
                    else                 rgb[i][j] >>= 1;
                }
            }

            led_is_updated();
        }
    }
}

/*
 * alternates flashing the words between two colors.
 * button: offset degrees (360/16)
 * knob:   hue (alternate hue will be either 90 or 180 degrees offset)
 */


void led_rgb_3_alternate(uint8_t preview, uint8_t tick)
{
    uint8_t i, j;

    uint16_t hue = userconf.ledprog_setting[3][1] * 6;
    uint16_t offset = (userconf.ledprog_setting[3][0] & 0xf) * 16 * 6;

    uint8_t is_flashing = 0;

    if ((userconf.cursor_color == CONF_CURSOR_OFF) || preview) {
        // flash at ~2Hz
        tick >>= 5;
        if (tick & 1) is_flashing = 1;
    } else {
        // match cursor
        if (cursor[userconf.cursor_color]) {
            is_flashing = 1;
        }
    }

    // don't need to update flashing state. this is handled by the cursor

    // set hues
    for (i = 0; i < 2; i++) {
        hsv2rgb_8b(hue, 255, 255, &prog_rgb[0], &prog_rgb[1], &prog_rgb[2]);
        hue += offset;
        hue %= 1536;

        if (is_flashing) {
            // set TECH
            if (!preview || ((preview & 0xf) == 3)) {
                for (j = 5; j < 9; j++) {
                    rgb[j][0] = prog_rgb[0];
                    rgb[j][1] = prog_rgb[1];
                    rgb[j][2] = prog_rgb[2];
                }
            }
        } else {
            // set RETRO
            if (!preview) {
                 for (j = 0; j < 5; j++) {
                    rgb[j][0] = prog_rgb[0];
                    rgb[j][1] = prog_rgb[1];
                    rgb[j][2] = prog_rgb[2];
                }
            } else {
                rgb[3][0] = prog_rgb[0];
                rgb[3][1] = prog_rgb[1];
                rgb[3][2] = prog_rgb[2];
            }
        }

        is_flashing++;
        is_flashing &= 1;
    }
}

/*
 * a green, orange or white phosphor sort of "typing,"
 * with quick but not instant fade-in and flashing of
 * an active character.
 * the only program that interferes with the cursor.
 * after "typing" and filling, the characters fade then
 * the program repeats.
 *
 * no user config for this program
 */

#define TYPING_CHAR_DELAY_MIN   12      // about 1/10 of a second
#define TYPING_CHAR_DELAY_MAX   500     // just shy of 4 seconds

// the idle thing looked like shit.
// it's also broken.
// instead of removing, just set to 0 for now,
// and maybe address it later.
static const uint8_t idle_glow[3] = {0, 0, 0}; // {12, 8, 8};
static const uint8_t typing[3][3] = {
    {255, 240, 240},    // white
    {  0, 240,   0},    // green
    {255,  16,   4},    // orange
};

static uint8_t typing_idx = 255;
static uint16_t typing_char_delay = 0;

static uint8_t typing_flash_state = 0;
static uint8_t typing_flash_delay = 0;

static uint8_t typing_fadeout;


void led_rgb_4_typing(uint8_t preview, uint8_t tick)
{
    uint8_t i, j;
    uint8_t w;
    uint16_t s;
    uint8_t color;
    uint8_t update = 0;

    // match cursor color (well, approximate, or go green)
    color = userconf.cursor_color;
    if (color == CONF_CURSOR_OFF) color = CONF_CURSOR_GREEN;

    // set flash
    if (!typing_flash_delay) {
        typing_flash_state++;
        typing_flash_state &= 1;

        typing_flash_delay = cursor_flash_rates[userconf.cursor_flash];
        update = 1;
    }

    typing_flash_delay--;

    // set rgb colors as appropriate
    for (i = 0; i < 9; i++) {
        for (j = 0; j < 3; j++) {
            if ((i < typing_idx) || ((i == typing_idx) && typing_flash_state)) {
                // these are on
                if (!preview || (i == 4) || (((preview & 0xf) == 4) && i >= 5)) {
                    rgb[i][j] = typing[color][j];
                }
            } else

            if (i > typing_idx) { // || ((i == typing_idx) && !typing_flash_state)) {
                // these are idle
                if (!preview || (i == 4) || (((preview & 0xf) == 4) && i >= 5)) {
                    rgb[i][j] = idle_glow[j];
                }
            } else

            // flashing cursor fadeout
            if (!preview || (i == 4) || (((preview & 0xf) == 4) && i >= 5)) {
                if ((i == typing_idx) && !typing_flash_state) {
                    if (rgb[i][j] > 32) rgb[i][j] -= 32; else rgb[i][j] = 0;
                    update = 1;
                }
            }
        }
    }

    // set cursor as appropriate
    if (!preview) {
        for (i = 0; i < 3; i++) {
            cursor[i] = 0;
        }

        // at the cursor
        if (typing_idx >= 9) {
            cursor[color] = typing_flash_state ? 240 : 12;
            if (typing_flash_delay <= 8) {
                w = 8 - typing_flash_delay;
                cursor[color] >>= w;
                if (cursor[color] < 12) cursor[color] = 0;      // idle value
            }
        }
    }

    // decay all the characters
    if (typing_idx == 10) {
        if (typing_fadeout >= 3) typing_fadeout--;
        else typing_fadeout = 0;

        if (!preview || (i == 4) || (((preview & 0xf) == 4) && i >= 5)) {
            for (j = 0; j < 3; j++) {
                s = rgb[8][j];
                s *= typing_fadeout;
                rgb[8][j] = s >> 8;
            }
        }

        w = ((preview & 0xf) == 4) ? 5 : 0;

        for (i = w; i < 8; i++) {
            rgb[i][0] = rgb[8][0];
            rgb[i][1] = rgb[8][1];
            rgb[i][2] = rgb[8][2];
        }

        if (!preview) {
            s = cursor[color];
            s *= typing_fadeout;
            cursor[color] = s >> 8;
        }
    }

    // are we on the next character?
    if (!typing_char_delay) {
        // next character
        typing_idx++;

        // start at this character in preview mode
        if (preview && typing_idx < 4) {
            typing_idx = 4;
        }

        if (typing_idx == 9) {
            // at the cursor, we wait two max periods
            typing_char_delay = TYPING_CHAR_DELAY_MAX << 1;
        } else if (typing_idx == 10) {
            // at the end, we wait one max period
            typing_char_delay = TYPING_CHAR_DELAY_MAX;
        } else {
            typing_char_delay = prng_scale16(TYPING_CHAR_DELAY_MIN, TYPING_CHAR_DELAY_MAX);
        }

        if (typing_idx > 10) typing_idx = 0;

        // reset cursor fade (used in last step)
        typing_fadeout = 0xff;

        // reset flash state and delay to reset cursor flash
        // comment this code to keep persistent cursor rate
        typing_flash_state = 0;
        typing_flash_delay = 0;
    }

    typing_char_delay--;

    if (update) {
        led_is_updated();
    }
}


/*
 * initialize settings for programs before they run
 * this can be run when changing programs to set them
 * to a known state before starting over
 */
void led_rgb_firstrun()
{
    uint8_t i;

    for (i = 0; i < 9; i++) {
        rgb[i][0] = 0;
        rgb[i][1] = 0;
        rgb[i][2] = 0;
    }

    // program 0: rainbow
    // nothing to set

    // program 1: lite then fade
    lite_direction = 0;
    lite_idx = 0;
    lite_timeout = 0;
    for (i = 0; i < 9; i++) {
        lite_persist[i] = 0;
    }

    // program 2: twinkle
    for (i = 0; i < 9; i++) twinkle_set[i] = 0;

    // program 3: alternating
    // nothing to set

    // program 4: typing
    typing_idx = 0;
    typing_char_delay = prng_scale16(TYPING_CHAR_DELAY_MIN, TYPING_CHAR_DELAY_MAX);

    typing_flash_state = 0;
    typing_flash_delay = userconf.cursor_flash;
}
