#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#include "rgbstring.h"

static unsigned char jiffies;

#define CHRISTMAS_TREE 1

#define rgb_return(r, g, b)	do { send_rgb((r), (g), (b)); return 1; } while(0)

#define VERT_SIZE 47

/* RNG from ADC noise */
static unsigned char rand_pool[8], rand_pool_off, prev_bit, rand_pool_out;

static void init_rng()
{
	ADCSRA |= _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1); // enable, clk/64
	ADMUX = _BV(REFS1) | _BV(MUX0) | _BV(MUX3); // 1.1V, PB5:PB5, gain 20
	DIDR0 = _BV(ADC0D);
	ADCSRA |= _BV(ADIE) | _BV(ADSC);
	rand_pool_off = 0;
	prev_bit = 0;
}

static unsigned char rand() {
	unsigned char rv = 0;

	rv = rand_pool[rand_pool_out];
	rand_pool_out++;
	if (rand_pool_out >= sizeof(rand_pool))
		rand_pool_out = 0;

	return rv;
}

ISR(ADC_vect) {
	ADCSRA |= _BV(ADSC);
	if ((rand_pool_off & 1) == 0) { // first bit of the pair
		prev_bit = ADCW & 1;
		rand_pool_off++;
	} else {
		unsigned char bit = ADCW & 1;
		if (bit == prev_bit) { // whitening fail: try again
			rand_pool_off--;
			return;
		}

		if (bit) {
			rand_pool[rand_pool_off >> 4]
				^= 1 << ((rand_pool_off >> 1) & 7);
		}

		rand_pool_off++;
		if (rand_pool_off >= 16*sizeof(rand_pool))
			rand_pool_off = 0;
	}
}

/* up and down running stars at the beginning/end of the strip */
static unsigned char updown_star, updown_star0, updown_down, updown_bright;

static void updown_star_init()
{
	updown_star0 = 0;
	updown_down = 1;
}

static void updown_star_frame()
{
	if (updown_star0 == 0) {
		updown_star0 = STRIP_SIZE-VERT_SIZE-8;
		updown_down = 0;
	} else if (updown_star0 == STRIP_SIZE + 8) {
		updown_star0 = VERT_SIZE+7;
		updown_down = 1;
	} else if (updown_star0 <= VERT_SIZE + 8) {
		updown_star0--;
	} else if (updown_star0 >= STRIP_SIZE-VERT_SIZE-8) {
		updown_star0++;
	}

	if (updown_down && updown_star0 < 8) {
		updown_star = 0;
		updown_bright = 1 << updown_star0;
	} else if (updown_down) {
		updown_star = updown_star0 - 8;
		updown_bright = 128;
	} else if (updown_star0 < STRIP_SIZE-VERT_SIZE) {
		updown_star = STRIP_SIZE-VERT_SIZE;
		updown_bright = 1 << (STRIP_SIZE-VERT_SIZE-updown_star0);
	} else {
		updown_star = updown_star0;
		updown_bright = 1;
	}
}

static unsigned char updown_star_pixel(unsigned char pos)
{
	if (pos >= VERT_SIZE && pos < STRIP_SIZE-VERT_SIZE)
		return 0;

	if (pos == updown_star) {
		send_rgb(updown_bright, updown_bright, updown_bright);
		if (updown_down) {
			updown_bright >>= 1;
			if (updown_bright)
				updown_star++;
		} else {
			updown_bright <<= 1;
			if (updown_bright)
				updown_star++;
		}
		return 1;
	}

	return 0;
}

/* down running stars at the beginning/end of the strip */
static unsigned char down_star, down_star0, down_bright;

static void down_star_init()
{
	down_star0 = STRIP_SIZE;
}

static void down_star_frame()
{
	if (down_star0 == 0) {
		down_star0 = STRIP_SIZE;
	} else {
		down_star0--;
	}

	if (down_star0 < 8) {
		down_star = 0;
		down_bright = 1 << down_star0;
	} else {
		down_star = down_star0;
		down_bright = 128;
	}
}

static unsigned char down_star_pixel(unsigned char pos)
{
	if (pos == down_star) {
		send_rgb(down_bright, down_bright, down_bright);
		down_bright >>= 1;
		if (down_bright)
			down_star++;
		return 1;
	}

	return 0;
}

/* middle stars */

static unsigned char midstar_pos0, midstar_pos, midstar_bright, midstar_color;

static void midstar_init()
{
	midstar_pos0 = STRIP_SIZE/2;
	midstar_color = 0;
}

static unsigned int slow_dim[] = {
	255, 182, 130, 92, 66, 47, 33, 24, 17, 12, 8, 6, 4, 3, 2, 1,
};

static void midstar_frame()
{
	unsigned char phase = (jiffies) & 0x1F;

	midstar_bright = phase & 0x10 ? slow_dim[phase & 0x0F]
		: slow_dim[15-(phase & 0x0F)];

	if (!phase) {
		unsigned char c = rand();
#ifdef CHRISTMAS_TREE
		midstar_pos0 = 2 + (c % (STRIP_SIZE - 4));
#else
		midstar_pos0 -= 2 + VERT_SIZE;
		midstar_pos0 += 16 + (c & 0x1F);
		midstar_pos0 &= 0x3F;
		midstar_pos0 += 2 + VERT_SIZE;
#endif
		midstar_color += 1 + (c & 0xC0 ? 0 : 1);
	}

	midstar_pos = midstar_pos0 - 2;
}

static unsigned char midstar_pixel(unsigned char pos)
{
	unsigned char dim;

	if (pos != midstar_pos)
		return 0;

	if (pos > midstar_pos0) {
		dim = pos - midstar_pos0;
		if (dim < 2)
			midstar_pos++;
	} else {
		dim = midstar_pos0 - pos;
		midstar_pos++;
	}

	dim = midstar_bright >> 2*dim;
	if (dim >= 2) {
		switch (midstar_color & 0x7) {
		case 0: send_rgb(dim, 0, 0); break;         // red
		case 1: send_rgb(dim, dim, dim); break;     // white
		case 2: send_rgb(0, dim, 0); break;         // green
		case 3: send_rgb(dim, 0, dim); break;       // violet
		case 4: send_rgb(dim, dim >> 2, 0); break;  // yellow/orange
		case 5: send_rgb(0, dim, dim >> 2); break;  // cyan
		case 6: send_rgb(0, 0, dim); break;         // blue
		case 7: send_rgb(dim, dim, 0); break;       // yellow
		}

		return 1;
	} else {
		return 0;
	}
}

static void background(unsigned char pos)
{
#ifdef CHRISTMAS_TREE
	switch ((pos >> 3) & 3) {
	//case 0: if (pos & 1) send_rgb(7, 0, 7); else send_rgb(0, 7, 5);
	//case 0: if (pos & 1) send_rgb(0, 0, 7); else send_rgb(7, 7, 7);
	case 0: if (pos & 1) send_rgb(0, 0, 7); else send_rgb(0, 7, 0);
		break;
	case 1: if (pos & 1) send_rgb(7, 0, 7); else send_rgb(7, 0, 0);
		break;
	case 2: if (pos & 1) send_rgb(7, 5, 0); else send_rgb(7, 7, 7);
		break;
	case 3: if (pos & 1) send_rgb(7, 0, 0); else send_rgb(0, 7, 0);
		break;
	}
#else
	send_rgb(0, 0, 7);
#endif
}

int main(void)
{
	init_log();
	init_rng();
	init_serial();

	_delay_ms(3000/8); // wait for a bit and then increase the CPU clock
	CLKPR = _BV(CLKPCE);
	CLKPR = 0;

	sei();

#ifdef CHRISTMAS_TREE
	down_star_init();
#else
	updown_star_init();
#endif
	midstar_init();

	while (1) {
		unsigned char i;

		++jiffies;

#ifdef CHRISTMAS_TREE
		down_star_frame();
#else
		updown_star_frame();
#endif
		midstar_frame();
		
		for (i = 0; i < STRIP_SIZE; i++) {
#ifdef CHRISTMAS_TREE
			if (down_star_pixel(i)) continue;
#else
			if (updown_star_pixel(i)) continue;
#endif
			if (midstar_pixel(i)) continue;
			background(i);
		}
		end_frame();
		_delay_ms(30);
	}
}
