/*
 * rgbled_prog.c
 * begin 20210720 true
 *
 * programs to run to show neat shit on the LEDs
 * programs may be duplicated with minimal changes between eyes and edge
 * this is fine right now - we have the space
 * note also that some programs may change once the LEDs run at higher bit depth
 * 
 * programs I'd like to get done:
 * - anything involving gravity, and using accelerometer
 * - things that will do color fades between colors; quickest hack would be implementing rgb2hsv
 */


#include "rgbled.h"

#include "misc/accel.h"

#include "../misc/intscale.h"
#include "../misc/sin7.h"
#include "../misc/tinymt.h"

#include "user_config.h"



const uint8_t rgb_map[RGBLED_COUNT] = {
    2,  5,  0,  3,  1,  4,
    7, 10,  6,  9,  8, 11
};

static uint8_t timeout;



void rgb_solid(uint8_t *a, uint16_t tick)
{
	// 1=bitfield, 2=timeout-set, 4=state, 5=hue, 6=sat, 7=val
	// bitfield:
	// 7: blank / off
	// 3: override altcolor (if bit 1 is not set)
	// 1: override hsv
	// 0: flash

	int i;
	color_hsv hsv = {0, 0, 0};

	// timeout
	if (!timeout || (timeout > a[2])) {
		timeout = a[2];
	} else {
		timeout--;
		return;
	}

	if (a[1] & 0x80) {
        // override off
		hsv.v = 0;
	} else {
        // color select
        if (a[1] & 0x02) {
            hsv.h = a[5] * 6;
            hsv.s = a[6];
            hsv.v = a[7];
        } else if (a[1] & 0x08) {
            hsv.h = uconf.altcolor_hue * 6;
            hsv.s = uconf.altcolor_sat;
            hsv.v = uconf.altcolor_val;
        } else {
            hsv.h = uconf.favcolor_hue * 6;
            hsv.s = uconf.favcolor_sat;
            hsv.v = uconf.favcolor_val;
        }

        // flash
        if (a[1] & 0x01) {
            a[4] ^= 0x01;
            if (a[4] & 0x01) hsv.v = 0;
        }
    }

	// update
	hsv_out[0].h = hsv.h;
	hsv_out[0].s = hsv.s;
	hsv_out[0].v = hsv.v;

	for (i = 1; i < RGBLED_COUNT; i++) {
		hsv_out[i].h = hsv_out[0].h;
		hsv_out[i].s = hsv_out[0].s;
		hsv_out[i].v = hsv_out[0].v;
	}
}

// todo: improve fading smoothness by doing fadeout every callback instead of on timeout
// this can be done once LED bit depth is increased
void rgb_flicker(uint8_t *a, uint16_t tick)
{
	// 0=speed, 1=bitfield, 2=timeout-set, 4=state, 6=max-val, 7=min-val
	// bitfield:
	// 7: min-val is not half range (0-255); if unset min-val is half range (128-255)
	// 6: apply a random hue instead of favcolor
	// 5: do not apply LED remapping (results in a cross update pattern)
	// 4: fades out all LEDs at speed setting (only if bit 1 SET)
	// 3: same flicker throughout (only if bit 1 NOT SET)
	// 2: make the LED random instead of sequential (only if bit 1 SET)
	// 1: only process one LED per update
	// 0: in single LED mode, reverse direction
	// speed: fadeout speed

	int i;
	uint16_t new_val;
	uint8_t min, max;

	// timeout
	if (!timeout || (timeout > a[2])) {
		timeout = a[2];
	} else {
		timeout--;
		return;
	}
	
	// set constants
	hsv_out[0].h = uconf.favcolor_hue * 6;
	hsv_out[0].s = uconf.favcolor_sat;

	if (a[6]) max = a[6]; else max = 255;

	// set value limit
	if (a[1] & 0x80) {
		min = a[7];
	} else {
		min = (a[7] >> 1) | 0x80;
	}

	// process and update LEDs
	for (i = 0; i < RGBLED_COUNT; i++) {
		// compute new val on first round or if all-same flicker is disabled
		if (!(a[1] & 0x08) || i == 0) {	
			new_val = u16_scale((prng_get16() & 0xff), 0, 255, min, max);
			new_val = (uconf.favcolor_val * new_val) / 256;			
		}

		// only doing one LED?
		if (a[1] & 0x02) {
			// is it random?
			if (a[1] & 0x04) {
				i = prng_get16() % RGBLED_COUNT;
			} else {
				// nope, sequential
				a[4]++;
				a[4] %= RGBLED_COUNT;
				i = a[4] % RGBLED_COUNT;
				
				// is it in reverse?
				if (a[1] & 0x01) {
					i = RGBLED_COUNT - 1 - i;
				}

				// correct position
				if (!(a[1] & 0x20)) {
					i = rgb_map[i];
				}
			}
		}
		
		// apply
		hsv_out[i].h = (a[1] & 0x40) ? (prng_get16() & 0x600) : hsv_out[0].h;
		hsv_out[i].s = hsv_out[0].s;
		hsv_out[i].v = new_val & 0xff;

		// bail if only doing one LED
		if (a[1] & 0x02) {
			// but make sure to fade LEDs if needed
			if (a[1] & 0x10) {
				for (i = 0; i < RGBLED_COUNT; i++) {
					if (hsv_out[i].v <= a[0]) {
						hsv_out[i].v = 0;
					} else {
						hsv_out[i].v -= a[0];
					}
				}
			}
			return;
		}
	}
}

uint8_t rgb_circles_divider = 0;
uint8_t rgb_circles_sec_ticks = 0;
void rgb_circles(uint8_t *a, uint16_t tick)
{
	// 0=speed, 1=bitfield, 2=timeout-set, 4=state, 5=hue, 6=sat, 7=val
	// bitfield:
	// 7: apply specified hue instead of favcolor
	// 6: apply a random hue
	// 5: trails desaturate
	// 4: secondary value is half of primary
	// 3: opposing is same hue as primary (unset is alt color hue/sat; is always primary val)
	// 2: enable opposing ball
	// 1: enable fading trails
	// 0: direction
	// others:
	// a[0][7:4]: fade rate of trails
	// a[0][0:3]: how many ticks until extra increment on secondary; 0 disables, 0xf sets opposing

	int i;
	uint16_t h, h2;
	uint8_t s, v;
	uint8_t x, y;
	uint8_t s2;

	uint8_t trailfade = a[0] >> 4;
	uint8_t desatfade = a[1] & 0x20;

	// fading
	rgb_circles_divider++;
	rgb_circles_divider %= 10;
	if (!rgb_circles_divider && (a[1] & 0x02)) {
		for (i = 0; i < RGBLED_COUNT; i++) {
			if (hsv_out[i].v <= trailfade) {
				hsv_out[i].v = 0;
			} else {
				hsv_out[i].v -= trailfade;
			}

			if (desatfade) {
				// fade to white too
				if (hsv_out[i].s <= (trailfade >> 1)) {
					hsv_out[i].s = 0;
				} else {
					hsv_out[i].s -= (trailfade >> 1);
				}
			}
		}
	}

	// timeout
	if (!timeout || (timeout > a[2])) {
		timeout = a[2];
	} else {
		timeout--;
		return;
	}

	uint8_t srate  = a[0] & 0x0f;	
	uint8_t dir    = a[1] & 0x01;
	uint8_t trail  = a[1] & 0x02;
	uint8_t second = a[1] & 0x04;
	uint8_t scolor = a[1] & 0x08;
	uint8_t secval = a[1] & 0x10;
	uint8_t rndhue = a[1] & 0x40;
	uint8_t sethue = a[1] & 0x80;


	// state
	x = (a[4] & 0xf) + 1;
	x %= 10;

	y = (a[4] >> 4) + 9;

	if (second) {
		if (srate == 0xf) {
			// opposing mode
			y = x + 5;
			y %= 10;
		} else if (srate) {
			// gradual offset mode
			srate <<= 2;
			rgb_circles_sec_ticks++;
			if (rgb_circles_sec_ticks > srate) {
				rgb_circles_sec_ticks = 0;
				y += 9;
			}
		}
	}

	y %= 10;

	// save state
	a[4] = (y << 4) | x;

	// apply direction and secondary
	if (dir) {
		x = 9 - x;
		y = 9 - y;
	}

	// colors
	s = s2 = a[6];
	v = a[7];

	h2 = uconf.altcolor_hue * 6;

	if (sethue) {
		h = a[5] * 6;
	} else if (rndhue) {
		h = prng_get16() & 0x5ff;
	} else {
		h = uconf.favcolor_hue * 6;
		s = uconf.favcolor_sat;
		v = uconf.favcolor_val;
		s2 = uconf.altcolor_sat;
	}

	// secondary
	if (scolor) {
		h2 = h;
		s2 = s;
	}

	// set the next item
	hsv_out[rgb_map[x]].h = h;
	hsv_out[rgb_map[x]].s = s;
	hsv_out[rgb_map[x]].v = v;

	if (second && x != y) {
		if (secval) v >>= 1;
		
		hsv_out[rgb_map[y]].h = h2;
		hsv_out[rgb_map[y]].s = s2;
		hsv_out[rgb_map[y]].v = v;
	}

	// clear those that are on if trails are not enabled
	if (!trail) {
		for (i = 0; i < RGBLED_COUNT; i++) {
			if (i != x || (second && (i != y))) {
				hsv_out[rgb_map[i]].v = 0;
			}
		}
	}	
}

uint8_t rgb_waving_divider = 0;
void rgb_waving(uint8_t *a, uint16_t tick)
{
	// 0=wait-delay, 1=bitfield, 2=timeout-set, 456=work
	// bitfield:
	// 5 trails desaturate
	// 1 enable fading trails
	// others:
	// a[0][7:4]: fade rate of trails
	// a[0][0:3]: movement rate of up-down (stall)

	int i;
	
	uint8_t trailfade = a[0] >> 4;
	uint8_t desatfade = a[1] & 0x20;

	rgb_waving_divider++;
	rgb_waving_divider %= 10;
	if (!rgb_waving_divider && (a[1] & 0x02)) {
		for (i = 0; i < RGBLED_COUNT; i++) {
			if (hsv_out[i].v <= trailfade) {
				hsv_out[i].v = 0;
			} else {
				hsv_out[i].v -= trailfade;
			}

			if (desatfade) {
				// fade to white too
				if (hsv_out[i].s <= (trailfade >> 1)) {
					hsv_out[i].s = 0;
				} else {
					hsv_out[i].s -= (trailfade >> 1);
				}
			}
		}
	}

	uint8_t stall = a[0] & 0x0f;

	// timeout
	if (!timeout || (timeout > a[2])) {
		timeout = a[2];
	} else {
		timeout--;
		return;
	}

	// fix
	if (a[4] > 3) a[4] = 0;
	if (a[5]) a[5]--;

	// clear values if trails not enabled
	if (!(a[1] & 0x02)) {
		for (i = 0; i < RGBLED_COUNT; i++) {
			hsv_out[i].v = 0;
		}
	}

	switch (a[4]) {
		case 0:
		case 2: {	// waiting state
			if (!a[5]) {
				// next step
				a[6] = 0;
				a[4]++;
			}
			break;
		}
		case 1: {	// moving down
			hsv_out[rgb_map[    a[6]]].h = uconf.favcolor_hue * 6;
			hsv_out[rgb_map[    a[6]]].s = uconf.favcolor_sat;
			hsv_out[rgb_map[    a[6]]].v = uconf.favcolor_val;

			hsv_out[rgb_map[9 - a[6]]].h = uconf.favcolor_hue * 6;
			hsv_out[rgb_map[9 - a[6]]].s = uconf.favcolor_sat;
			hsv_out[rgb_map[9 - a[6]]].v = uconf.favcolor_val;

			a[6]++;
			if (a[6] >= 5) {
				a[5] = stall;
				a[4]++;
				break;
			}

			break;
		}
		case 3: {	// moving up
			hsv_out[rgb_map[4 - a[6]]].h = uconf.favcolor_hue * 6;
			hsv_out[rgb_map[4 - a[6]]].s = uconf.favcolor_sat;
			hsv_out[rgb_map[4 - a[6]]].v = uconf.favcolor_val;

			hsv_out[rgb_map[5 + a[6]]].h = uconf.favcolor_hue * 6;
			hsv_out[rgb_map[5 + a[6]]].s = uconf.favcolor_sat;
			hsv_out[rgb_map[5 + a[6]]].v = uconf.favcolor_val;

			a[6]++;
			if (a[6] >= 5) {
				a[5] = stall;
				a[4]++;
				break;
			}

			break;
		}
	}
}



void rgb_copmode(uint8_t *a, uint16_t tick)
{
	// 0=work, 1=bitfield, 2=timeout-set, 3=timeout-work, 4=work, 56=steps, 7=val
	// bitfield:
	// 7 sync to tick 0
	// 6 skip pattern 2
	// 5 skip pattern 1
	// 4 don't override sat/val to maximums (not implemented)
	// 3 use different colors on either half during pattern 2
	// 2 use different colors on either half during pattern 1
	// 1 repeat subpattern 1
	// 0 use cop colors instead of pri/alt
	// others:
	// a[5][7:4] amount of time to delay in pattern 2
	// a[5][0:3] amount of times to repeat pattern 2 (solid color alternate)
	// a[6][7:4] amount of strobe pulses in pattern 1
	// a[6][0:3] amount of times to repeat pattern 1 (strobes)

	int i;
	
	uint8_t pattern, iter;
	uint8_t *seq;
	uint8_t w, x;
	uint8_t b;

	uint8_t cnt[4];

	color_hsv hsv;
	color_hsv hsv2;
	color_hsv pri, sec;

	// synchronize
	if (a[1] & 0x80) {
		if (!tick) {
			a[3] = 0;
			a[1] &= ~0x80;
		} else {
			return;
		}
	}
	
	// timeout
	if (!timeout || (timeout > a[2])) {
		timeout = a[2];
	} else {
		timeout--;
		return;
	}

	// colors to use
	if (a[1] & 0x01) {
		pri.h = 0x000;
		pri.s = 0xff;
		sec.h = 0x400;
		sec.s = 0xff;
	} else {
		pri.h = uconf.favcolor_hue * 6;
		pri.s = uconf.favcolor_sat;
		sec.h = uconf.altcolor_hue * 6;
		sec.s = uconf.altcolor_sat;
	}

	// pattern mode
	pattern  = a[0] >> 6;
	iter     = a[0] & 0x3f;
	seq      = &a[4];

	cnt[0] = a[6] & 0xf;
	cnt[1] = a[6] >> 4;
	cnt[2] = a[5] & 0xf;
	cnt[3] = a[5] >> 4;

	if ((pattern == 0) && (a[1] & 0x20)) pattern++;
	if ((pattern == 1) && (a[1] & 0x40)) pattern++;

	if (pattern > 1) pattern = 0;

	switch (pattern) {
		// strobe
		case 0: {
			w = cnt[1] << 1;
			if (!w) w = 32;
			b = (a[1] & 0x02) ? 0x02 : 0x01;
			x = cnt[0];
			if (!x) x = 16;
			x <<= b;			

			// set main color
			hsv.h = (iter & b) ? sec.h : pri.h;
			hsv.s = (iter & b) ? sec.s : pri.s;
			hsv.v = 0;

			// p1 alternate color mode
			if (a[1] & 0x04) {
				hsv2.h = (iter & b) ? pri.h : sec.h;
				hsv2.s = (iter & b) ? pri.s : sec.s;
			} else {
				hsv2.h = hsv.h;
				hsv2.s = hsv.s;
			}

			// set value if on
			if ((*seq < w) && !(*seq & 1)) {	
				hsv.v = a[7];
			}

			if (*seq > w) {
				// iteration done
				iter++;
				*seq = 0;
				if (iter >= x) {
					// pattern done
					pattern++;
					iter = 0;
				}

				break;
			}

			(*seq)++;
			break;
		}
		
		// solid alternate
		case 1: {
			w = cnt[3] << 1;
			if (!w) w = 32;
			x = cnt[2] << 1;
			if (!x) x = 32;

			// set main color
			hsv.h = (iter & 1) ? sec.h : pri.h;
			hsv.s = (iter & 1) ? sec.s : pri.s;
			hsv.v = 0;

			// p2 alternate color mode
			if (a[1] & 0x08) {
				hsv2.h = (iter & 1) ? pri.h : sec.h;
				hsv2.s = (iter & 1) ? pri.s : sec.s;
			} else {
				hsv2.h = hsv.h;
				hsv2.s = hsv.s;
			}

			// set value
			hsv.v = a[7];

			if (*seq > w) {
				// iteration done
				iter++;
				*seq = 0;
				if (iter >= x) {
					// pattern done
					pattern++;
					iter = 0;
				}

				break;
			}

			(*seq)++;
			break;
		}
	}

	// we only have two patterns
	pattern %= 2;

	// save state
	a[0] = (pattern << 6) | (iter & 0x3f);

	// apply to LEDs
	w = RGBLED_COUNT/2;
	for (i = 0; i < w; i++) {
		hsv_out[rgb_map[i]].h = hsv.h;
		hsv_out[rgb_map[i]].s = hsv.s;
		hsv_out[rgb_map[i]].v = hsv.v;
		
		hsv_out[rgb_map[i+w]].h = hsv2.h;
		hsv_out[rgb_map[i+w]].s = hsv2.s;
		hsv_out[rgb_map[i+w]].v = hsv.v;
	}
}

void rgb_fade_from_center(uint8_t *a, uint16_t tick)
{

}

void rgb_staticbar(uint8_t *a, uint16_t tick)
{

}

void rgb_gravitycheck(uint8_t *a, uint16_t tick)
{

}

void rgb_rainbow(uint8_t *a, uint16_t tick);
const uint8_t * rgb_rainbow_desc(uint8_t idx);


// implemented program table
const LedProgram rgb_pgm[6] = {
	{"Solid Color", rgb_solid,      0},
	{"Flicker",		rgb_flicker,    0},
	{"Circles",		rgb_circles,    0},
	{"Waving",		rgb_waving,     0},
	{"Rainbow",		rgb_rainbow,    rgb_rainbow_desc},
	{"Cop Mode",	rgb_copmode,    0},
};
