#include <Keypad.h>
#include <LiquidCrystal.h>
#include <Tone.h>
Tone tone1;
 
const byte led1 = 4; 
const byte led2 = 3;

const byte relay=2;


LiquidCrystal lcd(13, 12, 11, 10, 9, 8);

const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};

byte rowPins[ROWS] = {6, 7, A1, A0}; 
byte colPins[COLS] = {A2, A3, A4, A5};

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

void setup()
{
  pinMode(led1, OUTPUT); 
  pinMode(led2, OUTPUT); 
  pinMode(relay, OUTPUT); 
  tone1.begin(5);
  
  lcd.begin(16, 2);
  Serial.begin(9600);  
}

void loop() 
{
  byte choosed= mainMenu();

  if (choosed==0)
  {
    basicTimerMode();
  }
  else if (choosed==1)
  {
    disarmTheBombMode();
  }
  else if(choosed == 2)
  {
    dominationMode();
  }
}

/**
 * Crea el bucle para pedir una tecla en el keypad
 * **/
char getPressedKey()
{
  char key=NO_KEY;
  while (key == NO_KEY)
  { 
    key=keypad.getKey();
  }
  return key;
}

/*
 * Se utiliza para indicar que un numero ingresado 
 * en el keypad no es valido
 *
 * recibe la posicion que debe tener en el lcd y el renglon
 *
 * imprime una X en la posicion, la borra y toca una nota
 * al terminar el cursor vuelve a la posicion recibida 
 * **/
void printInvalidKey(byte posicionx, byte renglon)
{
  lcd.setCursor(posicionx, renglon);
  lcd.print('X');
  tone1.play(NOTE_C4, 200);
  delay(90);
  lcd.setCursor(posicionx, renglon);
  lcd.print(' ');
  lcd.setCursor(posicionx, renglon);
}

void playOnGameOver(byte seconds=30)
{
  byte fifthOfSec=0;
  char key= NO_KEY;
  digitalWrite(relay, HIGH); 
  while(key!='A')
  {
    key= keypad.getKey();
    
    if (fifthOfSec > seconds*5) 
    {
      digitalWrite(relay, LOW);
    }
    else 
    {
      digitalWrite(led1, HIGH);
      digitalWrite(led2, HIGH);
      delay(100);
      key= keypad.getKey();
      digitalWrite(led1, LOW);
      digitalWrite(led2, LOW);
      delay(100); 
      key= keypad.getKey();
    }

    fifthOfSec++;
  }
  digitalWrite(relay, LOW);
  
}


//----------------------------------------------------------------------------
//------------------------------- TEMPORIZADOR -------------------------------
//----------------------------------------------------------------------------

/**
 * Recibe un arreglo de tiempo, por cada segundo que pasa le resta 1 segundo
 *
 * @param hs_min_seg es el arreglo con los datos del runTimer, sus tres elementos son
 *                   [0]: horas, [1]: minutos, [2]: segundos
 *   
 * @param speedUpAtEnd se utiliza para verificar si se debe acelerar
 *                        los leds y el *pip* que hace el runTimer cada que resta un segundo
 * 
 * @post si no paso un segundo no se modificara el runTimer
 * **/
void runTimer(byte hs_min_seg[3], bool speedUpAtEnd=false, bool silentMode=false)
{
  static unsigned long secMillis = 0;
  static byte waitPartOfSeg = 0;

  uint16_t toneSpeed = 200;
  uint16_t period = 1000;

  byte horas = hs_min_seg[0];
  byte minutos = hs_min_seg[1];
  byte segundos = hs_min_seg[2];

  if(speedUpAtEnd==true)
  {
    speedUpTimer(segundos, period, toneSpeed);
  }
  
  if (segundos == 0 && minutos > 0) 
  {
    minutos--;
    segundos = 60;
  } 
  else if (minutos == 0 && horas > 0) 
  {
    horas--;
    minutos = 59;
    segundos = 60;
  }


  if (horas==0 && minutos==0 && segundos==0)
  {
    hs_min_seg[0] = 0;
    hs_min_seg[1] = 0;
    hs_min_seg[2] = 0;
  }
  else if ((millis()-secMillis) >= period) 
  {
    secMillis = millis();

    if (waitPartOfSeg * period >= 1000) 
    {
      segundos--;
      waitPartOfSeg = 1;
    }

    if (silentMode == false)
    {  
      tone1.play(NOTE_G5, toneSpeed);
      digitalWrite(led2, HIGH);
      digitalWrite(led1, HIGH);
      delay(10);
      digitalWrite(led2, LOW);
      digitalWrite(led1, LOW);
      delay(10);
    }

    waitPartOfSeg++;
  }
  

  hs_min_seg[0] = horas;
  hs_min_seg[1] = minutos;
  hs_min_seg[2] = segundos;
}



/**
 * Acelera el ruido del runTimer 
 * cuando quedan < de 10 segundos *2 
 * cuando quedan < de 5 segundos *4
 * **/
void speedUpTimer(byte segundos, uint16_t& period, uint16_t& toneSpeed)
{
  if (segundos > 10)
  {
    period = 1000;
  }
  else if (segundos <= 10 && segundos > 5)
  {
    period = 500;
  }
  else if (segundos <= 5)
  {
    toneSpeed = 100;
    period = 250;
  }
}

/**
 * Imprime el tiempo que recibe en el lcd, formato 00:00:00 (9 chars) y a partir de la posicion
 * especificada 
 * 
 * @param hs_min_seg es el arreglo que contiene el runTimer 
 *                   debe estar ordenado como horas, minutos, segundos
 * 
 * @param posx es la posicion en x a partir de la que se imprime el runTimer
 *
 * @param posy es el renglon en el que se imprime el runTimer
 * **/
void printTimer(byte hs_min_seg[3], byte posx, byte posy)
{
  byte horas = hs_min_seg[0];
  byte minutos = hs_min_seg[1];
  byte segundos = hs_min_seg[2];

  lcd.setCursor(posx, posy);

  // Imprime las horas
  if (horas < 10)
  {
    lcd.print("0");
  }
  lcd.print(horas);
  lcd.print(":");

  // Imprime los minutos
  if (minutos < 10)
  {
    lcd.print("0");
  }
  lcd.print(minutos);
  lcd.print(":");

  // Imprime los segundos
  if (segundos < 10)
  {
    lcd.print("0");
  }
  lcd.print(segundos);

}


void sumSecsToTimer(byte hs_min_seg[3], byte amount)
{
  hs_min_seg[2] += amount;
  hs_min_seg[1] += hs_min_seg[2]/60;

  hs_min_seg[2] %= 60;

  hs_min_seg[0] += hs_min_seg[1]/60;
  
  hs_min_seg[1] %= 60;

}

void countUpTimer(byte hs_min_seg[3], bool silentMode=false)
{
  static unsigned long secMillis = 0;

  if ((millis()-secMillis) >= 1000) 
    {
      secMillis = millis();
      tone1.play(NOTE_G5, 250);

      sumSecsToTimer(hs_min_seg, 1);

      if (silentMode == false)
      {  
        digitalWrite(led2, HIGH);
        digitalWrite(led1, HIGH);
        delay(10);
        digitalWrite(led2, LOW);
        digitalWrite(led1, LOW);
        delay(10);
      }
    }
}
//----------------------------------------------------------------------------
//----------------------------- FIN TEMPORIZADOR -----------------------------
//----------------------------------------------------------------------------

void setTimer(byte hs_ms_sgs[3])
{
  byte hms[6] = {0};
  char key;

  lcd.clear();
  lcd.print("Ingrese Tiempo");

  byte i = 0;
  while (i < 6)
  {
    hs_ms_sgs[0] = hms[0]*10 + hms[1];
    hs_ms_sgs[1] = hms[2]*10 + hms[3];
    hs_ms_sgs[2] = hms[4]*10 + hms[5];

    lcd.setCursor(15, 1);
    lcd.print(" ");
    delay(100);

    printTimer(hs_ms_sgs, 8, 1);

    key = getPressedKey();

    if (key == 'A')
    {
      break;
    }
    else if (key == 'B')
    {
      i = 0;
      hms[0]=0; hms[1]=0; hms[2]=0;
      hms[3]=0; hms[4]=0; hms[5]=0;
      lcd.clear();
      lcd.print("Ingrese Tiempo");
      tone1.play(NOTE_E5, 100);
    }
    else
    {
      //si no es un numero o si el primer numero es 0
      if ((key<'0' || key>'9') || (i==0 && key=='0'))
      {
        printInvalidKey(15, 1);
      }
      else
      {
        for (byte j=0; j<5;j++)
        {
          hms[j]= hms[j+1];
        }

        tone1.play(NOTE_B6, 200);//key normal
        hms[5]=key-'0';
        i++;
      }

      
    }
  }
  //hms=(h,h, m,m, s,s)
  hms[3] += hms[4]/6;//hms[4] decena segundos
  hms[4] = hms[4]%6;
  
  hms[1] += hms[2]/6;//hms[2] decena de minutos
  hms[2] = hms[2]%6;

  hs_ms_sgs[0] = hms[0]*10 + hms[1];
  hs_ms_sgs[1] = hms[2]*10 + hms[3];
  hs_ms_sgs[2] = hms[4]*10 + hms[5];

  lcd.clear();
  lcd.print("TIEMPO FINAL");
  printTimer(hs_ms_sgs, 8, 1);

  tone1.play(NOTE_C6, 200);
  delay(900);
}

void setPassword(char password[4])
{ 
  char key;
  byte currentDigit = 0;
  lcd.clear();
  lcd.print("Ingrese codigo");

  while(currentDigit < 4)
  {
    lcd.setCursor(6 + currentDigit, 1);
    key= getPressedKey();

    if (key=='B')
    {
      currentDigit=0;
      lcd.setCursor(6, 1);
      lcd.print("                ");
      tone1.play(NOTE_E5, 100);
    }
    else if (key<'0' || key>'9')
    {
      printInvalidKey(6+currentDigit, 1);
    }
    else
    {
      lcd.print(key);
      password[currentDigit] = key;
      currentDigit++;
      tone1.play(NOTE_C6, 200);
    }

  }
  delay(500);
  lcd.clear();
  lcd.print("Ingresaste: ");
  delay(100);
  lcd.setCursor(6,1);
  //  el lcd.print(password); imprime un caracter 'c' de mas
  // no se por que, antes no lo hacia
  lcd.print(password[0]);
  lcd.print(password[1]);
  lcd.print(password[2]);
  lcd.print(password[3]);

  tone1.play(NOTE_E6, 200);
  delay(1500);
  lcd.clear();
}

bool enterPassword(char password[4], byte time_hms[3])
{
  char entered[4]={0}; 
  bool areEqual;
  
  char key;
  byte currentDigit = 0;
  lcd.clear();
  lcd.print("Codigo: ");

  while (currentDigit<4 && (time_hms[0]>0 || time_hms[1]>0 || time_hms[2]>0))
  {
    runTimer(time_hms, true);
    lcd.setCursor(6+currentDigit, 1);
    key= keypad.getKey();

    if(key != NO_KEY)
    {
      if (key<'0' || key>'9')
      {
        printInvalidKey(6+currentDigit, 1);
      }
      else if (key=='B')
      {
        currentDigit=0;
        lcd.setCursor(6, 1);
        lcd.print("          ");
        tone1.play(NOTE_E5, 100);
      }
      else
      {
        lcd.setCursor(6+currentDigit, 1);
        lcd.print(key);
        entered[currentDigit]=key;
        tone1.play(NOTE_C6, 200);
        delay(100);
        lcd.setCursor(6+currentDigit, 1);
        lcd.print("*");
        currentDigit++;
      }
    }
    
  }

  areEqual=true;

  for (byte i=0; i<4; i++)
  {
    if (entered[i] != password[i])
    {
      areEqual=false;
    }
  }

  return areEqual;
}

void getPreparationTimer(byte preparationTimer[3])
{
  lcd.clear();
  lcd.print("Tiempo armado?");
  lcd.setCursor(0, 1);
  lcd.print("A: SI  B:NO");

  char key= getPressedKey();

  if(key=='A')
  { 
    lcd.clear();
    lcd.print("TIEMPO ARMADO:");

    tone1.play(NOTE_E6, 200);
    delay(800);
    setTimer(preparationTimer);
  }

  
}

void runPreparationTimer(byte preparationTimer[3])
{
  bool adviceToStart=(preparationTimer[0]>0 || preparationTimer[1]>0 || preparationTimer[2]>0);
  lcd.clear();
  lcd.print("TIEMPO ARMADO:");
  while(preparationTimer[0]>0 || preparationTimer[1]>0 || preparationTimer[2]>0)
  {
    runTimer(preparationTimer);
    printTimer(preparationTimer, 4, 1);
  }

  lcd.clear();
  lcd.print("Bomba armada");
  lcd.setCursor(0, 1);
  lcd.print("Correctamente");
  delay(1000);
  lcd.clear();
  
  if (adviceToStart==true)
  {
    playOnGameOver(5);
  }
}

void basicTimerMode()
{
  byte preparationTimer[3]={0};
  byte timer[3]={0};

  lcd.clear();
  lcd.print("Juego");
  lcd.setCursor(0, 1);
  lcd.print("   Temporizado");
  delay(500);

  getPreparationTimer(preparationTimer);

  lcd.clear();
  lcd.print("TIEMPO DE JUEGO");
  tone1.play(NOTE_E6, 200);
  delay(800);
  setTimer(timer);

  runPreparationTimer(preparationTimer);

  lcd.clear();
  lcd.print("Tiempo Restante");
  while (timer[0]>0 || timer[1]>0 || timer[2]>0)
  {
    runTimer(timer);
    printTimer(timer, 4, 1);
  }
  lcd.clear();
  lcd.print("Termino la");
  lcd.setCursor(0, 1);
  lcd.print("   Partida");

  playOnGameOver();
}

void disarmTheBombMode()
{
  byte preparationTimer[3]={0};
  byte mainTimer[3]={0};

  char settedPassword[4];
  char enteredPassword[4];
  bool correctPassword=false;
  byte attempts=3;

  byte changeInSec=70;
  char key;
  bool gameIsOver=false;

  lcd.clear();
  lcd.print("Desarmar Bomba");
  delay(500);
  setPassword(settedPassword);

  getPreparationTimer(preparationTimer);
  
  lcd.clear();
  lcd.print("TIEMPO DE JUEGO");
  tone1.play(NOTE_E6, 200);
  delay(800);
  setTimer(mainTimer);

  runPreparationTimer(preparationTimer);

  while ((mainTimer[0]>0 || mainTimer[1]>0 || mainTimer[2]>0) && gameIsOver==false)
  {
    runTimer(mainTimer, true);

    if (changeInSec != mainTimer[2])
    {
      changeInSec= mainTimer[2];
      lcd.clear();
      lcd.print("Tiempo Restante");
      printTimer(mainTimer, 4, 1);
    }

    key= keypad.getKey();

    if (key!=NO_KEY)
    {
      lcd.clear();
      lcd.print("ENTRANDO.");
      delay(200);
      lcd.print(".");
      delay(200);
      lcd.print(".");
      delay(200);
      lcd.clear();

      correctPassword= enterPassword(settedPassword, mainTimer);

      if (correctPassword==true)
      {
        gameIsOver=true;
      }
      else
      {
        attempts--;

        lcd.clear();
        lcd.print("intentos: ");
        lcd.print(attempts);
        delay(500);

        if (attempts==0)
        {
          gameIsOver=true;
        }
      }
    }

  }

  if (correctPassword==true)
  {
    lcd.clear();
    lcd.print("BIEN HECHO");
    lcd.setCursor(0, 1);
    lcd.print("      INSECTO");
    delay(600);
    lcd.clear();
    lcd.print("LA BOMBA");
    lcd.setCursor(0, 1);
    delay(100);
    lcd.print("FUE DESACTIVADA");
  }
  else
  {
    lcd.clear();
    lcd.print("LA RE CAGASTE");
    delay(500);
    lcd.clear();
    lcd.print("LA BOMBA");
    lcd.setCursor(0, 1);
    delay(100);
    lcd.print("     EXPLOTO!");    
  }
  
  playOnGameOver();
}

//-------------------------------------------------------------------
//------------------ DOMINATION AUXILIAR FUNCTIONS ------------------
//-------------------------------------------------------------------

void addSecToTeam(char& currentTeam, byte blueTimer[3], byte redTimer[3])
{
  if (currentTeam == 'B')
  {
    sumSecsToTimer(blueTimer, 1);
  }
  else if(currentTeam == 'R')
  {
    sumSecsToTimer(redTimer, 1);
  }
}

void printTimers(byte mainTimer[3], byte blueTimer[3], byte redTimer[3], bool printMain=true)
{
  static char timerToPrint= 'B';

  if(printMain==true)
  {
    lcd.clear();
    lcd.print("Tiempo ");
    printTimer(mainTimer, 7, 0);
  }


  if (timerToPrint == 'B')
  {
    lcd.setCursor(0, 1);
    lcd.print("               ");
    lcd.setCursor(0, 1);

    delay(100);
    
    lcd.print("Azul ");
    printTimer(blueTimer, 6, 1);

    timerToPrint= 'R';
  }
  else if(timerToPrint == 'R')
  {
    lcd.setCursor(0, 1);
    lcd.print("               ");
    lcd.setCursor(0, 1);

    delay(100);

    lcd.print("Rojo: ");
    printTimer(redTimer, 6, 1);

    timerToPrint= 'B';
  }

}

void checkTimeForComeBack(uint32_t& totalSecs, byte currentTimer[3], byte otherTimer[3], bool& gameOver)
{
  uint32_t currentSecs= currentTimer[0]*3600 + currentTimer[1]*60 + currentTimer[2];
  uint32_t otherSecs= otherTimer[0]*3600 + otherTimer[1]*60 + otherTimer[2];

  static const uint32_t halfTotalSecs= totalSecs >> 1;

  if (currentSecs > halfTotalSecs)
  {
    gameOver= true;
  }
  
}

void playDominationGameOver(byte blueTimer[3], byte redTimer[3]) {
  byte mainTimer[3]= {0, 0, 200};
  byte changeInSec= 0;
  char key= NO_KEY;

  runTimer(mainTimer, true, true);//reset the runTimer static vars

  digitalWrite(relay, HIGH);
  while (mainTimer[2]>0 && key != 'A') 
  {
    runTimer(mainTimer, false, true);
    key = keypad.getKey();
    
    if (changeInSec != mainTimer[2])
    {
      changeInSec=mainTimer[2];
      printTimers(mainTimer, blueTimer, redTimer, false);
    }

    digitalWrite(led1, HIGH);
    digitalWrite(led2, HIGH);
    delay(100); 
    digitalWrite(led1, LOW);
    digitalWrite(led2, LOW);
    delay(100); 
  }
  digitalWrite(relay, LOW);
}

//-------------------------------------------------------------------
//------------------ DOMINATION AUXILIAR FUNCTIONS ------------------
//-------------------------------------------------------------------


void dominationMode()
{
  byte blueTimer[3]={0};
  byte redTimer[3]={0};

  byte preparationTimer[3]={0};
  byte mainTimer[3]={0};

  uint32_t gameTotalSecs;

  char currentTeam='0';
  char key;

  byte changeInSec=70;
  bool gameIsOver=false;


  lcd.clear();
  lcd.print("Dominacion");
  delay(500);

  getPreparationTimer(preparationTimer);

  lcd.clear();
  lcd.print("TIEMPO DE JUEGO");
  tone1.play(NOTE_E6, 200);
  delay(800);

  setTimer(mainTimer);
  gameTotalSecs= mainTimer[0]*3600 + mainTimer[1]*60+ mainTimer[2];

  runPreparationTimer(preparationTimer);
  

  while ((mainTimer[0]>0 || mainTimer[1]>0 || mainTimer[2]>0) && gameIsOver==false)
  {
    if (currentTeam=='B' || currentTeam=='R')
    {
      runTimer(mainTimer);
    }


    if (changeInSec != mainTimer[2])
    {
      changeInSec= mainTimer[2];

      printTimers(mainTimer, blueTimer, redTimer);
      
      addSecToTeam(currentTeam, blueTimer, redTimer);
    }

    key= keypad.getKey();

    if (key == '*')
    {
      digitalWrite(relay, HIGH);
      delay(5000);
      currentTeam= 'B';
      digitalWrite(relay, LOW);
    }
    else if (key == 'D')
    {
      digitalWrite(relay, HIGH);
      delay(5000);
      currentTeam= 'R';
      digitalWrite(relay, LOW);
    }

    if(currentTeam=='B')
    {
      checkTimeForComeBack(gameTotalSecs, blueTimer, redTimer, gameIsOver);
    }
    else if(currentTeam=='R')
    {
      checkTimeForComeBack(gameTotalSecs, redTimer, blueTimer, gameIsOver);
    }

  }
 
  if(currentTeam=='B')
  {
    lcd.clear();
    lcd.print("  GANO AZUL");
  }
  else 
  {
    lcd.clear();
    lcd.print("  GANO ROJO");
  }

  playDominationGameOver(blueTimer, redTimer);

  
}

byte mainMenu()
{
  lcd.clear();
  const char rightArrow='>';
  const char leftArrow='<';

  char key='0';
  char gameModes[3][15]={"Temporizador", "Desarma Bomba", "Dominacion"};

  lcd.clear();
  lcd.print("Modo de juego");

  byte i=0;
  bool seleccionando=true;
  while(seleccionando)
  {
    lcd.setCursor(0, 1);
    lcd.print("                ");
    lcd.setCursor(2, 1);
    lcd.print(gameModes[i]);

    if (i==0)
    {
      lcd.setCursor(15, 1);
      lcd.print(rightArrow);
    }
    else if (i==2)
    {
      lcd.setCursor(0, 1);
      lcd.print(leftArrow);
    }
    else
    {
      lcd.setCursor(0, 1);
      lcd.print(leftArrow);

      lcd.setCursor(15, 1);
      lcd.print(rightArrow);
    }
    

    key= getPressedKey();

    if (key=='A')
    {
      seleccionando=false;
    }
    else if (key=='*')
    {
      if (i==0)
      {
        printInvalidKey(0, 1);
        
      }
      else
      {
        tone1.play(NOTE_B6, 200);//key normal
        i--;
      }
    }
    else if (key=='#')
    {
      if (i==2)
      {
        printInvalidKey(15, 1);
        
      }
      else
      {
        tone1.play(NOTE_B6, 200);//key normal
        i++;
      }
    }

  }

  return i;
}
NOCOMNCVCCGNDINLED1PWRRelay Module