#define F_CPU             3333333UL
#define ADDRESS_MYADDRESS  ADDRESS_MASTERMIND

// 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.DIRSET is the direction register for I/O port B
  // where a "0" (default) means input, a "1" means output.
  PORTA.DIR |= (PIN_LED_BUTTON |
                PIN_LED_GREEN |
                PIN_LED_RED);
  // PORTA.DIRCLR is the direction register for I/O port B
  // where a "1" (default) means input, a "0" means output.
  PORTA.DIR &= ~(PIN_BCD0 |
                 PIN_BCD1 |
                 PIN_BCD2 |
                 PIN_BCD3);
  PORTA.PIN4CTRL |= PORT_PULLUPEN_bm;
  PORTA.PIN5CTRL |= PORT_PULLUPEN_bm;
  PORTA.PIN6CTRL |= PORT_PULLUPEN_bm;
  PORTA.PIN7CTRL |= PORT_PULLUPEN_bm;
  // 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;
  PORTB.DIR &= ~PIN_BUTTON;
  PORTB.PIN0CTRL |= PORT_PULLUPEN_bm |
                    PORT_ISC_FALLING_gc;
  
  // PORTC.DIR is the direction register for I/O port C
  // where a "1" (default) means input, a "1" means output.
  PORTC.DIR |= (PIN_CHANNEL0 |
                PIN_CHANNEL1 |
                PIN_CHANNEL2 |
                PIN_CHANNEL3);
}

ISR(PORTB_PORT_vect) {
      // An interrupt on PORT B occurred

    // Only continue if the PIN_BUTTON caused the interrupt
    if (PORTB.INTFLAGS & PIN_BUTTON) {
        // Clear interrupt flag by writing '1'
        PORTB.INTFLAGS &= PIN_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.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() {          // Select the channel (0 is selected, 1 is deselected)
          PORTC.OUT |= (1 << gameChannelSelect);  // turn off current channel ('1' is off)
          gameChannelSelect++;                    // select next channel
          if (gameChannelSelect == GAMEDATA_CHANNELS) {            // if current channel is over max
            gameChannelSelect = 0;                // start at beginning
          }
          PORTC.OUT &= ~(1 << gameChannelSelect);  // turn on new channel ('0' is on)
          _delay_ms(5);  // POV delay

  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, j;
  unsigned char gameNumberCorrect = 0;
  unsigned char gamePositionCorrect = 0;

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

  gameState = GAMESTATE_INIT;
  while (1) {
    switch (gameState) {
      case GAMESTATE_INIT:
        // wait for initialization
        if (serialRXbufferIndex == PROTOCOL_RESPONSE_START) { // we received a frame
          switch (serialRXbuffer[PROTOCOL_COMMAND_START]) {  // process the command
            case COMMAND_SET_SOLUTION:
              for (gameChannelSelect = 0; gameChannelSelect < GAMEDATA_CHANNELS - 1; gameChannelSelect++) {
                gameData[gameChannelSelect] = serialRXbuffer[gameChannelSelect];
              }
              serialSendResponse(RESPONSE_ACK);
              gameState = GAMESTATE_READY;
              break;
            case COMMAND_STATUS:
              serialSendResponse(RESPONSE_ACK);
              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_INIT;
              break;
            default:
              break;
          }
        }

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

        PORTA.OUT |= PIN_LED_BUTTON;  // turn on LED in button

        gameChannelSelect = 0;
        while ((gameChannelSelect < GAMEDATA_CHANNELS) && (gameState != GAMESTATE_FINISHED)) { // add solved condition
          // Turn off LEDs to prevent bleeding of the signal in the next channel
          PORTA.OUT &= ~(PIN_LED_GREEN | PIN_LED_RED);
          // Select the next channel (0 is selected, 1 is deselected)
          PORTC.OUT |= (1 << gameChannelSelect);  // turn off current channel ('1' is off)
          gameChannelSelect++;                    // select next channel
          if (gameChannelSelect == GAMEDATA_CHANNELS) {            // if current channel is over max
            gameChannelSelect = 0;                // start at beginning
          }
          PORTC.OUT &= ~(1 << gameChannelSelect);  // turn on new channel ('0' is on)

          // Read the BCD wheel
          gameData[gameChannelSelect * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] = ((~PORTA.IN & 0xF0) >> 4);

//DEBUG: For some reason the PORTA check does not work outside the if statement...
if ((~PORTA.IN & 0xF0) == 0x00) gameData[gameChannelSelect * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] = 0;
if ((~PORTA.IN & 0xF0) == 0x10) gameData[gameChannelSelect * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] = 1;
if ((~PORTA.IN & 0xF0) == 0x20) gameData[gameChannelSelect * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] = 2;
if ((~PORTA.IN & 0xF0) == 0x30) gameData[gameChannelSelect * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] = 3;
if ((~PORTA.IN & 0xF0) == 0x40) gameData[gameChannelSelect * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] = 4;
if ((~PORTA.IN & 0xF0) == 0x50) gameData[gameChannelSelect * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] = 5;
if ((~PORTA.IN & 0xF0) == 0x60) gameData[gameChannelSelect * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] = 6;
if ((~PORTA.IN & 0xF0) == 0x70) gameData[gameChannelSelect * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] = 7;
if ((~PORTA.IN & 0xF0) == 0x80) gameData[gameChannelSelect * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] = 8;
if ((~PORTA.IN & 0xF0) == 0x90) gameData[gameChannelSelect * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] = 9;

         if (buttonPressed) {
            buttonPressed = 0;

            // reset all game values to 0
            gameNumberCorrect = 0;
            gamePositionCorrect = 0;
            for (i = 0; i < GAMEDATA_CHANNELS; i++) {
              gameData[i * GAMEDATA_CHANNEL_SIZE + GAMEDATA_MARK_POS] = 0;
            }
            // first calculate if any entered number is in the solution
            // We mark when a correct solution has been found, so it will only be calculated once
            // Otherwise entering 3333 for a 1234 solution would mark every 3 as a correct number.
            for (i = 0; i < GAMEDATA_CHANNELS; i++) { // select a channel entry
              for (j = 0; j < GAMEDATA_CHANNELS; j++) { // loop through channel solutions
                if ((gameData[i * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] == gameData[j * GAMEDATA_CHANNEL_SIZE + GAMEDATA_SOLUTION_POS]) && 
                    (gameData[j * GAMEDATA_CHANNEL_SIZE + GAMEDATA_MARK_POS] == 0)) {
                  gameData[j * GAMEDATA_CHANNEL_SIZE + GAMEDATA_MARK_POS] = 1;
                  gameNumberCorrect++;
                  j = GAMEDATA_CHANNELS;
                }
              }
            }
            // then calculate if any entered number is in the solution and in the right place
            for (i = 0; i < GAMEDATA_CHANNELS; i++) {
              if (gameData[i * GAMEDATA_CHANNEL_SIZE + GAMEDATA_ENTRY_POS] == gameData[i * GAMEDATA_CHANNEL_SIZE + GAMEDATA_SOLUTION_POS]) {
                gamePositionCorrect++;
              }
            }
            // finally set the LEDs
            for (i = 0; i < GAMEDATA_CHANNELS; i++) { // reset all leds to off
              gameData[i * GAMEDATA_CHANNEL_SIZE + GAMEDATA_LED_POS] = 0;
            }
            if (gameNumberCorrect > 0) {
              for (i = 0; i <= gameNumberCorrect - 1; i++){ // set number of red
                gameData[i * GAMEDATA_CHANNEL_SIZE + GAMEDATA_LED_POS] = GAME_LED_RED;
              }
            }
            if (gamePositionCorrect > 0) {
              for (i = 0; i < gamePositionCorrect; i++){ // set number of green
                gameData[i * GAMEDATA_CHANNEL_SIZE + GAMEDATA_LED_POS] = GAME_LED_GREEN;
              }
            }
            if (gamePositionCorrect == 4) {
              // SOLVED!!!
              gameState = GAMESTATE_FINISHED;
            }
          }
          // set the LED
          PORTA.OUT &= ~(PIN_LED_GREEN | PIN_LED_RED);                    // turn of any LEDs
          PORTA.OUT |= gameData[gameChannelSelect * GAMEDATA_CHANNEL_SIZE + GAMEDATA_LED_POS];  // turn on LEDs

          _delay_ms(5);  // POV delay
        }
// 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) {
          switch (serialRXbuffer[PROTOCOL_COMMAND_START]) {
            case COMMAND_STATUS:
              serialSendResponse(RESPONSE_SOLVED);
              break;
            case COMMAND_START:
              gameState = GAMESTATE_RUNNING;
              serialSendResponse(RESPONSE_ACK);
              break;
            default:
              break;
          }
        }

        PORTA.OUT &= ~(PIN_LED_BUTTON | 
                       PIN_LED_RED);
        PORTA.OUT |= PIN_LED_GREEN;
        PORTC.OUT &= ~(PIN_CHANNEL0 |
                       PIN_CHANNEL1 |
                       PIN_CHANNEL2 |
                       PIN_CHANNEL3);

        break;
      default:
        break;
    }
  }
}