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

#define TEMP_PIN 0
#define IMPULS0_PIN 1
#define IMPULS1_PIN 2
#define TAST_PIN 3

#define FET0_PIN 0
#define FET1_PIN 1

#define TIMEOUT 51 /* 2 seconds */

uint8_t led_color = 0;

uint8_t setting_timeout = 0;

uint8_t adc_pos = 0;
uint16_t adc_sum = 0;
uint16_t temperature_adc = 0;
uint16_t target = 0;

uint16_t temperature = 0;

uint8_t heat_pwm = 0;
uint8_t heat_on[2] = {0, 0};
uint8_t heat_off[2] = {0, 0};

uint8_t input_state = 0;
uint8_t dev_state = 0;

/* gray code to change lookup table, index is old state .. new state */
int8_t state_change[16] = {0, 1, -1, 0,  -1, 0, 0, 1,  1, 0, 0, -1,  0, -1, 1, 0};

ISR(TIM1_OVF_vect)
{
	/* timeout for displaying target temperature */
	if(setting_timeout) {setting_timeout--;}
	
	/* incremtening softpwm, toggling output if needed */
	heat_pwm++;
	if(heat_pwm == heat_on[0]) {PORTB |= (1 << FET0_PIN);}
	if(heat_pwm == heat_off[0]) {PORTB &= ~(1 << FET0_PIN);}
	if(heat_pwm == heat_on[1]) {PORTB |= (1 << FET1_PIN);}
	if(heat_pwm == heat_off[1]) {PORTB &= ~(1 << FET1_PIN);}
}

ISR(PCINT0_vect)
{
	uint8_t new_state = (PORTA & ((1 << IMPULS1_PIN) | (1 << IMPULS0_PIN) | (1 << TAST_PIN))) >> 1;
	uint8_t diff = new_state ^ input_state;
	if(!diff) {return;}
	
	if(diff & (1 << TAST_PIN) && new_state & (1 << TAST_PIN))
	{
		dev_state = !dev_state;
	}
	
	int8_t change = state_change[((input_state & 3) << 2) | (new_state & 3)];
	if(change)
	{
		target += change;
		setting_timeout = TIMEOUT;
	}
	
	input_state = new_state;
}

// Measure: 5V --- 10k --- --- PTY81-121 -- GND
//                        |
//                    uC ADC pin (with 1.11V reference)
uint16_t linearize_temp(uint16_t temp_in)
{
	static const uint16_t one_point_one_volt = (1 << 16) - (1 << 6);/* max value from 64 adc mesaurements */
	uint16_t temp_out = ~0; //burning
	// made from datasheet by:
	// for i in 677 740 807 877 951 1029 1111 1196 1286 1378 1475 1575 1679 1786 1896 2003 2103 2189; do calc 5*$i/9860*65472/1.11; done
	static const uint16_t coeffs[] =
	{
		// in, out
		20250, 0, // -20 celsius
		22134, 0, // -10 celsius
		24138, 0, // 0 celsius
		26232, 100, // 10 celsius
		28445, 200,
		30778, 300,
		33231, 400, // 50 celsius
		35773, 500, // ..
		38465, 600,
		41216, 700,
		44118, 800,
		47109, 900,
		50220, 1000,
		53420, 1100,
		56711, 1200,
		59911, 1300,
		62902, 1400,
		65474, 1500, // 150 celsius
		65535, 1520, // made-up, not from datasheet
	};
	uint8_t i=2;
	for (; i < (sizeof(coeffs)>>1)-2; i+=2)
	{
		if ( temp_in < coeffs[i] )
		{
			temp_out = (uint16_t) ( coeffs[i-1] + (uint32_t) ( (uint32_t) (temp_in-coeffs[i-2]) * (coeffs[i+1]-coeffs[i-1]) ) / (coeffs[i]-coeffs[i-2]));
			break;
		}
	}
	return temp_out;
}

void io_init(void)
{
	PORTA = (1 << TAST_PIN) | (1 << IMPULS1_PIN) | (1 << IMPULS0_PIN);/* enable pullups on inputs */
	PORTB = 0;
	DDRA = (1 << PA7);/* led outputs ... */
	DDRB = (1 << PB2) | (1 << FET0_PIN) | (1 << FET1_PIN);/* ... and FET output */
	input_state = PORTA & ((1 << IMPULS1_PIN) | (1 << IMPULS0_PIN) | (1 << TAST_PIN));
}

void led_init(void)
{
	TCCR0A = (1 << COM0A1) | (1 << COM0B1) | (1 << COM0B0) | (1 << WGM01) | (1 << WGM00);/* pwm enable: fast, A not inverted, B inverted */
	TCCR0B = (1 << CS02) | (1 << CS00);/* prescaler / 1024 */
	OCR0A = OCR0B = led_color;
}

void led_set(uint16_t value)
{
	OCR0A = OCR0B = led_color = value >> 8;
}

void led_off(void)
{
	OCR0A = 0;
	OCR0B = 255;
}

void adc_init(void)
{
	ADMUX = (1 << REFS1) | (TEMP_PIN << MUX0);/* Vref = 1.1, pin selection */
	ADCSRA = (1 << ADEN) | (1 << ADATE);/* adc enable, triggered */
	ADCSRB = (1 << ADTS2);/* trigger on counter 0 overflow */
	DIDR0 = (1 << TEMP_PIN);/* disable digital input on adc pin */
	/* result in (ADCH << 8) | ADCL, accessable as ADC? */
}

void heat_init(void)
{
	TCCR1B = (1 << WGM12) | (1 << CS10);/* some mode allowing to set timer TOP, no prescaler */
	TIMSK1 = (1 << TOIE1);/* interupt on overflow */
	OCR1A = 39063;
	/* insert loading of heat setting here */
}

void input_init(void)
{
	GIMSK = (1 << PCIE0);/* interrupt on change on PORT A */
	PCMSK0 = (1 << TAST_PIN) | (1 << IMPULS1_PIN) | (1 << IMPULS0_PIN);
}

int main(void)
{
	/* if we store the last setting somewhere calculate led_color from it before calling led_init() */
	
	io_init();
	led_init();
	adc_init();
	heat_init();
	input_init();
	
	for(;;)
	{
		if(ADCSRA & (1 << ADIF))
		{
			/* new adc result */
			ADCSRA |= (1 << ADIF);
			adc_sum += ADC;
			if(++adc_pos == 64)
			{
				temperature_adc = adc_sum;
				adc_sum = 0;
				adc_pos = 0;
				temperature = linearize_temp(temperature_adc);
			}
		}
		
		if(!dev_state) {led_off();}
		else if(setting_timeout) {led_set(target);}
		else {led_set(temperature);}
	}
	
	return 0;
}
