/*
 * rgbprog.c: making your eyes light up
 *
 * program notes:
 *   knob[0] is the left knob, 0-255.
 *   there are 8 programs selected by the knob[0] position.
 * 
 *   k0 passed to rgb programs is the 5 bits of position
 *   of the knob[0] value for the selected program.
 *   these can be used within the program.
 * 
 *   knob[1] is the right knob, 0-255.
 * 
 *   raw knob values can be read from adc_avg[ADC_SET0] (left)
 *   and adc_avg[ADC_SET1] (right).
 * 
 *   raw values are safe to use from the left knob,
 *   but will vary on the right knob depending on board version.
 *   thus it is recommended to only use the 8-bit smoothed
 *   value for the right knob.
 *
 * file creation: 20231015 0119
 */


#include "led.h"
#include "hsv2rgb.h"
#include "rand.h"
#include "userio.h"



#define hsv2rgb(idx, h, s, v)		hsv2rgb_8b(h, s, v, &rgb[idx].r, &rgb[idx].g, &rgb[idx].b)



void rgbprog_rainbow(uint8_t k0);
void rgbprog_rainbow_offset(uint8_t k0);
void rgbprog_static(uint8_t k0);
void rgbprog_flicker(uint8_t k0);
void rgbprog_randcolorfadebetween(uint8_t k0);
void rgbprog_randcolor(uint8_t k0);
void rgbprog_randcolorfadeinout(uint8_t k0);
void rgbprog_prog7(uint8_t k0);

static void (*proglist[8])(uint8_t) = {
	rgbprog_rainbow,
	rgbprog_rainbow_offset,
	rgbprog_static,
	rgbprog_flicker,
	rgbprog_randcolorfadebetween,
	rgbprog_randcolor,
	rgbprog_randcolorfadeinout,
	rgbprog_prog7
};



static uint16_t brite = 0;

static color_hsv hsv[2];
static color_rgb rgb[2];
static color_rgb rfade;



void rgb_setled(uint8_t ledidx, color_rgb *rgb)
{
	led_setrgb(ledidx, rgb->r << brite, rgb->g << brite, rgb->b << brite);
}

void rgb_fromhsv(uint8_t ledidx, uint16_t h, uint8_t s, uint8_t v)
{
	hsv2rgb_8b(h, s, v, &rgb[ledidx].r, &rgb[ledidx].g, &rgb[ledidx].b);
}

void rgb_fade(uint8_t fade)
{
	uint8_t i;

	uint16_t mod, invmod;

	uint8_t *rf = (uint8_t *)&rfade;
	uint8_t *r[2] = { (uint8_t *)&rgb[0], (uint8_t *)&rgb[1] };


	mod    = fade + 1;
	invmod = (255 - fade) + 1;

	for (i = 0; i < 3; i++) {
		if (!fade) {
			rf[i] = r[0][i];
			continue;
		}
		if (fade == 0xff) {
			rf[i] = r[1][i];
			continue;
		}

		rf[i] = ((r[0][i] * invmod) >> 8) + ((r[1][i] * mod) >> 8);
	}
}

void rgbprog_run()
{
	uint32_t i;
	uint8_t j;

	// if the button has been pushed, change the brightness
	if (userio_get_btn() > 0) {
		brite++;
		brite &= 0x3;
	}
	
	// which program to run?
	j = 0;
	for (i = 32; i <= 256; i += 32) {
		if (knob[0] < i) {
			if (proglist[j]) {
				proglist[j](knob[0] & 0x1f);
			}
			break;
		}
		j++;
	}
}

/*
 * program 0: rainbow program
 *
 * both eyes are set to same rgb rainbow pattern
 */
void rgbprog_rainbow(uint8_t k0)
{
	int16_t w;

	w  = knob[1];
	w *= 2;
	w  = 256 - w;

	hsv[0].h += w;
	if (hsv[0].h < 0) hsv[0].h += 0x3000;
	hsv[0].h %= 0x3000;

	hsv2rgb(0, hsv[0].h >> 3, 255, 255);
	rgb_setled(0, &rgb[0]);
	rgb_setled(1, &rgb[0]);
}

/*
 * program 1: rainbow program
 *
 * both eyes are set to rgb rainbow pattern,
 * with offset between the eyes
 */
void rgbprog_rainbow_offset(uint8_t k0)
{
	rgbprog_rainbow(k0);

	hsv[1].h  = hsv[0].h;
	hsv[1].h += k0 << 6;
	hsv[1].h %= 0x3000;

	hsv2rgb(1, hsv[1].h >> 3, 255, 255);
	rgb_setled(1, &rgb[1]);
}

/*
 * program 2: static color
 *
 * both eyes are set to a static color
 * 
 * left knob adjusts saturation
 * right knob adjusts color hue
 */
void rgbprog_static(uint8_t k0)
{
	hsv[0].h = knob[1] * 6;

	hsv2rgb(0, hsv[0].h, 131 + (k0 << 2), 255);
	rgb_setled(0, &rgb[0]);
	rgb_setled(1, &rgb[0]);
}

/*
 * program 3: flicker
 *
 * both eyes are set to a static flickering color
 * left knob adjusts flicker level
 * right knob adjusts color hue
 */
void rgbprog_flicker(uint8_t k0)
{
	uint8_t i;
	uint8_t val;

	// operate only 1/4 of the time
	hsv[0].s++;
	hsv[0].s &= 3;
	if (hsv[0].s) return;
	
	hsv[0].h = knob[1] * 6;

	// independent flicker for each eye
	for (i = 0; i < 2; i++) {
		// left knob sets flicker
		if ((rand() & 0xff) > (k0 << 3)) {
			// full bright
			val = 0xff;
		} else {
			// flicker
			val = 0x2f;
		}	

		hsv2rgb(i, hsv[0].h, 255, val);
		rgb_setled(i, &rgb[i]);
	}
}

/*
 * program 4: random fade between colors
 *
 * linear fade between two random colors
 */
void rgbprog_randcolorfadebetween(uint8_t k0)
{
	int16_t w;

	// set delay based on left knob
	hsv[0].s++;
	if (hsv[0].s > (k0 >> 1)) hsv[0].s = 0;
	if (hsv[0].s) return;

	// update new colors at the end of every fade
	hsv[0].v += 3;
	if (hsv[0].v <= 2) {
		// set new initial colors
		hsv[0].h = hsv[1].h;
		hsv[1].h = rand() % 1536;

		// clamp discrete colors to 64
		w = 4 * 6;
		hsv[1].h /= w;
		hsv[1].h *= w;

		hsv2rgb(0, hsv[0].h, 255, 255);
		hsv2rgb(1, hsv[1].h, 255, 255);
	}

	// do the fade
	rgb_fade(hsv[0].v);		
	// and output the result
	rgb_setled(0, &rfade);
	rgb_setled(1, &rfade);
}

/*
 * program 5: 
 *
 * change between two random colors
 * 
 * left knob sets single color or dual color
 * right knob sets speed
 */
void rgbprog_randcolor(uint8_t k0)
{
	// set delay based on right knob
	hsv[1].h += knob[1];
	hsv[1].h++;
	if (hsv[1].h >= 0x1000) {		// 1/16 rate
		hsv[1].h -= 0x1000;

		// update time
		hsv[0].h = rand() % 1536;
		hsv2rgb(0, hsv[0].h, 255, 255);

		if (k0 >= 16) {
			rgb_setled(rand() & 1, &rgb[0]);
		} else {
			rgb_setled(0, &rgb[0]);
			rgb_setled(1, &rgb[0]);
		}
	}
}

/*
 * program 6:
 *
 * fade up into a random color, hold, then fade out
 * 
 * left knob sets dwell
 * right knob sets cycle time
*/
void rgbprog_randcolorfadeinout(uint8_t k0)
{
	uint16_t w;

	// operate only 1/4 of the time
	hsv[0].s++;
	hsv[0].s &= 3;
	if (hsv[0].s) return;

	// hsv[1].h contains fade counter
	// hsv[1].s contains dwell counter
	// hsv[1].v contains our state
	hsv[1].v &= 0x3;

	switch (hsv[1].v) {
		case 0:
		case 2: {
			// ramp up/down

			// first run or new run?
			if (!hsv[1].h) {
				// set a new hue
				hsv[0].h = rand() % 1536;
			}

			// fade up or down
			hsv[1].h += (knob[1]);
			hsv[1].h++;
			
			// clamp value
			if (hsv[1].h > 0x7ff) {
				hsv[1].h = 0x7ff;
			}

			// set output
			w = hsv[1].v ? (0x7ff - hsv[1].h) : hsv[1].h;
			w >>= 3;

			hsv2rgb(0, hsv[0].h, 255, (w & 0xff));

			// are we done?
			if (hsv[1].h == 0x7ff) {
				hsv[1].h = 0;
				hsv[1].v++;
			}

			break;
		}

		case 1:
		case 3: {
			// hold
			
			// just getting to this step?
			if (!hsv[1].h) {
				// initialize step
				hsv[1].h++;
				hsv[1].s = 0;
			} else {
				// delay here
				w = hsv[1].s;
				w += k0;
				w++;

				// are we done?
				if (w > 0x100) {
					if (hsv[1].v == 3) {
						hsv[1].h = 0;
					}

					hsv[1].v++;
				} else {
					hsv[1].s = w & 0xff;
				}
			}
		}
	}

	rgb_setled(0, &rgb[0]);
	rgb_setled(1, &rgb[0]);
}

/*
 * program 7:
 *
 * turns LEDs off.
 * 
 * it's basically an empty program.
 * if you want to write your own, do it here
 * 
 * maybe you could write a smart morse generator,
 * or a cop mode, or...
 */
void rgbprog_prog7(uint8_t k0)
{
	rgb[0].r = rgb[0].g = rgb[0].b = 0;
	rgb_setled(0, &rgb[0]);
	rgb_setled(1, &rgb[0]);
}