/*
 * Blink'n'man
 * A blinkenbuttonleds gadget
 *
 * Copyright (C) 2011  B. Stultiens
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <stdlib.h>
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>

#define BIT_LR		0	/* left on port B */
#define BIT_LG		1
#define BIT_LB		2
#define BIT_LIN		3
#define BIT_RR		5	/* right on port D */
#define BIT_RG		4
#define BIT_RB		3
#define BIT_RIN		2

#define BIT_BFLAG	7	/* input change flag */

#define MAX_HUE	1541

#define bl_pressed()	((buttonl & (_BV(BIT_LIN) | _BV(BIT_BFLAG))) == (_BV(BIT_LIN) | _BV(BIT_BFLAG)))
#define bl_released()	((buttonl & (_BV(BIT_LIN) | _BV(BIT_BFLAG))) == _BV(BIT_BFLAG))
#define br_pressed()	((buttonr & (_BV(BIT_RIN) | _BV(BIT_BFLAG))) == (_BV(BIT_RIN) | _BV(BIT_BFLAG)))
#define br_released()	((buttonr & (_BV(BIT_RIN) | _BV(BIT_BFLAG))) == _BV(BIT_BFLAG))
#define bl_reset()	do { buttonl &= ~_BV(BIT_BFLAG); } while(0)
#define br_reset()	do { buttonr &= ~_BV(BIT_BFLAG); } while(0)

static volatile uint8_t r[2], g[2], b[2];
static volatile uint8_t plr, plg, plb;
static volatile uint8_t prr, prg, prb;
static volatile uint8_t buttonl, buttonr;
static volatile uint8_t bstatel, bstater;
static volatile uint8_t bcntl, bcntr;

static volatile uint8_t timer1_fired;

ISR(TIMER0_OVF0_vect)
{
	uint8_t t;
	t = _BV(BIT_LIN) | _BV(BIT_LR) | _BV(BIT_LG) | _BV(BIT_LB);
	if(++plr < r[0] ) t &= ~_BV(BIT_LR);
	if(++plg < g[0] ) t &= ~_BV(BIT_LG);
	if(++plb < b[0] ) t &= ~_BV(BIT_LB);
	PORTB = t;
	t = _BV(BIT_RIN) | _BV(BIT_RR) | _BV(BIT_RG) | _BV(BIT_RB);
	if(++prr < r[1]) t &= ~_BV(BIT_RR);
	if(++prg < g[1]) t &= ~_BV(BIT_RG);
	if(++prb < b[1]) t &= ~_BV(BIT_RB);
	PORTD = t;
	t = PINB;
	if((t  ^ buttonl) & _BV(BIT_LIN))
		bcntl = 0;
	if(!--bcntl)
		buttonl = (~t & _BV(BIT_LIN)) | _BV(BIT_BFLAG);
	t = PIND;
	if((t  ^ buttonr) & _BV(BIT_RIN))
		bcntr = 0;
	if(!--bcntr)
		buttonr = (~t & _BV(BIT_RIN)) | _BV(BIT_BFLAG);
}

ISR(TIMER1_COMP1_vect)
{
	timer1_fired++;
}

#define NUMB	(8)
#define NUM	(1 << (NUMB))
#define NUMN(n)	((NUM + 1) * n)
static void hsv_to_rgb(uint16_t h, uint8_t s, uint8_t v, uint8_t n)
{
        if(h < NUMN(1)) {
                h -= NUMN(0);
                r[n] = v;
                g[n] = (v * (uint8_t)(~((uint16_t)(s * (NUM-h)) >> NUMB))) >> 8;
                b[n] = (v * (uint8_t)(~s + 1)) >> 8;
        } else if(h < NUMN(2)) {
                h -= NUMN(1);
                r[n] = (v * (uint8_t)(~((uint16_t)(s * h) >> NUMB))) >> 8;
                g[n] = v;
                b[n] = (v * (uint8_t)(~s + 1)) >> 8;
        } else if(h < NUMN(3)) {
                h -= NUMN(2);
                r[n] = (v * (uint8_t)(~s + 1)) >> 8;
                g[n] = v;
                b[n] = (v * (uint8_t)(~((uint16_t)(s * (NUM-h)) >> NUMB))) >> 8;
        } else if(h < NUMN(4)) {
                h -= NUMN(3);
                r[n] = (v * (uint8_t)(~s + 1)) >> 8;
                g[n] = (v * (uint8_t)(~((uint16_t)(s * h) >> NUMB))) >> 8;
                b[n] = v;
        } else if(h < NUMN(5)) {
                h -= NUMN(4);
                r[n] = (v * (uint8_t)(~((uint16_t)(s * (NUM-h)) >> NUMB))) >> 8;
                g[n] = (v * (uint8_t)(~s + 1)) >> 8;
                b[n] = v;
        } else {
                h -= NUMN(5);
                r[n] = v;
                g[n] = (v * (uint8_t)(~s + 1)) >> 8;
                b[n] = (v * (uint8_t)(~((uint16_t)(s * h) >> NUMB))) >> 8;
        }
}

static uint16_t pph;
static uint8_t ppv;
static uint8_t pps;
static int8_t ppincs;
static int8_t ppincv;
static void ping_pong_step(void)
{
	hsv_to_rgb(pph, pps, ppv, 0);
	hsv_to_rgb(pph, pps, ~ppv, 1);
	if(!(ppv & 0x0f)) {
		pph++;
		if(pph > MAX_HUE)
			pph = 0;
		pps += ppincs;
		if(pps == 210 || pps == 255)
			ppincs = -ppincs;
	}
	ppv += ppincv;
	if(ppv == 255 || ppv == 3)
		ppincv = -ppincv;
}

static void color_step(void)
{
	hsv_to_rgb(pph, 255, ppv, 0);
	hsv_to_rgb(pph, 255, ppv, 1);

	ppv += ppincv;
	if(ppv == 255 || ppv == 3)
		ppincv = -ppincv;
	if(ppv == 3) {
		pph += (MAX_HUE+1)/17;
		if(pph > MAX_HUE)
			pph -= MAX_HUE+1;
	}
}

static uint16_t chl;
static uint16_t chr;
static void cycle_step(void)
{
	hsv_to_rgb(chl++, 255, 255, 0);
	hsv_to_rgb(chr++, 255, 255, 1);
	if(chl > MAX_HUE)
		chl = 0;
	if(chr > MAX_HUE)
		chr = 0;
}

static uint32_t rand_val;
static void lfsrrandom(void)
{
	int c = rand_val & 1;
	rand_val >>= 1;
	if(c)
		rand_val ^= 0xa6a6a6a6;
}
static void random_step(void)
{
	lfsrrandom();
	r[0] = rand_val;
	g[0] = rand_val >> 8;
	b[0] = rand_val >> 16;
	lfsrrandom();
	r[1] = rand_val;
	g[1] = rand_val >> 8;
	b[1] = rand_val >> 16;
}

#define MDOT	(128+1)		/* Dot */
#define MDASH	(128+2)		/* Dash */
#define MDP	(  0+1)		/* Dot pause */
#define MLP	(  0+3)		/* Letter pause */
#define MWP	(  0+5)		/* Word pause (should be 7) */

static const prog_uint8_t sos[] = {
	MDOT, MDP, MDOT, MDP, MDOT,		/* S */
	MLP,
	MDASH, MDP, MDASH, MDP, MDASH,		/* O */
	MLP,
	MDOT, MDP, MDOT, MDP, MDOT,		/* S */
	MWP,
	MDASH, MDP, MDOT,			/* N */
	MLP,
	MDOT, MDP, MDOT,			/* I */
	MLP,
	MDOT, MDP, MDOT, MDP, MDOT,		/* S */
	MLP,
	MDOT, MDP, MDOT, MDP, MDOT,		/* S */
	MLP,
	MDOT,					/* E */
	MWP,
	MDASH, MDP, MDASH,			/* M */
	MLP,
	MDOT, MDP, MDASH,			/* A */
	MLP,
	MDASH, MDP, MDOT,			/* N */
	MLP,
	MDASH, MDP, MDOT, MDP, MDOT,		/* D */
	MWP,
};

static void handle_bl(void)
{
	uint8_t i;
	for(i = 0; i < sizeof(sos); i++) {
		uint8_t m = pgm_read_byte(&sos[i]);
		if(m & 0x80) {
			hsv_to_rgb(pph, 255, 255, 0);
			hsv_to_rgb(pph, 255, 255, 1);
		} else {
			hsv_to_rgb(0, 0, 0, 0);
			hsv_to_rgb(0, 0, 0, 1);
		}
		m = (m & 0x7f) * 50;
		while(timer1_fired < m) {
			if(bl_released())
				return;
		}
		timer1_fired = 0;
		pph += 31;
		if(pph > MAX_HUE)
			pph -= MAX_HUE+1;
	}
}

int main()
{
	cli();

	/* Processor init */
	GIMSK = 0;
	MCUCR = 0;

	/* Leds output and button input with pull-up */
	PORTB = _BV(BIT_LR) | _BV(BIT_LG) | _BV(BIT_LB) | _BV(BIT_LIN);
	DDRB  = _BV(BIT_LR) | _BV(BIT_LG) | _BV(BIT_LB);
	PORTD = _BV(BIT_RR) | _BV(BIT_RG) | _BV(BIT_RB) | _BV(BIT_RIN);
	DDRD  = _BV(BIT_RR) | _BV(BIT_RG) | _BV(BIT_RB);

	TCCR0 = 1;	/* Prescale 1:1 -> 15625Hz -> PWM @ 61Hz */
	TIMSK = _BV(TOIE0) | _BV(OCIE1A);

	//OCR1A = 6250;	/* 80Hz */
	OCR1A = 10000;	/* 50Hz */
	TCCR1A = 0;
	TCCR1B = _BV(CTC1) | _BV(CS10);	/* CTC mode prescale 1:8 */

	/* Set PWM counters at intervals to prevent surge currents */
	plr = 0;
	plg = 1;
	plb = 2;
	prr = 3;
	prg = 4;
	prb = 5;

	rand_val = 0xdeadbeef;

	sei();				/* Go running */

	pph = 0;
	ppv = 100;
	pps = 250;
	ppincs = -1;
	ppincv = 1;
	chl = 0;
	chr = MAX_HUE / 6;

	/* Main loop */
	while(1) {
		while(!br_pressed()) {
			if(bl_pressed())
				handle_bl();
			if(timer1_fired >= 2) {
				timer1_fired = 0;
				ping_pong_step();
			}
		}
		br_reset();
		while(!br_pressed()) {
			if(bl_pressed())
				handle_bl();
			if(timer1_fired >= 4) {
				timer1_fired = 0;
				cycle_step();
			}
		}
		br_reset();
		while(!br_pressed()) {
			if(bl_pressed())
				handle_bl();
			if(timer1_fired >= 80) {
				timer1_fired = 0;
				random_step();
			}
		}
		br_reset();
		while(!br_pressed()) {
			if(bl_pressed())
				handle_bl();
			if(timer1_fired >= 2) {
				timer1_fired = 0;
				color_step();
			}
		}
		br_reset();
	}
}
