////////////////////--LIBRARIES--////////////////////

#include <Chrono.h> // used for the stopwatches
#include <LightChrono.h> // this is part of Chrono
#include <MD_Parola.h> // library for the led matrix
#include <SPI.h> // this is the communication library for the MAX7219
#include <CtrlBtn.h> // this is used for the button and debounce logic

////////////////////--VARIABLES--////////////////////

uint16_t randomTime; // used to store the random time before the stimulus
uint16_t reactionTime; // stores the time it takes for the user to react

////////////////////--SETTINGS--////////////////////

char STIMULUS_TYPE = 'L'; 
//S for sound, L for light, B for both
//STIMULUS_TYPE will stay at its original value unless it is
//changed over serial with checkForModeChange()
#define BUTTON_PIN 9 // change to pin of your button
#define BUZZER_PIN 10 // change to pin of you buzzer
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW // led matrix type
#define MAX_DEVICES 4 // number of MAX devices
#define CS_PIN 12 // change to your chip select pin
#define MIN_RANDOM_DELAY 1000 // set to you desired lower bound of random timer length
#define MAX_RANDOM_DELAY 5001 // set to you desired upper bound of random timer length

////////////////////--Parola--////////////////////

MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); // define the Parola object with hardware spi

////////////////////--Chrono--////////////////////

Chrono timerForRandomDelay; // this timer is used to randimize the delay before before stimulus
Chrono reactionStopwatch; // this timer is used to record how long it takes you to press the button

////////////////////--CTRL buttons--////////////////////

CtrlBtn button(BUTTON_PIN, 30, nullptr, nullptr); //CTRL handler with debounce duration

////////////////////--Setup--////////////////////

void setup() {
  randomSeed(analogRead(A0)); // Set random seed to the value of a floating pin
  Serial.begin(115200); // Start serial communication
  P.begin(); // innitiate parola
  P.setIntensity(0); // set the brightness to the lowest 0-15
  pinMode(BUZZER_PIN, OUTPUT); // set the pinmode for the buzzer
}

////////////////////--Loop--////////////////////

void loop() {
  randomTime = random(MIN_RANDOM_DELAY, MAX_RANDOM_DELAY); // set the random time for the stimulus
  if (waitForStimulus(randomTime) == false) { // activate the delay and restart if the button was pressed
    return;
  }
  activateStimulus(STIMULUS_TYPE); // start the stimulus with whatever mode it is set to
  reactionTime = recordReactionTime(); // record the time it takes for the user to press the button and set reactionTime to that value
  deactivateStimulus(); // turn off the stumulus whenever the user has reacted
  printTime(reactionTime); // print the reaction time to the serial 
  waitForRelease(); // make sure the user isn't stll pressing
  checkForModeChange(); // checks if the user has used the serial monitor to change the mode
}

bool waitForStimulus(uint16_t waitTime) {
  timerForRandomDelay.restart(); // restart the timer
  while (timerForRandomDelay.elapsed() < waitTime) { // wait until the rimer is done
    button.process(); // check the button while the timer is going
    if (button.isPressed()) { // if the button was pressed too early
      P.print("EARLY!"); // print "EARLY!" on the screen
      delay(500);
      P.displayClear();
      return false; // return false so the rest of the code knows the user pressed early
    }
  }
  return true; // otherwise return true
}

void activateStimulus(char stimulus) {
  switch (stimulus) { 
    case 'S': // if the stumulus = 'S'
      digitalWrite(BUZZER_PIN, HIGH); // turn the buzzer on
      break;
    case 'L': // if the stumulus = 'L'
      P.setInvert(true); // turn the screen on by inverting it
      P.displayClear();
      break;
    default: // if the stumulus = anything else
      P.setInvert(true); // turn on buzzer and screen
      P.displayClear();
      digitalWrite(BUZZER_PIN, HIGH);
      break;
  }
}

uint16_t recordReactionTime() {
  reactionStopwatch.restart(); // start the reaction stopwatch
  while (button.isReleased() && reactionStopwatch.elapsed() < 1000) { // while the button is not pressed and the timer is belo 1000 milliseconds
    button.process(); // check the button for presses
  }
  return reactionStopwatch.elapsed(); // return the value of the stopwatch
}

void deactivateStimulus() {
  P.setInvert(false); // turn off the buzzer and screen
  P.displayClear();
  digitalWrite(BUZZER_PIN, LOW);
}

void printTime(uint16_t time) {
  Serial.println(time); // print the time on the serial monitor
}

void waitForRelease() {
  while (button.isPressed()) { // wait until the button is pressed 
    button.process();
  }
}

void checkForModeChange() {
  if(Serial.available() > 0){ // if there is any message sent to the arduino
    char serialData = Serial.read(); // set serialData to that value
    switch (serialData) {
      case 'S': // if serialData = 'S'
        STIMULUS_TYPE = 'S'; // set the stimulus type to sound
        Serial.println("STIMULUS_TYPE set to sound.");
        break;
      case 'L': // if serialData = 'L'
        STIMULUS_TYPE = 'L'; // set the stimulus type to light
        Serial.println("STIMULUS_TYPE set to light.");
        break;
      case 'B': // if serialData = 'B'
        STIMULUS_TYPE = 'B'; // set the stimulus type to sount + light
        Serial.println("STIMULUS_TYPE set to sound + light.");
        break;
      case 'P': // if serialData = 'P'
        Serial.println("Paused."); // puase the game
        while (Serial.available() > 0) { // clear the serial cache
          Serial.read();
        }
        while (true) { // wait until the loop is broken out of
          if(Serial.available() > 0){ // if a serial message is availabe
            char serialData = Serial.read(); // set serialData to the new message
            if (serialData == 'P'){ // if serialData = 'P'
              Serial.println("Unpaused."); // unpause the game
              break; // break out of the while loop
            }
          }
        }
        break;
      default: // if the serial message is not equal to 'S', 'L', 'B', or 'P'
        Serial.println("Not a valid stimulus type."); // print the proper instructions for the settings
        Serial.println("'S' = sound");
        Serial.println("'L' = light");
        Serial.println("'B' = sound + light");
        Serial.println("'P' = pause/unpause");
        break;
    }
    while (Serial.available() > 0) { // clear the serial cache
      Serial.read();
    }
  }
}