#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>

// --- Custom RGB565 Colors ---
#define COLOR_WHITE     0xFFFF
#define COLOR_BLACK     0x0000
#define COLOR_GREEN     0x03E0
#define COLOR_DARKGREY  0x7BEF
#define COLOR_RED       0xF800
#define COLOR_BLUE      0x001F
#define COLOR_YELLOW    0xFFE0
#define COLOR_CYAN      0x07FF
#define COLOR_ORANGE    0xFD20
#define COLOR_PURPLE    0x8010

// --- Layout & UI Constants ---
const int HEADER_Y      = 15;
const int DIVIDER_Y     = 45;
const int APP_START_Y   = 60;
const int APP_SPACING   = 32;

// --- TFT Display Pin Definitions ---
#define TFT_CS   5
#define TFT_DC   27
#define TFT_RST  33

// --- Physical Button Pins ---
#define PIN_BTN_RED     21   // Select / Back
#define PIN_BTN_YELLOW  22   // Down / End / Quit
#define PIN_BTN_BLUE    26   // Up / Start / Restart
#define PIN_BTN_WHITE   4    // Dedicated Lock Button

// --- Joystick & Buzzer Pins ---
#define PIN_JOY_X       34   // Analog VRx
#define PIN_JOY_Y       35   // Analog VRy
#define PIN_JOY_SW      32   // Joystick Button (Confirm)
#define PIN_BUZZER      25   // Passive Buzzer

Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);

// --- Screen States ---
enum AppState {
  STATE_LOCK,
  STATE_HOME,
  STATE_ABOUT,
  STATE_GAMES_MENU,
  STATE_PING_PONG_INTRO,
  STATE_PING_PONG,
  STATE_PING_PONG_GAMEOVER,
  STATE_SNAKE_INTRO,
  STATE_SNAKE,
  STATE_SNAKE_GAMEOVER,
  STATE_TETRIS_INTRO,
  STATE_TETRIS,
  STATE_TETRIS_GAMEOVER,
  STATE_MUSIC_MENU,
  STATE_MUSIC_PLAYING,
  STATE_STOPWATCH,
  STATE_TIMER_SETUP,
  STATE_TIMER_RUNNING
};

enum Direction {
  CENTER,
  UP,
  DOWN,
  LEFT,
  RIGHT
};

AppState currentScreen = STATE_LOCK;
int selectedIndex = 0;
int gameSelectedIndex = 0;
int musicSelectedIndex = 0;

// App Menu List
const char* apps[] = {"About", "Games", "Music", "Stopwatch", "Timer"};
const int totalApps = 5;

// Games List
const char* gamesMenu[] = {"Ping Pong", "Snake", "Tetris"};
const int totalGames = 3;

// Music List
const char* musicMenu[] = {"Nokia Tune", "Mario Theme", "Tetris", "Happy Birthday", "Imperial March"};
const int totalMusic = 5;

// About Screen Scroll Offset
int aboutScrollOffset = 0;
const int maxAboutScroll = 60;

// Stopwatch Variables
unsigned long swStartTime = 0;
unsigned long swElapsedTime = 0;
bool swRunning = false;

// Timer Variables
int timerMins = 0;
int timerSecs = 10;
bool editingSeconds = true;
unsigned long timerTargetMillis = 0;

// --- Snake Variables (Landscape 320x240) ---
const int MAX_SNAKE = 150;
int snakeX[MAX_SNAKE];
int snakeY[MAX_SNAKE];
int snakeLength = 3;
int oldTailX, oldTailY;
int foodX, foodY;
const int PLAYER_SIZE = 10;
Direction snakeDir = RIGHT;

// --- Ping Pong Variables (Landscape 320x240) ---
int playerPaddleY = 100;
int oldPlayerPaddleY = 100;
int aiPaddleY = 100;
int oldAiPaddleY = 100;
const int PADDLE_WIDTH = 6;
const int PADDLE_HEIGHT = 35;
float ballX = 160, ballY = 120;
float oldBallX = 160, oldBallY = 120;
float ballSpeedX = 3, ballSpeedY = 2;
const int BALL_SIZE = 6;
int playerScore = 0, aiScore = 0;

// --- Tetris Variables (Landscape 320x240) ---
const int TETRIS_COLS = 10;
const int TETRIS_ROWS = 20;
const int T_BLOCK = 10;
const int TETRIS_X = 110;
const int TETRIS_Y = 20;
byte tetrisGrid[20][10];
int currentPiece = 0;
int currentRot = 0;
int pieceX = 3, pieceY = 0;
int tetrisScore = 0;
unsigned long lastTetrisDrop = 0;
int tetrisDropSpeed = 400;

// Tetromino definitions (I, J, L, O, S, T, Z)
const byte tetrominos[7][4][4] = {
  {{0,0,0,0},{1,1,1,1},{0,0,0,0},{0,0,0,0}},
  {{1,0,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
  {{0,0,1,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
  {{1,1,0,0},{1,1,0,0},{0,0,0,0},{0,0,0,0}},
  {{0,1,1,0},{1,1,0,0},{0,0,0,0},{0,0,0,0}},
  {{0,1,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
  {{1,1,0,0},{0,1,1,0},{0,0,0,0},{0,0,0,0}}
};

uint16_t tetrisColors[] = {COLOR_BLACK, COLOR_CYAN, COLOR_BLUE, COLOR_ORANGE, COLOR_YELLOW, COLOR_GREEN, COLOR_PURPLE, COLOR_RED};

// Debounce control
unsigned long lastButtonPress = 0;
const unsigned long debounceDelay = 200;
unsigned long lastJoystickAction = 0;
const unsigned long joystickDelay = 250;

// Forward declarations
void playBootSound();
void playLockSound();
void playMusicTrack(int trackIndex);
void drawMusicMenuScreen();
void drawMusicPlayingScreen(const char* trackName);
void drawLockScreen();
void showGameOverScreen(const char* title, int score);

void setup() {
  Serial.begin(115200);

  pinMode(PIN_BTN_RED, INPUT_PULLUP);
  pinMode(PIN_BTN_YELLOW, INPUT_PULLUP);
  pinMode(PIN_BTN_BLUE, INPUT_PULLUP);
  pinMode(PIN_BTN_WHITE, INPUT_PULLUP);
  pinMode(PIN_JOY_SW, INPUT_PULLUP);
  pinMode(PIN_BUZZER, OUTPUT);

  tft.begin();
  tft.setRotation(1); // Landscape mode (320x240)
  
  drawLockScreen();
}

void loop() {
  handleButtons();
  handleAppLoops();
}

// ==========================================
// SOUND FX & MUSIC ENGINE
// ==========================================

void playBootSound() {
  tone(PIN_BUZZER, 523, 80);  // C5
  delay(90);
  tone(PIN_BUZZER, 659, 80);  // E5
  delay(90);
  tone(PIN_BUZZER, 784, 80);  // G5
  delay(90);
  tone(PIN_BUZZER, 1046, 200); // C6
  delay(220);
  noTone(PIN_BUZZER);
}

void playLockSound() {
  tone(PIN_BUZZER, 784, 80);  // G5
  delay(90);
  tone(PIN_BUZZER, 659, 80);  // E5
  delay(90);
  tone(PIN_BUZZER, 523, 150); // C5
  delay(160);
  noTone(PIN_BUZZER);
}

void playMusicTrack(int trackIndex) {
  drawMusicPlayingScreen(musicMenu[trackIndex]);
  
  if (trackIndex == 0) { // Nokia Tune
    int notes[] = {659, 587, 369, 415, 277, 330, 246, 277, 330, 415, 659, 587, 494, 523, 440, 440};
    int durations[] = {140, 140, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 400, 500};
    int totalNotes = 16;
    for (int i = 0; i < totalNotes; i++) {
      if (digitalRead(PIN_BTN_YELLOW) == LOW || digitalRead(PIN_BTN_RED) == LOW || digitalRead(PIN_BTN_WHITE) == LOW) break;
      tone(PIN_BUZZER, notes[i], durations[i] * 0.8);
      delay(durations[i]);
      noTone(PIN_BUZZER);
    }
  } 
  else if (trackIndex == 1) { // Mario Theme
    int notes[] = {659, 659, 0, 659, 0, 523, 659, 0, 784, 0, 392, 0, 523, 0, 392, 0, 330, 0, 440, 494, 466, 440, 392, 659, 784, 880, 698, 784, 659, 523, 587, 494};
    int durations[] = {120, 120, 60, 120, 60, 120, 120, 60, 240, 120, 240, 120, 180, 60, 180, 60, 180, 60, 180, 180, 120, 180, 120, 120, 120, 180, 120, 120, 180, 120, 120, 120};
    int totalNotes = 32;
    for (int i = 0; i < totalNotes; i++) {
      if (digitalRead(PIN_BTN_YELLOW) == LOW || digitalRead(PIN_BTN_RED) == LOW || digitalRead(PIN_BTN_WHITE) == LOW) break;
      if (notes[i] > 0) {
        tone(PIN_BUZZER, notes[i], durations[i] * 0.8);
      }
      delay(durations[i]);
      noTone(PIN_BUZZER);
    }
  }
  else if (trackIndex == 2) { // Tetris
    int notes[] = {659, 494, 523, 587, 523, 494, 440, 440, 523, 659, 587, 523, 494, 523, 587, 659, 523, 440, 440, 0};
    int durations[] = {180, 90, 90, 180, 90, 90, 180, 90, 90, 180, 90, 90, 250, 180, 180, 250, 180, 250, 350, 200};
    int totalNotes = 20;
    for (int i = 0; i < totalNotes; i++) {
      if (digitalRead(PIN_BTN_YELLOW) == LOW || digitalRead(PIN_BTN_RED) == LOW || digitalRead(PIN_BTN_WHITE) == LOW) break;
      if (notes[i] > 0) {
        tone(PIN_BUZZER, notes[i], durations[i] * 0.8);
      }
      delay(durations[i]);
      noTone(PIN_BUZZER);
    }
  }
  else if (trackIndex == 3) { // Happy Birthday
    int notes[] = {392, 392, 440, 392, 523, 494, 392, 392, 440, 392, 587, 523, 392, 392, 784, 659, 523, 494, 440, 698, 698, 659, 523, 587, 523};
    int durations[] = {150, 150, 300, 300, 300, 500, 150, 150, 300, 300, 300, 500, 150, 150, 300, 300, 300, 300, 500, 150, 150, 300, 300, 300, 600};
    int totalNotes = 25;
    for (int i = 0; i < totalNotes; i++) {
      if (digitalRead(PIN_BTN_YELLOW) == LOW || digitalRead(PIN_BTN_RED) == LOW || digitalRead(PIN_BTN_WHITE) == LOW) break;
      tone(PIN_BUZZER, notes[i], durations[i] * 0.8);
      delay(durations[i]);
      noTone(PIN_BUZZER);
    }
  }
  else if (trackIndex == 4) { // Imperial March
    int notes[] = {440, 440, 440, 349, 523, 440, 349, 523, 440, 659, 659, 659, 698, 523, 415, 440, 349, 523, 440};
    int durations[] = {250, 250, 250, 180, 80, 250, 180, 80, 500, 250, 250, 250, 180, 80, 250, 180, 80, 500};
    int totalNotes = 19;
    for (int i = 0; i < totalNotes; i++) {
      if (digitalRead(PIN_BTN_YELLOW) == LOW || digitalRead(PIN_BTN_RED) == LOW || digitalRead(PIN_BTN_WHITE) == LOW) break;
      tone(PIN_BUZZER, notes[i], durations[i] * 0.8);
      delay(durations[i]);
      noTone(PIN_BUZZER);
    }
  }
  noTone(PIN_BUZZER);
}

// ==========================================
// SCREEN RENDERERS
// ==========================================

void drawLockScreen() {
  tft.fillScreen(COLOR_WHITE);
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(3);
  tft.setCursor(110, 65);
  tft.print("CURIO");

  tft.drawRect(40, 160, 240, 40, COLOR_DARKGREY);
  tft.setTextColor(COLOR_BLACK);
  tft.setTextSize(1);
  tft.setCursor(90, 176);
  tft.print("Press RED to unlock");
}

void drawHomeScreen() {
  tft.fillScreen(COLOR_WHITE);
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(2);
  tft.setCursor(15, HEADER_Y);
  tft.print("CURIO");

  tft.drawFastHLine(15, DIVIDER_Y, 290, COLOR_DARKGREY);

  for (int i = 0; i < totalApps; i++) {
    int yPos = APP_START_Y + (i * APP_SPACING);
    tft.setCursor(20, yPos);
    
    if (i == selectedIndex) {
      tft.setTextColor(COLOR_GREEN);
      tft.print("> ");
    } else {
      tft.setTextColor(COLOR_BLACK);
      tft.print("  ");
    }
    tft.print(apps[i]);
  }
}

void drawGamesMenuScreen() {
  tft.fillScreen(COLOR_WHITE);
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(2);
  tft.setCursor(15, HEADER_Y);
  tft.print("< Games");

  tft.drawFastHLine(15, DIVIDER_Y, 290, COLOR_DARKGREY);

  for (int i = 0; i < totalGames; i++) {
    int yPos = APP_START_Y + (i * APP_SPACING);
    tft.setCursor(20, yPos);
    
    if (i == gameSelectedIndex) {
      tft.setTextColor(COLOR_GREEN);
      tft.print("> ");
    } else {
      tft.setTextColor(COLOR_BLACK);
      tft.print("  ");
    }
    tft.print(gamesMenu[i]);
  }
  
  tft.setTextSize(1);
  tft.setCursor(20, 210);
  tft.setTextColor(COLOR_DARKGREY);
  tft.print("BLUE/YEL: Navigate | Joy Click: Select | RED: Back");
}

void drawMusicMenuScreen() {
  tft.fillScreen(COLOR_WHITE);
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(2);
  tft.setCursor(15, HEADER_Y);
  tft.print("< Music");

  tft.drawFastHLine(15, DIVIDER_Y, 290, COLOR_DARKGREY);

  for (int i = 0; i < totalMusic; i++) {
    int yPos = 55 + (i * 26);
    tft.setCursor(20, yPos);
    
    if (i == musicSelectedIndex) {
      tft.setTextColor(COLOR_GREEN);
      tft.print("> ");
    } else {
      tft.setTextColor(COLOR_BLACK);
      tft.print("  ");
    }
    tft.print(musicMenu[i]);
  }
  
  tft.setTextSize(1);
  tft.setCursor(20, 210);
  tft.setTextColor(COLOR_DARKGREY);
  tft.print("BLUE/YEL: Navigate | JOY: Play | RED: Back");
}

void drawMusicPlayingScreen(const char* trackName) {
  tft.fillScreen(COLOR_WHITE);
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(2);
  tft.setCursor(15, HEADER_Y);
  tft.print("< Music");

  tft.drawFastHLine(15, DIVIDER_Y, 290, COLOR_DARKGREY);

  tft.setTextColor(COLOR_BLACK);
  tft.setTextSize(2);
  tft.setCursor(20, 75);
  tft.print("Playing...");

  tft.setCursor(20, 115);
  tft.setTextColor(COLOR_GREEN);
  tft.print("♪ ");
  tft.print(trackName);

  tft.setTextSize(1);
  tft.setCursor(20, 195);
  tft.setTextColor(COLOR_DARKGREY);
  tft.print("YEL: Stop  |  RED: Back");
}

void drawAboutScreen() {
  tft.fillScreen(COLOR_WHITE);
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(2);
  tft.setCursor(15, HEADER_Y);
  tft.print("< About");

  tft.drawFastHLine(15, DIVIDER_Y, 290, COLOR_DARKGREY);

  int startY = 60 - aboutScrollOffset;
  tft.setTextColor(COLOR_BLACK);
  tft.setTextSize(1);
  
  tft.setCursor(20, startY);
  tft.print("A portable handheld");
  tft.setCursor(20, startY + 15);
  tft.print("computer powered");
  tft.setCursor(20, startY + 30);
  tft.print("by ESP32.");

  tft.setCursor(20, startY + 60);
  tft.print("Made by");
  tft.setCursor(20, startY + 75);
  tft.setTextSize(2);
  tft.setTextColor(COLOR_GREEN);
  tft.print("Aylin Muzaffarli");

  tft.setCursor(20, startY + 105);
  tft.setTextSize(1);
  tft.setTextColor(COLOR_BLACK);
  tft.print("August 2026");
}

void drawStopwatchScreen() {
  tft.fillScreen(COLOR_WHITE);
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(2);
  tft.setCursor(15, HEADER_Y);
  tft.print("< Stopwatch");

  tft.drawFastHLine(15, DIVIDER_Y, 290, COLOR_DARKGREY);

  tft.setTextColor(COLOR_BLACK);
  tft.setTextSize(3);
  tft.setCursor(50, 90);
  tft.print("00:00.000");

  tft.setTextSize(1);
  tft.setCursor(20, 185);
  tft.setTextColor(COLOR_DARKGREY);
  tft.print("BLUE: Start/Pause | YEL: Reset | RED: Back");
}

void drawTimerSetupScreen() {
  tft.fillScreen(COLOR_WHITE);
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(2);
  tft.setCursor(15, HEADER_Y);
  tft.print("< Timer Setup");

  tft.drawFastHLine(15, DIVIDER_Y, 290, COLOR_DARKGREY);

  tft.setTextColor(COLOR_BLACK);
  tft.setTextSize(3);
  tft.setCursor(75, 95);

  if (!editingSeconds) tft.print("["); else tft.print(" ");
  if (timerMins < 10) tft.print("0");
  tft.print(timerMins);
  if (!editingSeconds) tft.print("]"); else tft.print(" ");

  tft.print(":");

  if (editingSeconds) tft.print("["); else tft.print(" ");
  if (timerSecs < 10) tft.print("0");
  tft.print(timerSecs);
  if (editingSeconds) tft.print("]"); else tft.print(" ");

  tft.setTextSize(1);
  tft.setCursor(15, 170);
  tft.setTextColor(COLOR_DARKGREY);
  tft.print("Joy Left/Right: Switch Min/Sec");
  tft.setCursor(25, 185);
  tft.print("Joy Up/Down: Adjust Value (+/-)");
  tft.setCursor(25, 200);
  tft.print("Press Joy SW: Start | RED: Back");
}

void drawTimerRunningScreen(int remainingTotalSecs) {
  tft.fillScreen(COLOR_WHITE);
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(2);
  tft.setCursor(15, HEADER_Y);
  tft.print("< Timer Active");

  tft.drawFastHLine(15, DIVIDER_Y, 290, COLOR_DARKGREY);

  int mins = remainingTotalSecs / 60;
  int secs = remainingTotalSecs % 60;

  tft.setTextColor(COLOR_BLACK);
  tft.setTextSize(3);
  tft.setCursor(85, 100);
  if (mins < 10) tft.print("0");
  tft.print(mins);
  tft.print(":");
  if (secs < 10) tft.print("0");
  tft.print(secs);

  tft.setTextSize(1);
  tft.setCursor(55, 195);
  tft.setTextColor(COLOR_DARKGREY);
  tft.print("Press Joy SW to Stop");
}

void showGameOverScreen(const char* title, int score) {
  tft.fillScreen(COLOR_WHITE);
  tft.setTextColor(COLOR_RED);
  tft.setTextSize(3);
  tft.setCursor(80, 45);
  tft.println(title);

  tft.setTextColor(COLOR_BLACK);
  tft.setTextSize(2);
  tft.setCursor(100, 95);
  tft.print("Score: ");
  tft.println(score);

  tft.setCursor(55, 145);
  tft.println("BLUE Button: Restart");
  tft.setCursor(55, 185);
  tft.println("YELLOW Button: End");
}

// ==========================================
// INPUT & APP LOGIC HANDLERS
// ==========================================

void handleButtons() {
  if (millis() - lastButtonPress < debounceDelay) return;

  bool redPressed = (digitalRead(PIN_BTN_RED) == LOW);
  bool yellowPressed = (digitalRead(PIN_BTN_YELLOW) == LOW);
  bool bluePressed = (digitalRead(PIN_BTN_BLUE) == LOW);
  bool whitePressed = (digitalRead(PIN_BTN_WHITE) == LOW);
  bool joyPressed = (digitalRead(PIN_JOY_SW) == LOW);
  
  int joyX = analogRead(PIN_JOY_X);
  int joyY = analogRead(PIN_JOY_Y);

  if (currentScreen == STATE_LOCK) {
    if (redPressed) {
      lastButtonPress = millis();
      playBootSound();
      currentScreen = STATE_HOME;
      selectedIndex = 0;
      drawHomeScreen();
    }
  } 
  else {
    // Dedicated White Button (GPIO 4) locks screen instantly from anywhere when unlocked
    if (whitePressed) {
      lastButtonPress = millis();
      playLockSound();
      currentScreen = STATE_LOCK;
      drawLockScreen();
      return;
    }

    if (currentScreen == STATE_HOME) {
      if (bluePressed) {
        lastButtonPress = millis();
        selectedIndex = (selectedIndex - 1 + totalApps) % totalApps;
        drawHomeScreen();
      } 
      else if (yellowPressed) {
        lastButtonPress = millis();
        selectedIndex = (selectedIndex + 1) % totalApps;
        drawHomeScreen();
      } 
      else if (redPressed) {
        lastButtonPress = millis();
        if (selectedIndex == 0) {
          currentScreen = STATE_ABOUT;
          aboutScrollOffset = 0;
          drawAboutScreen();
        } else if (selectedIndex == 1) {
          currentScreen = STATE_GAMES_MENU;
          gameSelectedIndex = 0;
          drawGamesMenuScreen();
        } else if (selectedIndex == 2) {
          currentScreen = STATE_MUSIC_MENU;
          musicSelectedIndex = 0;
          drawMusicMenuScreen();
        } else if (selectedIndex == 3) {
          currentScreen = STATE_STOPWATCH;
          swStartTime = millis();
          swElapsedTime = 0;
          swRunning = false;
          drawStopwatchScreen();
        } else if (selectedIndex == 4) {
          currentScreen = STATE_TIMER_SETUP;
          timerMins = 0;
          timerSecs = 10;
          editingSeconds = true;
          drawTimerSetupScreen();
        }
      }
    }
    else if (currentScreen == STATE_GAMES_MENU) {
      if (bluePressed) {
        lastButtonPress = millis();
        gameSelectedIndex = (gameSelectedIndex - 1 + totalGames) % totalGames;
        drawGamesMenuScreen();
      }
      else if (yellowPressed) {
        lastButtonPress = millis();
        gameSelectedIndex = (gameSelectedIndex + 1) % totalGames;
        drawGamesMenuScreen();
      }
      else if (joyPressed) {
        lastButtonPress = millis();
        if (gameSelectedIndex == 0) {
          showPingPongIntro();
        } else if (gameSelectedIndex == 1) {
          showSnakeIntro();
        } else if (gameSelectedIndex == 2) {
          showTetrisIntro();
        }
      }
      else if (redPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_HOME;
        drawHomeScreen();
      }
    }
    else if (currentScreen == STATE_MUSIC_MENU) {
      if (bluePressed) {
        lastButtonPress = millis();
        musicSelectedIndex = (musicSelectedIndex - 1 + totalMusic) % totalMusic;
        drawMusicMenuScreen();
      }
      else if (yellowPressed) {
        lastButtonPress = millis();
        musicSelectedIndex = (musicSelectedIndex + 1) % totalMusic;
        drawMusicMenuScreen();
      }
      else if (joyPressed) {
        lastButtonPress = millis();
        playMusicTrack(musicSelectedIndex);
        drawMusicMenuScreen();
      }
      else if (redPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_HOME;
        drawHomeScreen();
      }
    }
    else if (currentScreen == STATE_MUSIC_PLAYING) {
      if (yellowPressed || redPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_MUSIC_MENU;
        drawMusicMenuScreen();
      }
    }
    else if (currentScreen == STATE_PING_PONG_INTRO) {
      if (bluePressed) {
        lastButtonPress = millis();
        startPingPongGame();
      }
      else if (yellowPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_GAMES_MENU;
        drawGamesMenuScreen();
      }
    }
    else if (currentScreen == STATE_PING_PONG_GAMEOVER) {
      if (bluePressed) {
        lastButtonPress = millis();
        startPingPongGame();
      }
      else if (yellowPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_GAMES_MENU;
        drawGamesMenuScreen();
      }
    }
    else if (currentScreen == STATE_SNAKE_INTRO) {
      if (bluePressed) {
        lastButtonPress = millis();
        startSnakeGame();
      }
      else if (yellowPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_GAMES_MENU;
        drawGamesMenuScreen();
      }
    }
    else if (currentScreen == STATE_SNAKE_GAMEOVER) {
      if (bluePressed) {
        lastButtonPress = millis();
        startSnakeGame();
      }
      else if (yellowPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_GAMES_MENU;
        drawGamesMenuScreen();
      }
    }
    else if (currentScreen == STATE_TETRIS_INTRO) {
      if (bluePressed) {
        lastButtonPress = millis();
        startTetrisGame();
      }
      else if (yellowPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_GAMES_MENU;
        drawGamesMenuScreen();
      }
    }
    else if (currentScreen == STATE_TETRIS_GAMEOVER) {
      if (bluePressed) {
        lastButtonPress = millis();
        startTetrisGame();
      }
      else if (yellowPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_GAMES_MENU;
        drawGamesMenuScreen();
      }
    }
    else if (currentScreen == STATE_ABOUT) {
      if (bluePressed) {
        lastButtonPress = millis();
        aboutScrollOffset = max(0, aboutScrollOffset - 15);
        drawAboutScreen();
      } 
      else if (yellowPressed) {
        lastButtonPress = millis();
        aboutScrollOffset = min(maxAboutScroll, aboutScrollOffset + 15);
        drawAboutScreen();
      } 
      else if (redPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_HOME;
        drawHomeScreen();
      }
    }
    else if (currentScreen == STATE_STOPWATCH) {
      if (redPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_HOME;
        drawHomeScreen();
      }
      else if (bluePressed) {
        lastButtonPress = millis();
        if (!swRunning) {
          swRunning = true;
          swStartTime = millis() - swElapsedTime;
        } else {
          swRunning = false;
        }
      }
      else if (yellowPressed) {
        lastButtonPress = millis();
        swRunning = false;
        swElapsedTime = 0;
        drawStopwatchScreen();
      }
    }
    else if (currentScreen == STATE_TIMER_SETUP) {
      if (redPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_HOME;
        drawHomeScreen();
      }

      if (millis() - lastJoystickAction > joystickDelay) {
        if (joyX < 1000) {
          editingSeconds = false;
          lastJoystickAction = millis();
          drawTimerSetupScreen();
        } else if (joyX > 3000) {
          editingSeconds = true;
          lastJoystickAction = millis();
          drawTimerSetupScreen();
        }

        if (joyY < 1000) {
          lastJoystickAction = millis();
          if (!editingSeconds) {
            if (timerMins < 59) timerMins++;
          } else {
            if (timerSecs < 59) timerSecs++;
          }
          drawTimerSetupScreen();
        } else if (joyY > 3000) {
          lastJoystickAction = millis();
          if (!editingSeconds) {
            if (timerMins > 0) timerMins--;
          } else {
            if (timerSecs > 0) timerSecs--;
          }
          drawTimerSetupScreen();
        }
      }

      if (joyPressed) {
        lastButtonPress = millis();
        int totalSecs = (timerMins * 60) + timerSecs;
        if (totalSecs > 0) {
          timerTargetMillis = millis() + (totalSecs * 1000UL);
          currentScreen = STATE_TIMER_RUNNING;
          drawTimerRunningScreen(totalSecs);
        }
      }
    }
    else if (currentScreen == STATE_TIMER_RUNNING) {
      if (joyPressed) {
        lastButtonPress = millis();
        currentScreen = STATE_TIMER_SETUP;
        drawTimerSetupScreen();
      }
    }
  }
}

void handleAppLoops() {
  if (currentScreen == STATE_STOPWATCH && swRunning) {
    swElapsedTime = millis() - swStartTime;
    unsigned long totalSecs = swElapsedTime / 1000;
    unsigned long mins = totalSecs / 60;
    unsigned long secs = totalSecs % 60;
    unsigned long millisecs = swElapsedTime % 1000;

    tft.fillRect(45, 80, 240, 40, COLOR_WHITE);
    tft.setTextColor(COLOR_BLACK);
    tft.setTextSize(3);
    tft.setCursor(50, 90);
    
    if (mins < 10) tft.print("0");
    tft.print(mins);
    tft.print(":");
    if (secs < 10) tft.print("0");
    tft.print(secs);
    tft.print(".");
    if (millisecs < 100) tft.print("0");
    if (millisecs < 10) tft.print("0");
    tft.print(millisecs);
  }
  else if (currentScreen == STATE_TIMER_RUNNING) {
    unsigned long now = millis();
    if (now >= timerTargetMillis) {
      playTimerAlarm();
      currentScreen = STATE_TIMER_SETUP;
      drawTimerSetupScreen();
    } else {
      unsigned long remainingMillis = timerTargetMillis - now;
      int remainingSecs = (remainingMillis / 1000) + 1;
      drawTimerRunningScreen(remainingSecs);
      delay(200);
    }
  }
  else if (currentScreen == STATE_PING_PONG) {
    updatePingPong();
  }
  else if (currentScreen == STATE_SNAKE) {
    updateSnake();
  }
  else if (currentScreen == STATE_TETRIS) {
    updateTetris();
  }
}

// ==========================================
// GAME ENGINES
// ==========================================

void showSnakeIntro() {
  currentScreen = STATE_SNAKE_INTRO;
  tft.fillScreen(COLOR_WHITE);
  
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(3);
  tft.setCursor(110, 45);
  tft.println("SNAKE");

  tft.setTextColor(COLOR_BLACK);
  tft.setTextSize(2);
  tft.setCursor(55, 110);
  tft.println("BLUE Button: Start");
  tft.setCursor(55, 150);
  tft.println("YELLOW Button: End");
}

void startSnakeGame() {
  currentScreen = STATE_SNAKE;
  tft.fillScreen(COLOR_BLACK);
  snakeLength = 3;
  for (int i = 0; i < snakeLength; i++) {
    snakeX[i] = 160 - (i * PLAYER_SIZE);
    snakeY[i] = 120;
  }
  snakeDir = RIGHT;
  spawnFood();
  drawFood();
  drawPlayer();
}

void showPingPongIntro() {
  currentScreen = STATE_PING_PONG_INTRO;
  tft.fillScreen(COLOR_WHITE);
  
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(3);
  tft.setCursor(85, 45);
  tft.println("PING PONG");

  tft.setTextColor(COLOR_BLACK);
  tft.setTextSize(2);
  tft.setCursor(55, 110);
  tft.println("BLUE Button: Start");
  tft.setCursor(55, 150);
  tft.println("YELLOW Button: End");
}

void startPingPongGame() {
  currentScreen = STATE_PING_PONG;
  tft.fillScreen(COLOR_BLACK);
  playerScore = 0;
  aiScore = 0;
  playerPaddleY = 100;
  oldPlayerPaddleY = 100;
  aiPaddleY = 100;
  oldAiPaddleY = 100;
  ballX = 160;
  ballY = 120;
  oldBallX = 160;
  oldBallY = 120;
  ballSpeedX = (random(0, 2) == 0) ? 3 : -3;
  ballSpeedY = (random(0, 2) == 0) ? 2 : -2;

  for (int i = 0; i < 240; i += 10) {
    tft.drawFastVLine(160, i, 5, COLOR_DARKGREY);
  }
}

void showTetrisIntro() {
  currentScreen = STATE_TETRIS_INTRO;
  tft.fillScreen(COLOR_WHITE);
  
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(3);
  tft.setCursor(110, 45);
  tft.println("TETRIS");

  tft.setTextColor(COLOR_BLACK);
  tft.setTextSize(2);
  tft.setCursor(55, 110);
  tft.println("BLUE Button: Start");
  tft.setCursor(55, 150);
  tft.println("YELLOW Button: End");
}

void spawnTetrisPiece() {
  currentPiece = random(0, 7) + 1;
  currentRot = 0;
  pieceX = 3;
  pieceY = 0;
  if (checkTetrisCollision(pieceX, pieceY, currentRot)) {
    currentScreen = STATE_TETRIS_GAMEOVER;
    showGameOverScreen("GAME OVER", tetrisScore);
  }
}

void startTetrisGame() {
  currentScreen = STATE_TETRIS;
  tft.fillScreen(COLOR_BLACK);
  tetrisScore = 0;
  tetrisDropSpeed = 400; // Reset speed
  for (int r = 0; r < TETRIS_ROWS; r++) {
    for (int c = 0; c < TETRIS_COLS; c++) {
      tetrisGrid[r][c] = 0;
    }
  }
  tft.drawRect(TETRIS_X - 2, TETRIS_Y - 2, (TETRIS_COLS * T_BLOCK) + 4, (TETRIS_ROWS * T_BLOCK) + 4, COLOR_DARKGREY);
  spawnTetrisPiece();
  lastTetrisDrop = millis();
}

bool checkTetrisCollision(int px, int py, int prot) {
  for (int r = 0; r < 4; r++) {
    for (int c = 0; c < 4; c++) {
      int rx = c, ry = r;
      for (int rot = 0; rot < prot; rot++) {
        int temp = rx;
        rx = 3 - ry;
        ry = temp;
      }
      if (tetrominos[currentPiece - 1][ry][rx]) {
        int boardX = px + c;
        int boardY = py + r;
        if (boardX < 0 || boardX >= TETRIS_COLS || boardY >= TETRIS_ROWS) return true;
        if (boardY >= 0 && tetrisGrid[boardY][boardX]) return true;
      }
    }
  }
  return false;
}

void mergeTetrisPiece() {
  for (int r = 0; r < 4; r++) {
    for (int c = 0; c < 4; c++) {
      int rx = c, ry = r;
      for (int rot = 0; rot < currentRot; rot++) {
        int temp = rx;
        rx = 3 - ry;
        ry = temp;
      }
      if (tetrominos[currentPiece - 1][ry][rx]) {
        int boardX = pieceX + c;
        int boardY = pieceY + r;
        if (boardY >= 0 && boardY < TETRIS_ROWS && boardX >= 0 && boardX < TETRIS_COLS) {
          tetrisGrid[boardY][boardX] = currentPiece;
        }
      }
    }
  }
}

void clearTetrisLines() {
  int linesCleared = 0;
  for (int r = TETRIS_ROWS - 1; r >= 0; r--) {
    bool full = true;
    for (int c = 0; c < TETRIS_COLS; c++) {
      if (!tetrisGrid[r][c]) {
        full = false;
        break;
      }
    }
    if (full) {
      linesCleared++;
      for (int tr = r; tr > 0; tr--) {
        for (int c = 0; c < TETRIS_COLS; c++) {
          tetrisGrid[tr][c] = tetrisGrid[tr - 1][c];
        }
      }
      for (int c = 0; c < TETRIS_COLS; c++) tetrisGrid[0][c] = 0;
      r++;
    }
  }

  // Classic Arcade Scoring (multiplied by level progression)
  if (linesCleared == 1) tetrisScore += 100;
  else if (linesCleared == 2) tetrisScore += 300;
  else if (linesCleared == 3) tetrisScore += 500;
  else if (linesCleared >= 4) tetrisScore += 800;

  // Progressive speed up based on score
  tetrisDropSpeed = max(100, 400 - (tetrisScore / 500) * 50);
}

void drawTetrisBoard() {
  for (int r = 0; r < TETRIS_ROWS; r++) {
    for (int c = 0; c < TETRIS_COLS; c++) {
      tft.fillRect(TETRIS_X + (c * T_BLOCK), TETRIS_Y + (r * T_BLOCK), T_BLOCK - 1, T_BLOCK - 1, tetrisColors[tetrisGrid[r][c]]);
    }
  }
  for (int r = 0; r < 4; r++) {
    for (int c = 0; c < 4; c++) {
      int rx = c, ry = r;
      for (int rot = 0; rot < currentRot; rot++) {
        int temp = rx;
        rx = 3 - ry;
        ry = temp;
      }
      if (tetrominos[currentPiece - 1][ry][rx]) {
        int boardX = pieceX + c;
        int boardY = pieceY + r;
        if (boardY >= 0 && boardY < TETRIS_ROWS && boardX >= 0 && boardX < TETRIS_COLS) {
          tft.fillRect(TETRIS_X + (boardX * T_BLOCK), TETRIS_Y + (boardY * T_BLOCK), T_BLOCK - 1, T_BLOCK - 1, tetrisColors[currentPiece]);
        }
      }
    }
  }

  tft.setTextSize(1);
  tft.setTextColor(COLOR_WHITE, COLOR_BLACK);
  tft.setCursor(15, 50);
  tft.print("Score:");
  tft.setCursor(15, 65);
  tft.setTextSize(2);
  tft.print(tetrisScore);
}

void updateTetris() {
  if (digitalRead(PIN_BTN_YELLOW) == LOW) {
    currentScreen = STATE_GAMES_MENU;
    drawGamesMenuScreen();
    delay(200);
    return;
  }

  if (millis() - lastJoystickAction > joystickDelay) {
    int joyX = analogRead(PIN_JOY_X);
    int joyY = analogRead(PIN_JOY_Y);
    bool joyPressed = (digitalRead(PIN_JOY_SW) == LOW);

    if (joyX < 1000) {
      if (!checkTetrisCollision(pieceX - 1, pieceY, currentRot)) {
        pieceX--;
        lastJoystickAction = millis();
      }
    }
    else if (joyX > 3000) {
      if (!checkTetrisCollision(pieceX + 1, pieceY, currentRot)) {
        pieceX++;
        lastJoystickAction = millis();
      }
    }

    if (joyY < 1000 || joyPressed) {
      int nextRot = (currentRot + 1) % 4;
      if (!checkTetrisCollision(pieceX, pieceY, nextRot)) {
        currentRot = nextRot;
        lastJoystickAction = millis();
      }
    } else if (joyY > 3000) {
      if (!checkTetrisCollision(pieceX, pieceY + 1, currentRot)) {
        pieceY++;
        lastJoystickAction = millis();
      }
    }
  }

  if (millis() - lastTetrisDrop > tetrisDropSpeed) {
    if (!checkTetrisCollision(pieceX, pieceY + 1, currentRot)) {
      pieceY++;
    } else {
      mergeTetrisPiece();
      clearTetrisLines();
      spawnTetrisPiece();
    }
    lastTetrisDrop = millis();
  }

  drawTetrisBoard();
  delay(30);
}

void spawnFood() {
  bool valid = false;
  while (!valid) {
    foodX = random(0, 32) * PLAYER_SIZE;
    foodY = random(0, 24) * PLAYER_SIZE;
    valid = true;
    for (int i = 0; i < snakeLength; i++) {
      if (foodX == snakeX[i] && foodY == snakeY[i]) {
        valid = false;
        break;
      }
    }
  }
}

void drawFood() {
  tft.fillRect(foodX, foodY, PLAYER_SIZE, PLAYER_SIZE, COLOR_RED);
}

void drawPlayer() {
  tft.fillRect(oldTailX, oldTailY, PLAYER_SIZE, PLAYER_SIZE, COLOR_BLACK);
  for (int i = 0; i < snakeLength; i++) {
    tft.fillRect(snakeX[i], snakeY[i], PLAYER_SIZE, PLAYER_SIZE, COLOR_GREEN);
  }
}

Direction getJoystickDirection() {
  int x = analogRead(PIN_JOY_X);
  int y = analogRead(PIN_JOY_Y);
  if (x < 1000) return LEFT;
  if (x > 3000) return RIGHT;
  if (y < 1000) return UP;
  if (y > 3000) return DOWN;
  return CENTER;
}

void updatePingPong() {
  if (digitalRead(PIN_BTN_YELLOW) == LOW) {
    currentScreen = STATE_GAMES_MENU;
    drawGamesMenuScreen();
    delay(300);
    return;
  }

  int joyVal = analogRead(PIN_JOY_Y);
  oldPlayerPaddleY = playerPaddleY;
  
  if (joyVal < 1000) {
    playerPaddleY -= 6;
  } else if (joyVal > 3000) {
    playerPaddleY += 6;
  }

  if (playerPaddleY < 0) playerPaddleY = 0;
  if (playerPaddleY > 240 - PADDLE_HEIGHT) playerPaddleY = 240 - PADDLE_HEIGHT;

  oldAiPaddleY = aiPaddleY;
  if (aiPaddleY + (PADDLE_HEIGHT / 2) < ballY - 4) {
    aiPaddleY += 3;
  } else if (aiPaddleY + (PADDLE_HEIGHT / 2) > ballY + 4) {
    aiPaddleY -= 3;
  }

  if (aiPaddleY < 0) aiPaddleY = 0;
  if (aiPaddleY > 240 - PADDLE_HEIGHT) aiPaddleY = 240 - PADDLE_HEIGHT;

  oldBallX = ballX;
  oldBallY = ballY;
  ballX += ballSpeedX;
  ballY += ballSpeedY;

  if (ballY <= 0) { ballY = 0; ballSpeedY = -ballSpeedY; }
  if (ballY >= 240 - BALL_SIZE) { ballY = 240 - BALL_SIZE; ballSpeedY = -ballSpeedY; }

  if (ballX <= 16 && ballX >= 10) {
    if (ballY + BALL_SIZE >= aiPaddleY && ballY <= aiPaddleY + PADDLE_HEIGHT) {
      ballX = 16;
      ballSpeedX = -ballSpeedX;
      float hitOffset = (ballY + (BALL_SIZE/2)) - (aiPaddleY + (PADDLE_HEIGHT/2));
      ballSpeedY += hitOffset * 0.05;
      ballSpeedY = constrain(ballSpeedY, -5, 5); // Clamping ball speed
    }
  }

  if (ballX + BALL_SIZE >= 304 && ballX <= 310) {
    if (ballY + BALL_SIZE >= playerPaddleY && ballY <= playerPaddleY + PADDLE_HEIGHT) {
      ballX = 304 - BALL_SIZE;
      ballSpeedX = -ballSpeedX;
      float hitOffset = (ballY + (BALL_SIZE/2)) - (playerPaddleY + (PADDLE_HEIGHT/2));
      ballSpeedY += hitOffset * 0.05;
      ballSpeedY = constrain(ballSpeedY, -5, 5); // Clamping ball speed
    }
  }

  if (ballX < 0) {
    aiScore++;
    ballX = 160; ballY = 120;
    ballSpeedX = 3; ballSpeedY = random(-2, 3);
  }
  if (ballX > 320) {
    playerScore++;
    ballX = 160; ballY = 120;
    ballSpeedX = -3; ballSpeedY = random(-2, 3);
  }

  if ((int)oldBallX != (int)ballX || (int)oldBallY != (int)ballY) {
    tft.fillRect((int)oldBallX, (int)oldBallY, BALL_SIZE, BALL_SIZE, COLOR_BLACK);
  }

  if (oldAiPaddleY != aiPaddleY) {
    if (aiPaddleY > oldAiPaddleY) {
      tft.fillRect(10, oldAiPaddleY, PADDLE_WIDTH, aiPaddleY - oldAiPaddleY, COLOR_BLACK);
    } else {
      tft.fillRect(10, aiPaddleY + PADDLE_HEIGHT, PADDLE_WIDTH, oldAiPaddleY - aiPaddleY, COLOR_BLACK);
    }
  }

  if (oldPlayerPaddleY != playerPaddleY) {
    if (playerPaddleY > oldPlayerPaddleY) {
      tft.fillRect(304, oldPlayerPaddleY, PADDLE_WIDTH, playerPaddleY - oldPlayerPaddleY, COLOR_BLACK);
    } else {
      tft.fillRect(304, playerPaddleY + PADDLE_HEIGHT, PADDLE_WIDTH, oldPlayerPaddleY - playerPaddleY, COLOR_BLACK);
    }
  }

  if ((int)oldBallX <= 163 && (int)oldBallX >= 157) {
    for (int i = 0; i < 240; i += 10) {
      tft.drawFastVLine(160, i, 5, COLOR_DARKGREY);
    }
  }

  tft.setTextSize(2);
  tft.setTextColor(COLOR_WHITE, COLOR_BLACK);
  tft.setCursor(130, 10);
  tft.print(aiScore);
  tft.setCursor(180, 10);
  tft.print(playerScore);

  tft.fillRect(10, aiPaddleY, PADDLE_WIDTH, PADDLE_HEIGHT, COLOR_BLUE);       
  tft.fillRect(304, playerPaddleY, PADDLE_WIDTH, PADDLE_HEIGHT, COLOR_GREEN); 
  tft.fillRect((int)ballX, (int)ballY, BALL_SIZE, BALL_SIZE, COLOR_YELLOW);   

  delay(20);
}

void updateSnake() {
  if (digitalRead(PIN_BTN_YELLOW) == LOW) {
    currentScreen = STATE_GAMES_MENU;
    drawGamesMenuScreen();
    delay(200);
    return;
  }

  Direction dir = getJoystickDirection();
  if (dir == LEFT && snakeDir != RIGHT) snakeDir = LEFT;
  if (dir == RIGHT && snakeDir != LEFT) snakeDir = RIGHT;
  if (dir == UP && snakeDir != DOWN) snakeDir = UP;
  if (dir == DOWN && snakeDir != UP) snakeDir = DOWN;

  oldTailX = snakeX[snakeLength - 1];
  oldTailY = snakeY[snakeLength - 1];

  for (int i = snakeLength - 1; i > 0; i--) {
    snakeX[i] = snakeX[i - 1];
    snakeY[i] = snakeY[i - 1];
  }

  if (snakeDir == LEFT)  snakeX[0] -= PLAYER_SIZE;
  if (snakeDir == RIGHT) snakeX[0] += PLAYER_SIZE;
  if (snakeDir == UP)    snakeY[0] -= PLAYER_SIZE;
  if (snakeDir == DOWN)  snakeY[0] += PLAYER_SIZE;

  // Wall collision check
  bool selfCollision = false;
  // Self-collision check
  for (int i = 1; i < snakeLength; i++) {
    if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
      selfCollision = true;
      break;
    }
  }

  if (snakeX[0] < 0 || snakeX[0] >= 320 || snakeY[0] < 0 || snakeY[0] >= 240 || selfCollision) {
    currentScreen = STATE_SNAKE_GAMEOVER;
    showGameOverScreen("GAME OVER", snakeLength - 3);
    delay(500);
    return;
  }

  if (snakeX[0] == foodX && snakeY[0] == foodY) {
    if (snakeLength < MAX_SNAKE) {
      snakeX[snakeLength] = oldTailX;
      snakeY[snakeLength] = oldTailY;
      snakeLength++;
    }
    spawnFood();
    drawFood();
  }

  drawPlayer();
  delay(200);
}

void drawComingSoon(const char* appName) {
  tft.fillScreen(COLOR_WHITE);
  tft.setTextColor(COLOR_GREEN);
  tft.setTextSize(2);
  tft.setCursor(15, HEADER_Y);
  tft.print("< ");
  tft.print(appName);

  tft.drawFastHLine(15, DIVIDER_Y, 290, COLOR_DARKGREY);

  tft.setTextColor(COLOR_BLACK);
  tft.setTextSize(2);
  tft.setCursor(95, 130);
  tft.print("Coming soon...");
}

void playTimerAlarm() {
  for (int i = 0; i < 5; i++) {
    tone(PIN_BUZZER, 1000, 200);
    delay(300);
  }
  noTone(PIN_BUZZER);
}