/*
 * led_prog.c
 *
 *  Created on: Aug 3, 2023
 *      Author: true
 *
 */


#include <stdint.h>
#include <stdlib.h>

#include "adxl.h"
#include "btn.h"
#include "hsv2rgb.h"
#include "led_sk6x_spi.h"
#include "led_user.h"
#include "prng.h"
#include "userconf.h"



#define LEDPROG_MAX_PROGRAMS		10



void ledprog00_twinkle_white();
void ledprog01_twinkle_rgb_rand();
void ledprog02_twinkle_rgb_set();
void ledprog03_rainbow();
void ledprog04_flicker_same();
void ledprog05_flicker_all();
void ledprog06_ramper();
void ledprog07_toggles();
void ledprog08_accel_x_solid();
void ledprog09_accel_y_rainbow_shift();



const void (*led_prog_list[])() = {
	ledprog00_twinkle_white,
	ledprog01_twinkle_rgb_rand,
	ledprog02_twinkle_rgb_set,
	ledprog03_rainbow,
	ledprog04_flicker_same,
	ledprog05_flicker_all,
	ledprog06_ramper,
	ledprog07_toggles,
	ledprog08_accel_x_solid,
	ledprog09_accel_y_rainbow_shift,
};

static uint8_t  led_prog_active = 0;
static uint32_t work[4];
static uint8_t  dwell;

static uint8_t r, g, b;

static uint8_t prog_mode = 0;
static uint8_t prog_tmr = 0;
static uint8_t prog_led = 0;
static uint8_t prog_idx = 0;

/*
 * program pages:
 * 1-4: 4 program bytes for active led program
 */



void ledprog_default()
{
	uconf.led[0][0] = 3;		// dwell
	uconf.led[0][1] = 240;		// threshold

	uconf.led[1][0] = 3;		// dwell
	uconf.led[1][1] = 230;		// threshold

	uconf.led[2][0] = 2;		// dwell
	uconf.led[2][1] = 220;		// threshold
	uconf.led[2][2] = 0;		// hue
	uconf.led[2][3] = 224;		// sat

	uconf.led[3][0] = 2;		// dwell
	uconf.led[3][1] = 0;		// unused
	uconf.led[3][2] = 2;		// speed
	uconf.led[3][3] = 30;		// offset

	uconf.led[4][0] = 5;		// dwell
	uconf.led[4][1] = 248;		// threshold
	uconf.led[4][2] = 170;		// hue
	uconf.led[4][3] = 32;		// base

	uconf.led[5][0] = 7;		// dwell
	uconf.led[5][1] = 240;		// threshold
	uconf.led[5][2] = 185;		// hue
	uconf.led[5][3] = 24;		// base

	uconf.led[6][0] = 0;		// dwell
	uconf.led[6][1] = 192;		// speed
	uconf.led[6][2] = 30;		// hue
	uconf.led[6][3] = 6;		// faderate

	uconf.led[7][0] = 1;		// dwell
	uconf.led[7][1] = 0x48;		// 7-4: faderate, 3-0: linger
	uconf.led[7][2] = 170;		// hue1
	uconf.led[7][3] = 0;		// hue2

	uconf.led[8][0] = 0;		// dwell
	uconf.led[8][1] = 6;		// sensitivity
	uconf.led[8][2] = 0;		// hue offset
	uconf.led[8][3] = 255;		// saturation

	uconf.led[9][0] = 0;		// rgb or two-color mode (lowest bit)
	uconf.led[9][1] = 6;		// sensitivity
	uconf.led[9][2] = 42;		// base hue
	uconf.led[9][3] = 40;		// offset (rgb) or alt_hue-128offset (two-color)
}

void ledprog_change(uint8_t idx)
{
	uint8_t i;

	led_prog_active = idx;
	dwell = 0;

	for (i = 0; i < 4; i++) {
		work[i] = 0;
	}
}

void ledprog_btn_push_cb(uint8_t idx)
{
	if (!prog_mode) return;

	switch (idx) {
		case BTN_MODE: {	// back or down
			if (!uconf.led[led_prog_active][prog_idx]) {
				uconf.led[led_prog_active][prog_idx] = 0xff;
			} else {
				uconf.led[led_prog_active][prog_idx]--;
			}
			break;
		}
		case BTN_SET: {		// forward or up
			uconf.led[led_prog_active][prog_idx]++;
			break;
		}
	}
}

void ledprog_btn_held_cb(uint8_t idx)
{
	if (idx == BTN_PROG) {
		prog_mode ^= 1;
		prog_idx = prog_tmr = 0;

		// when leaving programming mode we need to commit some things
		if (!prog_mode) {
			// make sure user LED is off
			userled_set(0);

			// save settings to flash
			uconf.led_prog_idx = led_prog_active;
			uconf_save();
		}

		return;
	}

	if (prog_mode) {
		switch (idx) {
			case BTN_MODE:
			case BTN_SET: {
				ledprog_btn_push_cb(idx);
				break;
			}
		}

		// allow retrigger
		btn[idx].state &= ~(BTN_HELDRT | BTN_HELDRT_CB);
	} else {
		// do not retrigger on these
		if (btn[idx].state & BTN_HELDRT_CB) return;

		switch (idx) {
			case BTN_MODE: {	// return to user's default program
				ledprog_change(uconf.led_prog_idx);
				led_sk6x_brightness(uconf.led_bright ? uconf.led_bright : 1);
				break;
			}
			case BTN_SET: {		// change active zones
				uconf.led_zone++;
				if (uconf.led_zone > 7) uconf.led_zone = 1;
				led_sk6x_zone_ena(uconf.led_zone);
				break;
			}
		}
	}
}

void ledprog_btn_release_cb(uint8_t idx)
{
	if (btn[idx].state & BTN_HELD) {
		btn[idx].state = 0;
		return;
	}

	if (prog_mode) {
		switch (idx) {
			case BTN_PROG: {	// change program variable page
				prog_idx++;
				if (prog_idx >= 4) prog_idx = 0;
				break;
			}
		}
	} else {
		switch (idx) {
			case BTN_MODE: {	// change active program
				led_prog_active++;
				if (led_prog_active >= LEDPROG_MAX_PROGRAMS) led_prog_active = 0;
				ledprog_change(led_prog_active);

				// increase brightness if we are dark
				if (uconf.led_bright) {
					break;
				}
			}
			case BTN_PROG: {	// change brightness
				uconf.led_bright++;
				if (uconf.led_bright > 5) uconf.led_bright = 0;
				led_sk6x_brightness(uconf.led_bright);
				break;
			}
			case BTN_SET: {		// do nothing?

			}
		}
	}

	// clear any button state as button is now idle
	btn[idx].state = 0;
}

void ledprog_btn()
{
	btn[BTN_PROG].held_cb     = ledprog_btn_held_cb;
	btn[BTN_PROG].release_cb  = ledprog_btn_release_cb;
	btn[BTN_PROG].hold_thresh = BTN_HOLD_1_00S;

	btn[BTN_MODE].push_cb     = ledprog_btn_push_cb;
	btn[BTN_MODE].held_cb     = ledprog_btn_held_cb;
	btn[BTN_MODE].release_cb  = ledprog_btn_release_cb;
	btn[BTN_MODE].hold_thresh = BTN_HOLD_1_00S;
	btn[BTN_MODE].hold_retrig = 16;

	btn[BTN_SET].push_cb      = ledprog_btn_push_cb;
	btn[BTN_SET].held_cb      = ledprog_btn_held_cb;
	btn[BTN_SET].release_cb   = ledprog_btn_release_cb;
	btn[BTN_SET].hold_thresh  = BTN_HOLD_1_00S;
	btn[BTN_SET].hold_retrig  = 16;
}

void ledprog_run()
{
	if (prog_mode) {
		// we need to see the LEDs
		if (!uconf.led_bright) {
			uconf.led_bright++;
			led_sk6x_brightness(uconf.led_bright);
		}

		// 2 second LED flash timer
		if (!prog_tmr) prog_led = prog_idx + 1;

		// flash LED to indicate the page we are programming
		if (prog_led) {
			switch (prog_tmr & 0x1f) {
				case 0x00: {
					userled_set(255);
					break;
				}
				case 0x0a: {
					userled_set(0);
					prog_led--;
					break;
				}
			}
		}

		prog_tmr++;
	}

	// always run a program
	if (!dwell || led_prog_active == 9) {
		dwell = uconf.led[led_prog_active][0] & 0xf;
		led_prog_list[led_prog_active]();
	} else {
		dwell--;
	}
}

void ledprogi_twinkle(uint8_t r, uint8_t g, uint8_t b, uint8_t thresh)
{
	uint8_t x;

	x = prng_get8();

	led_sk6x_set_all(0, 0, 0);

	if (x > thresh) {
		x = prng_get32() & 0x7;
		led_sk6x_set(x, r, g, b);
	}
}

/*
 * uconf.led:
 * 0	dwell
 * 1	threshold
 */
void ledprog00_twinkle_white()
{
	ledprogi_twinkle(0xff, 0xff, 0xff, uconf.led[0][1]);
}

/*
 * uconf.led:
 * 0	dwell
 * 1	threshold
 */
void ledprog01_twinkle_rgb_rand()
{
	uint32_t x;

	x = prng_get32();

	// set values
	r = (x >> 24);
	g = (x >> 16) & 0xff;
	b = (x >>  8) & 0xff;

	// randomly reduce colors to remove "whiteness"
	switch (x & 0x3) {
		case 1: {
			r >>= 4;
			g >>= 4;
			break;
		}
		case 2: {
			g >>= 4;
			b >>= 4;
			break;
		}
		case 3: {
			b >>= 4;
			r >>= 4;
			break;
		}
	}

	ledprogi_twinkle(r, g, b, uconf.led[1][1]);
}


/*
 * uconf.led:
 * 0	dwell
 * 1	threshold
 * 2	hue
 * 3	sat
 */
void ledprog02_twinkle_rgb_set()
{
	hsv2rgb_8b(uconf.led[2][2] * 6, uconf.led[2][3], 255, &r, &g, &b);

	ledprogi_twinkle(r, g, b, uconf.led[2][1]);
}

/*
 * uconf.led:
 * 0	dwell
 * 2	speed
 * 3	offset
 *
 * work:
 * 0	base hue
 */
void ledprog03_rainbow()
{
	uint8_t i;
	uint16_t w;

	work[0] += (uconf.led[3][2] * 6) + 1;
	work[0] %= 1536;
	w = work[0];

	for (i = 0; i < 8; i++) {
		hsv2rgb_8b(w, 255, 255, &r, &g, &b);

		// knock off some bits of low values to make solid colors more vivid
		if (r <= 3) r >>= 1;
		if (g <= 3) g >>= 1;
		if (b <= 3) b >>= 1;

		led_sk6x_set(i, r, g, b);

		w += (uconf.led[3][3] * 6) + 1;
		w %= 1536;
	}
}

void ledprogi_flicker(uint8_t idx)
{
	uint16_t h;
	uint8_t x;

	h = uconf.led[idx][2] * 6;

	x = prng_get8();

	if (x > uconf.led[idx][1]) {
		x = 255;
	} else {
		x = uconf.led[idx][3];
	}

	x &= ~0x07;
	x |= prng_get32() & 0x07;

	hsv2rgb_8b(h, 255, x, &r, &g, &b);
}

/*
 * uconf.led:
 * 0	dwell
 * 1	threshold
 * 2	hue
 * 3	base
 */
void ledprog04_flicker_same()
{
	ledprogi_flicker(4);
	led_sk6x_set_all(r, g, b);
}

/*
 * uconf.led
 * 0	dwell
 * 1	threshold
 * 2	hue
 * 3	base
 */
void ledprog05_flicker_all()
{
	uint8_t i;

	for (i = 0; i < 8; i++) {
		ledprogi_flicker(5);
		led_sk6x_set(i, r, g, b);
	}
}

/*
 * uconf.led
 * 0	dwell
 * 1	speed
 * 2	hue
 * 3	faderate
 *
 * work:
 * 0	ramper state
 */
void ledprog06_ramper()
{
	uint8_t i;

	uint16_t h;
	int16_t spd;

	uint16_t floor = 0;
	uint8_t v[8];
	uint8_t fr;


	h = uconf.led[6][2] * 6;
	fr = uconf.led[6][3] & 0x3f;

	// speed -64 to -1, +1 to +64
	spd = (uconf.led[6][1] >> 1) - 64;
	if (spd >= 0) spd++;
	work[0] += spd;
	work[0] &= 0xfff;

	// unpack fade values
	for (i = 0; i < 4; i++) {
		v[0 + i] = work[2] >> (i * 8);
		v[4 + i] = work[3] >> (i * 8);
	}

	for (i = 0; i < 8; i++) {
		// set to max if within range
		if (work[0] >= floor && work[0] <= floor + 64) {
			v[i] = 255;
		}

		// set LED
		hsv2rgb_8b(h, 255, v[i], &r, &g, &b);
		led_sk6x_set(i, r, g, b);

		// set fadeout value
		if (fr > v[i]) {
			v[i] = 0;
		} else {
			v[i] -= fr;
		}

		// both sides of top LEDs match
		if (i == 5) {
			i++;
			led_sk6x_set(i, r, g, b);
		}

		// work up the chain
		floor += 0x180;
	}

	// pack fade values
	work[2] = work[3] = 0;
	for (i = 0; i < 4; i++) {
		work[2] |= v[0 + i] << (i * 8);
		work[3] |= v[4 + i] << (i * 8);
	}
}

/*
 * uconf.led
 * 0	dwell
 * 1	top half: fade rate, bottom half: linger
 * 2	hue1
 * 3	hue2
 *
 * work:
 * 0	state machine
 * 1	linger state
 * 2	fade state
 */
void ledprog07_toggles()
{
	uint8_t i;
	uint8_t x, y;
	uint8_t r2, g2, b2;
	// int16_t e, f, h;

	switch (work[0]) {
		case 0:		// color2 even, color3 odd
		case 2: {	// color2 odd, color3 even
			x = work[0] ? 2 : 3;
			y = x ^ 1;

			hsv2rgb_8b((uconf.led[7][x] * 6), 255, 255, &r,  &g,  &b);
			hsv2rgb_8b((uconf.led[7][y] * 6), 255, 255, &r2, &g2, &b2);

			for (i = 0; i < 8; i += 2) {
				led_sk6x_set(i + 0, r,  g,  b);
				led_sk6x_set(i + 1, r2, g2, b2);
			}

			work[0]++;
			work[1] = uconf.led[7][1] & 0xf;
			work[2] = uconf.led[7][1] >> 4;
			break;
		}
		case 1:
		case 3: {
			//linger
			if (!work[1]) {
				// fade
				/* this ended up looking like shit, so nixed for now
				if (!work[2]) {
					work[0]++;
				} else {
					// calculate huefade step amount
					e =  uconf.led[7][2];			// set hue1
					e -= uconf.led[7][3];			// subtract hue2
					e =  abs(e) * 6;				// expanded full size
					f =  e / (uconf.led[7][1] >> 4);// only a step of this
					f *= work[2];

					// apply fade
					x = (work[0] & 2) ? 3 : 2;
					y = x ^ 1;

					h = (uconf.led[7][x] * 6);
					h += f;
					h %= 1536;
					hsv2rgb_8b(h, 255, 255, &r,  &g,  &b);

					h = (uconf.led[7][y] * 6);
					h += (e - f);
					h %= 1536;
					hsv2rgb_8b(h, 255, 255, &r2, &g2, &b2);

					for (i = 0; i < 8; i += 2) {
						led_sk6x_set(i + 0, r,  g,  b);
						led_sk6x_set(i + 1, r2, g2, b2);
					}

					work[2]--;
				}
				*/
				work[0]++;
			} else work[1]--;
			break;
		}
	}

	if (work[0] > 3) work[0] = 0;
}

/*
 * uconf.led
 * 0	dwell
 * 1	sensitivity
 * 2	hue_offset
 * 3	sat
 */
void ledprog08_accel_x_solid()
{
	uint16_t h;

	uint8_t z;
	uint8_t s;

	// get hue from axis
	h = abs(adxl_summing.x);
	h *= (uconf.led[8][1] & 0xf);
	h +=  (uconf.led[8][2] * 6);
	h %= 1536;

	// get brightness from Z
	if (abs(adxl_summing.z) > 224) {
		z = 224;
	} else z = abs(adxl_summing.z) & 0xff;

	// get saturation with cap
	s = uconf.led[8][3];
	if (s < 128) s = 128;

	hsv2rgb_8b(h, s, 255 - z, &r,  &g,  &b);
	led_sk6x_set_all(r,  g,  b);
}

/*
 * uconf.led
 * 0	dwell
 * 1	sensitivity
 * 2	base_hue
 * 3	expansion
 */
/*
void ledprog09_accel_y_rainbow_fade()
{
	uint8_t i;

	int16_t h;
	int16_t hstep;

	uint8_t z;

	// get hue from axis
	hstep = adxl_summing.y;
	// cap
	if (hstep >  255) hstep =  255;
	if (hstep < -255) hstep = -255;
	// set step size by expanding, then divide by number of LED "sections"
	hstep *= 6;
	hstep /= 7;
	// expand by expansion amount
	if (hstep > 0) hstep += (uconf.led[9][3] * 2);
	else           hstep -= (uconf.led[9][3] * 2);

	// set initial hue to be 2 steps back (to center on center LED in badge)
	h  = uconf.led[9][2] * 6;
	h -= hstep;
	h -= hstep;
	if (hstep < 0) hstep += 1536;

	// get brightness from Z
	if (abs(adxl_summing.z) > 224) {
		z = 224;
	} else z = abs(adxl_summing.z) & 0xff;

	for (i = 0; i < 8; i++) {
		hsv2rgb_8b(h, 255, 255 - z, &r,  &g,  &b);
		led_sk6x_set(i, r,  g,  b);

		// both sides of top LEDs match
		if (i == 5) {
			i++;
			led_sk6x_set(i, r, g, b);
		}

		// move forward another step
		h += hstep;
		h += uconf.led[9][3];
		h %= 1536;
	}
}
*/

/*
 * uconf.led
 * 0	rgb or two-color (lowest bit)
 * 1	sensitivity
 * 2	base_hue
 * 3	offset (rgb) or alt_hue-128offset (two-color)
 */
void ledprog09_accel_y_rainbow_shift()
{
	uint8_t i;

	uint16_t h;
	int16_t y;
	int16_t check;

	int16_t m;

	uint8_t z;

	// get hue from axis
	y = adxl_summing.y;
	// cap
	if (y >  255) y =  255;
	if (y < -256) y = -256;

	y = 0 - y;

	// get brightness from Z
	if (abs(adxl_summing.z) > 224) {
		z = 224;
	} else z = abs(adxl_summing.z) & 0xff;

	check = -256;
	for (i = 0; i < 8; i++) {
		if (i != 7) {
			m = abs(i - 3) * 45;
			m = 150 - m;
		} else {
			m = 15;
		}

		if (y >= check && y < (check + m)) {
			h = uconf.led[9][2];
		} else {
			h = uconf.led[9][3] + 128;
			h &= 0xff;
		}

		h *= 6;
		check += m;

		hsv2rgb_8b(h, 255, 255 - z, &r,  &g,  &b);
		led_sk6x_set(i, r,  g,  b);

		// both sides of top LEDs match
		if (i == 5) {
			i++;
			led_sk6x_set(i, r, g, b);
		}
	}
}
