//#define OC_TEST //enables test harness for offensive coordinator

/****************************************************************************
 Module
 SMOffensiveCoordinator.c

 Description
 OffensiveCoordinator state machine and module.  Handles all writes and reads
 to and from the OC.  Write commands are on a timer, reads are interrupt-driven.
 Commands are stored in a queue, and can be added with a certain priority.
 ****************************************************************************/
/*----------------------------- Include Files -----------------------------*/
#include "GlobalHeader.h"
#include "SMOffensiveCoordinator.h"

/*----------------------------- Module Defines ----------------------------*/
#define PENDING_PAUSE 37500 //100ms at 375kHz
#define INPROGRESS_PAUSE 37500//100ms at 375kHz

//Queue properties
#define OC_QUEUE_LENGTH 4 // 32 bits/COMMAND_LENGTH
#define OC_COMMAND_LENGTH 8 //in bits
#define OC_COMMAND_TIMEOUT 15 //repetitions

/*---------------------------- Module Functions ---------------------------*/
static Event_t DuringReady(Event_t Event);
static Event_t DuringInProgress(Event_t Event);
static Event_t DuringPending(Event_t Event);

/*---------------------------- Module Variables ---------------------------*/
// state and timer variables
static OCState_t CurrentState;
static unsigned char OC_TimerExpiredFlag = 0;
static unsigned char OC_ReadProcessedFlag = 0; //used to mask timer expired

//SPI events flags and events are set and handled internally.
static unsigned char SPI_NewRead;
static unsigned char SPI_NewReadFlag = 0; //is there a new Response?
static unsigned char SPI_NewResponse; //Stored response

//OC Response flag and event are set internally but are for external use via query functions
static unsigned char OC_NewResponseFlag = 0; //have a new response for the main program
static unsigned char OC_NewResponse;

//OC Queue is used internally but its length can be queried
static unsigned long OC_CommandQueue = 0; //8bits per item, 32 bits = 4 item queue
static unsigned char OC_CommandQueueLast = 0; //Index of last entry in command queue

//OC Last Command stores command corresponding to most recent result.
static unsigned char OC_CurrentCommand;
static unsigned char OC_LastCommand;

static unsigned char OC_CommandCounter; //Counts repetitions for timeout


/*------------------------------ Module Code ------------------------------*/
/****************************************************************************
 Function
 QueryOCTimerFlag

 Parameters
 none

 Returns
 1 if the OC timer has expired and a response has been received from the OC.
 0 otherwise

 Description
 Returns whether the OC timer has expired, masked by whether we have recevied
 a response from the OC.
 ****************************************************************************/
unsigned char QueryOCTimerFlag (void)
{
	unsigned char FlagReturn = 0;
	if (OC_ReadProcessedFlag == 1)	//read processed used to mask timer expired
	{
		DisableInterrupts;
		FlagReturn = OC_TimerExpiredFlag;
		OC_TimerExpiredFlag = 0;
		EnableInterrupts;
	}
	return FlagReturn;
}

/****************************************************************************
 Function
 QueryOCReadFlag

 Parameters
 none

 Returns
 1 if there is a new read from the SPI, 0 otherwise

 Description
 Checks for a new read on the SPI port.  If so, it stores the value.
 ****************************************************************************/
unsigned char QueryOCReadFlag (void)
{
	unsigned char ReturnVal = 0;
	DisableInterrupts;
	if (SPI_NewReadFlag == 1)
	{
		ReturnVal = 1;
		SPI_NewReadFlag = 0;
		SPI_NewResponse = SPI_NewRead;
	}
	EnableInterrupts;
	return ReturnVal;
}

/****************************************************************************
 Function
 QueryOCQueueLength

 Parameters
 none

 Returns
 unsigned char length of queue

 Description
 Returns length of queue
 ****************************************************************************/
unsigned char QueryOCQueueLength (void)
{
	return OC_CommandQueueLast;
}

/****************************************************************************
 Function
 QueryOCCommandCounter

 Parameters
 none

 Returns
 1 if we have waited for a response to a command for more than the timeout limit.

 Description
 Checks timeout for waiting for a response for a single command.
 ****************************************************************************/
unsigned char QueryOCCommandCounter (void)
{
	return (OC_CommandCounter > OC_COMMAND_TIMEOUT);
}

/****************************************************************************
 Function
 QueryOCNewResponseFlag

 Parameters
 none

 Returns
 1 if the field has returned a response, 0 otherwise

 Description
 Checks whether the game master has responded to a command
 ****************************************************************************/
unsigned char QueryOCNewResponseFlag(void)
{
	unsigned char ReturnVal;
	ReturnVal = OC_NewResponseFlag;
	OC_NewResponseFlag = 0;
	return ReturnVal;
}

/****************************************************************************
 Function
 QueryOCResponse

 Parameters
 none

 Returns
 unsigned char - the response from the field

 Description
 Returns what the most recent response from the field was.
 ****************************************************************************/
unsigned char QueryOCResponse (void)
{
	return OC_NewResponse;
}

/****************************************************************************
 Function
 QueryOCLastCommand

 Parameters
 none

 Returns
 unsigned char - the command corresponding to the new response

 Description
 Returns the command corresponding to the most recent response.
 ****************************************************************************/
unsigned char QueryOCLastCommand (void)
{
	return OC_LastCommand;
}

/****************************************************************************
 Function
 OC_AddCommand

 Parameters
 unsigned char Command - 8bit command to add, unsigned char Priority

 Returns
 1 if command successfully added to queue, 0 otherwise

 Description
 Adds Command to the command queue.  If added with priority > 0, the command
 is added to at worst that spot.  If priority == 0, the command is added to
 the end of the queue, unless the queue is full in which case the command is
 NOT added, and the function returns 0 for failure.
 ****************************************************************************/
unsigned char OC_AddCommand(unsigned char Command, unsigned char Priority)
{
	unsigned char Success = 0;
	//trim priority to the queue length
	Priority = (Priority > OC_CommandQueueLast ? OC_CommandQueueLast : Priority);

	if (Priority == 0) //if no priority,
	{
		if (OC_CommandQueueLast < OC_QUEUE_LENGTH) //if queue not full
		{
			OC_CommandQueue += ((long)(Command) << (unsigned char)(OC_COMMAND_LENGTH*OC_CommandQueueLast)); //shift queue and add command
			OC_CommandQueueLast += 1; //update queue length
			Success = 1;
		}
	}
	else //if priority >0
	{
		unsigned long FirstPart;
		unsigned long MiddlePart;
		unsigned long LastPart;
		unsigned char InsertPoint = OC_COMMAND_LENGTH*(Priority - 1); //where to put command
		//construct parts of queue before and after command, then rejoin them
		FirstPart = ((OC_CommandQueue >> InsertPoint) << InsertPoint);
		LastPart = OC_CommandQueue - FirstPart;
		MiddlePart = ((long)(Command) << (unsigned char)(InsertPoint));
		OC_CommandQueue = (FirstPart<<OC_COMMAND_LENGTH) + MiddlePart + LastPart;
		OC_CommandQueueLast += (OC_CommandQueueLast < OC_QUEUE_LENGTH ? 1 : 0); //update queue length
		Success = 1;
	}

	return Success;

}

/****************************************************************************
 Function
 OC_QueryDispenser

 Parameters
 unsigned char Dispenser to query, unsigned char Priority of command

 Returns
 success of AddCommand call.

 Description
 Adds a query dispenser command with priority Priority
 ****************************************************************************/
unsigned char OC_QueryDispenser(unsigned char Dispenser, unsigned char Priority)
{
	unsigned char Command = (OC_QueryCommandBase | (OC_CommandDispenserMask & Dispenser));
	return (OC_AddCommand(Command, Priority));
}

/****************************************************************************
 Function
 OC_RequestBall

 Parameters
 unsigned char Dispenser to get ball from, unsigned char Priority of command

 Returns
 success of AddCommand call.

 Description
 Adds a request ball command with priority Priority
 ****************************************************************************/
unsigned char OC_RequestBall(unsigned char Dispenser, unsigned char Priority)
{
	unsigned char Command = (OC_RequestCommandBase | (OC_CommandDispenserMask & Dispenser));
	return (OC_AddCommand(Command, Priority));
}

/****************************************************************************
 Function
 OC_WriteCommand

 Parameters
 unsigned char Command - command to write

 Returns
 1 if successful write, 0 otherwise

 Description
 Writes a command to the OC.
 ****************************************************************************/
static unsigned char OC_WriteCommand (unsigned char Command)
{
	if (((SPISR & _S12_SPTEF ) == _S12_SPTEF)) //check to make sure ok to write
    {
    	OC_ReadProcessedFlag = 0;
		SPIDR = Command;		//write command
		//printf("write %#x\r\n",Command);
		return 1;
    }
    return 0;
}


/****************************************************************************
 Function
 RunOffensiveCoordinatorSM

 Parameters
 Event_t

 Returns
 Event_t

 Description
 Runs Offensive coordinator state machine.  checks for new commands and
 responses from OC and Game Master.  Resends appropriate commands in case of
 'pending' response.
 ****************************************************************************/
Event_t RunOffensiveCoordinatorSM(Event_t CurrentEvent)
{
	unsigned char MakeTransition = FALSE;/* are we making a state transition? */
	OCState_t NextState = CurrentState;

	switch ( CurrentState )
	{
		case OC_Ready :
			CurrentEvent = DuringReady(CurrentEvent);
			//process any events
			if ( CurrentEvent != EV_NO_EVENT )        //If an event is active
			{
				switch (CurrentEvent)
				{
					unsigned char NewCommand;
					case EV_OC_NewCommand : 				//If we have a new command to send
						NewCommand = (OC_CommandQueue % (1 << OC_COMMAND_LENGTH));  //pull new command from queue
						if (OC_WriteCommand(NewCommand)) 		//Tries to write new command
						{
							OC_CurrentCommand = NewCommand;
							//Reorganize Queue
							OC_CommandQueue >>= OC_COMMAND_LENGTH;
							OC_CommandQueueLast -= 1;
							OC_CommandCounter = 0;

							NextState = OC_InProgress;//Decide what the next state will be
							MakeTransition = TRUE; //mark that we are taking a transition
						}
						break;
				}
			}
			break;

		case OC_InProgress :
			CurrentEvent = DuringInProgress(CurrentEvent);
			//process any events
			if ( CurrentEvent != EV_NO_EVENT )        //If an event is active
			{
				switch (CurrentEvent)
				{
					case EV_OC_NewRead:			//if we have a new read
						OC_ReadProcessedFlag = 1; //ok to transition now
						break;
					case EV_OC_TimerExpired :  //If timer expired
					case EV_OC_CounterExpired: //(or if the counter is up)
						switch (SPI_NewResponse)
						{
							default:
								NextState = OC_Pending; //switch to pending state
								if (OC_WriteCommand(OC_DefaultCommand) == 0)  ;//write dummy byte. if 0 then error

								break;
						}
						MakeTransition = TRUE; //mark that we are taking a transition
						break;
				}
			}
			break;


		case OC_Pending :
			CurrentEvent = DuringPending(CurrentEvent);
			//process any events
			if ( CurrentEvent != EV_NO_EVENT )        //If an event is active
			{
				switch (CurrentEvent)
				{
					case EV_OC_NewRead:
						OC_ReadProcessedFlag = 1; //ok to transition now
						break;
					case EV_OC_TimerExpired : //If timer expired
						switch (SPI_NewResponse)
						{
							case OC_LastRequestPending:	//if no response from field yet
								if (OC_WriteCommand(0x74) == 0)    ; //query status of last request
																	// if 0 then error
								OC_CommandCounter += 1;				//add to command counter
								NextState = OC_InProgress;		//transition back to inprogress
								break;
							case OC_DispenserQueryPending0: //disp0  -- if we are querying a dispenser
							case OC_DispenserQueryPending1: //disp1
							case OC_DispenserQueryPending2: //disp2
							case OC_DispenserQueryPending3: //disp3
								if (OC_WriteCommand(OC_CurrentCommand) == 0)    ; //retry last request
																				// if 0 then error
								OC_CommandCounter += 1;        	//add to command counter
								NextState = OC_InProgress;		//transition back to inprogress
								break;

							default:	//other cases
								OC_NewResponseFlag = 1;  //indicate that we have a response from the field
								OC_NewResponse = SPI_NewResponse;
								OC_LastCommand = OC_CurrentCommand;
								NextState = OC_Ready;
								break;
						}
						MakeTransition = TRUE; //mark that we are taking a transition
						break;

					case EV_OC_CounterExpired : //if timer expired, counter limit exceeded
						OC_CommandCounter = 0;
						printf("timed out\r\n");
						switch (SPI_NewResponse)
						{
							default:
								OC_NewResponseFlag = 1;  //indicate that we have a response from the field
								OC_NewResponse = SPI_NewResponse;
								OC_LastCommand = OC_CurrentCommand;
								NextState = OC_Ready;
								break;
						}
						MakeTransition = TRUE; //mark that we are taking a transition
						break;
				}
			}
			break;

    }

    //   If we are making a state transition
    if (MakeTransition == TRUE)
    {
		//   Execute exit function for current state
		(void)RunOffensiveCoordinatorSM(EV_EXIT);
		CurrentState = NextState; //Modify state variable
		//   Execute entry function for new state
		(void)RunOffensiveCoordinatorSM(EV_ENTRY);
	}
	return(CurrentEvent);
}
/****************************************************************************
 Function
 StartOffensiveCoordinatorSM

 Parameters
 Event_t

 Returns
 none

 Description
 Starts OC state machine, initializes SPI and OutputCompare for timer.  Output
 compare shares timer with the game timer.  the prescales must match.
 ****************************************************************************/
void StartOffensiveCoordinatorSM ( Event_t CurrentEvent )
{
	CurrentState = OC_Ready;

	//SPI Init
    //Baud Rate
    SPIBR|=_S12_SPR1;  //Divide by 8 pre-scale
    SPIBR|=(_S12_SPR2 | _S12_SPR0 | _S12_SPPR0 | _S12_SPPR1 | _S12_SPPR2);

    SPICR1|=(_S12_CPOL | _S12_CPHA); //Type 3
    SPICR1|=_S12_MSTR; //Sets E128 as master
    SPICR1|=_S12_SPIE; //Receive interrupt enable
    SPICR1|=_S12_SPE;  //Enable SPI

    SPICR1|=_S12_SSOE;
    SPICR2|=_S12_MODFEN;

	//shares timer with Game Timer system.  Make sure timers match.
	//OC Output Compare Init
   	TIM2_TSCR1 |= _S12_TEN;
	TIM2_TSCR2 |= (_S12_PR2 | _S12_PR1); //Prescale of 64.  375 kHz

	TIM2_TCTL1 &= ~(_S12_OL5 | _S12_OM5);// no output change
	TIM2_TIOS |= (_S12_IOS5);			 //  output compare
	//start with OC disabled.  enable when sending command
	TIM2_TFLG1 = (_S12_C5F);

	// call the entry function (if any) for the ENTRY_STATE
	(void)RunOffensiveCoordinatorSM(EV_ENTRY);
}

/****************************************************************************
 Function
 QueryOffensiveCoordinatorStateSM

 Parameters
 none

 Returns
 OCState_t current machine state

 Description
 returns state of state machine
 ****************************************************************************/
OCState_t QueryOffensiveCoordinatorSM ( void )
{
	return(CurrentState);
}

/***************************************************************************
 private functions
 ***************************************************************************/

/****************************************************************************
 Function
 DuringReady

 Parameters
 Event_t

 Returns
 Event_t

 Description
 just wait.
 ****************************************************************************/
static Event_t DuringReady( Event_t Event)
{
    // process EV_ENTRY & EV_EXIT events
    if ( Event == EV_ENTRY)
    {
    }else if ( Event == EV_EXIT)
    {
    }else
		// do the 'during' function for this state
    {
    }
    return(Event);
}

/****************************************************************************
 Function
 DuringInProgress

 Parameters
 Event_t

 Returns
 Event_t

 Description
 On entry, set and enable timer.  on exit, disable timer.
 ****************************************************************************/
static Event_t DuringInProgress(Event_t Event)
{
    // process EV_ENTRY & EV_EXIT events
    if ( Event == EV_ENTRY)
    {
	  	TIM2_TC5 = TIM2_TCNT + INPROGRESS_PAUSE; //set timer
    	TIM2_TIE |= _S12_C5I;  //Interrupt enable
    }else if ( Event == EV_EXIT)
    {
    	TIM2_TIE &= ~_S12_C5I; //Disable Interrupt
    }else
    {
    }
    return(Event);
}

/****************************************************************************
 Function
 DuringPending

 Parameters
 Event_t

 Returns
 Event_t

 Description
 On entry, set and enable timer.  on exit, disable timer.
 ****************************************************************************/
static Event_t DuringPending( Event_t Event)
{
    // process EV_ENTRY & EV_EXIT events
    if ( Event == EV_ENTRY)
    {
        TIM2_TC5 = TIM2_TCNT + PENDING_PAUSE; //set timer
    	TIM2_TIE |= _S12_C5I;  //Interrupt enable
    }else if ( Event == EV_EXIT)
    {
  		TIM2_TIE &= ~_S12_C5I; //Disable Interrupt
    }else
		// do the 'during' function for this state
    {
    }
    return(Event);
}

/****************************************************************************
 Interrupt
 OC_Timer

 Description
 Sets flag when timer has expired for waiting between commands.
 ****************************************************************************/
void interrupt _Vec_tim2ch5 OC_Timer (void)
{
	OC_TimerExpiredFlag = 1;
	TIM2_TFLG1 = _S12_C5F;
}

/****************************************************************************
 Interrupt
 SPI_Interrupts

 Description
 If interrupt was due to read event, stores new read and sets flag. Interrupt
 does not trigger when write command complete.
 ****************************************************************************/
void interrupt _Vec_spi SPI_Interrupts(void)
{
    if ((SPISR & _S12_SPIF ) == _S12_SPIF)
	{
		SPI_NewRead = SPIDR;
		SPI_NewReadFlag = 1;
	}
}


/****************************************************************************
 Test Harness for OC module

 Description
 allows separate testing of OC by adding commands through keyboard input.
 ****************************************************************************/
#ifdef OC_TEST
#include "ChkEvents.h"
void main(void) {
	StartOffensiveCoordinatorSM(EV_ENTRY);
	TMRS12_Init(TMRS12_RATE_1MS);
	EnableInterrupts;
	printf("Init done\r\n");
	while(1)
	{
		static unsigned int FirstLoop = 1;
		static unsigned int LastTime = 10000;
		Event_t CurrentEvent = CheckEvents();
		if (CurrentEvent != EV_NO_EVENT)
			//	printf("Event: %d\r\n", CurrentEvent);
			(void)RunOffensiveCoordinatorSM(CurrentEvent);

		if ( kbhit() != 0)       // there was a key pressed
		{
			unsigned char KeyStroke = getchar();
			switch ((KeyStroke))
			{
				case 'q' : OC_AddCommand(0x78, 0); break;  //game status
				case 'w' : OC_AddCommand(0b01110001, 0); break;  //how many balls disp 1
				case 'e' : OC_AddCommand(0b01000001, 0); break;  //dispense ball disp 1
				case 'r' : OC_AddCommand(0x4F, 0); break;  //3pt size
				case 't' : OC_AddCommand(0x74, 0); break;  //status of last request
				case 'a' : OC_QueryDispenser(0, 0); break;
				case 's' : OC_QueryDispenser(1, 2); break;
				case 'd' : OC_QueryDispenser(2, 0); break;
				case 'f' : OC_QueryDispenser(3, 2); break;
				case 'z' : OC_RequestBall(0, 0); break;
				case 'x' : OC_RequestBall(1, 0); break;
				case 'c' : OC_RequestBall(2, 0); break;
				case 'v' : OC_RequestBall(3, 0); break;
			}
		}

		if (CurrentEvent == EV_OC_NewResponse)
		{
			unsigned char LastCommand;
			unsigned char NewResponse;
			LastCommand = QueryOCLastCommand();
			NewResponse = QueryOCResponse();
			printf("Command 0x%x   Response 0x%x \r\n",LastCommand, NewResponse);
		}
	}
}
#endif