#define F_CPU             3333333UL
#define ADDRESS_MYADDRESS  ADDRESS_SIMONSAYS

// 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 "protocol.h"

void ioInit() {
  // PORTA.DIRCLR is the direction register for I/O port B
  // where a "1" (default) means input, a "0" means output.
  PORTA.DIRCLR |= PIN_BUTTON0 |
                  PIN_BUTTON1 |
                  PIN_BUTTON2 |
                  PIN_BUTTON3;
  PORTA.PIN4CTRL |= PORT_PULLUPEN_bm;
  PORTA.PIN5CTRL |= PORT_PULLUPEN_bm;
  PORTA.PIN6CTRL |= PORT_PULLUPEN_bm;
  PORTA.PIN7CTRL |= PORT_PULLUPEN_bm;
/*
  PORTA.PIN4CTRL |= PORT_PULLUPEN_bm |
                    PORT_ISC_FALLING_gc;
  PORTA.PIN5CTRL |= PORT_PULLUPEN_bm |
                    PORT_ISC_FALLING_gc;
  PORTA.PIN6CTRL |= PORT_PULLUPEN_bm |
                    PORT_ISC_FALLING_gc;
  PORTA.PIN7CTRL |= PORT_PULLUPEN_bm |
                    PORT_ISC_FALLING_gc;
*/
  // PORTB.DIRSET is the direction register for I/O port B
  // where a "0" (default) means input, a "1" means output.
  PORTB.DIRSET |= PIN_LED_ONBOARD;
  // PORTC.DIRSET is the direction register for I/O port C
  // where a "0" (default) means input, a "1" means output.
  PORTC.DIRSET |= PIN_LED_BUTTON0 |
                  PIN_LED_BUTTON1 |
                  PIN_LED_BUTTON2 |
                  PIN_LED_BUTTON3;

  PORTC.OUT &= ~(PIN_LED_BUTTON0 |
                 PIN_LED_BUTTON1 |
                 PIN_LED_BUTTON2 |
                 PIN_LED_BUTTON3); // turn off all leds
}

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

  // Only continue if the PIN_BUTTONs caused the interrupt
  if ((PORTA.INTFLAGS & PIN_BUTTON0) ||
      (PORTA.INTFLAGS & PIN_BUTTON1) ||
      (PORTA.INTFLAGS & PIN_BUTTON2) ||
      (PORTA.INTFLAGS & PIN_BUTTON3)) {
    // Clear interrupt flag by writing '1'
    PORTA.INTFLAGS &= (PIN_BUTTON0 |
                       PIN_BUTTON1 |
                       PIN_BUTTON2 |
                       PIN_BUTTON3);

    gameData[gameDataIndex] = ((~PORTA.IN & 0xF0) >> 4);
//    gameDataIndex++;
  }
}

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 / 10; // but we need 1/10th seconds
  RTC.PER = 102; // but we need 1/10th seconds

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

  tenthSeconds++;

  if (tenthSeconds > 9) {
    tenthSeconds=0;
    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.CTRLA is a control register for the serial port
  //   USART_TXCIE_bm enables the transmit complete interrupt
  //   USART_RXCIE_bm enables the receive complete interrupt
  USART0.CTRLA |= USART_RXCIE_bm;
  // USART0.CTRLB is a control register for the serial port
  //   USART_RXEN_bm enables the receive function
  //   USART_MPCM_bm enables the MPCM function
  USART0.CTRLB |= USART_RXEN_bm |
                  USART_MPCM_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) and by default disabled on clients (set as input)
  //   PIN3_bm is the bitmask for PIN 3 (RX)
  //   PIN4_bm is the bitmask for PIN 4 (Direction for RS485, 1 = receive)
  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;

  ALIASserialSetRS485ModeReceive;
}

void serialEnableTX() {
  USART0.CTRLB |= USART_TXEN_bm;
  PORTB.DIR |= PIN_TX;
}

void serialDisableTX() {
  USART0.CTRLB &= ~USART_TXEN_bm;
  PORTB.DIR &= ~PIN_TX;
}

void serialSendResponse(unsigned char data) {
  serialEnableTX();
  ALIASserialSetRS485ModeSend;

  while (!(USART0.STATUS & USART_DREIF_bm));
  USART0.TXDATAL = data;
  while (!(USART0.STATUS & USART_DREIF_bm));

  USART0.CTRLB |= USART_MPCM_bm;  // go back to MPCM mode
  serialRXbufferIndex = 0;  // ready to receive a new frame

  ALIASonboadLEDoff;
  ALIASserialSetRS485ModeReceive;
  serialDisableTX();
}

ISR(USART0_RXC_vect) {
  // Interrupt vector for RX ready
  //   This interrupt fires when a full byte has been received and loaded into the buffer

  // Record the received byte from the buffer
  unsigned char data;
  data = USART0.RXDATAL;

  //  Use MPCM-bit to check if we are in MPCM mode
  if (USART0.CTRLB & USART_MPCM_bm) {
    // Expecting an address frame
    if ((data == ADDRESS_MYADDRESS) || (data == ADDRESS_BROADCAST)) {
      ALIASonboadLEDon;
      // Our address or the broadcast address has been received
      // Disable the MPCM mode to receive the data frame
      USART0.CTRLB &= ~USART_MPCM_bm;
      // Store address in the buffer
      serialRXbufferIndex = 0;
      serialRXbuffer[serialRXbufferIndex] = data;
      serialRXbufferIndex++;
    } else {
      // Not our address, remain in MPCM mode
    }
  } else {
    // Expecting a data frame
    if ((USART0.RXDATAH & (USART_FERR_bm | USART_BUFOVF_bm)) == 0) {
      // No errors detected, store the byte in the buffer
      serialRXbuffer[serialRXbufferIndex] = data;
      serialRXbufferIndex++;
      if (serialRXbufferIndex >= PROTOCOL_RESPONSE_START) {
        // Complete frame received, signal the complete frame
        serialRXbufferIndex = PROTOCOL_RESPONSE_START;
      }
    }
  }
  // Spiral disable the timeout interrupt
}

int main() {
  // Initialize variables
  unsigned char i;
  unsigned char readButton;

  // Initialize hardware
  ioInit();
  rtcInit();
  serialInit();
  // Enable global interrupts
  sei();

gameState = GAMESTATE_INIT;

  while (1) {
    switch (gameState) {
      case GAMESTATE_INIT:
        if (serialRXbufferIndex == PROTOCOL_RESPONSE_START) { // we received a frame
          switch (serialRXbuffer[PROTOCOL_COMMAND_START]) {  // process the command
            case COMMAND_STATUS:
              serialSendResponse(RESPONSE_ACK);
              break;
            case COMMAND_SET_SOLUTION:
              for (i = 0; i < (PROTOCOL_RESPONSE_START - PROTOCOL_DATA_START) - 1; i++) { // Loop through the received solution bytes
                gameData[i * 2 + 0] = (serialRXbuffer[PROTOCOL_DATA_START + i] >> 4);  // save the upper nibble
                gameData[i * 2 + 1] = (serialRXbuffer[PROTOCOL_DATA_START + i] & 0x0F);// save the lower nibble
              }
              serialSendResponse(RESPONSE_ACK);
              gameState = GAMESTATE_READY;
              break;
            default:
              serialSendResponse(RESPONSE_NACK);
              break;
          }
        }
        break;
      case GAMESTATE_READY:
        if (serialRXbufferIndex == PROTOCOL_RESPONSE_START) { // we received a frame
          switch (serialRXbuffer[PROTOCOL_COMMAND_START]) {  // process the command
             case COMMAND_START:
              gameState = GAMESTATE_RUNNING;
              serialSendResponse(RESPONSE_ACK);
              break;
            default:
              serialSendResponse(RESPONSE_NACK);
              break;
          }
        }
        break;
      case GAMESTATE_RUNNING:
// DEBUG: Disable serial commands when puzzle is running
USART0.CTRLB &= ~USART_RXEN_bm; 

        if (serialRXbufferIndex == PROTOCOL_RESPONSE_START) { // we received a frame
          switch (serialRXbuffer[PROTOCOL_COMMAND_START]) {  // process the command
            case COMMAND_STATUS:
              serialSendResponse(RESPONSE_NOT_SOLVED);
              break;
            case COMMAND_STOP:
              serialSendResponse(RESPONSE_ACK);
              gameState = GAMESTATE_READY;
              break;
            default:
              serialSendResponse(RESPONSE_NACK);
              break;
          }
        }

// Game logic starts here
// When done, set gameState to RTC_PERIOD / 10GAMESTATE_FINISHED

        gameLevel = 1;
        while ((gameLevel <= GAMEDATA_PATTERN_LENGTH) && (gameLevel > 0)) {
          // display sequence so far
          gameDataIndex = 0;
          while (gameDataIndex < gameLevel) { // all these while loops are used to prevent a blocking delay()
            tenthSeconds = 0;
            while (tenthSeconds < (GAME_DIFFICULTY * 3)) {
              PORTC.OUT |= (gameData[gameDataIndex] & 0x0F);
            }
            tenthSeconds = 0;
            while (tenthSeconds < (GAME_DIFFICULTY)) {
              PORTC.OUT &= ~(gameData[gameDataIndex] & 0x0F);
            }
            gameDataIndex++;
          }

          // get input
          gameDataIndex = 0; // start at the beginning
          while ((gameDataIndex < gameLevel) && (gameLevel > 0)) { 
            readButton = 0;
            while (readButton == 0) {
              readButton = ((~PORTA.IN & 0xF0) >> 4);
            }

            PORTC.OUT |= readButton; // turn on corresponding LED
            while (readButton == ((~PORTA.IN & 0xF0) >> 4));
            PORTC.OUT &= ~readButton; // turn off corresponding LED

            // check input
            if (readButton == gameData[gameDataIndex]) {
              // answer was correct, continue
              gameDataIndex++;
            } else {
              // answer was wrong, restart
              gameLevel = 0; // signal error
            }
          }
          if (gameLevel != 0) {
            gameLevel++;
          }
  
          _delay_ms(250);
        }
        if (gameLevel != 0) {
          gameState = GAMESTATE_FINISHED;
        }

// Game logic ends here

        break;
      case GAMESTATE_FINISHED:
// DEBUG: Enable serial commands when puzzle is running
USART0.CTRLB |= USART_RXEN_bm; 

        if (serialRXbufferIndex == PROTOCOL_RESPONSE_START) { // we received a frame
          switch (serialRXbuffer[PROTOCOL_COMMAND_START]) {  // process the command
            case COMMAND_STATUS:
              serialSendResponse(RESPONSE_SOLVED);
              break;
            case COMMAND_STOP:
              serialSendResponse(RESPONSE_ACK);
              gameState = GAMESTATE_READY;
              break;
            default:
              serialSendResponse(RESPONSE_NACK);
              break;
          }
        }
        break;
      default:
        break;  
    }
  }
}