#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Initialize the LCD, set the address to 0x27 or 0x3F depending on your module
LiquidCrystal_I2C lcd(0x27, 16, 2);  // 16 columns, 2 rows

// Pin setup
int ledPins[] = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13};  // Pins for the 8 LEDs
int buttonPin = 2;  // Pin for the button
int buzzerPin = 3;  // Pin for the piezo buzzer
int middleLED = 5;   // The middle LED position (adjust if needed)
int direction = 1;   // Initial direction (1 = forward, -1 = backward)
int currentLED = 0;  // Start at the first LED
int level = 1;       // Start at level 1
int delayTime = 300; // Initial delay time for LED movement (in ms)
int score = 0;       // Player score
int levelTime = 10;  // Time allowed for each level (in seconds)
unsigned long startTime;  // Time when the level starts
unsigned long lastMoveTime = 0;  // Time when the LEDs last moved
unsigned long lastToneTime = 0;  // Time to control the duration of the tone
unsigned long buttonDebounceTime = 0; // For button debouncing
unsigned long debounceDelay = 50;     // Debounce delay in milliseconds

int lastRemainingTime = -1; // To track the last displayed time
int lastLevel = -1;         // To track the last displayed level
int lastScore = -1;         // To track the last displayed score

bool buttonPressed = false;
bool tonePlaying = false;  // Flag to indicate whether the tone is currently playing
bool gameEnded = false;    // Flag to indicate the end of the game

// Function to play a tone non-blocking
void startTone(int frequency) {
  tone(buzzerPin, frequency);  // Start playing the tone
  tonePlaying = true;          // Set tone playing flag
  lastToneTime = millis();     // Record when the tone started
}

void stopTone() {
  noTone(buzzerPin);           // Stop playing the tone
  tonePlaying = false;         // Reset tone playing flag
}

void setup() {
  // Initialize the LCD
  lcd.init();
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("HUNTER Game");
  delay(2000);  // Display the welcome message for 2 seconds

  // Pin configurations
  for (int i = 0; i < 10; i++) {
    pinMode(ledPins[i], OUTPUT);  // Set all LED pins as outputs
  }
  pinMode(buttonPin, INPUT_PULLUP);  // Set button pin as input with pullup
  pinMode(buzzerPin, OUTPUT);  // Set buzzer pin as output
  
  Serial.begin(9600);  // Start serial for debugging
  Serial.println("Welcome to the Knight Rider Hunter Game!");
  
  lcd.clear();
  displayStatus();  // Display the initial level, score, and time
  startTime = millis();  // Record when the level starts
  lastMoveTime = millis();  // Initialize the last move time
}

void loop() {
  // If the game has ended, stop further operations
  if (gameEnded) {
    return;
  }

  // Check if the timer has expired
  unsigned long elapsedTime = (millis() - startTime) / 1000;
  int remainingTime = levelTime - elapsedTime;

  // If time has run out, end the game
  if (remainingTime <= 0) {
    endGame();  // Call the function to end the game
    return;     // Stop further execution
  }

  // Handle LED movement (non-blocking using millis)
  if (millis() - lastMoveTime >= delayTime) {
    moveLED();
    lastMoveTime = millis();  // Reset the last move time
    startTone(1000);  // Start playing the 1000Hz tone when moving LED
  }

  // Stop the tone after 50ms
  if (tonePlaying && millis() - lastToneTime >= 50) {
    stopTone();  // Stop the tone after 50ms
  }

  // Check if the button is pressed (with debouncing)
  int buttonState = digitalRead(buttonPin);
  if (buttonState == LOW && millis() - buttonDebounceTime > debounceDelay) {
    buttonDebounceTime = millis();  // Reset debounce timer
    if (currentLED == middleLED) {
      Serial.println("You win this level!");
      score++;  // Increase score for a successful hit
      startTone(2000);  // Play victory tone
      delay(500);  // Play for 500ms
      stopTone();
      levelUp();  // Go to the next level
    } else {
      Serial.println("Missed! Try again.");
      startTone(500);  // Play fail sound
      delay(300);  // Play for 300ms
      stopTone();
    }
    delay(500);  // Short pause after button press
  }

  // Update LCD with the remaining time and score only if there's a change
  if (remainingTime != lastRemainingTime || level != lastLevel || score != lastScore) {
    displayStatus();
    lastRemainingTime = remainingTime;
    lastLevel = level;
    lastScore = score;
  }
}

// Function to move the LEDs
void moveLED() {
  // Turn off all LEDs
  for (int i = 0; i < 10; i++) {
    digitalWrite(ledPins[i], LOW);
  }

  // Light up the current LED
  digitalWrite(ledPins[currentLED], HIGH);

  // Move the LED in the current direction
  currentLED += direction;

  // Reverse direction if we reach the end of the LED array
  if (currentLED == 9 || currentLED == 0) {
    direction = -direction;
  }
}

// Function to advance to the next level
void levelUp() {
  level++;  // Increase the level
  delayTime -= 30;  // Decrease delay time to make LEDs move faster

  // Ensure delayTime doesn't go below a certain threshold (e.g., 50 ms)
  if (delayTime < 50) {
    delayTime = 50;
  }

  Serial.print("Level Up! Now at Level: ");
  Serial.println(level);
  Serial.print("New LED Speed (ms): ");
  Serial.println(delayTime);

  // Play level-up sound
  startTone(1500);  // Play a tone at 1.5kHz
  delay(300);       // Play for 300ms
  stopTone();
  delay(1000);  // Give a 1-second pause before starting the next level

  // Reset the timer for the next level
  startTime = millis();
}

// Function to display the current level, score, and remaining time
void displayStatus() {
  // Calculate the remaining time in seconds
  unsigned long elapsedTime = (millis() - startTime) / 1000;  // Convert to seconds
  int remainingTime = levelTime - elapsedTime;  // Calculate the remaining time
  
  // Ensure the remaining time does not go negative
  if (remainingTime < 0) {
    remainingTime = 0;
  }
  
  lcd.setCursor(0, 0);
  lcd.print("Lvl:");
  lcd.print(level);

  lcd.setCursor(6, 0);
  lcd.print("Time:");
  lcd.print(remainingTime);  // Correctly print the remaining time in seconds
  
  // Clear any leftover characters by overwriting with a space
  if (remainingTime < 10) {
    lcd.print(" ");  // Overwrite the extra digit when remainingTime is a single digit
  }

  lcd.setCursor(0, 1);
  lcd.print("Score:");
  lcd.print(score);
}



// Function to end the game when the timer runs out
void endGame() {
  gameEnded = true;  // Set the flag to end the game

  // Turn off all LEDs
  for (int i = 0; i < 8; i++) {
    digitalWrite(ledPins[i], LOW);
  }

  // Display "End Game" and the final score on the LCD
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("End Game");
  lcd.setCursor(0, 1);
  lcd.print("Score:");
  lcd.print(score);

  // Play a sound to indicate the game has ended
  startTone(300);  // Play a low tone
  delay(1000);     // Play for 1 second
  stopTone();

  // Wait for 3 seconds before starting a new game
  delay(3000);

  // Restart the game
  resetGame();
}

// Function to reset the game and start a new one
void resetGame() {
  gameEnded = false;  // Reset the game end flag
  score = 0;          // Reset the score to 0
  level = 1;          // Reset the level to 1
  delayTime = 300;    // Reset the delay time for LED movement
  startTime = millis();  // Reset the timer

  // Clear the LCD and display the initial status
  lcd.clear();
  displayStatus();

  Serial.println("New game started!");
  
  // Restart the LED movement immediately
  lastMoveTime = millis();
}
