// Include required header files
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>
#include <string.h>

// Define labels
#define PWM_CYCLE_PERIOD    208     // 1 ms = 208 w prescaler of 16
#define LED_PIN             PIN1_bm
#define SENSOR_PIN          PIN2_bm
#define BUTTON_PIN          PIN3_bm
#define TX_PIN              PIN6_bm
#define RX_PIN              PIN7_bm
#define STATE_SLEEPING      0
#define STATE_AWAKE         1
#define REQUIRED_STROKES    4
#define NEXT_LOWER          0
#define NEXT_HIGHER         1

volatile bool gbState = STATE_SLEEPING; // store global system state
volatile bool gbADCready = 0;           // store ADC measurement available state
volatile uint8_t giADCvalue = 0;        // store last ADC measurement value

void BUTTON_init() {
    // Initialize the I/O and use it on pin A3

    PORTA.DIR &= ~(PIN3_bm);
    // PIN3CTRL is the control register for pin A3,
    //    PORT_PULLUPEN_bm enables the pull up resistor,
    //    PORT_ISC_FALLING_gc only triggers the interrupt on the falling edge.
    PORTA.PIN3CTRL |= PORT_PULLUPEN_bm
                     | PORT_ISC_FALLING_gc;
}

void LED_init(){
    // Initialize the PWM of timer A and use it on pin A1

    // PORTA.DIR is the direction register for I/O port A
    // where a "0" (default) means input, a "1" means output.
    //   PIN1_bm is the bitmask for PIN 1 (hardware pin 4)
    PORTA.DIR |= PIN1_bm;

    TCA0.SINGLE.PER = PWM_CYCLE_PERIOD;
    TCA0.SINGLE.CMP1 = 0;

    // TCA0.SINGLE.CTRLA is a control register for TCA
    //    TCA_SINGLE_CLKSEL_DIV16_gc divides the clock si300gnal by 16
    //    TCA_SINGLE_ENABLE_bm enables the timer
    TCA0.SINGLE.CTRLA |= TCA_SINGLE_CLKSEL_DIV16_gc |
                         TCA_SINGLE_ENABLE_bm;

    // TCA0.SINGLE.CTRLB is a control register for TCA
    //   TCA_SINGLE_CMP0EN_bm enables the comparator
    //   TCA_SINGLE_WGMODE_SINGLESLOPE_gc selects the single slope PWM
    TCA0.SINGLE.CTRLB |= TCA_SINGLE_CMP1EN_bm |
                         TCA_SINGLE_WGMODE_SINGLESLOPE_gc;
}

void RTC_init() {
    // Initialize the Real Time Clock

    // Wait for all register to be synchronized
    while (RTC.STATUS > 0) {
        ;
    }

    // RTC.PER is the period register
    //   A period of 1024 will result in one interrupt per second
    RTC.PER = 1024; 

    // RTC.CLKSEL is the clock selection register,
    //   RTC_CLKSEL_INT32K_gc is the predefined value for the internal 32kHz oscillator.
    RTC.CLKSEL |= RTC_CLKSEL_INT32K_gc;

    // RTC.CTRLA is the control register for the RTC,
    //   RTC_PRESCALER_DIV32_gc is the Clock/32 divider (32768 / 32 = 1024 ticks per second), 
    //   RTC_RTCEN_bm will enable the RTC,
    //   RTC_RUNSTDBU_bm will run the RTC even if the uC is in sleep mode.
    RTC.CTRLA |= RTC_PRESCALER_DIV32_gc |
                 RTC_RTCEN_bm |
                 RTC_RUNSTDBY_bm;
    
    // RTC.INTCTL is the RTC interrupt control register,
    //    RTC_OVF_bm will enable the Overflow interrupt (when the counter is full and cycles to zero).
    RTC.INTCTRL |= RTC_OVF_bm;
}

void SENSOR_init() {
    // Initialize the ADC with photo sensor and use it on pin A2

    // PORTA.DIR is the direction register for I/O port A
    // where a "0" (default) means input, a "1" means output.
    //   PIN2_bm is the bitmask for PIN 2 (TX) (hardware pin 5)
    PORTA.IN &= ~(PIN2_bm);

    // PIN2CTRL is the control register for pin 2,
    //    PORT_PULLUPEN_bm disables the pull up resistor,
    PORTA.PIN2CTRL &= ~(PORT_PULLUPEN_bm);

    // ADC0.CTRLA is a control register for the ADC
    //   ADC_ENABLE_bm enables the ADC
    //   ADC_RESSEL_10BIT_gc sets the ADC resolution to 8 bit
    ADC0.CTRLA = ADC_ENABLE_bm |
                 ADC_RESSEL_8BIT_gc;

    // ADC0.CTRLC is a control register for the ADC
    //   ADC_PRESC_DIV4_gc sets the ADC prescaler to 4
    //   ADC_REFSEL_VDDREF_gc sets the reference voltage to uC power voltage
    ADC0.CTRLC |= ADC_PRESC_DIV4_gc |
                  ADC_REFSEL_VDDREF_gc;
    
    // ADC0.MUXPOS is the selection register for the ADC multiplexer
    //   ADC_MUXPOS_AIN2_gc selects PIN 2
    ADC0.MUXPOS  |= ADC_MUXPOS_AIN2_gc;

    ADC0.INTCTRL |= ADC_RESRDY_bm;
}

void SERIAL_init() {
    // Initialize the serial port and use it on ports A6 and A7

    // USART0.BAUD determines the baudrate of the serial port
    //   The value is calculated using the formula (3333333 * 64 / (16 * BAUD_RATE)) + 0.5
    USART0.BAUD = 1389;
    // USART0.CTRLB is a control register for the serial port
    //   USARD_TXEN_bm enables the transmit function
    //   USARD_RXEN_bm enables the receive function
    USART0.CTRLB |= USART_TXEN_bm |
                    USART_RXEN_bm;
    // PORTA.DIR is the direction register for I/O port A
    // where a "0" (default) means input, a "1" means output.
    //   PIN6_bm is the bitmask for PIN 6 (TX) (hardware pin 2)
    //   PIN7_bm is the bitmask for PIN 7 (RX) (hardware pin 3)
    PORTA.DIR |= PIN6_bm;
    PORTA.DIR &= ~(PIN7_bm);
}

void SERIAL_sendString(char *str) {
    // Send a string to the serial port

    for(size_t i = 0; i < strlen(str); i++) {
        while (!(USART0.STATUS & USART_DREIF_bm)) {
            ;
        }
        USART0.TXDATAL = str[i];
    }
}

void SERIAL_sendInt(int16_t *value) {
    // Send an int-converted-to-string to the serial port
    
    char str[5];
    // Convert the int to a string
    sprintf(str, "%d", value);

    // Send the string to the serial port
    SERIAL_sendString(str);
}

int main(void) {
    // Main program

    // Declare variables
    int16_t iBrightness = 0;        
    bool bStrokeState = NEXT_HIGHER;
    uint8_t iNewADC = 0;
    uint8_t iOldADC = 0;
    uint8_t iStrokeCount = 0;

    // Initialize peripherals
    BUTTON_init();
    LED_init();
    RTC_init();
    SENSOR_init();
    SERIAL_init();
    SERIAL_sendString("The hedgehog is alive!\r\n");

    // Enable global interrupts
    sei();

    while (1) {
        // Loop forever
        while (gbState == STATE_SLEEPING) {
            // The hedgehog is in a sleeping state

            SERIAL_sendString("Zzzzz....\r\n");

            iBrightness = 0;
            while (gbState == STATE_SLEEPING && iBrightness < PWM_CYCLE_PERIOD) {
                TCA0.SINGLE.CMP1 = ++iBrightness;
                _delay_ms(1);
            }
            while (gbState == STATE_SLEEPING && iBrightness > 0) {
                TCA0.SINGLE.CMP1 = --iBrightness;
                _delay_ms(2);
            }
            while (gbState == STATE_SLEEPING && iBrightness < 384) {
                ++iBrightness;
                _delay_ms(1);
            }
        }

        while (gbState == STATE_AWAKE) {
            // The hedgehog is in an awake state

            // Set the LED to max brightness
            TCA0.SINGLE.CMP1 = PWM_CYCLE_PERIOD;

            if (gbADCready == 1) {
                // if a new ADC measurement is available

                // Acknowledge the processing of the ADC measurement
                gbADCready = 0;
                iNewADC = giADCvalue;

                SERIAL_sendString("Huh?... ");
                SERIAL_sendString("NewADC: "); SERIAL_sendInt(iNewADC); 
                SERIAL_sendString(" - OldADC: "); SERIAL_sendInt(iOldADC);
                SERIAL_sendString(" - Stroke: "); SERIAL_sendInt(iStrokeCount);
                SERIAL_sendString("\r\n");

                if (iNewADC > iOldADC) {
                    // If the photo sensor is obscured

                    if (bStrokeState == NEXT_HIGHER) {
                        // Was it not obscured before?
                        // Next time, it should be not obscured
                        bStrokeState = NEXT_LOWER;                    
                    } else {
                        // If the soothing rhythm is broken, reset all strokes
                        iStrokeCount = 0;
                    }
                }
                if (iNewADC < iOldADC) {
                    // If the photo sensor is not obscured

                    if ( bStrokeState == NEXT_LOWER) {
                        // Was it obscured before?
                        // Then this is a stroke
                        iStrokeCount++;
                        // Next time, it should be not obscured
                        bStrokeState = NEXT_HIGHER;
                    } else {
                        // If the soothing rhythm is broken, reset all strokes
                        iStrokeCount = 0;
                    }
                }
                // Store the ADC value for use next time
                iOldADC = iNewADC;

                if (iStrokeCount > REQUIRED_STROKES) {
                    // There were enough strokes

                    // No given strokes can be used next time
                    iStrokeCount = 0;

                    // Go to sleep...
                    gbState = STATE_SLEEPING;
                }

            } else {
                _delay_ms(1);
            }
        }
    }
}

ISR(ADC0_RESRDY_vect) {
    // ADC measurement is ready

    // Clear interrupt flag by writing '1'
    ADC0.INTFLAGS = ADC_RESRDY_bm;

    gbADCready = 1;
    giADCvalue = ADC0.RES;
}

ISR(PORTA_PORT_vect) {
    // An interrupt on PORT A occurred

    // Only continue if the BUTTON_PIN caused the interrupt
    if (PORTA.INTFLAGS & BUTTON_PIN) {
        // Clear interrupt flag by writing '1'
        PORTA.INTFLAGS &= BUTTON_PIN;

        if (gbState == STATE_SLEEPING) {
            gbState = STATE_AWAKE;
//        } else {
//            gbState = STATE_SLEEPING;
        }
    }
}

ISR(RTC_CNT_vect) {
    // The RTC generated a timed interrupt

    // Clear flag by writing '1'
    RTC.INTFLAGS &= RTC_OVF_bm;

    // ADC0.COMMAND is the regiter for setting ADC commands
    //   ADC+STCONV_bm starts the conversion from A to D
    ADC0.COMMAND = ADC_STCONV_bm;
}