#include <LiquidCrystal.h>
#include <EEPROM.h>

#define SOFTWARE_VERSION "Version 2.1"

// Buttons and button voltage values
#define BTN_PIN     A0
#define BTN_RIGHT   0
#define BTN_UP      1
#define BTN_DOWN    2
#define BTN_LEFT    3
#define BTN_SELECT  4
#define BTN_NONE    5

#define V_BTN_RIGHT  50
#define V_BTN_UP     250
#define V_BTN_DOWN   400
#define V_BTN_LEFT   600
#define V_BTN_SELECT 799
#define V_BTN_NONE   999


// PINS
#define PIN_RESET   11
#define PIN_1       10
#define PIN_2       A5
#define PIN_4       A4
#define PIN_8       A3
#define PIN_16      A2
#define PIN_32      A1
#define PIN_RELAY   12

// DELAY VALUES (ms)
#define DELAY_INIT            1000         
#define DELAY_BEFORE_RESET    200
#define DELAY_RESET           100
#define DELAY_BUTTON          200
#define DELAY_SCROLL          1000


// Game definitions  note game_size has the trailing zero in it.
#define NUMBER_OF_GAMES 63
#define MAX_NAME_SIZE   33        


// GAME NAMES, constant size so its possible to modify these from  bin file too
const char game_1[MAX_NAME_SIZE]  PROGMEM = "Astra SuperStars";
const char game_2[MAX_NAME_SIZE]  PROGMEM = "Baku Baku Animal";
const char game_3[MAX_NAME_SIZE]  PROGMEM = "Batman Forever  ";
const char game_4[MAX_NAME_SIZE]  PROGMEM = "Columns 97      ";
const char game_5[MAX_NAME_SIZE]  PROGMEM = "Cotton 2        ";
const char game_6[MAX_NAME_SIZE]  PROGMEM = "Cotton Boomerang";
const char game_7[MAX_NAME_SIZE]  PROGMEM = "Danchi deHanafud";
const char game_8[MAX_NAME_SIZE]  PROGMEM = "Decathlete      ";
const char game_9[MAX_NAME_SIZE]  PROGMEM = "Die Hard Arcade ";
const char game_10[MAX_NAME_SIZE] PROGMEM = "Touryuu Densetsu";
const char game_11[MAX_NAME_SIZE] PROGMEM = "Final Fight Reve";
const char game_12[MAX_NAME_SIZE] PROGMEM = "Funky Head Boxer";
const char game_13[MAX_NAME_SIZE] PROGMEM = "Zenkoku Seifuku ";
const char game_14[MAX_NAME_SIZE] PROGMEM = "Final Arch      ";
const char game_15[MAX_NAME_SIZE] PROGMEM = "Golden Axe: The ";
const char game_16[MAX_NAME_SIZE] PROGMEM = "Guardian Force  ";
const char game_17[MAX_NAME_SIZE] PROGMEM = "Groove On Fight ";
const char game_18[MAX_NAME_SIZE] PROGMEM = "Hanagumi Taisen ";
const char game_19[MAX_NAME_SIZE] PROGMEM = "Karaoke Quiz Int";
const char game_20[MAX_NAME_SIZE] PROGMEM = "Pro Mahjong Kiwa";
const char game_21[MAX_NAME_SIZE] PROGMEM = "Maru-Chan de Goo";
const char game_22[MAX_NAME_SIZE] PROGMEM = "Mausuke no Ojama";
const char game_23[MAX_NAME_SIZE] PROGMEM = "Virtual MahjongI";
const char game_24[MAX_NAME_SIZE] PROGMEM = "Othello Shiyouyo";
const char game_25[MAX_NAME_SIZE] PROGMEM = "Pebble Beach Gol";
const char game_26[MAX_NAME_SIZE] PROGMEM = "Princess Kurara ";
const char game_27[MAX_NAME_SIZE] PROGMEM = "Puyo Puyo Sun   ";
const char game_28[MAX_NAME_SIZE] PROGMEM = "Radiant Silvergu";
const char game_29[MAX_NAME_SIZE] PROGMEM = "Sando-R: Puzzle ";
const char game_30[MAX_NAME_SIZE] PROGMEM = "Sea Bass Fishing";
const char game_31[MAX_NAME_SIZE] PROGMEM = "Shanghai: The Gr";
const char game_32[MAX_NAME_SIZE] PROGMEM = "Shienryu        ";
const char game_33[MAX_NAME_SIZE] PROGMEM = "Soukyugurentai  ";
const char game_34[MAX_NAME_SIZE] PROGMEM = "Steep Slope Slid";
const char game_35[MAX_NAME_SIZE] PROGMEM = "Suikoenbu       ";
const char game_36[MAX_NAME_SIZE] PROGMEM = "TecmoWorldCup'98";
const char game_37[MAX_NAME_SIZE] PROGMEM = "Virtua Fighter K";
const char game_38[MAX_NAME_SIZE] PROGMEM = "Virtua Fighter R";
const char game_39[MAX_NAME_SIZE] PROGMEM = "Winter Heat     ";
const char game_40[MAX_NAME_SIZE] PROGMEM = "Zen Nippon Pro W";
const char game_41[MAX_NAME_SIZE] PROGMEM = "Ejihon Tantei Ji";
const char game_42[MAX_NAME_SIZE] PROGMEM = "DaeJeon! SanJeon";
const char game_43[MAX_NAME_SIZE] PROGMEM = "Critter Crusher ";
const char game_44[MAX_NAME_SIZE] PROGMEM = "Magical Zunou Po";
const char game_45[MAX_NAME_SIZE] PROGMEM = "Microman Battle ";
const char game_46[MAX_NAME_SIZE] PROGMEM = "Name Club       ";
const char game_47[MAX_NAME_SIZE] PROGMEM = "Print Club 2    ";
const char game_48[MAX_NAME_SIZE] PROGMEM = "Print Club 2Vol3";
const char game_49[MAX_NAME_SIZE] PROGMEM = "Print Club Pokem";
const char game_50[MAX_NAME_SIZE] PROGMEM = "Stress Busters  ";
const char game_51[MAX_NAME_SIZE] PROGMEM = "Technical Bowlin";
const char game_52[MAX_NAME_SIZE] PROGMEM = "Virtual Mahjong ";
const char game_53[MAX_NAME_SIZE] PROGMEM = "Game 53         ";
const char game_54[MAX_NAME_SIZE] PROGMEM = "Game 54         ";
const char game_55[MAX_NAME_SIZE] PROGMEM = "Game 55         ";
const char game_56[MAX_NAME_SIZE] PROGMEM = "Game 56         ";
const char game_57[MAX_NAME_SIZE] PROGMEM = "Game 57         ";
const char game_58[MAX_NAME_SIZE] PROGMEM = "Game 58         ";
const char game_59[MAX_NAME_SIZE] PROGMEM = "Game 59         ";
const char game_60[MAX_NAME_SIZE] PROGMEM = "Game 60         ";
const char game_61[MAX_NAME_SIZE] PROGMEM = "Game 61         ";
const char game_62[MAX_NAME_SIZE] PROGMEM = "Game 62         ";
const char game_63[MAX_NAME_SIZE] PROGMEM = "Game 63         ";
const char _END_[]           PROGMEM  = "----------------";   // This can be used to find namelist from binary


// Name pointers
const char* const game_list[] PROGMEM = {
  game_1, game_2, game_3, game_4, game_5, game_6, game_7, game_8, game_9,
  game_10, game_11, game_12, game_13, game_14, game_15, game_16, game_17, game_18, game_19,
  game_20, game_21, game_22, game_23, game_24, game_25, game_26, game_27, game_28, game_29,
  game_30, game_31, game_32, game_33, game_34, game_35, game_36, game_37, game_38, game_39,
  game_40, game_41, game_42, game_43, game_44, game_45, game_46, game_47, game_48, game_49,
  game_50, game_51, game_52, game_53, game_54, game_55, game_56, game_57, game_58, game_59,
  game_60, game_61, game_62, game_63, 
  _END_
};

// Global variables
int indexGame    = 0;   // current menu item
int selectedGame = 0;   // selected game



//init screen
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

/******************************************************************
 * Function readButton()
 *  
 * returns button pressed comparing the voltage values 
 * from analog pin
 ******************************************************************/
int readButton() {

  int adc_button = analogRead(BTN_PIN);  //read button

  if (adc_button > V_BTN_NONE   ) return BTN_NONE;
  if (adc_button < V_BTN_RIGHT  ) return BTN_RIGHT;
  if (adc_button < V_BTN_UP     ) return BTN_UP;
  if (adc_button < V_BTN_DOWN   ) return BTN_DOWN;
  if (adc_button < V_BTN_LEFT   ) return BTN_LEFT;
  if (adc_button < V_BTN_SELECT ) return BTN_SELECT;
}


/******************************************************************
 * Function showGameName
 *  
 *  Shows current game in the lcd screen
 *  
 *  Parameters:
 *    int game_number   Game name to show, 0 = first in the list
 *  
 ******************************************************************/
void showGameName(int game_number)
{
  char buffer[MAX_NAME_SIZE+1];

  // Copy name from program memory to the buffer
  strcpy_P(buffer, (char*)pgm_read_word(&(game_list[game_number])));

  // initialize screen
  lcd.clear();
  lcd.setCursor(0, 0);

  /********************
   * Print game number
   ********************/
  if (indexGame == selectedGame) lcd.print(">");
  lcd.print("Game ");
  lcd.print(game_number + 1);
  lcd.print(".");

  /******************
   * Print game name
   ******************/
  lcd.setCursor(0, 1);
  lcd.print(buffer);
}

/******************************************************************
 * Function setCurrentGame
 *  
 *  Sets current game in menu
 *  
 *  Parameters:
 *    int game_number   Game to set as current
 *  
 ******************************************************************/
void setCurrentGame(int gameNumber) 
{
  // check that game number is within boundaries
  if (gameNumber < 0 ) gameNumber = 0;
  if (gameNumber > NUMBER_OF_GAMES - 1) gameNumber = NUMBER_OF_GAMES - 1;

  // set and show game
  indexGame = gameNumber;
  showGameName(indexGame);
}


/******************************************************************
 * Function setPins
 *  
 *  Set pins according to selected game
 *  
 *  Parameters:
 *    int Game   Game number to select 
 * 
 ******************************************************************/
void setPins(int Game)
{
  // value to set is game index + 1
  int pinValue = Game + 1;

  // digitalWrite tests for LOW and LOW is 0 so actually (pinValue & <bit>)
  // would work without ? : (if) 
  digitalWrite(PIN_1,  (pinValue & B000001) ? HIGH : LOW) ;
  digitalWrite(PIN_2,  (pinValue & B000010) ? HIGH : LOW);
  digitalWrite(PIN_4,  (pinValue & B000100) ? HIGH : LOW);
  digitalWrite(PIN_8,  (pinValue & B001000) ? HIGH : LOW);
  digitalWrite(PIN_16, (pinValue & B010000) ? HIGH : LOW);
  digitalWrite(PIN_32, (pinValue & B100000) ? HIGH : LOW);
}

/******************************************************************
 * Function resetSTV
 *  
 *  Resets the board and the multicart
 ******************************************************************/
void resetSTV() {
 
  delay(DELAY_BEFORE_RESET);    // Not necessary but lets wait a bit
  digitalWrite(PIN_RELAY,HIGH);
  digitalWrite(PIN_RESET,HIGH); // This connects GND (through 4066) to the reset line of ST-V and multicart
                                // pulling both LOW to reset them
  delay(DELAY_RESET);           // Keep it LOW 
  digitalWrite(PIN_RESET,LOW);  // disconnects GND from reset lines -> goes HIGH
  digitalWrite(PIN_RELAY,LOW);
}


/******************************************************************
 * Function setup
 *  
 *  Standard Arduino initialization function 
 ******************************************************************/
void setup() {

  // set pin modes
  pinMode(PIN_1,OUTPUT);
  pinMode(PIN_2,OUTPUT);
  pinMode(PIN_4,OUTPUT);
  pinMode(PIN_8,OUTPUT);
  pinMode(PIN_16,OUTPUT);
  pinMode(PIN_32,OUTPUT);
  pinMode(PIN_RESET,OUTPUT);
  pinMode(PIN_RELAY,OUTPUT);
  
  pinMode(BTN_PIN,  INPUT);

  // should be low from setting pin mode, but lets set it 
  digitalWrite(PIN_RESET, LOW);
  digitalWrite(PIN_RELAY, LOW);
  
  // setup the LCD
  lcd.begin(16, 2);   
  lcd.setCursor(0, 0);
  lcd.print("INITIALIZING...");  
  lcd.setCursor(0, 1);
  lcd.print(SOFTWARE_VERSION);
  

  // delay before setting pins just to let everything initialize 
  delay(DELAY_INIT);      

  // Read last selected game from EEPROM
  selectedGame = EEPROM.read(0);

  // if not valid value, set it as first game
  if (selectedGame < 0 or selectedGame >= NUMBER_OF_GAMES) selectedGame = 0;
  indexGame = selectedGame;  
  
  setPins(selectedGame);
  showGameName(indexGame);
}

/******************************************************************
 * Function loop
 *  
 *  Standard Arduino main loop 
 ******************************************************************/
void loop() {

  // read button and act
  switch (readButton()) {
    case BTN_UP:  
      setCurrentGame(indexGame-1);
      delay(DELAY_BUTTON);
      break;
    case BTN_DOWN:
      setCurrentGame(indexGame+1);
      delay(DELAY_BUTTON);
      break;
    case BTN_LEFT:
      setCurrentGame(indexGame-10);
      delay(DELAY_BUTTON);
      break;
    case BTN_RIGHT:
      setCurrentGame(indexGame+10);
      delay(DELAY_BUTTON);
      break;
    case BTN_SELECT:
      if (selectedGame != indexGame ) {
         selectedGame = indexGame;
         EEPROM.write(0,selectedGame);
      }
 
      setPins(selectedGame);
      resetSTV();
      showGameName(indexGame);
      
      delay(DELAY_BUTTON);
      break;
  }
 
}
