#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/wdt.h>

#include <util/delay.h>

#define LED_PORT PORTB
#define LED_DIR DDRB
#define LED_PINS PINB
#define PIN_RED (1 << PB4)
#define PIN_GREEN (1 << PB0)
#define PIN_BLUE (1 << PB2)

// Thermistor resistance in ohm
#define R35 3700
#define R45 1400
#define R65 800
// Resistance of second resistor
#define RB 24000

#define ADC_INPUT



constexpr uint8_t threshold(uint32_t r_t, uint32_t r_b) {
  return (r_t << 8) / r_b;
}

template<typename T, uint8_t N>
struct RingBuffer {
  T buffer[N];
  uint8_t next = 0;
  uint8_t n = 0;

  T mean() {
    uint32_t sum = 0;
    for(uint8_t i = 0; i < n; i++) {
      sum += buffer[i];
    }

    return static_cast<T>(sum / n);
  }

  void push(T v) {
    buffer[next++] = v;
    next %= N;
    if(n < N) n++;
  }
};

RingBuffer<uint8_t, 6> adc_buffer;
uint16_t adc_value = 0;
uint8_t wake_up_count = 240;
uint8_t cold_wakeups = 0;

ISR(ADC_vect){
  adc_value = ADCL;
  adc_value |= (ADCH << 8);
}

ISR(WDT_vect){
  // Wake up
  wake_up_count++;
}

// Resets watchdog timer and enters ADC noise reduction mode
inline uint16_t run_adc() {
  MCUCR = (MCUCR & 0b11100111) | 0b00001000;
  // Reset watchdog timer
  wdt_reset();
  // Enable sleep mode
  MCUCR |= (1 << SE);
  // sleep while waiting for ADC conversion
  sleep_mode();
  // Disable sleep mode
  MCUCR &= ~(1 << SE);
  return adc_value;
}

int main(void){
  // Enable pull-up resistor after tristate (see datasheet)
  LED_PORT |= (PIN_RED | PIN_GREEN | PIN_BLUE);
  // configure as output
  LED_DIR |= (PIN_RED | PIN_GREEN | PIN_BLUE);

  // Select single ended input on ADC3
  ADMUX |= (1 << REFS1) | (1 << MUX1) | (1 << MUX0);
  // Enable ADC, ADC auto triggering, and ADC interrupts
  //ADCSRA |= (1 << ADEN) | (1 << ADATE) | (1 << ADIE);
  ADCSRA |= (1 << ADEN) | (1 << ADIE);
  // Configure ADC to trigger on overflow of timer0
  //ADCSRB |= (1 << ADTS2);

  /*
  // Halt timer for configuration
  GTCCR |= (1 << TSM);
  // Enable timer0 overflow interrupt
  TIMSK |= (1 << TOIE0);
  // Use no prescaling on timer0
  TCCR0B |= (1 << CS00);
  // Start timer
  GTCCR &= ~(1 << TSM);
  */

  // Disable ADC buffer on unused ADC pins
  DIDR0 |= (1 << ADC0D) | (1 << ADC2D) | (1 << ADC1D);
  // Disable analog comparator
  ACSR |= (1 << ACD);
  // Shut down timer0, timer1 and USI module
  PRR |= (1 << PRTIM0) | (1 << PRTIM1) | (1 << PRUSI) | (1 << PRTIM0);

  // Reset Watchdog
  MCUSR &= ~(1 << WDRF);
  WDTCR |= (1 << WDCE) | (1 << WDE);
  WDTCR &= ~(1 << WDE);
  // Configure watchdog timer to generate interrupt every second
  WDTCR |= (1 << WDIE) | (1 << WDP2) | (1 << WDP1);

  // Enable interrupts
  sei();

  // Select ADC noise reduction sleep mode
  MCUCR |= (1 << SM0);
  LED_PORT |= (PIN_RED | PIN_GREEN | PIN_BLUE);

  uint16_t D_VCC = 0;
  uint16_t D_T = 0;

  while(1){
    // Select voltage divider as ADC input and internal 1.1V as reference
    ADMUX = 0b10000011;
    D_T = run_adc();

    // Measure VCC sporadically
    if(wake_up_count >= 240) {
      // Select VCC as reference and internal 1.1V as input
      ADMUX = 0b00001100;
      wake_up_count = 0;
      // Allow reference voltage to settle
      _delay_ms(5);
      D_VCC = run_adc();
    }
    
    // Turn all leds off
    LED_PORT |= (PIN_RED | PIN_GREEN | PIN_BLUE);

    // Calculate (R_thermistor * 2^8) / R_balance, which fits in a single byte (for this setup)
    uint8_t r = static_cast<uint8_t>((((uint32_t)D_T * D_VCC) << 8) / ((((uint32_t)1 << 20) - D_VCC * D_T)));
    adc_buffer.push(r);

    auto temp = adc_buffer.mean();
    if(temp >= threshold(R35, RB)) {
      // It's cold. Wait for a couple of iterations, then turn off.
      if(cold_wakeups < 30) {
        LED_PORT &= ~(PIN_GREEN);
        cold_wakeups++;
      } else if(cold_wakeups < 60) {
        // Increase WDT interval to 2 seconds
        WDTCR = 0b01001111;
      } else if(cold_wakeups < 90) {
        // 4 seconds
        WDTCR = 0b01101000;
      } else if(cold_wakeups < 120) {
        // 8 seconds
        WDTCR = 0b01101001;
      }
    } else {
      // Reset cold wakeup counter and watchdog timer prescaler
      cold_wakeups = 0;
      WDTCR = 0b01001110;
      if(temp > threshold(R45, RB)) {
        LED_PORT &= ~(PIN_GREEN | PIN_RED);
      } else if(temp > threshold(R65, RB)) {
        LED_PORT &= ~(PIN_GREEN);
      } else {
        LED_PORT &= ~PIN_RED;
      }
    }

    // Select power down sleep mode
    MCUCR = (MCUCR & 0b11100111) | 0b00010000;
    MCUCR |= (1 << SE);
    sleep_mode();
    MCUCR &= ~(1 << SE);
  }
}