#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>

#define DEBUG 1  // Set Debug level 0-1 to prints result on serial monitor (Set it to 0 when you don't need serial prints)

#if DEBUG == 1
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#else
#define debug(x)
#define debugln(x)
#endif

const int rows[4] = { 6, 7, 8, 9 };         // Digital pins for rows
const int columns[4] = { A0, A1, A2, A3 };  // Analog pins for columns

int previousPotValue = 0;  // Variable to hold previous pot value
int ldrThreshold = 0;      // Variable to hold LDR threshold value

#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
// #define HARDWARE_TYPE MD_MAX72XX::FC16_HW
// Defining size, and output pins
#define MAX_DEVICES 4
#define CS_PIN 3

// Create a new instance of the MD_Parola class with hardware SPI connection
MD_Parola myDisplay = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);


#define CLK_PIN 13
#define DATA_PIN 11

#define pushButtonPin 2
#define ledPin 4
#define potentiometerPin A4
#define inactivityTimeout 120000  // 2 minutes standby
unsigned long lastIdleScrollTime = millis(); // Variable to hold last idle scroll time
unsigned long  lastScoreShownTime = millis(); // Variable to hold last score shown time

const int ldrPins[] = { A0, A1, A2, A3 };
const int ldrThresholdDefault = 75;  // Default LDR sensitivity threshold


unsigned long startTime, endTime, reactionTime;
unsigned long recentScores[5];
unsigned long lastActivity = -inactivityTimeout;
int scoreCount = 0;
bool gameActive = false;
bool showHighScores = false;

bool exitGame = false; // Flag to exit the game

String sc1, sc2, sc3, sc4, sc5;  // Variables for recent scores in string form

String lastMessage = "";  // Variable to hold last displayed message on dot matrix

bool checkLdrArray() {
  int ldrValues[4][4];  // Array to store LDR readings

  // Iterate through each row
  for (int i = 0; i < 4; i++) {
    // Activate the current row
    digitalWrite(rows[i], HIGH);
    // debug("Row ");
    // debug(i + 1);

    // Read all columns for the current row
    for (int j = 0; j < 4; j++) {
      ldrValues[i][j] = map(analogRead(columns[j]), 0, 1023, 0, 100) > ldrThreshold ? 1 : 0;
      // debug(", LDR Value ");
      // debug(ldrValues[i][j]);
      if (ldrValues[i][j] == 1) {
        for (int i = 0; i < 4; i++) {  // reset pins low
          digitalWrite(rows[i], LOW);
          digitalWrite(columns[j], LOW);
        }
        return true;
      }
      // debug(", Col ");
      // debug(j + 1);
      // debug(": ");
      // debug(ldrValues[i][j]);
    }
    // debugln();


    // Deactivate the current row
    digitalWrite(rows[i], LOW);
  }
  return false;
}
void checkButtonPress() { // Function to check if the button is pressed for more than 3 seconds
  if (digitalRead(pushButtonPin) == LOW) { // Check for button press if it's more then 3 seconds then stop the game and display the high score
    debugln("Button Pressed");
    unsigned long pressStart = millis(); // Start the timer
    while (digitalRead(pushButtonPin) == LOW); // Wait for the button to be released
    unsigned long pressDuration = millis() - pressStart; // Calculate the duration of the button press
    if (pressDuration >= 3000) { // If the button was pressed for more than 3 seconds
      exitGame = true; // Set the exitGame flag to true
      showHighScores = true; // Set the showHighScores flag to true
      gameActive = false; // Set the gameActive flag to false
      myDisplay.displayClear(); // Clear the display
      return; // Exit the function
    }
  }
}

void standbyMode() {
  myDisplay.displayClear();
  // myDisplay.print("L.E.D. shooter");
  lastMessage = "L.E.D. shooter";

  // Convert String to char array
  char scrollText[lastMessage.length() + 1];
  lastMessage.toCharArray(scrollText, lastMessage.length() + 1);



  // Set the scrolling parameters
  myDisplay.displayScroll(scrollText, PA_CENTER, PA_SCROLL_LEFT, 100);



  // Animate the scroll until complete
  while (!myDisplay.displayAnimate()) {
    // Continuously update the animation
    if (digitalRead(pushButtonPin) == LOW) {
      return;
    }
  }
  debugln("L.E.D. Shooter");
}

void customDelay(unsigned long waitTime) {
  unsigned long startTime = millis();
  while (millis() - startTime <= waitTime) {
    checkForPotentiometerChange();  // keep checking if potentiometer value changes
    checkButtonPress();             // check if button is pressed for more than 3 seconds
    if (exitGame) { // If the exitGame flag is set to true then exit the function
      return;
    }
  }
}

void startGame() {
  for (int round = 1; round <= 5; round++) {
    myDisplay.print("READY");
    lastMessage = "READY";
    debugln("Get Ready");
    customDelay(random(5000, 10000));

    digitalWrite(ledPin, HIGH);
    myDisplay.print("FIRE");
    lastMessage = "FIRE";
    debugln("Fire");
    startTime = millis();

    bool hit = false;
    while (millis() - startTime <= 5000) {
      checkForPotentiometerChange();  // keep checking if potentiometer value changes
      checkButtonPress();             // check if button is pressed for more than 3 seconds
      if (exitGame) { // If the exitGame flag is set to true then exit the function
        return;
      }
      if (checkLdrArray()) {
        hit = true;
        break;
      }
    }

    digitalWrite(ledPin, LOW);

    if (hit) {
      endTime = millis();

      if (exitGame) { // If the exitGame flag is set to true then exit the function
        return;
      }
      reactionTime = endTime - startTime;
      debug("Reaction Time: ");
      debug(reactionTime);
      debugln(" ms");
      addScore(reactionTime);
      myDisplay.setTextAlignment(PA_RIGHT);
      myDisplay.print(String(reactionTime));
      lastMessage = String(reactionTime);
      for (int i = 0; i < 3; i++) {
        digitalWrite(ledPin, HIGH);
        customDelay(250);
        digitalWrite(ledPin, LOW);
        customDelay(250);
      }
      customDelay(3000);
      updateScoreStrings();
      if (exitGame) { // If the exitGame flag is set to true then exit the function
        return;
      }
    } else {
      myDisplay.setTextAlignment(PA_CENTER);
      myDisplay.print("MISS");
      Serial.println("Miss");



      customDelay(3000);
      if (exitGame) { // If the exitGame flag is set to true then exit the function
        return;
      }
    }
    customDelay(2000);
  }
}

void setup() {
  pinMode(pushButtonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
  // Intialize the object
  myDisplay.begin();

  // Set the intensity (brightness) of the display (0-15)
  myDisplay.setIntensity(0);

  // Clear the display
  myDisplay.displayClear();

  // Initialize LDR Array pins
  // Initialize row pins as outputs
  for (int i = 0; i < 4; i++) {
    pinMode(rows[i], OUTPUT);
    digitalWrite(rows[i], LOW);
  }

  // Initialize column pins as inputs
  for (int i = 0; i < 4; i++) {
    pinMode(columns[i], INPUT);
  }

  myDisplay.begin();

  myDisplay.setTextAlignment(PA_CENTER);
  standbyMode();
  lastIdleScrollTime = millis(); // Set the last idle scroll time

  previousPotValue = map(analogRead(potentiometerPin), 0, 1023, 0, 100);  // Read potentiometer mapped value
  ldrThreshold = previousPotValue;                                        // Set LDR threshold to potentiometer value
  resetScores();
  debugln("Start...");
}

void checkForPotentiometerChange() {
  int potValue = map(analogRead(potentiometerPin), 0, 1023, 0, 100);
  if (abs(potValue - previousPotValue) > 2) {
    previousPotValue = potValue;
    ldrThreshold = previousPotValue;
    String sensitivityMsg = "Sensitivity: " + String(ldrThreshold);
    myDisplay.displayClear();
    myDisplay.print(ldrThreshold);
    debug("Threshold:");
    debugln(ldrThreshold);
    delay(1000);
    myDisplay.displayClear();
    myDisplay.print(lastMessage);
  }
}
void loop() {
  unsigned long currentMillis = millis();
  checkForPotentiometerChange();

  // Check button press
  if (digitalRead(pushButtonPin) == LOW) {
    debugln("Button Pressed");
    delay(50);  // Debounce
    unsigned long pressStart = millis();
    while (digitalRead(pushButtonPin) == LOW)
      ;
    unsigned long pressDuration = millis() - pressStart;

    if (pressDuration >= 3000) {  // Long press
      debugln("Button pressed for more then 3 show score");
      showHighScores = true;
      gameActive = false;
      myDisplay.displayClear();
      printScores();
      DisplayDot();
    } else {  // Short press
      gameActive = true;
      showHighScores = false;
      myDisplay.displayClear();
      startGame();
      // Step 5: Print recent scores sorted in descending order
      printScores();
      DisplayDot();
      lastScoreShownTime = millis(); // Set the last score shown time
    }
    lastActivity = currentMillis;
  }

  // Standby mode after inactivity

  if (currentMillis - lastActivity > inactivityTimeout && !gameActive && !showHighScores) {
    if (millis() - lastIdleScrollTime > 10000) { // Scroll the message after 10 seconds of inactivity
      standbyMode();
      lastIdleScrollTime = millis();
    }
  } else if ((millis() - lastScoreShownTime) > 15000) { // If the high score is displayed then scroll the message after 10 seconds
    DisplayDot();
    lastScoreShownTime = millis();
  }
}


void resetScores() {
  for (int i = 0; i < 5; i++) {
    recentScores[i] = 0;
  }
  updateScoreStrings();
}



void addScore(unsigned long newScore) {
  if (scoreCount < 5) {
    recentScores[scoreCount++] = newScore;
  } else {
    for (int i = 1; i < 5; i++) {
      recentScores[i - 1] = recentScores[i];
    }
    recentScores[4] = newScore;
  }
  sortScores();
  updateScoreStrings();
}



void sortScores() {
  for (int i = 0; i < scoreCount - 1; i++) {
    for (int j = i + 1; j < scoreCount; j++) {
      if (recentScores[i] > recentScores[j]) {  // Sort descending
        unsigned long temp = recentScores[i];
        recentScores[i] = recentScores[j];
        recentScores[j] = temp;
      }
    }
  }
}



void printScores() {
  debugln("Recent Scores:");
  for (int i = 0; i < scoreCount; i++) {
    debug(i + 1);
    debug(". ");
    debug(recentScores[i]);
    debugln(" ms");
  }
}



void updateScoreStrings() {
  sc1 = (scoreCount > 0) ? String(recentScores[0]) : "";  //+ " ms" : "N/A";
  sc2 = (scoreCount > 1) ? String(recentScores[1]) : "";  //+ " ms" : "N/A";
  sc3 = (scoreCount > 2) ? String(recentScores[2]) : "";  //+ " ms" : "N/A";
  sc4 = (scoreCount > 3) ? String(recentScores[3]) : "";  //+ " ms" : "N/A";
  sc5 = (scoreCount > 4) ? String(recentScores[4]) : "";  //+ " ms" : "N/A";
}
void DisplayDot() {
  // Reset game and score flags and also exit game flag
  gameActive = false;
  showHighScores = false;
  exitGame = false;

  myDisplay.displayClear();
  String ST = " 1st " + sc1 + " 2nd " + sc2 + " 3rd " + sc3 + " 4th " + sc4 + " 5th " + sc5;



  // Convert String to char array
  char scrollText[ST.length() + 1];
  ST.toCharArray(scrollText, ST.length() + 1);



  // Set the scrolling parameters
  myDisplay.displayScroll(scrollText, PA_CENTER, PA_SCROLL_LEFT, 100);



  // Animate the scroll until complete
  while (!myDisplay.displayAnimate()) {
    // Continuously update the animation
    // Check if button is pressed then exit the function
    if (digitalRead(pushButtonPin) == LOW) {
      return;
    }
  }
}