/* #includes */
#include <stdio.h>
#include <ME218_C32.h>
#include <timers12.h>		/* used for TMRS12_X functions */
#include "scoreboard.h"

/* #defines to make the code more readable */
#define CLOCKBITHI      BIT3HI
#define CLOCKBITLO      BIT3LO
#define COMPSCOREBITHI  BIT4HI
#define COMPSCOREBITLO  BIT4LO
#define TIMER1BITHI     BIT5HI
#define TIMER1BITLO     BIT5LO
#define TIMER10BITHI    BIT6HI
#define TIMER10BITLO    BIT6LO
#define USERSCOREBITHI  BIT7HI
#define USERSCOREBITLO  BIT7LO

/* #define string indices such that '1572' indicates time of 57 and scores: user: 1, comp: 2 */
#define COMP            3	/* Computer Score */
#define TIMER1          2	/* 1s digit of timer */
#define TIMER10         1	/* 10s digit of timer */
#define USER            0	/* User score */

/* #defines for what test function to run when running the scoreboard independently */
#define NONE      0
#define CYCLE     1
#define TESTSIG   2
#define SCORETEST 3


/* Local Function Prototypes */
static void initialize(void);				
static void pulse_clock(void);
static void wait_milliseconds(int milliseconds);
static void load_shift_register(char *stringAtoH[]);
static void display(char *input);

static void cycle_test(void);
static void SetUserScore(char input);			
static void SetComputerScore(char input);

/* Local Variables */
static char scoreboard[] = "    \0";	/* initialize the module variable 'scoreboard' to track its current contents */

/*********************************/
/******** PUBLIC FUNCTIONS *******/
/*********************************/

/***** Main() *******/
/* This function is for use only in testing purposes, and can be omitted by #define in the header file */
#ifdef TESTMODE
void main( void) {
  int foolwhile = 5;
  int n;
  int mode = SCORETEST;
  int expired = FALSE;
  initialize();

  if(mode == TESTSIG) {
    while(foolwhile) {
        display("2222");
        for (n=0; n<50; n++);    
    }
  }
  
  if(mode == SCORETEST) {
    while(foolwhile) {
      /* test the initialization and decrementing functions */
      printf("Initializing Scoreboard...\r\n");  
      InitializeScoreboard();
			wait_milliseconds(2000);

      printf("Now decrement the timer until it expires\r\n");
      printf("Initial Timer value is %c%c\r\n",scoreboard[TIMER10],scoreboard[TIMER1]);
      /* make sure the timer expired flag works */
      while (expired == FALSE) {
        wait_milliseconds(300); 
        expired = DecrementScoreboardTime();
        printf("Timer value is %c%c\r\n",scoreboard[TIMER10],scoreboard[TIMER1]);  
      }
      printf("Timer Expired!!\r\n");
     
      /* Test the score incrementing function */
      expired = FALSE;
      printf("Time to increment the player's score\r\n");
      while (expired == FALSE) {
 
        expired = IncrementPlayerScore();
        printf("Player Score is %c\r\n",scoreboard[USER]);  
        wait_milliseconds(1000);
      } 
    }
  }
  
  if(mode == CYCLE) cycle_test();
  
  return;
}
#endif

/***** InitializeScoreboard() *******/
/* Sets the output pins for the C32, sets player and computer scores to 0 */
/* and sets the timer to the value given in the header file. 		  */
void InitializeScoreboard(void) {
  initialize();
  SetUserScore('0');
  SetComputerScore('0');
  SetScoreboardTime("INITIAL_TIME");
  updatedisplay();
  return;  
}

/***** SetScoreboardTime() *******/
/* Set the scoreboard timer to the time given as an input string of two */
/* digits.  E.G. "60" or "99" or "00"					*/
void SetScoreboardTime(char time[]) {
  scoreboard[TIMER1]  = time[1];	/* Set 1s digit of timer */
  scoreboard[TIMER10] = time[0];	/* Set 10s digit of timer */
  updatedisplay();			/* Write to the display */
  return; 
}

/***** IncrementPlayerScore() *******/
/* Adds one to the player score and returns a 1 if it is maxed out.	*/
/* If the score is still in an ok range (i.e. is below 9) it returns 0  */
int IncrementPlayerScore(void) {
  int maxedout = FALSE;
  /* Use a switch case to handle 'incrementing' a character value */
  switch (scoreboard[USER]) {
    case '0' : SetUserScore('1'); break;
    case '1' : SetUserScore('2'); break;
    case '2' : SetUserScore('3'); break;
    case '3' : SetUserScore('4'); break;
    case '4' : SetUserScore('5'); break;
    case '5' : SetUserScore('6'); break;
    case '6' : SetUserScore('7'); break;
    case '7' : SetUserScore('8'); break;
    case '8' : SetUserScore('9'); maxedout=TRUE; printf("Player Score maxed out\r\n"); break;
    default  : printf("Invalid Player Score value\r\n"); break;
  }
  updatedisplay();
  return maxedout;
}

/***** IncrementComputerScore() *******/
/* Adds one to the computer score and returns a 1 if it is maxed out.	*/
/* If the score is still in an ok range (i.e. is below 9) it returns 0  */
int IncrementComputerScore(void) {
  int maxedout = FALSE;
  /* Use a switch case to handle 'incrementing' a character value */
  switch (scoreboard[COMP]) {
    case '0' : SetComputerScore('1'); break;
    case '1' : SetComputerScore('2'); break;
    case '2' : SetComputerScore('3'); break;
    case '3' : SetComputerScore('4'); break;
    case '4' : SetComputerScore('5'); break;
    case '5' : SetComputerScore('6'); break;
    case '6' : SetComputerScore('7'); break;
    case '7' : SetComputerScore('8'); break;
    case '8' : SetComputerScore('9'); maxedout=TRUE; printf("Comp Score maxed out\r\n"); break;
    default  : printf("Invalid Comp Score value\r\n"); break;
  }
  updatedisplay();
  return maxedout;
}

/***** DecrementScoreboardTime() *******/
/* Subtracts one from the current timer and returns TRUE if the timer is expired */

int DecrementScoreboardTime(void) {
  int timerexpired = FALSE;
  /* Use switch cases to decrement the timer value stored as a character */
  switch (scoreboard[TIMER1]) {
    case '0' : 
      scoreboard[TIMER1] = '9';
      switch (scoreboard[TIMER10]) {
	/* handle rollover into the 10s digit */
        case '0' : scoreboard[TIMER1]  = '0'; SetComputerScore('1'); timerexpired=TRUE; ; break;
        case '1' : scoreboard[TIMER10] = '0'; break;
        case '2' : scoreboard[TIMER10] = '1'; break;
        case '3' : scoreboard[TIMER10] = '2'; break;
        case '4' : scoreboard[TIMER10] = '3'; break;
        case '5' : scoreboard[TIMER10] = '4'; break;
        case '6' : scoreboard[TIMER10] = '5'; break;
        case '7' : scoreboard[TIMER10] = '6'; break;
        case '8' : scoreboard[TIMER10] = '7'; break;
        case '9' : scoreboard[TIMER10] = '8'; break;
        default  : printf("Invalid Value Exists in Timer 10 spot\r\n"); break;
      }
      break;   
    case '1' : scoreboard[TIMER1] = '0'; break;
    case '2' : scoreboard[TIMER1] = '1'; break;
    case '3' : scoreboard[TIMER1] = '2'; break;
    case '4' : scoreboard[TIMER1] = '3'; break;
    case '5' : scoreboard[TIMER1] = '4'; break;
    case '6' : scoreboard[TIMER1] = '5'; break;
    case '7' : scoreboard[TIMER1] = '6'; break;
    case '8' : scoreboard[TIMER1] = '7'; break;
    case '9' : scoreboard[TIMER1] = '8'; break;
    default  : printf("Invalid Value Exists in Timer 1 spot\r\n"); break;
  }
  updatedisplay();
  return timerexpired;  
}

/***** UpdateDisplay() *******/
/* Force the display to update to the contents in memory */
void updatedisplay(void) {
  display(scoreboard);
  return;
}

/***** UpdateDisplayFractionally() *******/
/* To prevent updating the display from slowing the E&S framework	*/
/* call this function to only occasionally update it (10% of the time)  */
/* when including this function in a display loop.  This is recommended */
/* as otherwise the display may drift due to shift register problems    */
void updatedisplayfractionally(void) {
  static int n = 0;
  n++;
  if (n % 10 == 0) {
    display(scoreboard);
  }
  return;
}

/**********************************/
/******** PRIVATE FUNCTIONS *******/
/**********************************/

/***** Initialize() *******/
/* Initializes the outputs on the C32 */
static void initialize(void) 
{
  // Set Ports T0-1 to outputs
  DDRT = DDRT | (CLOCKBITHI | COMPSCOREBITHI | TIMER1BITHI | TIMER10BITHI | USERSCOREBITHI);
  return;
}

/***** pulse_clock() *******/
/* briefly pulse the clock high */
static void pulse_clock(void)
{
  int n = 0;
  for (n=0; n<50; n++);
  PTT = PTT | CLOCKBITHI;	  /* Rising edge on the clock */
  for (n=0; n<50; n++);      	  /* Waste some time to let the pulse go through */
  PTT = PTT & CLOCKBITLO;	  /* Falling edge on the clock */ 
  for (n=0; n<10; n++);
  return;  
}

/***** wait_milliseconds() *******/
/* Convenient, blocking function to wait during initialization or testing sequences */
static void wait_milliseconds(int milliseconds)
{
  int time_started;
  TMRS12_Init(TMRS12_RATE_1MS);  /* ensure timer module is started and the tick is right */
  time_started = TMRS12_GetTime();
  while (TMRS12_GetTime()-time_started<=milliseconds) 
  {	/* wait for the count to reach the critical value */
  } 
  return;
}

/***** display() *******/
/* Uses a switch case to parse an string of 4 input characters into patterns */
/* for display on a 7-segment LED display.  It then calls the function       */
/* load_shift_register to write the sequences to the scoreboard. 	     */
static void display(char *input)
{
  char *output[4];
  int i;
  
  /* Repeat the matching for each input character */
  for (i=0; i<4; i++) {
    
    switch (input[i]) { 
    /* Map characters to appropriate patterns of segments.  1s are on, 0s are off   */
    /* Sequences correspond to the standard A,B,C,D,E,F,G,DP labelling of 7-segment */
    /* displays. 								    */
      case '0' : output[i] = "11111100"; break;
      case '1' : output[i] = "01100000"; break;
      case '2' : output[i] = "11011010"; break;
      case '3' : output[i] = "11110010"; break;
      case '4' : output[i] = "01100110"; break;
      case '5' : output[i] = "10110110"; break;
      case '6' : output[i] = "10111110"; break;
      case '7' : output[i] = "11100000"; break;
      case '8' : output[i] = "11111110"; break;
      case '9' : output[i] = "11110110"; break;
      case 'A' : output[i] = "11101110"; break;  
      case 'B' : output[i] = "00111110"; break;
      case 'C' : output[i] = "10011100"; break;
      case 'D' : output[i] = "01111010"; break;
      case 'E' : output[i] = "10011110"; break;
      case 'F' : output[i] = "10001110"; break;
      case 'H' : output[i] = "01101110"; break;
      case 'I' : output[i] = "01100000"; break;
      case 'O' : output[i] = "11111100"; break;
      case 'S' : output[i] = "10110110"; break;
      case 'L' : output[i] = "00011100"; break; 
      case 'P' : output[i] = "11001110"; break;
      case 'Y' : output[i] = "01110110"; break;
      case ' ' : output[i] = "00000000"; break;      
      /* Debugging cases */
      case 'a' : output[i] = "10000000"; printf("a\r\n"); break;      
      case 'b' : output[i] = "01000000"; printf("b\r\n"); break;      
      case 'c' : output[i] = "00100000"; printf("c\r\n"); break;      
      case 'd' : output[i] = "00010000"; printf("d\r\n"); break;      
      case 'e' : output[i] = "00001000"; printf("e\r\n"); break;      
      case 'f' : output[i] = "00000100"; printf("f\r\n"); break;      
      case 'g' : output[i] = "00000010"; printf("g\r\n"); break;      
      case 'p' : output[i] = "00000001"; printf("dp\r\n"); break;
      /* Default Case */     
      default : output[i] = "00000000"; break;
    }
  }
  load_shift_register(output);	/* load the sequences to the shift register */
  return; 									
}


/***** load_shift_register() *******/
/* Loads the output of the display function to the shift registers */
static void load_shift_register(char *displayinfo[])
{
  int i;
  
  /* clear out what's in the register by writing a bunch of HIs to turn it all off*/
  for(i=1; i<=8; i++) {
    PTT = PTT & (COMPSCOREBITLO & TIMER1BITLO & TIMER10BITLO & USERSCOREBITLO);  
    pulse_clock();
  }  
  /* Load the new display, changing a single segment across all */
  /* four displays, then pulsing the shared clock to set it     */
  /* and move on to the next segment */ 
  for(i=7; i>=0; i--) {
 
    if (displayinfo[USER][i] == '1') PTT = PTT | (USERSCOREBITHI);
    if (displayinfo[USER][i] == '0') PTT = PTT & (USERSCOREBITLO);
    
    if (displayinfo[COMP][i] == '1') PTT = PTT | (COMPSCOREBITHI);
    if (displayinfo[COMP][i] == '0') PTT = PTT & (COMPSCOREBITLO);
    
    if (displayinfo[TIMER1][i] == '1') PTT = PTT | (TIMER1BITHI);
    if (displayinfo[TIMER1][i] == '0') PTT = PTT & (TIMER1BITLO);
    
    if (displayinfo[TIMER10][i] == '1') PTT = PTT | (TIMER10BITHI);
    if (displayinfo[TIMER10][i] == '0') PTT = PTT & (TIMER10BITLO);

    pulse_clock();
  }
  return; 
}

/***** Cycle_test() *******/
/* Displays a looping message on the scoreboard */

static void cycle_test(void) {
  /* Display a never ending loop of "HI 218 PEOPLE" */
  int foolwhile = 5;
  while(foolwhile) {
      display("   H"); wait_milliseconds(600);
      display("  HI"); wait_milliseconds(600);
      display(" HI "); wait_milliseconds(600);
      display("HI 2"); wait_milliseconds(600);
      display("I 21"); wait_milliseconds(600);
      display(" 218"); wait_milliseconds(600);	
      display("218 "); wait_milliseconds(600); 
      display("18 P"); wait_milliseconds(600);  
      display("8 PE"); wait_milliseconds(600);
      display(" PEO"); wait_milliseconds(600);
      display("PEOP"); wait_milliseconds(600);
      display("EOPL"); wait_milliseconds(600);
      display("OPLE"); wait_milliseconds(600);
      display("PLE "); wait_milliseconds(600);
      display("LE  "); wait_milliseconds(600);
      display("E   "); wait_milliseconds(600);
      display("    "); wait_milliseconds(600);        
    }
}


/***** SetUserScore() *******/
/* Allows the User Score to be set to any value.  Used for initalization */
static void SetUserScore(char input) {
  scoreboard[USER] = input;
  return;
}

/***** SetComputerScore() *******/
/* Allows the Computer Score to be set to any value.  Used for initalization */
static void SetComputerScore(char input) {
  scoreboard[COMP]  = input;
  return;
}