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

// Pins for the display
#define TFT_CS     10
#define TFT_RST    9
#define TFT_DC     8

// Pins for the buttons
const int leftButtonPin = 2;
const int rightButtonPin = 3;
const int downButtonPin = 4;
const int rotateButtonPin = 5;
const int restartButtonPin = 6;

// Pin for the buzzer
const int buzzerPin = 7;

// Game board dimensions
const int boardWidth = 10;
const int boardHeight = 20;

// Colors
#define COLOR_BACKGROUND ILI9341_BLACK
#define COLOR_BLOCK ILI9341_WHITE
#define COLOR_TEXT ILI9341_RED

// TFT display
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

// Game board array
int gameBoard[boardWidth][boardHeight];

// Tetris pieces
const int pieces[7][4][4] = {
  { {0, 0, 0, 0},
    {1, 1, 1, 1},
    {0, 0, 0, 0},
    {0, 0, 0, 0} },

  { {2, 2, 2, 0},
    {0, 2, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0} },

  { {3, 3, 3, 0},
    {3, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0} },

  { {0, 4, 4, 0},
    {4, 4, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0} },

  { {5, 5, 0, 0},
    {0, 5, 5, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0} },

  { {6, 6, 6, 0},
    {0, 0, 6, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0} },

  { {7, 7, 0, 0},
    {7, 7, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0} }
};

// Current piece position and type
int currentPiece[4][4];
int currentX = 3;
int currentY = 0;

// Game state
bool gameOver = false;
int score = 0;
int highScore = 0;
int level = 1;
int linesCleared = 0;
int gameSpeed = 500;

// EEPROM address for high score
const int highScoreAddress = 0;

void setup() {
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(COLOR_BACKGROUND);

  pinMode(leftButtonPin, INPUT_PULLUP);
  pinMode(rightButtonPin, INPUT_PULLUP);
  pinMode(downButtonPin, INPUT_PULLUP);
  pinMode(rotateButtonPin, INPUT_PULLUP);
  pinMode(restartButtonPin, INPUT_PULLUP);
  pinMode(buzzerPin, OUTPUT);

  // Read high score from EEPROM
  EEPROM.get(highScoreAddress, highScore);

  resetGame();
}

void loop() {
  if (!gameOver) {
    handleInput();
    updateGame();
    drawGameBoard();
    drawScore();
    delay(gameSpeed);
  } else {
    drawGameOverScreen();
    if (digitalRead(restartButtonPin) == LOW) {
      resetGame();
    }
  }
}

void resetGame() {
  memset(gameBoard, 0, sizeof(gameBoard));
  spawnPiece();
  gameOver = false;
  score = 0;
  level = 1;
  linesCleared = 0;
  gameSpeed = 500;
}

void drawGameBoard() {
  tft.fillScreen(COLOR_BACKGROUND);

  for (int x = 0; x < boardWidth; x++) {
    for (int y = 0; y < boardHeight; y++) {
      if (gameBoard[x][y] != 0) {
        tft.fillRect(x * 12, y * 12, 12, 12, COLOR_BLOCK);
      }
    }
  }

  drawPiece(currentPiece, currentX, currentY);
}

void drawPiece(const int piece[4][4], int x, int y) {
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      if (piece[i][j] != 0) {
        tft.fillRect((x + i) * 12, (y + j) * 12, 12, 12, COLOR_BLOCK);
      }
    }
  }
}

void drawScore() {
  tft.setTextColor(COLOR_TEXT, COLOR_BACKGROUND);
  tft.setTextSize(2);
  tft.setCursor(10, 250);
  tft.print("Score: ");
  tft.print(score);
  tft.setCursor(10, 270);
  tft.print("Level: ");
  tft.print(level);
  tft.setCursor(10, 290);
  tft.print("High Score: ");
  tft.print(highScore);
}

void handleInput() {
  if (digitalRead(leftButtonPin) == LOW) {
    movePiece(-1, 0);
  }
  if (digitalRead(rightButtonPin) == LOW) {
    movePiece(1, 0);
  }
  if (digitalRead(downButtonPin) == LOW) {
    movePiece(0, 1);
  }
  if (digitalRead(rotateButtonPin) == LOW) {
    rotatePiece();
  }
}

void movePiece(int dx, int dy) {
  if (!collision(currentPiece, currentX + dx, currentY + dy)) {
    currentX += dx;
    currentY += dy;
  }
}

void rotatePiece() {
  int rotatedPiece[4][4];
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      rotatedPiece[j][3 - i] = currentPiece[i][j];
    }
  }

  if (!collision(rotatedPiece, currentX, currentY)) {
    memcpy(currentPiece, rotatedPiece, sizeof(rotatedPiece));
  }
}

bool collision(const int piece[4][4], int x, int y) {
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      if (piece[i][j] != 0) {
        int newX = x + i;
        int newY = y + j;

        if (newX < 0 || newX >= boardWidth || newY < 0 || newY >= boardHeight || gameBoard[newX][newY] != 0) {
          return true;
        }
      }
    }
  }
  return false;
}

void updateGame() {
  if (collision(currentPiece, currentX, currentY + 1)) {
    mergePiece();
    clearLines();
    spawnPiece();
    if (collision(currentPiece, currentX, currentY)) {
      gameOver = true;
      tone(buzzerPin, 200, 500); // Game over sound
      // Save high score if it's a new one
      if (score > highScore) {
        highScore = score;
        EEPROM.put(highScoreAddress, highScore);
      }
    }
  } else {
    currentY++;
  }
}

void mergePiece() {
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      if (currentPiece[i][j] != 0) {
        gameBoard[currentX + i][currentY + j] = currentPiece[i][j];
      }
    }
  }
}

void clearLines() {
  int lines = 0;
  for (int y = 0; y < boardHeight; y++) {
    bool fullLine = true;
    for (int x = 0; x < boardWidth; x++) {
      if (gameBoard[x][y] == 0) {
        fullLine = false;
        break;
      }
    }

    if (fullLine) {
      lines++;
      for (int ty = y; ty > 0; ty--) {
        for (int tx = 0; tx < boardWidth; tx++) {
          gameBoard[tx][ty] = gameBoard[tx][ty - 1];
        }
      }
      for (int tx = 0; tx < boardWidth; tx++) {
        gameBoard[tx][0] = 0;
      }
      tone(buzzerPin, 1000, 100); // Line clear sound
    }
  }
  score += lines * 10;
  linesCleared += lines;
  if (linesCleared >= level * 10) {
    level++;
    gameSpeed = max(100, gameSpeed - 50); // Increase speed but ensure a minimum delay
  }
}

void spawnPiece() {
  int pieceType = random(0, 7);
  memcpy(currentPiece, pieces[pieceType], sizeof(currentPiece));
  currentX = 3;
  currentY = 0;
}

void drawGameOverScreen() {
  tft.fillScreen(COLOR_BACKGROUND);
  tft.setTextColor(COLOR_TEXT, COLOR_BACKGROUND);
  tft.setTextSize(3);
  tft.setCursor(30, 120);
  tft.print("Game Over");
  tft.setTextSize(2);
  tft.setCursor(30, 160);
  tft.print("Score: ");
  tft.print(score);
  tft.setCursor(30, 190);
  tft.print("High Score: ");
  tft.print(highScore);
  tft.setCursor(30, 220);
  tft.print("Press to Restart");
}