/**************************************************************************

SPI querying module

**************************************************************************/

/*---------------------------- Include Files ----------------------------*/
#include <stdio.h>
#include <hidef.h>      /* common defines and macros */
#include "S12eVec.h"		/* interrupts */
#include <timers12.h>
#include "game.h"

#include "SPI.h"

/*--------------------------- Module Defines ----------------------------*/
#define SEND_QUEUE_SIZE		10
#define JUNK_COMMAND			0x50

#define NEW_COM_TIME			100
#define SEND_COM_TIME		1
#define SEND_JUNK_TIME		1
#define RECEIVE_RESP_TIME	1

#define QUERY_DISPENSE_PENDING	0xB0
#define DISPENSE_NUMBER				0xC0
#define REQUEST_PENDING 			0xD1
#define REQUEST_SUCCEEDED 			0xDF
#define REQUEST_FAILED				0xD0
#define GAME_PAUSED					0xE0
#define GAME_RUNNING					0xE1
#define GAME_OVER_RED_WON			0xE2
#define GAME_OVER_GREEN_WON		0xE4
#define INVALID_COMMAND				0xFE

#define PRINT_SPI					1

/*---------------------------- Module Types -----------------------------*/
typedef enum {
	SENDING_COMMAND,
	SENDING_JUNK,
	RECEIVING_RESPONSE,
	WAITING_FOR_NEW_COMMAND
} SPI_STATE_t;

/*-------------------------- Module Variables ---------------------------*/
static unsigned char sendQueue[SEND_QUEUE_SIZE];
static unsigned char currSendIndex;
static unsigned char requestedCommand;
SPI_STATE_t currSPIState;
game_info_s *game_info;

/*-------------------------- Module Functions ---------------------------*/
void HandleSPIResponse(unsigned char response);

/*----------------------------- Module Code -----------------------------*/

void InitSPI(game_info_s *gameInfo) {

  unsigned int i;

  // setup SPI to mode 3, master, MSB first, no interrupts
  SPICR1 |= _S12_MSTR; // Set E128 as master
  SPICR1 &= ~_S12_LSFBE; // Set to send/receive most significant bit first
  SPIBR |= (_S12_SPPR0 | _S12_SPPR1| _S12_SPPR2); // SPPR = 7;
  SPIBR |= _S12_SPR1; // SPR = 2, so divisor = (1+7)*2^(2+1) = 64 (24 Mhz / 64 = 375 KHz)
  SPICR1 |= _S12_CPOL | _S12_CPHA; // Polarity: Active Low, sample even edges (mode 3)
  SPICR1 |= _S12_SSOE; // Enable slave select output
  SPICR2 |= _S12_MODFEN; // Hardware automatically lowers/raises SS
  SPICR1 |= _S12_SPE; // Enable SPI

  for (i=0; i<SEND_QUEUE_SIZE; i++) {
  	sendQueue[i] = QUERY_GAME;
  }

  currSendIndex = 0;
  requestedCommand = QUERY_GAME;
  currSPIState = WAITING_FOR_NEW_COMMAND;

  TMRS12_InitTimer(SPI_TIMER, NEW_COM_TIME);

  game_info = gameInfo;

  printf("printspi: %d \r\n", PRINT_SPI);
}

// Add new command to send queue
void AddNewCommand(unsigned char command) {

	requestedCommand = command;

	if(currSendIndex < SEND_QUEUE_SIZE - 2)
		sendQueue[currSendIndex+1] = command;
	else
		sendQueue[0] = command;
}

void UpdateSPI(void) {

	unsigned char temp, response;
	SPI_STATE_t nextState;

	switch(currSPIState) {

		case WAITING_FOR_NEW_COMMAND:
			// If the new command timer has expired and it is OK to transmit
			if(TMRS12_IsTimerExpired(SPI_TIMER) == TMRS12_EXPIRED && (SPISR & _S12_SPTEF) ==  _S12_SPTEF) {
				TMRS12_ClearTimerExpired(SPI_TIMER);
				// Load in new command to send queue
				SPIDR = sendQueue[currSendIndex];
				nextState = SENDING_COMMAND;
				TMRS12_InitTimer(SPI_TIMER, SEND_COM_TIME);
			}
			else {
				nextState = WAITING_FOR_NEW_COMMAND;
			}
			break;

		case SENDING_COMMAND:
			// If the send command timer has expired and data was transferred to the SPIDR
			if(TMRS12_IsTimerExpired(SPI_TIMER) == TMRS12_EXPIRED && (SPISR & _S12_SPIF) ==  _S12_SPIF) {
				TMRS12_ClearTimerExpired(SPI_TIMER);
  				// Read the SPIDR to begin transmission
  				temp = SPIDR;
  				nextState = SENDING_JUNK;
				TMRS12_InitTimer(SPI_TIMER, SEND_JUNK_TIME);
			}
			else {
				nextState = SENDING_COMMAND;
			}
		break;

		case SENDING_JUNK:
			// If the send junk timer has expired and it is OK to transmit
			if(TMRS12_IsTimerExpired(SPI_TIMER) == TMRS12_EXPIRED && (SPISR & _S12_SPTEF) ==  _S12_SPTEF) {
				TMRS12_ClearTimerExpired(SPI_TIMER);
				SPIDR = JUNK_COMMAND;
				nextState = RECEIVING_RESPONSE;
				TMRS12_InitTimer(SPI_TIMER, RECEIVE_RESP_TIME);
			}
			else {
				nextState = SENDING_JUNK;
			}
		break;

		case RECEIVING_RESPONSE:
			// If the receving response timer has expired and data was transferred to the SPIDR
			if(TMRS12_IsTimerExpired(SPI_TIMER) == TMRS12_EXPIRED && (SPISR & _S12_SPIF) ==  _S12_SPIF) {
				TMRS12_ClearTimerExpired(SPI_TIMER);
				response = SPIDR;
				HandleSPIResponse(response);
				// Replace last send command with default
				sendQueue[currSendIndex] = QUERY_GAME;
				currSendIndex = (currSendIndex+1)%SEND_QUEUE_SIZE; // Move over to the next command we need to send
				nextState = WAITING_FOR_NEW_COMMAND;
				TMRS12_InitTimer(SPI_TIMER, NEW_COM_TIME);
			}
			else {
					nextState = RECEIVING_RESPONSE;
			}
		break;

	}

	currSPIState = nextState;
}

void HandleSPIResponse(unsigned char response) {

	unsigned char commandtype = 0;
	unsigned char secondByte = 0;
	unsigned char nextSendIndex;

	nextSendIndex = (currSendIndex+1)%(SEND_QUEUE_SIZE-1); // Don't overwrite the query game slot

	switch(sendQueue[currSendIndex]) {

		case QUERY_STATE:
			switch(response) {

				case REQUEST_PENDING:
				// Query state again
					printf("Request pending \r\n");
					sendQueue[nextSendIndex] = QUERY_STATE;
				break;

				// If requested command failed, send it again
				case REQUEST_FAILED:
					sendQueue[nextSendIndex] = requestedCommand;
					printf("Requested command failed! \r\n");
				break;

				// If requested succeeded, update flags accordingly
				case REQUEST_SUCCEEDED:
					if (requestedCommand == HOOP_2) {
						printf("Set to HOOP2 \r\n");
					}
					else if (requestedCommand == HOOP_3) {
						printf("Set to HOOP3 \r\n");
					}
					else {
						// Ball has been dispensed
						printf("Ball Dispensed \r\n");
						game_info->ballDispensed[response%4] = 1;
					}
				break;
			}
		break;

		case QUERY_GAME:
			switch(response) {

				case GAME_PAUSED:
					game_info->currGameCondition = PAUSED;
					printf("Game Paused \r\n");
				break;

				case GAME_RUNNING:
					game_info->currGameCondition = RUNNING;
					//printf("Game Running \r\n");
				break;

				case GAME_OVER_RED_WON:
					printf("Game Over, Red Won \r\n");
					game_info->currGameCondition = RED_WON;
				break;

				case GAME_OVER_GREEN_WON:
					printf("Game Over, Green Won \r\n");
					game_info->currGameCondition = GREEN_WON;
				break;
			}
		break;

		// Ball dispension/Hoop3/Hoop2/Ball Query
		default:
			switch(response) {
				// If requested command forwarded, start querying status of last request
				case REQUEST_PENDING:
					sendQueue[nextSendIndex] = QUERY_STATE;
				break;
				// If requested command not forwarded, send it again
				case REQUEST_FAILED:
					printf("Request failed!!! \r\n");
					sendQueue[nextSendIndex] = requestedCommand;
				break;

				// Query of dispenser pending, query again
				case QUERY_DISPENSE_PENDING+0:
				case QUERY_DISPENSE_PENDING+4:
				case QUERY_DISPENSE_PENDING+8:
				case QUERY_DISPENSE_PENDING+12:
					printf("Query of dispenser pending, querying again \r\n");
					sendQueue[nextSendIndex] = requestedCommand;
				// Dispenser xx has yy balls available
				break;

				// Received number of balls in dispenser
				case DISPENSE_NUMBER+0+0:
				case DISPENSE_NUMBER+0+1:
				case DISPENSE_NUMBER+0+2:
				case DISPENSE_NUMBER+0+3:;
					game_info->availableBalls[0] = response%4;
					printf("Dispenser 0 has %d balls available \r\n", game_info->availableBalls[0]);
					game_info->queryComplete = 1;
				break;

				case DISPENSE_NUMBER+4+0:
				case DISPENSE_NUMBER+4+1:
				case DISPENSE_NUMBER+4+2:
				case DISPENSE_NUMBER+4+3:
					game_info->availableBalls[1] = response%4;
					printf("Dispenser 1 has %d balls available \r\n", game_info->availableBalls[1]);
					game_info->queryComplete = 1;
				break;

				case DISPENSE_NUMBER+8+0:
				case DISPENSE_NUMBER+8+1:
				case DISPENSE_NUMBER+8+2:
				case DISPENSE_NUMBER+8+3:
					game_info->availableBalls[2] = response%4;
					printf("Dispenser 2 has %d balls available \r\n", game_info->availableBalls[2]);
					game_info->queryComplete = 1;
				break;

				case DISPENSE_NUMBER+12+0:
				case DISPENSE_NUMBER+12+1:
				case DISPENSE_NUMBER+12+2:
				case DISPENSE_NUMBER+12+3:
					game_info->availableBalls[3] = response%4;
					printf("Dispenser 3 has %d balls available \r\n", game_info->availableBalls[3]);
					game_info->queryComplete = 1;
				break;

				default:
					printf("Unexpected response %x to command %x \r\n", response, sendQueue[currSendIndex]);
				break;
			}
			break;
	}

}