#define F_CPU 20000000UL
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/io.h>

#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>

#include "color_hsv.h"
#define HUE_MAX 610
#define HUE_MIN 350
#define HUE_MID ((HUE_MAX + HUE_MIN) / 2)
#define HUE_DIF (HUE_MAX - HUE_MIN)
#define HUE_SAT 100	// 0 = white, 100 = color
#define HUE_VAL 20	// 0 = black, 100 = color
#define HUE_STEP 8	// Bis 3 sollte gehen
const uint32_t colorSteps = HUE_DIF / HUE_STEP;
//~ const uint32_t colorSteps = 5;

#define LEDS 30

// Prototypes:
void writeZero(void);
void writeOne(void);
void writeRGB(uint8_t red, uint8_t green, uint8_t blue);
void nextColor(hsv_t* hsvList, rgb_t* rgbList, hsv_t start, hsv_t end);

// Interrupt variables:
int8_t volatile status = 1;
uint16_t volatile min = HUE_MID;
uint16_t volatile max = HUE_MID;
uint32_t volatile time = 0;
uint32_t volatile T_half = 0;
uint32_t volatile oldTime = 0;
uint32_t volatile cooldown = 3000000;	// * 50 ns = 150 ms disabled interrupt
uint8_t volatile triggered = 0;
//~ uint32_t volatile delay = 0;

// On External Interrupt:
ISR(INT1_vect) {
	uint32_t now = time + TCNT1;
	//~ triggered = (triggered + 1) % 4;
	if(now > cooldown) {
		triggered = true;
		//~ EIMSK &= 0xfd;	// Disable INT1
		//~ EICRA &= 0xf3;	// 
		PORTC &= 0xef;	// Turn LED off while INT1 disabled (PORTC3 = PIN26)
		PORTC ^= 0x02;	// toggle LED on INT1 rising edge (PORTC1 = PIN24)
		//~ uint64_t time2 = TCNT2;
		//~ uint64_t time1 = TCNT1;
		//~ uint64_t time0 = TCNT0;
		//~ T_half = time2 | (time1 << 9) | (time0 << 26);
		T_half = now;
		//~ delay = T_half / 16;	// Delay for the next possible external interrupt
		cooldown = T_half / 2;
		//~ TCNT2 = 0;		// Reset Timer/Counter2
		//~ TCNT1 = 0;		// Reset Timer/Counter1
		TCNT0 = 0;		// Reset Timer/Counter0
		time = 0;
		oldTime = 0; 	
	}
	//~ triggered = 0;
	min = HUE_MID;
	max = HUE_MID;
	//~ status = (status == 1 ? -1 : 1);
}

//~ // On Timer Overflow Interrupt:
ISR(TIMER1_OVF_vect) {
	time += 0x00010000;
}

int main(void) {    

	EIMSK |= 0x02;	// External Interrupt Mask Register
					// Enable INT1 (PIN5) for interrupt
	EICRA |= 0x0c;	// External Interrupt Control Register
					// INT1 on falling edge
	
	TIMSK1 |= 0x01;	// Timer/Counter1 Interrupt Mask Register
					// Enable overflow interrupt
	TCCR1B |= 0x01;	// Timer/Counter1 Control Register B
					// Prescale Factor 1
	
	
	SREG |= 0x80;	// Status Register
					// Enable global interrupts
	
	DDRC = 0x3f;	// Digital Direction PORTC[5:0] = output
	PORTC = 0x00;	
	
	// Timer concatenation
	// Timer 2 (8 Bit, 50 ns)
	//~ DDRB |= 0x08;	// Digital Direction PORTB3 = output (PIN17 OC2A)
	//~ PORTB |= 0x08;	// POTRB3 = HIGH
	//~ TCNT2 = 0x00;	// Timer/Counter2 value register clear
	//~ OCR2A = 0xff;	// Timer/Counter2 output compare register A
	//~ TIMSK2 |= 0x02;	// Timer/Counter2 Interrupt Mask Register
	                //~ // Enable compare A match interrupt
	//~ TCCR2A |= 0x40;	// Timer/Counter2 Control Register A
					//~ // Toggle OC2A on compare match
	//~ TCCR2B |= 0x01;	// Timer/Counter2 Control Register B
					//~ // Prescale Factor 1
	//~ // Timer 1 (16 Bit, 25.55 µs)
	//~ DDRB |= 0x02;	// Digital Direction PORTB1 = output (PIN15 OC1A)
	//~ PORTB |= 0x02;	// POTRB1 = HIGH
	//~ TCNT1 = 0x0000;	// Timer/Counter1 value register clear
	//~ OCR1A = 0xffff;	// Timer/Counter1 output compare register A
	//~ TIMSK1 |= 0x02;	// Timer/Counter1 Interrupt Mask Register
					//~ // Enable compare register A match interrupt
	//~ TCCR1A |= 0x40;	// Timer/Counter1 Control Register A
					//~ // Toggle OC1A on compare match
	//~ TCCR1B |= 0x07;	// Timer/Counter1 Control Register B
					//~ // External Clock Source on T1 (PIN11) on rising edge
	//~ // Timer 0 (8 Bit, 3,34891515 s)
	//~ TCNT0 = 0x00;	// Timer/Counter0 value register clear
	//~ TCCR0B |= 0x07;	// Timer/Counter0 Control Register B
					//~ // External clock source on T0 (PIN6, PD4) on rising edge
	
	// Init color
	hsv_t colorsHSV[LEDS];
	rgb_t colorsRGB[LEDS];
	interpolate(
		init_hsv_t(min, HUE_SAT, HUE_VAL),	// from color
		init_hsv_t(max, HUE_SAT, HUE_VAL),	// to color
		LEDS,
		colorsHSV
	);
	hsv2rgbList(colorsHSV, colorsRGB, LEDS);
	// Assign color
	for(int i = 0; i < LEDS; i++) {
		writeRGB( colorsRGB[i].r, colorsRGB[i].g, colorsRGB[i].b);
	}
	
	// Next color
	int8_t sign = -1;
	min += sign * HUE_STEP;
	max -= sign * HUE_STEP;
	interpolate(
		init_hsv_t(min, HUE_SAT, HUE_VAL),	// from color
		init_hsv_t(max, HUE_SAT, HUE_VAL),	// to color
		LEDS,
		colorsHSV
	);
	hsv2rgbList(colorsHSV, colorsRGB, LEDS);	
	
	while(1) {
		//~ uint64_t time2 = TCNT2;
		//~ uint64_t time1 = TCNT1;
		//~ uint64_t time0 = TCNT0;
		//~ uint64_t now = time2 | (time1 << 9) | (time0 << 26);
		uint32_t now = time + TCNT1;
		//~ if(now > delay) {
			//~ PORTC |= 0x08; // PORTC3 = HIGH
		//~ }
		//~ if(now > delay * 2) {
			//~ PORTC &= 0xf7; // PORTC3 = LOW
		//~ }
		
		if(now > cooldown) {
			//~ EIMSK |= 0x02;	// Enable INT1
			PORTC |= 0x10;	// Turn on LED while INT1 enabled (PORTC3 = PIN26)
		}
		
		uint32_t T_step = T_half / colorSteps;
		
		// Check if it is time for next color
		if(now > oldTime) {	// + T_step || (now < T_step && oldTime == 0)) {
			oldTime += T_step;
			PORTC ^= 0x04;	// toggle LED when writing new color (PORTC2 = PIN25)
			// Assign color
			//~ EIMSK &= 0xfd;	//~ cli();
			nextColor(  colorsHSV, colorsRGB, init_hsv_t(min, HUE_SAT, HUE_VAL), init_hsv_t(max, HUE_SAT, HUE_VAL));
			for(int i = 0; i < LEDS; i++) {
				writeRGB( colorsRGB[i].r, colorsRGB[i].g, colorsRGB[i].b);
			}
			//~ EIMSK |= 0x02;	//~ sei();
			//~ _delay_us(51);
			
			// Next color
			min += sign * HUE_STEP;
			max -= sign * HUE_STEP;
			
			//~ interpolate(
				//~ init_hsv_t(min, HUE_SAT, HUE_VAL),	// from color
				//~ init_hsv_t(max, HUE_SAT, HUE_VAL),	// to color
				//~ LEDS,
				//~ colorsHSV
			//~ );
			//~ hsv2rgbList(colorsHSV, colorsRGB, LEDS);
			// Check if reached amplitude
			if(sign == 1 && (min >= HUE_MAX || max <= HUE_MIN)) {
				sign = -1;
			} else if(sign == -1 && (min <= HUE_MIN || max >= HUE_MAX)) {
				sign = 1;
			}
			//~ if(status == 1 && sign == 1 && (min >= HUE_MAX || max <= HUE_MIN)) {
				//~ sign = -1;
			//~ } else if(status == 1 && sign == -1 && (min <= HUE_MIN || max >= HUE_MAX)) {
				//~ sign = 1;
			//~ } else if(status == -1 && sign == 1 && (min <= HUE_MIN || max >= HUE_MAX)) {
				//~ sign = -1;
			//~ } else if(status == -1 && sign == -1 && (min >= HUE_MAX || max <= HUE_MIN)) {
				//~ sign = 1;
			//~ }
		}
	}
}

void inline nextColor(hsv_t* hsvList, rgb_t* rgbList, hsv_t start, hsv_t end) {
	interpolate(start,	// from color
				end,	// to color
				LEDS,
				hsvList
			);
	hsv2rgbList(hsvList, rgbList, LEDS);
}

/***********************************************************************
*
* 	TIMING
* 	
************************************************************************
* 	f=20MHz -> T=0,05 µs = 50 ns
* 
*	0,05 µs *  6  == 0.3  µs ~ 0.4  µs (+- 150ns) == [0.25, 0.55] µs
*	0,05 µs * 14  == 0.7  µs ~ 0.8  µs (+- 150ns) == [0.65, 0.95] µs
*	0,05 µs * 15  == 0.75 µs ~ 0.85 µs (+- 150ns) == [0.70, 1.00] µs
*	0,05 µs *  8  == 0.35 µs ~ 0.45 µs (+- 150ns) == [0.30, 0.60] µs
*	  51 µs                  > 50   µs
************************************************************************/
// AVR-GCC optimizes multiple "nop"s on every optimization level
// So we use timing from:
// https://github.com/cpldcpu/light_ws2812/blob/master/light_ws2812_AVR/Light_WS2812/light_ws2812.c
#define w_nop1  __asm__("nop      \n\t")
#define w_nop2  __asm__("rjmp .+0 \n\t")
#define w_nop4  w_nop2; w_nop2
#define w_nop8  w_nop4; w_nop4
#define w_nop16 w_nop8; w_nop8	// Not used

#define wait6 w_nop2; w_nop4
#define wait8 w_nop8
#define wait14 wait8; wait6
#define wait15 wait14; w_nop1

// WS2812B protocol
inline void writeZero() {
	PORTC |= 0x01;	// PORTC0 = 1, PORTC[5:1] = invariant
	wait6;
	PORTC &= 0xfe;	// PORTC0 = 0, PORTC[5:1] = invariant
	wait15;
}

// WS2812B protocol
inline void writeOne() {
	PORTC |= 0x01;	// PORTC0 = 1, PORTC[5:1] = invariant
	wait14;
	PORTC &= 0xfe;	// PORTC0 = 0, PORTC[5:1] = invariant
	wait8;
}

inline void writeRGB(uint8_t red, uint8_t green, uint8_t blue) {
	int i;
	
	for( i = 128; i > 0; i >>= 1 ) {
		if( green & i ){
			writeOne();			
		} else {
			writeZero();
		}
	}
	
	for( i = 128; i > 0; i >>= 1 ) {
		if( red & i ){
			writeOne();			
		} else {
			writeZero();
		}
	}
	
	for( i = 128; i > 0; i >>= 1 ) {
		if( blue & i ){
			writeOne();			
		} else {
			writeZero();
		}
	}
}
