#include <Arduino.h>
#include "Arduino_GFX_Library.h"
#include "pin_config.h"

// Display setup
Arduino_DataBus *bus = new Arduino_ESP32SPI(LCD_DC, LCD_CS, LCD_SCK, LCD_MOSI);
Arduino_GFX *gfx = new Arduino_ST7789(bus, LCD_RST, 0, true, LCD_WIDTH, LCD_HEIGHT, 0, 20, 0, 0);

// GPIO definitions
#define BUTTON_UP 18
#define BUTTON_DOWN 16
#define BUTTON_LEFT 2
#define BUTTON_RIGHT 10
#define BUZZER_PIN 33

#define GRID_WIDTH 30
#define GRID_HEIGHT 35
#define CELL_SIZE 8

#define FOOD_X_MIN 5
#define FOOD_X_MAX 25
#define FOOD_Y_MIN 5
#define FOOD_Y_MAX 30

struct SnakeSegment {
    int x;
    int y;
};

SnakeSegment snake[100];
int snakeLength = 5;
int dx = 1, dy = 0;
uint16_t foodColor = RED;
int foodX, foodY;
int foodEaten = 0;
int prevTailX, prevTailY;
bool gameOver = false;

const int charWidth = 12;
const int screenMidX = LCD_WIDTH / 2;

void placeFood() {
    foodX = random(FOOD_X_MIN, FOOD_X_MAX);
    foodY = random(FOOD_Y_MIN, FOOD_Y_MAX);
}

void clearScoreArea() {
    gfx->fillRect(0, 0, LCD_WIDTH, 24, BLACK);
}

void updateFoodCounter() {
    clearScoreArea();
    gfx->setTextSize(2);
    gfx->setTextColor(WHITE);
    String paddedScore = String(foodEaten);
    while (paddedScore.length() < 3) paddedScore = "0" + paddedScore;
    String scoreText = "Food: " + paddedScore;
    int textPixelWidth = scoreText.length() * charWidth;
    int cursorX = screenMidX - (textPixelWidth / 2);
    gfx->setCursor(cursorX, 4);
    gfx->println(scoreText);
}

bool checkSelfCollision() {
    for (int i = 1; i < snakeLength; i++) {
        if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
            return true;
        }
    }
    return false;
}

void resetGame() {
    gfx->fillScreen(BLACK);
    dx = 1;
    dy = 0;
    snakeLength = 5;
    foodEaten = 0;
    gameOver = false;

    for (int i = 0; i < snakeLength; i++) {
        snake[i] = { GRID_WIDTH / 2 - i, GRID_HEIGHT / 2 };
    }

    placeFood();
    updateFoodCounter();
}

void beepFood() {
    tone(BUZZER_PIN, 1200, 100); // Short beep
}

void playGameOverMelody() {
    int melody[] = { 880, 784, 698, 622, 523, 466, 440, 0 };
    int duration = 300;

    for (int i = 0; melody[i] != 0; i++) {
        tone(BUZZER_PIN, melody[i], duration);
        delay(duration + 50);
    }

    noTone(BUZZER_PIN);
}

void showGameOverScreen() {
    gfx->fillScreen(BLACK);

    gfx->setTextSize(4);
    gfx->setTextColor(RED);
    String gameOverText = "GAME OVER";
    int textWidth = gameOverText.length() * 24;
    gfx->setCursor(screenMidX - (textWidth / 2), 90);
    gfx->println(gameOverText);

    gfx->setTextSize(2);
    gfx->setTextColor(WHITE);
    String scoreText = "Score: " + String(foodEaten);
    gfx->setCursor(screenMidX - (scoreText.length() * 12 / 2), 140);
    gfx->println(scoreText);

    playGameOverMelody();
    delay(1000);
    resetGame();
}

void setup() {
    Serial.begin(115200);
    gfx->begin();
    gfx->fillScreen(BLACK);

    pinMode(BUTTON_UP, INPUT_PULLUP);
    pinMode(BUTTON_DOWN, INPUT_PULLUP);
    pinMode(BUTTON_LEFT, INPUT_PULLUP);
    pinMode(BUTTON_RIGHT, INPUT_PULLUP);
    pinMode(BUZZER_PIN, OUTPUT);

    resetGame();
}

void loop() {
    if (gameOver) return;

    if (!digitalRead(BUTTON_UP) && dy == 0) { dx = 0; dy = -1; }
    else if (!digitalRead(BUTTON_DOWN) && dy == 0) { dx = 0; dy = 1; }
    else if (!digitalRead(BUTTON_LEFT) && dx == 0) { dx = -1; dy = 0; }
    else if (!digitalRead(BUTTON_RIGHT) && dx == 0) { dx = 1; dy = 0; }

    prevTailX = snake[snakeLength - 1].x;
    prevTailY = snake[snakeLength - 1].y;
    for (int i = snakeLength - 1; i > 0; i--) {
        snake[i] = snake[i - 1];
    }
    snake[0].x += dx;
    snake[0].y += dy;

    if (snake[0].x >= GRID_WIDTH) snake[0].x = 0;
    if (snake[0].x < 0) snake[0].x = GRID_WIDTH - 1;
    if (snake[0].y >= GRID_HEIGHT) snake[0].y = 0;
    if (snake[0].y < 0) snake[0].y = GRID_HEIGHT - 1;

    if (checkSelfCollision()) {
        gameOver = true;
        showGameOverScreen();
        return;
    }

    if (snake[0].x == foodX && snake[0].y == foodY) {
        snakeLength++;
        foodEaten++;
        placeFood();
        updateFoodCounter();
        beepFood();
    }

    gfx->fillRect(prevTailX * CELL_SIZE, prevTailY * CELL_SIZE, CELL_SIZE, CELL_SIZE, BLACK);

    for (int i = 0; i < snakeLength; i++) {
        uint8_t brightness = map(i, 0, snakeLength, 255, 80);
        uint16_t color = gfx->color565(brightness, 255 - brightness / 3, brightness / 4);
        gfx->fillRoundRect(snake[i].x * CELL_SIZE, snake[i].y * CELL_SIZE, CELL_SIZE, CELL_SIZE, 2, color);
    }

    int pulse = (millis() / 100) % 4;
    int size = CELL_SIZE - 4 + pulse;
    int offset = (CELL_SIZE - size) / 2;
    gfx->fillRoundRect(foodX * CELL_SIZE + offset, foodY * CELL_SIZE + offset, size, size, 2, foodColor);

    // ⏩ Speed up if button is held
    int frameDelay = 150;
    if (!digitalRead(BUTTON_UP) || !digitalRead(BUTTON_DOWN) || !digitalRead(BUTTON_LEFT) || !digitalRead(BUTTON_RIGHT)) {
        frameDelay = 80;
    }
    delay(frameDelay);
}

