#define F_CPU 3333333UL

// Include required header files
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/delay.h>
#include <stdio.h>
#include <string.h>
#include "main.h"
//#include "display.h"
#include "protocol.h"

void ioInit() {
  // PORTB.DIR is the direction register for I/O port B
  // where a "0" (default) means input, a "1" means output.
  PORTB.DIR |= PIN_LED_ONBOARD;
  // PORTC.DIR is the direction register for I/O port C
  // where a "0" (default) means input, a "1" means output.
  PORTC.DIRCLR |= PIN_BUTTON;
  PORTC.PIN0CTRL = PORT_PULLUPEN_bm | 
                   PORT_ISC_FALLING_gc;  // NOTE TO SELF, can this be more configurable?
  PORTC.DIRSET |= PIN_LED_BUTTON |
                  PIN_BUZZER |
                  PIN_SPARE;
}

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

    // Only continue if the BUTTON_PIN caused the interrupt
    if (PORTC.INTFLAGS & PIN_BUTTON) {
        // Clear interrupt flag by writing '1'
        PORTC.INTFLAGS &= PIN_BUTTON;
        
        // Someone pressed the button...
        buttonPressed = 1;
    }
}

void rtcInit() {
  // 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 = RTC_PERIOD; 

  // 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;
}

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

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

  seconds++;
}

void serialInit() {
  // 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 = SERIAL_BAUDRATE;
  // 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;
  // PORTB.DIR is the direction register for I/O port B
  // where a "0" (default) means input, a "1" means output.
  //   PIN2_bm is the bitmask for PIN 2 (TX)
  //   PIN3_bm is the bitmask for PIN 3 (RX)
  //   PIN4_bm is the bitmask for PIN 4 (Direction for RS485)
  PORTB.DIR |= PIN_TX;
  PORTB.DIR &= ~PIN_RX;
  PORTB.DIR |= PIN_DIR;

  // Set 9 bit frame size (for MPCM)
  USART0.CTRLC |= USART_CHSIZE_9BITH_gc;
  ALIASserialSetRS485ModeSend;
}

void serialSendCommand(unsigned char address, unsigned char command, char *data) {
  ALIASonboadLEDon;
  ALIASserialSetRS485ModeSend;

  while (!(USART0.STATUS & USART_DREIF_bm));
  USART0.TXDATAH |= USART_MPCM_bm;  // signal address frame
  USART0.TXDATAL = address;

  while (!(USART0.STATUS & USART_DREIF_bm));
  USART0.TXDATAH &= ~USART_MPCM_bm; // signal data frame
  USART0.TXDATAL = command;

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

  ALIASserialSetRS485ModeReceive;
}

char serialReceiveResponse() {
  unsigned char data;

  ALIASserialSetRS485ModeReceive;

  while (!(USART0.STATUS & USART_RXCIF_bm));  // Wait till data is available
  data = USART0.RXDATAL;                      // Read data (clears flag)
  while (!(USART0.STATUS & USART_RXCIF_bm));  // Wait till flag is cleared
  
  ALIASonboadLEDoff;
  ALIASserialSetRS485ModeSend;
  
  return data;
}

int main() {
  unsigned char serialResponse;
  unsigned char solution[4];
  unsigned char nextPuzzle;

  gameState = GAMESTATE_INIT;
    
  // Initialize hardware
  ioInit();
  rtcInit();
  serialInit();
  // Enable global interrupts
  sei();
  // Initialze puzzles
    
  // Initialize Master Mind
    _delay_ms(1000);
    solution[0] = '1';
    solution[1] = '3';
    solution[2] = '3';
    solution[3] = '7';
    serialSendCommand(ADDRESS_MASTERMIND, COMMAND_SET_SOLUTION, solution);
    serialResponse = serialReceiveResponse();
  // Initialize Simon Says
    _delay_ms(250);
    solution[0] = '0x12';
    solution[1] = '0x44';
    solution[2] = '0x84';
    solution[3] = '0x22';
    serialSendCommand(ADDRESS_SIMONSAYS, COMMAND_SET_SOLUTION, solution);
    serialResponse = serialReceiveResponse();
    _delay_ms(250);

  gameState = GAMESTATE_READY;
    
  while (1) {
    switch (gameState) {
      case GAMESTATE_READY:
        // blink Button LED
        while (buttonPressed == 0) {
          if (PORTC.IN & PIN_LED_BUTTON) {
            PORTC.OUT &= ~PIN_LED_BUTTON;
          } else {
            PORTC.OUT |= PIN_LED_BUTTON;
          }
          _delay_ms(100);
        }
        buttonPressed = 0;
        PORTC.OUT |= PIN_BUZZER;
        _delay_ms(50);
        PORTC.OUT &= ~PIN_BUZZER;
        PORTC.OUT &= ~PIN_LED_BUTTON;
        gameState = GAMESTATE_RUNNING_1;
        
        break;
      case GAMESTATE_RUNNING_1:
        serialSendCommand(ADDRESS_MASTERMIND, COMMAND_START, "0000");
        //                                                    ^^^^ dummy data
        serialResponse = serialReceiveResponse();
        while (!buttonPressed);
        buttonPressed = 0;
        gameState = GAMESTATE_RUNNING_2;

        break;
      case GAMESTATE_RUNNING_2:
        serialSendCommand(ADDRESS_MASTERMIND, COMMAND_STOP, "0000");
        serialResponse = serialReceiveResponse();
        serialSendCommand(ADDRESS_SIMONSAYS, COMMAND_START, "0000");
        serialResponse = serialReceiveResponse();
        while (!buttonPressed);
        buttonPressed = 0;
        gameState = GAMESTATE_FINISHED;
        break;
      case GAMESTATE_FINISHED:
        serialSendCommand(ADDRESS_SIMONSAYS, COMMAND_STOP, "0000");
        serialResponse = serialReceiveResponse();
        gameState = GAMESTATE_READY;
        break;
      default:
        break;
    }
  }
}