/**
 * GC9GNKE - "Tens boa memoria ?? 
 * Algum codigo foi tirado (exemplos) do tutorial da Sparkfun:
 * https://learn.sparkfun.com/tutorials/sik-experiment-guide-for-arduino---v32/experiment-16-simon-says 
 * 
 */

#include <Servo.h>
#include "pitches.h"


//trocar para 0 para desativar debugging
//trocar para 1 para ativar debugging
#define DEBUG 0

#define BUZZER  12

#define SERVO 3
Servo myservo;

#define soundButtonTop  262
#define soundButtonLeft 330
#define soundButtonBottom 392 
#define soundButtonRight 494

int soundsArray[4] = {soundButtonTop,soundButtonLeft, soundButtonBottom,soundButtonRight};
/*
 * Definitions for buttons and leds
 */
#define ledTop 11
#define ledLeft 10
#define ledBottom 9
#define ledRight 8


int ledsArray[4] = {ledTop, ledLeft, ledBottom, ledRight};

//buttons definitions
#define buttonTop 7
#define buttonLeft 6
#define buttonBottom 5
#define buttonRight 4


#define NO_BUTTON 0

int buttonsArray[4] = {buttonTop, buttonLeft, buttonBottom, buttonRight};

/**
 * Program variables
 */

// Numero de nivels que o utilizador tem que fazer
// para ganhar
// vamos apenas usar 5 dos 6 que temos definidos
#define NUM_LEVELS 5

//tempo (milisegundos - 3 segundos) que o geocacher tem para pressionar o botao
#define BUTTON_TIMEOUT 3000

#define ledReady 13

//current level
int curLevel = 0;

/**
 * definicao de tempos para os LEDs acenderem e desligaram
 * Nivel mais altos, menos tempo para mostrar
 * 2s, 1,5s, 900ms, 700ms, 600ms, 500ms -  
 */
//Para 6 niveis
//int timeLevels[NUM_LEVELS] = {1000, 900, 750, 650, 600, 500};

//Para 5 niveis
int timeLevels[NUM_LEVELS] = {1000, 900, 750, 650, 600};

/**
 * Tamanho das sequencias para memorizar e repetir
 * Mais alto o nivel, maior a sequencia a memorizar
 * Primeiro nivel acende 4 botoes
 * Segundo nivel, acende 5 botoes e assim sucessivamente. 
 * 4, 5, 6, 7, 8, 10
 */
//Para 6 niveis
//int sequences[NUM_LEVELS] = {4, 5, 5, 6, 7, 8};

// Para 5 niveis
int sequences[NUM_LEVELS] = {4, 5, 5, 6, 7};

long debounceDelay = 200;    // tempo para "deixar de saltar". aumentar se botao regista mais que uma vez


void setup() {
  // put your setup code here, to run once:
  //LED PINS
  for (int i = 0; i < 4; i++)
    pinMode (ledsArray[i], OUTPUT);

  //led Ready
  pinMode (ledReady, OUTPUT);

  //Button PINS
  for (int i = 0; i < 4; i++)
    pinMode (buttonsArray[i], INPUT_PULLUP);

  // Servo
  myservo.attach(SERVO);

  //Serial port
  Serial.begin(115200);
  initializeLeds();
  delay(3000);
}

/*
 * Just show off a animation for startup
 */
void initializeLeds() {
  int delay_timer = 100;
  for (int i = 0; i < 4; i++) {
    digitalWrite (ledsArray[i], HIGH);
    delay(delay_timer);
    digitalWrite (ledsArray[i], LOW);
  }
}


void loop() {

  //play something a do something
  //until user presses a key to start the game. 
  flashLevelWaiting();
  delay(2000); //esperar 2s ate comecar o jogo
  
  //when ready, if return 0 is not ok
  //return 1, win and play sound
  if (playMemoryGame()) {
    //play vitory
    if (DEBUG) Serial.println("A tocar vitoria...");
    playVencedor();
    delay(1000);
    //open door
    if (DEBUG) Serial.println ("Opening logbook door");
    myservo.write(170);
    //indicar que ja podem abrir a porta
    showOpenLogBook();
    //wait 5s
    delay(5000);
    //put in normal position
    if (DEBUG) Serial.println ("Closing logbook door");
    myservo.write(90);
    //wait until a button is pressed
    do {} while (!getButton());
    delay(100);
  }
  else {
    //play gameover
    if (DEBUG) Serial.println("Perdeu....");
    playGameOver();
    delay (1000);
  } 
  //reset game levels
  curLevel = 0;
}


boolean playMemoryGame () {
  //lets randomize 
   for (int z = 0; z < NUM_LEVELS; z++) {
    Serial.println("Current Level: " + String(curLevel));
    
    /*
     * create array with current level sequences
     * Just create an array -
     * Sequences is the number of buttons to press from memory
     * curLevel is current level - current level will
     * be the index in the sequences array - the more high in the level
     * the more button presses will have to remember
     */
    int flashArray[sequences[curLevel]];

    /*
    //fill the array with random Leds
    for (int y = 0; y < sequences[curLevel]; y++)
      flashArray[y] = ledsArray[random(0,3)];
    */
  
    randomizeGame (flashArray, sequences[curLevel]);

    //call the function that will light up the leds
    showLevel (flashArray);
    readyLevel(); //mostrar ao jogador que pode pressionar botoes
    if (DEBUG) Serial.println ("Waiting for user");
    
    //now, let's match the pressed buttons with the game
    if (!parseUserLevel(flashArray)) {
      //User missed something, ending game
      if (DEBUG) Serial.println(" Gameover");
      return 0;
    }
    //Estamos aqui, o utilizador acertou todos os botoes
    //esperar 1s ate ao proximo nivel
    delay(1000);
    curLevel += 1;
    if (DEBUG) Serial.println("Next Level: " + String(curLevel));
   }

  //if we got here, winner
   return 1;
}

void randomizeGame (int flashArray[], int arraySize) {
  //make sure this is really random
  randomSeed(analogRead(A0));
  //fill the array with random Leds
  for (int y = 0; y < arraySize; y++)
    flashArray[y] = ledsArray[random(0,4)];
}

/**
 * Function show the random LEDs according with the Arrays
 * playLevel - show what buttons user must press
 * 
 */
void showLevel (int flashArray[]) {
  //lets light up some leds
  for (int x = 0; x < sequences[curLevel]; x ++) {
    Serial.print(String(flashArray[x]) + " ");
    //digitalWrite (flashArray[x], HIGH);
    tonner (flashArray[x]);
    delay(timeLevels[curLevel]); 
    //digitalWrite (flashArray[x], LOW);
    delay(timeLevels[curLevel]);
  }
}

/*
 * Function to get what button was pressed
 */
int getButton () {
  long startTime = millis();
  //get what button was pressed and return it
  // we return the pin number - it's the same on the array
  while ( (millis() - startTime) < BUTTON_TIMEOUT) {
    if (digitalRead(buttonTop) == 0) return (ledTop);
    else if (digitalRead(buttonLeft) == 0) return (ledLeft);
    else if (digitalRead(buttonRight) == 0) return (ledRight);
    else if (digitalRead(buttonBottom) == 0) return (ledBottom);
  }
  return 0;
}

/*
 * parse user input for current level
 * Every button in presses, we will check if is the same on the array
 * If it fails, return 0
 */
int parseUserLevel (int flashArray[]) {
  int numPressButtons = 0;
  
  while (numPressButtons < sequences[curLevel]) {
    long startTime = millis();
    int button = getButton();
    if (DEBUG) Serial.println("Button pressed: " + String(button));
    if (button != NO_BUTTON) {
      //play music
      tonner (button);
      if (DEBUG) Serial.println("Button pressed inside: " + String(button));
      delay (debounceDelay); //debounce to prevent multiple presses
      //check if button pressed is the same as in the array
      if (button == flashArray[numPressButtons]) {
        if (DEBUG) Serial.println ("corret button");
        //next button
        numPressButtons++;
        if (DEBUG) Serial.println("numPress: " + String(numPressButtons));
        setLedsPressed (NO_BUTTON);
        continue; //advance next
      }
      if (DEBUG) Serial.println("Error button");
      return 0;
    } //end if button
    if (DEBUG) Serial.println("TIMEOUT");
    return 0;
  }
  //we got here, user got them right
  return 1;
}

//light up the LED of the button pressed
void setLedsPressed(int buttonPressed) {
  if (buttonPressed ==  ledTop)
    digitalWrite (ledTop, HIGH);
  else 
    digitalWrite (ledTop, LOW);
  
  if (buttonPressed ==  ledLeft)
    digitalWrite (ledLeft, HIGH);
  else 
    digitalWrite (ledLeft, LOW);

  if (buttonPressed ==  ledBottom)
    digitalWrite (ledBottom, HIGH);
  else 
    digitalWrite (ledBottom, LOW);

  if (buttonPressed ==  ledRight)
    digitalWrite (ledRight, HIGH);
  else 
    digitalWrite (ledRight, LOW);
}

void play (long note, int duration = 150) {
  tone (BUZZER,note,duration);
  delay (1+duration);
}


void tonner (int buttonPressed) {
  //light leds
  setLedsPressed (buttonPressed);
  
  switch (buttonPressed) {
    case ledTop:
      play (soundButtonTop);
      break;
    case ledLeft:
      play (soundButtonLeft);
      break;
    case ledBottom:
      play (soundButtonBottom);
      break;
    case ledRight:
      play (soundButtonRight);
      break;    
  }
  setLedsPressed(0);
}

// Mostra um modo descontraido ate que estejam prontos a jogar.
void modoDistrair(void) {
  while(1) {
    setLedsPressed(ledTop);
    delay(10);
    if (getButton() != NO_BUTTON) break;
    setLedsPressed(ledLeft);
    delay(10);
    if (getButton() != NO_BUTTON) break;
    setLedsPressed(ledBottom);
    delay(10);
    if (getButton() != NO_BUTTON) break;
    setLedsPressed(ledRight);
    delay(10);
    if (getButton() != NO_BUTTON) break;
  }
  setLedsPressed(0); //desliga os LEDs
}

//GameOver
void playGameOver() {
  int pressedButton;
  //turn all the LEDs on
  for (int j = 0; j <= 3; j++) {
    digitalWrite(ledsArray[j], HIGH);
  }
  tone(BUZZER, 130, 250);   //E6
  delay(275);
  tone(BUZZER, 73, 250);   //G6
  delay(275);
  tone(BUZZER, 65, 150);   //E7
  delay(175);
  tone(BUZZER, 98, 500);   //C7
  delay(500);

  delay(200);
  setLedsPressed(0);
}

//
void playVencedor() {

  //turn all the LEDs on
  for (int j = 0; j <= 3; j++) {
    digitalWrite(ledsArray[j], HIGH);
  }

  for (int d = 0; d < 3; d++) {
    //play the 1Up noise
    tone(BUZZER, 1318, 150);   //E6
    delay(175);
    tone(BUZZER, 1567, 150);   //G6
    delay(175);
    tone(BUZZER, 2637, 150);   //E7
    delay(175);
    tone(BUZZER, 2093, 150);   //C7
    delay(175);
    tone(BUZZER, 2349, 150);   //D7
    delay(175);
    tone(BUZZER, 3135, 500);   //G7
    delay(500);
  }

  delay(100);
}

/*
 * Esta funcao vai acender todos os leds
 * para indicar que esta a espera
 */
void flashLevelWaiting () {
  while (1) {
  //flash all of the LEDs when the game starts
    for (int i = 0; i <= 3; i++) {
      tone(BUZZER, soundsArray[i], 200); //play one of the 4 tones
      //turn all of the leds on
      digitalWrite(ledsArray[0], HIGH);
      digitalWrite(ledsArray[1], HIGH);
      digitalWrite(ledsArray[2], HIGH);
      digitalWrite(ledsArray[3], HIGH);
      delay(100);         //wait for a moment
      //turn all of the leds off
      digitalWrite(ledsArray[0], LOW);
      digitalWrite(ledsArray[1], LOW);
      digitalWrite(ledsArray[2], LOW);
      digitalWrite(ledsArray[3], LOW);
      delay(100);   //wait for a moment
    }//this will repeat 4 times
  if (getButton() != NO_BUTTON) return;
  }
}

/*
 * Funcao para mostrar ao utilizador
 * que estamos prontos para aceitar os inputs
 */
void readyLevel () {
  digitalWrite (ledReady, HIGH);
  tone (BUZZER, NOTE_CS6, 150);
  delay(100);
  digitalWrite (ledReady, LOW);
}

/*
 * Funcao que mostra que podemos abrir o logbook
 */
void showOpenLogBook() {
  for (int z=0; z < 10; z++) {
    digitalWrite (ledReady, HIGH);
    tone (BUZZER, NOTE_DS5, 100);
    delay(50);
    digitalWrite (ledReady, LOW);
    delay(50);
  }
}
