#include <vector>
#include <stack>
#include <random>
#include <cstdio>
#include <iostream>
#include <fstream>
#include <ctime>
#include <algorithm>
#include <string>

#include "Umfeld.h"
#include "Gamepad.h"
#include "PVector.h"
#include <SDL3/SDL.h>
#include <MazeGenerator.h>
#include "audio/Sampler.h"

using namespace umfeld;
using namespace umfeld::subsystem;

enum class GameState {
    MENU,
    PLAYING,
    PAUSED,
    WIN,
    ENTER_NAME,
    LEADERBOARD,
    RULES
};

enum class GameMode {
    ONE_PLAYER_EASY,
    ONE_PLAYER_HARD,
    TWO_PLAYER
};

std::vector<GameMode> availableModes = {
    GameMode::ONE_PLAYER_EASY,
    GameMode::ONE_PLAYER_HARD,
    GameMode::TWO_PLAYER
};

int currentModeIndex = 0;
GameMode currentMode = availableModes[0];
int menuItem = 0;

GameState state = GameState::MENU;

MazeGenerator* maze = nullptr;
Sampler* winSampler = nullptr;
Sampler* bgMusic = nullptr;
PFont* font = nullptr;

bool winSoundPlayed = false;
bool musicStarted = false;
bool highScoreCheckedForThisWin = false;

// Timer variables
Uint64 startTime = 0;
Uint64 elapsedTime = 0;
Uint64 pauseStartTime = 0;
Uint64 totalPausedTime = 0;
bool timerRunning = false;

// Name entry (arcade style, 3 letters)
std::string playerName = "AAA";
int currentCharIndex = 0;
const int MAX_NAME_LENGTH = 3;

// Leaderboard view mode
GameMode leaderboardViewMode = GameMode::ONE_PLAYER_EASY;

struct LeaderboardEntry {
    std::string name;
    Uint64 timeMs;
    GameMode mode;
    std::string date;
};

class Leaderboard {
public:
    std::vector<LeaderboardEntry> entries;
    static const size_t MAX_ENTRIES_PER_MODE = 10;

    void load(const std::string& filename = "leaderboard.dat") {
        std::ifstream file(filename, std::ios::binary);
        if (!file) return;
        size_t count;
        file.read(reinterpret_cast<char*>(&count), sizeof(count));
        entries.resize(count);
        for (auto& e : entries) {
            size_t len;
            file.read(reinterpret_cast<char*>(&len), sizeof(len));
            e.name.resize(len);
            file.read(&e.name[0], len);
            file.read(reinterpret_cast<char*>(&e.timeMs), sizeof(e.timeMs));
            file.read(reinterpret_cast<char*>(&e.mode), sizeof(e.mode));
            file.read(reinterpret_cast<char*>(&len), sizeof(len));
            e.date.resize(len);
            file.read(&e.date[0], len);
        }
        sortEntries();
    }

    void save(const std::string& filename = "leaderboard.dat") const {
        std::ofstream file(filename, std::ios::binary);
        if (!file) return;
        size_t count = entries.size();
        file.write(reinterpret_cast<const char*>(&count), sizeof(count));
        for (const auto& e : entries) {
            size_t len = e.name.size();
            file.write(reinterpret_cast<const char*>(&len), sizeof(len));
            file.write(e.name.c_str(), len);
            file.write(reinterpret_cast<const char*>(&e.timeMs), sizeof(e.timeMs));
            file.write(reinterpret_cast<const char*>(&e.mode), sizeof(e.mode));
            len = e.date.size();
            file.write(reinterpret_cast<const char*>(&len), sizeof(len));
            file.write(e.date.c_str(), len);
        }
    }

    bool isHighScore(Uint64 time, GameMode mode) const {
        int count = 0;
        Uint64 worst = 0;
        for (const auto& e : entries) {
            if (e.mode == mode) {
                count++;
                if (e.timeMs > worst) worst = e.timeMs;
            }
        }
        if (count < MAX_ENTRIES_PER_MODE) return true;
        return time < worst;
    }

    void addEntry(const LeaderboardEntry& newEntry) {
        entries.push_back(newEntry);
        sortEntries();
        std::vector<LeaderboardEntry> filtered;
        for (int m = 0; m < 3; ++m) {
            GameMode mode = static_cast<GameMode>(m);
            int added = 0;
            for (const auto& e : entries) {
                if (e.mode == mode && added < MAX_ENTRIES_PER_MODE) {
                    filtered.push_back(e);
                    added++;
                }
            }
        }
        entries = filtered;
        save();
    }

    std::vector<LeaderboardEntry> getScoresForMode(GameMode mode) const {
        std::vector<LeaderboardEntry> result;
        for (const auto& e : entries) {
            if (e.mode == mode) result.push_back(e);
        }
        return result;
    }

private:
    void sortEntries() {
        std::sort(entries.begin(), entries.end(),
            [](const LeaderboardEntry& a, const LeaderboardEntry& b) {
                if (a.mode != b.mode) return a.mode < b.mode;
                return a.timeMs < b.timeMs;
            });
    }
};

Leaderboard leaderboard;

// -------------------------------------------------------------------
// Helper functions
// -------------------------------------------------------------------
std::string formatTime(Uint64 milliseconds) {
    Uint64 totalSeconds = milliseconds / 1000;
    Uint64 minutes = totalSeconds / 60;
    Uint64 seconds = totalSeconds % 60;
    char buffer[10];
    snprintf(buffer, sizeof(buffer), "%02llu:%02llu", minutes, seconds);
    return std::string(buffer);
}

std::string modeToString(GameMode mode) {
    switch (mode) {
        case GameMode::ONE_PLAYER_EASY: return "Easy";
        case GameMode::ONE_PLAYER_HARD: return "Hard";
        case GameMode::TWO_PLAYER:      return "Two Player";
    }
    return "";
}

void resetTimer() {
    startTime = SDL_GetTicks();
    elapsedTime = 0;
    totalPausedTime = 0;
    pauseStartTime = 0;
    timerRunning = true;
}

void restartGame() {
    winSoundPlayed = false;
    highScoreCheckedForThisWin = false;
    float size = maze->getMazeSizeFactor();
    maze->setMazeSize(size, width, height);
    resetTimer();
    musicStarted = false;
}

void settings() {
    size(display_width, display_height);
    enable_gamepads();
    audio();
    audio_sample_acquisition_mode = AUDIO_PER_SAMPLE_NO_INPUT;
}

void setup() {
    int numPads = 0;
    SDL_JoystickID* gamepads = SDL_GetGamepads(&numPads);
    if (numPads > 0) pad = SDL_OpenGamepad(gamepads[0]);

    font = loadFont("RobotoMono-Regular.ttf", 48);
    textFont(font);

    maze = new MazeGenerator(width, height, 20, 4.0f);
    winSampler = loadSample("win.wav");
    bgMusic = loadSample("bg_music.wav");

    leaderboard.load();
}

// -------------------------------------------------------------------
// Drawing functions
// -------------------------------------------------------------------

void drawMenu() {
    fill(0);
    textSize(20);
    textAlign(CENTER, CENTER);
    text("MAZE GAME", width / 2, height / 2 - 100);

    textSize(18);
    std::vector<std::string> menuItems = {
        "One Player - Easy",
        "One Player - Hard",
        "Two Players",
        "Leaderboard",
        "Rules"
    };

    for (int i = 0; i < menuItems.size(); i++) {
        float y = height / 2 - 40 + (i * 40);
        if (i == menuItem) {
            text("> " + menuItems[i] + " <", width / 2, y);
        } else {
            text("  " + menuItems[i] + "  ", width / 2, y);
        }
    }

    textSize(16);
    text("Up/Down on D-Pad: move   A: select", width / 2, height / 2 + 150);
}

void drawPlaying() {
    if (bgMusic && !musicStarted) {
        bgMusic->rewind();
        bgMusic->set_looping();
        bgMusic->play();
        musicStarted = true;
    }

    switch (currentMode) {
        case GameMode::ONE_PLAYER_EASY:
            maze->getOnePlayerEasyUI();
            break;
        case GameMode::ONE_PLAYER_HARD:
            background(255);
            maze->getOnePlayerHardUI();
            break;
        case GameMode::TWO_PLAYER:
            maze->draw();
            break;
    }

    if (timerRunning) {
        elapsedTime = SDL_GetTicks() - startTime - totalPausedTime;
    }

    fill(0);
    textSize(20);
    textAlign(LEFT, TOP);
    text("Pause: +   Menu: -\nMode: " + modeToString(currentMode), 10, 10);
    textAlign(RIGHT, TOP);
    text("Time: " + formatTime(elapsedTime), width - 10, 10);

    if (maze->hasReachedExit()) {
        state = GameState::WIN;
        winSoundPlayed = false;
        timerRunning = false;
    }
}

void drawPaused() {
    drawPlaying();
    fill(0, 150);
    rect(0, 0, width, height);
    fill(255);
    textAlign(CENTER, CENTER);
    textSize(36);
    text("PAUSED", width / 2, height / 2);
    textSize(20);
    text("Time: " + formatTime(elapsedTime), width / 2, height / 2 - 50);
    text("Mode: " + modeToString(currentMode), width / 2, height / 2 - 20);
}

void drawWin() {
    if (!winSoundPlayed && winSampler) {
        winSampler->rewind();
        winSampler->play();
        winSoundPlayed = true;
        if (bgMusic) bgMusic->stop();
    }

    if (!highScoreCheckedForThisWin) {
        if (leaderboard.isHighScore(elapsedTime, currentMode)) {
            playerName = "AAA";
            currentCharIndex = 0;
            state = GameState::ENTER_NAME;
            return;
        }
        highScoreCheckedForThisWin = true;
    }

    fill(0);
    textAlign(CENTER, CENTER);
    textSize(48);
    text("YOU WIN!", width / 2, height / 2 - 40);
    textSize(20);
    text("Final Time: " + formatTime(elapsedTime), width / 2, height / 2 + 10);
    text("Mode: " + modeToString(currentMode), width / 2, height / 2 + 40);
    text("Press A to restart", width / 2, height / 2 + 80);
}

void drawNameEntry() {
    background(0);
    fill(255);
    textAlign(CENTER, CENTER);
    textSize(36);
    text("NEW HIGH SCORE!", width/2, height/2 - 100);
    textSize(28);
    text("Enter your name (3 letters)", width/2, height/2 - 40);

    textSize(64);
    float spacing = 80;
    float startX = width/2 - spacing;
    float y = height/2 + 20;

    for (int i = 0; i < MAX_NAME_LENGTH; i++) {
        float x = startX + i * spacing;
        if (i == currentCharIndex) {
            fill(255, 255, 0);
            text(std::string(1, playerName[i]), x, y);
            stroke(255, 255, 0);
            line(x - 20, y + 35, x + 20, y + 35);
        } else {
            fill(200);
            text(std::string(1, playerName[i]), x, y);
        }
    }

    textSize(18);
    fill(150);
    textAlign(CENTER, BOTTOM);
    text("Up/Down: change letter   A: next letter   B: cancel", width/2, height - 30);
}

void drawLeaderboard() {
    background(0);
    fill(255);
    textAlign(CENTER, TOP);
    textSize(36);
    text("HIGH SCORES - " + modeToString(leaderboardViewMode), width/2, 30);

    auto scores = leaderboard.getScoresForMode(leaderboardViewMode);
    int y = 100;
    int rank = 1;
    textSize(24);
    for (const auto& entry : scores) {
        std::string line = std::to_string(rank) + ".  " + entry.name;
        if (entry.name.length() < 15) line.append(15 - entry.name.length(), ' ');
        line += "  " + formatTime(entry.timeMs);
        text(line, width/2, y);
        y += 40;
        rank++;
        if (rank > 10) break;
    }

    if (scores.empty()) {
        textSize(28);
        text("No scores yet!", width/2, height/2);
    }

    textSize(16);
    fill(150);
    text("L / R shoulder: change mode   B: back to menu", width/2, height - 30);
}

void drawRules() {
    fill(255);
    textAlign(CENTER, CENTER);
    textSize(36);
    fill(0);
    text("RULES", width / 2, height / 2 - 100);
    textSize(20);
    text("In this game, you will use the controller to find your way out of the maze. \n In easy mode you will be able to see the maze, while in hard mode you will have to only rely on the vibrations \nYou will feel the steps with a soft vibration, while hitting a wall will result in a stronger vibration. \nIn two players mode, one of you won't look at the screen, while the other one will give directions.", width / 2, height / 2 + 40);
}


void draw() {
    background(255);
    switch (state) {
        case GameState::MENU:         drawMenu(); break;
        case GameState::PLAYING:      drawPlaying(); break;
        case GameState::PAUSED:       drawPaused(); break;
        case GameState::WIN:          drawWin(); break;
        case GameState::ENTER_NAME:   drawNameEntry(); break;
        case GameState::LEADERBOARD:  drawLeaderboard(); break;
        case GameState::RULES:        drawRules(); break;
    }
}

// -------------------------------------------------------------------
// Input handling
// -------------------------------------------------------------------
void keyPressed() {
    if (state == GameState::MENU) {
        if (keyCode == UP) {
            menuItem = (menuItem - 1 + 4) % 4;
        }
        else if (keyCode == DOWN) {
            menuItem = (menuItem + 1) % 4;
        }
        else if (key == ' ' || key == '\n') {
            if (menuItem == 3) {
                leaderboardViewMode = currentMode;
                state = GameState::LEADERBOARD;
            } else {
                currentMode = static_cast<GameMode>(menuItem);
                currentModeIndex = menuItem;
                restartGame();
                state = GameState::PLAYING;
            }
        }
    }
    else if (state == GameState::PLAYING) {
        if (key == 'p' || key == 'P') {
            state = GameState::PAUSED;
            pauseStartTime = SDL_GetTicks();
            timerRunning = false;
        }
        if (key == 'm' || key == 'M') {
            state = GameState::MENU;
            bgMusic->stop();
            restartGame();
        }
        if (keyCode == UP) {
            float s = std::max(2.0f, maze->getMazeSizeFactor() - 0.5f);
            maze->setMazeSize(s, width, height);
            resetTimer();
        }
        if (keyCode == DOWN) {
            float s = std::min(8.0f, maze->getMazeSizeFactor() + 0.5f);
            maze->setMazeSize(s, width, height);
            resetTimer();
        }
        if (key == 'D' || key == 'd') maze->movePlayer(1, 0);
        if (key == 'A' || key == 'a') maze->movePlayer(-1, 0);
        if (key == 'S' || key == 's') maze->movePlayer(0, 1);
        if (key == 'W' || key == 'w') maze->movePlayer(0, -1);
        if (key == ' ') restartGame();
        if (key == 'H' || key == 'h') {
            int offsetX = maze->getOffsetX();
            int offsetY = maze->getOffsetY();
            maze->drawWalls(offsetX, offsetY);
        }
    }
    else if (state == GameState::PAUSED && (key == 'p' || key == 'P')) {
        state = GameState::PLAYING;
        totalPausedTime += SDL_GetTicks() - pauseStartTime;
        timerRunning = true;
    }
    else if (state == GameState::WIN && key == ' ') {
        restartGame();
        state = GameState::PLAYING;
    }
}

void gamepad_button(const int id, const int button, const bool down) {
    if (!down) return;

    if (state == GameState::MENU) {
        if (button == 11) {
            menuItem = (menuItem - 1 + 5) % 5;
        }
        else if (button == 12) {
            menuItem = (menuItem + 1) % 5;
        }
        else if (button == 1) {
            if (menuItem == 3) {
                leaderboardViewMode = currentMode;
                state = GameState::LEADERBOARD;
            } else if (menuItem == 4) {
            // Handle Rules selection
            state = GameState::RULES;  // You'll need to add this state
            } else {
                currentMode = static_cast<GameMode>(menuItem);
                currentModeIndex = menuItem;
                restartGame();
                state = GameState::PLAYING;
            }
        }
    }

    else if (state == GameState::RULES) {
        if (button == 0 || button == 4) {
            state = GameState::MENU;
        }
    }

    else if (state == GameState::PLAYING) {
        if (button == 6) {
            state = GameState::PAUSED;
            pauseStartTime = SDL_GetTicks();
            timerRunning = false;
            return;
        }
        if (button == 14) maze->movePlayer(1, 0);
        if (button == 13) maze->movePlayer(-1, 0);
        if (button == 12) maze->movePlayer(0, 1);
        if (button == 11) maze->movePlayer(0, -1);
        if (button == 9) {
            restartGame();
            timerRunning = true;
        }
        //if (button == 0) {
        //    float s = std::min(8.0f, maze->getMazeSizeFactor() + 0.5f);
        //    maze->setMazeSize(s, width, height);
        //    resetTimer();
        //    bgMusic->rewind();
        //    bgMusic->set_looping();
        //}
        //if (button == 1) {
        //    float s = std::max(2.0f, maze->getMazeSizeFactor() - 0.5f);
        //    maze->setMazeSize(s, width, height);
        //    resetTimer();
        //    bgMusic->rewind();
        //    bgMusic->set_looping();
        //}
        if (button == 4) {
            state = GameState::MENU;
            bgMusic->stop();
            restartGame();
        }
        if (button == 9) {
            int offsetX = maze->getOffsetX();
            int offsetY = maze->getOffsetY();
            maze->drawWalls(offsetX, offsetY);
        }
    }
    else if (state == GameState::PAUSED) {
        if (button == 6 || button == 0) {
            state = GameState::PLAYING;
            totalPausedTime += SDL_GetTicks() - pauseStartTime;
            timerRunning = true;
        }
    }
    else if (state == GameState::WIN) {
        if (button == 1) {
            restartGame();
            state = GameState::PLAYING;
        }
    }
    else if (state == GameState::ENTER_NAME) {
        if (button == 11) {
            char& c = playerName[currentCharIndex];
            if (c == 'Z') c = 'A';
            else c++;
        }
        else if (button == 12) {
            char& c = playerName[currentCharIndex];
            if (c == 'A') c = 'Z';
            else c--;
        }
        else if (button == 1) {
            if (currentCharIndex < MAX_NAME_LENGTH - 1) {
                currentCharIndex++;
            } else {
                LeaderboardEntry newEntry;
                newEntry.name = playerName;
                newEntry.timeMs = elapsedTime;
                newEntry.mode = currentMode;
                time_t now = time(nullptr);
                newEntry.date = std::ctime(&now);
                newEntry.date.pop_back();
                leaderboard.addEntry(newEntry);
                leaderboardViewMode = currentMode;
                state = GameState::LEADERBOARD;
            }
        }
        else if (button == 0) {
            state = GameState::WIN;
        }
    }
    else if (state == GameState::LEADERBOARD) {
        if (button == 9) {
            int modeIdx = static_cast<int>(leaderboardViewMode);
            modeIdx = (modeIdx - 1 + 3) % 3;
            leaderboardViewMode = static_cast<GameMode>(modeIdx);
        }
        else if (button == 10) {
            int modeIdx = static_cast<int>(leaderboardViewMode);
            modeIdx = (modeIdx + 1) % 3;
            leaderboardViewMode = static_cast<GameMode>(modeIdx);
        }
        else if (button == 0) {
            state = GameState::MENU;
        }
    }
}

void audioEvent(float& left, float& right) {
    float sample = 0.0f;
    if (winSampler) sample += winSampler->process();
    if (bgMusic)    sample += bgMusic->process();
    sample = std::max(-1.0f, std::min(1.0f, sample));
    left = sample;
    right = sample;
}

void exit() {
    delete maze;
}