#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.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 */

const uint16_t LED_GREEN_TEMP = 300;
const uint16_t LED_RED_TEMP = 1200;

#define TARGET_TEMP_MIN 300
#define TARGET_TEMP_MAX 1520

uint8_t setting_timeout = 0;

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

uint16_t temperature = 0;

uint8_t heat_output = 0;

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

uint8_t input_state = 0;
uint8_t dev_state = 0;

#define TEN_SECONDS 256
uint16_t startup = TEN_SECONDS;

uint8_t eeprom_write = 0;
#define ONE_MINUTE 1536
uint16_t eeprom_clk = ONE_MINUTE;

/* 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};
//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--;}
	if(startup) {startup--;}
	if(eeprom_clk) {eeprom_clk--;}
	else
	{
		eeprom_write = 1;
		eeprom_clk = ONE_MINUTE;
	}
	
	/* 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 = (PINA & ((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 - 1)) && new_state & (1 << (TAST_PIN - 1)))
	{
		dev_state = !dev_state;
	}
	
	int8_t change = state_change[((input_state & 3) << 2) | (new_state & 3)];
	if(change)
	{
		target += change * 10; // 1 celsius per step
		if(target < TARGET_TEMP_MIN) {target = TARGET_TEMP_MIN;}
		else if(target > TARGET_TEMP_MAX) {target = TARGET_TEMP_MAX;}
		setting_timeout = TIMEOUT;
	}
	
	input_state = new_state;
}

inline uint8_t control_output(void)
{
	if(temperature > target)
	{
		return 0;
	}
	else if(temperature + 100 > target)
	{
		return (target - temperature) * 255 / 100;
	}
	else
	{
		return 255;
		//return (uint8_t)((uint16_t)((uint32_t)((uint32_t)255*target)/temperature)/TARGET_TEMP_MAX);
	}
}

// Measure: 5V --- 10k --- --- PTY81-121 -- GND
//                        |
//                    uC ADC pin (with 1.11V reference)
inline uint16_t linearize_temp(uint16_t temp_in)
{
	uint16_t temp_out = TARGET_TEMP_MAX; //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[] PROGMEM =
	{
		// in, out
		20250, 0, // -20 celsius
		22134, 0, // -10 celsius
		24138, 0, // 0 celsius
		26232, 100, // 10 celsius
		28445, 200,
		30778, 300,
		33231, 400, // 40 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, TARGET_TEMP_MAX // 152 celsius, made-up, not from datasheet
	};
	uint8_t i=2;
	for(; i < sizeof(coeffs) / sizeof(coeffs[0]); i += 2)
	{
		if(temp_in < pgm_read_word(&coeffs[i]))
		{
			temp_out = (uint16_t) (
                    pgm_read_word(&coeffs[i-1]) +
                    (uint32_t) (
                        (uint32_t) (temp_in - pgm_read_word(&coeffs[i-2])) *
                        (pgm_read_word(&coeffs[i+1])-pgm_read_word(&coeffs[i-1]))
                    ) /
                    (pgm_read_word(&coeffs[i])-pgm_read_word(&coeffs[i-2])));
			break;
		}
	}
	return temp_out;
}

inline 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 = PINA & ((1 << IMPULS1_PIN) | (1 << IMPULS0_PIN) | (1 << TAST_PIN));
}

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

inline void led_set(uint16_t value)
{
    uint8_t pwmval;
	if (LED_GREEN_TEMP <= value && value < LED_RED_TEMP) {
		pwmval = ((uint32_t) (value-LED_GREEN_TEMP) * 255)/(LED_RED_TEMP-LED_GREEN_TEMP);
	} else if (value <= LED_GREEN_TEMP) {
		pwmval = 0;
	} else {
		pwmval = 255;
	}
	OCR0A = pwmval;
	OCR0B = pwmval;
}

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

inline void adc_init(void)
{
	ADMUX = (1 << REFS1) | (TEMP_PIN << MUX0);/* Vref = 1.1, pin selection */
	ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | (1 << ADPS2);/* adc enable, free running mode, prescaler 16 */
	DIDR0 = (1 << TEMP_PIN);/* disable digital input on adc pin */
	/* result in (ADCH << 8) | ADCL, accessable as ADC */
}

inline void heat_init(void)
{
	TCCR1A = (1 << WGM11) | (1 << WGM10);
	TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);/* some mode allowing to set timer TOP, no prescaler */
	TIMSK1 = (1 << TOIE1);/* interupt on overflow */
	OCR1A = 39063;
	
	/* read eeprom */
	while(EECR & (1 << EEPE)) {;}
	EEAR = 0;
	EECR = (1 << EERE);
	target = EEDR << 8;
	EEARL = 1;
	EECR |= (1 << EERE);
	target |= EEDR;
}

inline 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)
{
	io_init();
	adc_init();
	heat_init();
	input_init();
	led_init();
	sei();
	
	for(;;)
	{
		if(ADCSRA & (1 << ADIF))
		{
			/* new adc result */
			ADCSRA |= (1 << ADIF);
			adc_sum += ADC;
			if(++adc_pos == 64)
			{
				//adc_sum = 47000;
				temperature = linearize_temp(adc_sum);
				//temperature = 1100;
				adc_sum = 0;
				adc_pos = 0;
				if(1)//dev_state)
				{
					heat_output = control_output();
					if(startup && heat_output > 96) {heat_output = 96;}
					heat_on[0] = 0;
					heat_on[1] = 128;
					heat_off[0] = heat_output;
					heat_off[1] = heat_output + 128;
				}
				/*else
				{
					heat_on[0] = heat_on[1] = heat_off[0] = heat_off[1] = 0;
				}*/
			}
		}
		
		/*if(!dev_state) {led_off();}
		else */if(setting_timeout) {led_set(target);}
		else {led_set(temperature);}
		
		if(eeprom_write && dev_state)
		{
			while(EECR & (1 << EEPE)) {;}
			EEARL = 0;
			EEDR = (target >> 8) & 0xff;
			EECR |= (1 << EEMPE);
			EECR |= (1 << EEPE);
			
			while(EECR & (1 << EEPE)) {;}
			EEARL = 1;
			EEDR = target & 0xff;
			EECR |= (1 << EEMPE);
			EECR |= (1 << EEPE);
			
			eeprom_write = 0;
		}
	}
	
	return 0;
}
