/**
   Simon Game for Arduino

   The physical layout of the game setup is based on the following:
   https://wokwi.com/arduino/libraries/demo/simon-game
*/

/* headers */
#include "pitches.h"

/* symbolic constants */
#define TRUE    1
#define FALSE   0
#define NULL	 	0

#define MAX_ROUND_NUMBER 5
#define SEC 1000

#define EXPECT_MORE_INPUT		0
#define EXPECT_NO_MORE_INPUT	1
#define WRONG					2


/* Color indexes */
#define GREEN   0
#define BLUE    1
#define YELLOW  2
#define RED     3

/* Define the pins for LEDs and buttons */

#define GREEN_BTN   4
#define BLUE_BTN    3
#define YELLOW_BTN  2
#define RED_BTN     5
#define STOP_BTN   	7

#define GREEN_LED   11
#define BLUE_LED    10
#define YELLOW_LED 	9 
#define RED_LED     12

#define SPEAKER_PIN 8

#define QUIT    0
#define CORRECT 1
#define WRONG   2

#define NO_BUTTON_WAS_PRESSED 99
#define NUMBER_OF_BUTTONS 		5

#define MAX_WAIT_TIME 						SEC*15
#define MAX_NO_MORE_INPUT_TIME 		SEC*2
#define FLASH_TIME_INTERVAL 			SEC*1
#define ROUND_INTERVAL 						SEC*0.5
#define GAME_START_FLASH_INTERVAL SEC*0.5
#define BUTTON_SOUND_LENGTH 			SEC*0.5

/*Result of shouldContinueToNextRound*/
#define CONTINUE 	1
#define RESTART 	2


/* The array that contains the pin number of all the buttons/leds */
/* Indexed by the color */
const int buttons[NUMBER_OF_BUTTONS] = {GREEN_BTN, BLUE_BTN, YELLOW_BTN, RED_BTN, STOP_BTN};
const int leds[] = {GREEN_LED, BLUE_LED, YELLOW_LED, RED_LED};
/* The array that contains the tones of all the colors */
/* Indexed by the color */
/* The 5th is an empty sound for the stop button*/
const int gameTones[] = { NOTE_G3, NOTE_C4, NOTE_E4, NOTE_G5, NOTE_B0};

/* sorry, unimplemented: non-trivial designated initializers not supported */
//const int btns2toneIndex[] = {[GREEN_BTN] = GREEN, [BLUE_BTN] = BLUE, [YELLOW_BTN] = YELLOW, [RED_BTN] = RED, [STOP_BTN] = 5};
const int btns2toneIndex[] = {0, 0, 2, 1, 0, 3, 0, 5};

/* work function */
void runSimon(void);
int start();
void checkTimer();

/* work function */
void runSimon() {
	Serial.println("\nrun Simon...");

	/* Initialize the random number generator*/
	randomSeed(micros());
	start();
}

/*
    Generate a random number between lower and upper, inclusive
*/
int randInt(int lower, int upper) {
	return random(upper - lower + 1) + lower;
}

/*
    Fill data[] with random values between 0 and 3, inclusive
*/
void generateSequence(int length, int data[]) {

	for (int i = 0; i < length; i++) {
		data[i] = randInt(0, 3);
	}
}

int isUserQuit() {
	return digitalRead(STOP_BTN) == LOW;
}

int isTimeOver(unsigned long startTime, unsigned long milliseconds) {
	unsigned long currentTime = millis();

	return ((currentTime - startTime) > milliseconds);
}

int isTheButtonPressed(int buttonPin) {
	if (digitalRead(buttonPin) == LOW) {
		while (digitalRead(buttonPin) == LOW) {

		/*Do nothing*/
		}
		/* Now the button is released */
		return TRUE;
	}
	return FALSE;
}

/* Return the pin number of the button that was pressed or NO_BUTTON_WAS_PRESSED */
int detectThePressedButton() {

	for (int i = 0; i < NUMBER_OF_BUTTONS; i++) {
		if (isTheButtonPressed(buttons[i])) {

			return buttons[i];
		}
	}
	return NO_BUTTON_WAS_PRESSED;
}

/*
- Returns
	- QUIT?
	- CORRECT?
	- WRONG?
*/
int checkUserAnswers(int correctAnswers[], int numberOfCorrectAnswers) {
	unsigned long startTime = millis();
	if (isUserQuit()) {
		return QUIT;
	}
	return checkUserInput(correctAnswers, numberOfCorrectAnswers, startTime, 0);
}

/*
Returns
	- EXPECT_NO_MORE_INPUT
	- EXPECT_MORE_INPUT
	- WRONG
*/
int checkAnswer(int correctAnswers[], int numberOfCorrectAnswers, int i, int currentButton) {
	int correctAnswer = correctAnswers[i];
	int correctAnswerBtn = buttons[correctAnswer];
	if (correctAnswerBtn == currentButton) {
		if (numberOfCorrectAnswers == i + 1) {
			return EXPECT_NO_MORE_INPUT;
		}
		return EXPECT_MORE_INPUT;
	}
	return WRONG;
}

int expectNoMoreInput(unsigned long noMoreInputStartTime) {
	int detectThePressedButtonResult;
	detectThePressedButtonResult = detectThePressedButton();

	if (detectThePressedButtonResult == STOP_BTN) {
		return QUIT;
	}

	if (detectThePressedButtonResult == GREEN_BTN ||
			detectThePressedButtonResult == RED_BTN ||
			detectThePressedButtonResult == YELLOW_BTN ||
			detectThePressedButtonResult == BLUE_BTN ) {

		playBtnSound(detectThePressedButtonResult);

		return WRONG;
	}

	if (isTimeOver(noMoreInputStartTime, MAX_NO_MORE_INPUT_TIME)) {
		return CORRECT;
	}

	return expectNoMoreInput(noMoreInputStartTime);
}


void playBtnSound(int btnPin) {
	tone(SPEAKER_PIN, gameTones[btns2toneIndex[btnPin]]);
	delay(BUTTON_SOUND_LENGTH);
	noTone(SPEAKER_PIN);
}


/*
	@param i The index of the current answer to check
*/
int checkUserInput(int correctAnswers[], int numberOfCorrectAnswers, unsigned long startTime, int i) {
	int detectThePressedButtonResult = NO_BUTTON_WAS_PRESSED;
	int checkAnswerResult = 100;
	unsigned long noMoreInputStartTime;

	if (!isTimeOver(startTime, MAX_WAIT_TIME)) {
	
		detectThePressedButtonResult = detectThePressedButton();

		if (detectThePressedButtonResult == GREEN_BTN ||
				detectThePressedButtonResult == RED_BTN ||
				detectThePressedButtonResult == YELLOW_BTN ||
				detectThePressedButtonResult == BLUE_BTN ) {

			playBtnSound(detectThePressedButtonResult);

			checkAnswerResult = checkAnswer(correctAnswers, numberOfCorrectAnswers, i, detectThePressedButtonResult);
		}

	}
	if (detectThePressedButtonResult == STOP_BTN) {
		return QUIT;
	}

	if (detectThePressedButtonResult == NO_BUTTON_WAS_PRESSED && !isTimeOver(startTime, MAX_WAIT_TIME)) {
		return checkUserInput(correctAnswers, numberOfCorrectAnswers, startTime, i);
	}

	if (checkAnswerResult == EXPECT_MORE_INPUT) {
		return checkUserInput(correctAnswers, numberOfCorrectAnswers, startTime, i + 1);
	}
	if (checkAnswerResult == EXPECT_NO_MORE_INPUT) {
		noMoreInputStartTime = millis();
		return expectNoMoreInput(noMoreInputStartTime);
	}
	if (isTimeOver(startTime, MAX_WAIT_TIME) || checkAnswerResult == WRONG) {
		return WRONG;
	}
}


void flashLEDs(int colorsArr[], int n, int time_interval) {
	int i;
	int color_i;

	for (i = 0; i < n; i++) {
		color_i = colorsArr[i];

		digitalWrite(leds[color_i], HIGH);
		tone(SPEAKER_PIN, gameTones[color_i]);

		delay(time_interval / 2);

		digitalWrite(leds[color_i], LOW);
 		noTone(SPEAKER_PIN);

		if (i == n - 1) {
			// Skip the final delay
			return ;
		}
		delay(time_interval / 2);
	}
}

void showStartSignals() {
	int i;
	int n = 3;

	for (i = 0; i < n; i++) {
		digitalWrite(GREEN_LED, HIGH);
		digitalWrite(BLUE_LED, HIGH);
		digitalWrite(YELLOW_LED, HIGH);
		digitalWrite(RED_LED, HIGH);

		delay(GAME_START_FLASH_INTERVAL);
		
		digitalWrite(GREEN_LED, LOW);
		digitalWrite(BLUE_LED, LOW);
		digitalWrite(YELLOW_LED, LOW);
		digitalWrite(RED_LED, LOW);

		delay(GAME_START_FLASH_INTERVAL);
	}
}

void showWinSignals() {
	// https://wokwi.com/arduino/libraries/demo/simon-game playLevelUpSound

	tone(SPEAKER_PIN, NOTE_E4);
  delay(150);
  tone(SPEAKER_PIN, NOTE_G4);
  delay(150);
  tone(SPEAKER_PIN, NOTE_E5);
  delay(150);
  tone(SPEAKER_PIN, NOTE_C5);
  delay(150);
  tone(SPEAKER_PIN, NOTE_D5);
  delay(150);
  tone(SPEAKER_PIN, NOTE_G5);
  delay(150);
  noTone(SPEAKER_PIN);
}

void showLoseSignals() {
	// https://wokwi.com/arduino/libraries/demo/simon-game gameOver

  // Play a Wah-Wah-Wah-Wah sound
  tone(SPEAKER_PIN, NOTE_DS5);
  delay(300);
  tone(SPEAKER_PIN, NOTE_D5);
  delay(300);
  tone(SPEAKER_PIN, NOTE_CS5);
  delay(300);
  for (int i = 0; i < 10; i++) {
    for (int pitch = -10; pitch <= 10; pitch++) {
      tone(SPEAKER_PIN, NOTE_C5 + pitch);
      delay(5);
    }
  }
  noTone(SPEAKER_PIN);
  delay(500);
}

int shouldContinueToNextRound(int nextRoundNumber) {
	if (nextRoundNumber <= MAX_ROUND_NUMBER) {
		return CONTINUE;
	}
	return RESTART;
}


int correct(int nextRoundNumber, int correctAnswers[]) {
	int shouldContinueToNextRoundResult;

	Serial.println("Correct\n");

	showWinSignals();
	shouldContinueToNextRoundResult = shouldContinueToNextRound(nextRoundNumber);
	delay(ROUND_INTERVAL);


	if (shouldContinueToNextRoundResult == CONTINUE) {
		return newRound(correctAnswers, nextRoundNumber);
	}

	if (shouldContinueToNextRoundResult == RESTART) {
		return start();
	}
}

int wrong(){
	Serial.println("Wrong\n");	
	showLoseSignals();
	delay(ROUND_INTERVAL);

	return start();
}

int newRound(int correctAnswers[], int currentRoundNumber) {
	int checkUserAnswersResult;

	flashLEDs(correctAnswers, currentRoundNumber, FLASH_TIME_INTERVAL);

	checkUserAnswersResult = checkUserAnswers(correctAnswers, currentRoundNumber);

	// Serial.println("\ncheckUserAnswersResult: ");
	// Serial.println(checkUserAnswersResult);
	if (checkUserAnswersResult == QUIT) {
		return gameEnd();
	}

	if (checkUserAnswersResult == CORRECT) {
		return correct(currentRoundNumber + 1, correctAnswers);
	}

	if (checkUserAnswersResult == WRONG) {
		return wrong();
	}
}


int start() {
	int currentRoundNumber = 1;
	int correctAnswers[MAX_ROUND_NUMBER];

	generateSequence(MAX_ROUND_NUMBER, correctAnswers);

	Serial.println("\nStart the game...");

	// Serial.println("correctAnswers: ");
	// for (int i = 0; i < MAX_ROUND_NUMBER; i++) {
	// 	Serial.print(correctAnswers[i]);
	// 	Serial.print(" ");
	// }

	showStartSignals();

	return newRound(correctAnswers, currentRoundNumber);
}

int gameEnd() {
	Serial.println("\nGame ended...");
	return QUIT;
}


void setup()
{

	Serial.begin(9600);
	// Serial.println("setup");

	/* Set all the LEDs andd button pins */
	
	pinMode(GREEN_BTN, INPUT_PULLUP);
	pinMode(BLUE_BTN, INPUT_PULLUP);
	pinMode(YELLOW_BTN, INPUT_PULLUP);
	pinMode(RED_BTN, INPUT_PULLUP);
	pinMode(STOP_BTN, INPUT_PULLUP);

	pinMode(GREEN_LED, OUTPUT);
	pinMode(BLUE_LED, OUTPUT);
	pinMode(YELLOW_LED, OUTPUT);
	pinMode(RED_LED, OUTPUT);

 	pinMode(SPEAKER_PIN, OUTPUT);

	runSimon();
}

void loop()
{

	// runSimon();


}