#include <vector>
#include <stack>
#include <random>
#include <algorithm>
#include <cstdio>
#include "Umfeld.h"
#include "Gamepad.h"
#include "PVector.h"
#include <SDL3/SDL.h>
#include <iostream>

using namespace umfeld;
using namespace umfeld::subsystem;

SDL_Gamepad* pad = nullptr;

void triggerInvalidMoveRumble() {
    if (!pad) return;
    Uint16 strength = 5000;
    Uint32 duration = 1000;
    SDL_RumbleGamepad(pad, strength, strength, duration);
}

void triggerValidMoveRumble() {
    if (!pad) return;
    Uint16 strength = 1000;
    Uint32 duration = 20;
    SDL_RumbleGamepad(pad, strength, strength, duration);
}

class MazeGenerator {
private:
    int cols, rows;
    int cellSize;
    float mazeSizeFactor;
    std::vector<std::vector<bool>> visited;
    std::vector<std::vector<bool>> walls;
    
    int playerX, playerY;
    int exitX, exitY;
    
    const int dx[4] = {1, 0, -1, 0};
    const int dy[4] = {0, 1, 0, -1};

    // Cached background layer
    PGraphics* mazeBackground;

    void calculateMazeDimensions(int w, int h) {
        mazeSizeFactor = std::clamp(mazeSizeFactor, 2.5f, 6.0f);
        cols = (w - cellSize) / (cellSize * mazeSizeFactor);
        rows = (h - cellSize) / (cellSize * mazeSizeFactor);
        cols = std::clamp(cols, 3, 50);
        rows = std::clamp(rows, 3, 50);
    }

    void initializeMaze() {
        visited.assign(rows, std::vector<bool>(cols, false));
        walls.assign(rows * 2 + 1, std::vector<bool>(cols * 2 + 1, true));
        exitX = cols - 1;
        exitY = rows - 1;
    }

    void generateMaze() {
        for (auto& row : visited) std::fill(row.begin(), row.end(), false);
        for (auto& row : walls) std::fill(row.begin(), row.end(), true);
        
        std::random_device rd;
        std::mt19937 gen(rd());
        
        int startX = std::uniform_int_distribution<int>(0, cols - 1)(gen);
        int startY = std::uniform_int_distribution<int>(0, rows - 1)(gen);
        
        std::stack<std::pair<int, int>> stack;
        stack.push({startX, startY});
        visited[startY][startX] = true;
        walls[startY * 2 + 1][startX * 2 + 1] = false;
        
        while (!stack.empty()) {
            int x = stack.top().first;
            int y = stack.top().second;
            
            std::vector<int> directions = {0, 1, 2, 3};
            std::shuffle(directions.begin(), directions.end(), gen);
            
            bool found = false;
            for (int dir : directions) {
                int nx = x + dx[dir];
                int ny = y + dy[dir];
                
                if (nx >= 0 && nx < cols && ny >= 0 && ny < rows && !visited[ny][nx]) {
                    walls[y * 2 + 1 + dy[dir]][x * 2 + 1 + dx[dir]] = false;
                    walls[ny * 2 + 1][nx * 2 + 1] = false;
                    visited[ny][nx] = true;
                    stack.push({nx, ny});
                    found = true;
                    break;
                }
            }
            if (!found) stack.pop();
        }
        
        walls[0][1] = false;  // Entrance at top

        // After generating, render the static background
        renderBackground();
    }

    // Renders walls onto the cached PGraphics
    void renderBackground() {
        int mazeWidth = (cols * 2 + 1) * cellSize;
        int mazeHeight = (rows * 2 + 1) * cellSize;

        // Create or resize the graphics buffer if necessary
        if (mazeBackground == nullptr) {
            mazeBackground = createGraphics(mazeWidth, mazeHeight);
        } else if (mazeBackground->width != mazeWidth || mazeBackground->height != mazeHeight) {
            delete mazeBackground;
            mazeBackground = createGraphics(mazeWidth, mazeHeight);
        }

        mazeBackground->beginDraw();
        mazeBackground->background(255); // White background (same as main)
        mazeBackground->noStroke();
        mazeBackground->fill(0);         // Black walls

        // Draw all wall cells
        for (int y = 0; y < walls.size(); y++) {
            for (int x = 0; x < walls[y].size(); x++) {
                if (walls[y][x]) {
                    mazeBackground->rect(x * cellSize, y * cellSize, cellSize, cellSize);
                }
            }
        }
        mazeBackground->endDraw();
    }

public:
    MazeGenerator(int w, int h, int cellSize = 20, float mazeSizeFactor = 2.5f) 
        : cellSize(cellSize), mazeSizeFactor(mazeSizeFactor), mazeBackground(nullptr) {
        
        calculateMazeDimensions(w, h);
        initializeMaze();
        generateMaze();
        
        playerX = 0;
        playerY = 0;
    }

    ~MazeGenerator() {
        if (mazeBackground) delete mazeBackground;
    }

    void setMazeSize(float newSizeFactor, int w, int h) {
        mazeSizeFactor = newSizeFactor;
        calculateMazeDimensions(w, h);
        initializeMaze();
        generateMaze();
        
        playerX = 0;
        playerY = 0;
    }

    float getMazeSizeFactor() const {
        return mazeSizeFactor;
    }

    // For debugging: directly draw walls on the main canvas (not using cache)
    void drawWalls(int offsetX, int offsetY) {
        stroke(0);
        fill(0);
        for (int y = 0; y < walls.size(); y++) {
            for (int x = 0; x < walls[y].size(); x++) {
                if (walls[y][x]) {
                    rect(offsetX + x * cellSize,
                         offsetY + y * cellSize,
                         cellSize,
                         cellSize);
                }
            }
        }
    }

    void movePlayer(int dx, int dy) {
        int newX = playerX + dx;
        int newY = playerY + dy;
        
        if (isValidMove(playerX, playerY, newX, newY)) {
            playerX = newX;
            playerY = newY;
            triggerValidMoveRumble();
            SDL_RumbleGamepad(pad, 500, 500, 20);
        } else {
            SDL_RumbleGamepad(pad, 5000, 5000, 1000);
            triggerInvalidMoveRumble();
        }
    }

    bool isValidMove(int fromX, int fromY, int toX, int toY) {
        if (toX < 0 || toX >= cols || toY < 0 || toY >= rows) return false;
        int wallX = fromX * 2 + 1 + (toX - fromX);
        int wallY = fromY * 2 + 1 + (toY - fromY);
        return !walls[wallY][wallX];
    }

    void draw() {
        background(255);
        
        int mazeWidth = (cols * 2 + 1) * cellSize;
        int mazeHeight = (rows * 2 + 1) * cellSize;
        int offsetX = (width - mazeWidth) / 2;
        int offsetY = (height - mazeHeight) / 2;

        // Draw the static background (walls)
        if (mazeBackground) {
            image(mazeBackground, offsetX, offsetY);
        }

        // Draw dynamic elements
        drawPlayer(offsetX, offsetY);
        drawExit(offsetX, offsetY);
    }

    void drawPlayer(int offsetX, int offsetY) {
        int cellCenterX = offsetX + (playerX * 2 + 1) * cellSize + cellSize / 2;
        int cellCenterY = offsetY + (playerY * 2 + 1) * cellSize + cellSize / 2;
        fill(0, 0, 255);
        noStroke();
        ellipse(cellCenterX, cellCenterY, cellSize * 0.8, cellSize * 0.8);
    }

    void drawExit(int offsetX, int offsetY) {
        int ex = offsetX + (exitX * 2 + 1) * cellSize;
        int ey = offsetY + (exitY * 2 + 1) * cellSize;
        fill(0, 255, 0);
        noStroke();
        rect(ex + cellSize * 0.1f,
             ey + cellSize * 0.1f,
             cellSize * 0.8f,
             cellSize * 0.8f);
    }

    void getOnePlayerHardUI() {
        int mazeWidth = (cols * 2 + 1) * cellSize;
        int mazeHeight = (rows * 2 + 1) * cellSize;
        int offsetX = (width - mazeWidth) / 2;
        int offsetY = (height - mazeHeight) / 2;
        
        stroke(1);
        noFill();
        rect(offsetX, offsetY, mazeWidth, mazeHeight);
        
        drawExit(offsetX, offsetY);
    }

    void getOnePlayerEasyUI() {
        int mazeWidth = (cols * 2 + 1) * cellSize;
        int mazeHeight = (rows * 2 + 1) * cellSize;
        int offsetX = (width - mazeWidth) / 2;
        int offsetY = (height - mazeHeight) / 2;

        stroke(1);
        noFill();
        rect(offsetX, offsetY, mazeWidth, mazeHeight);
        
        fill(0, 0, 255);
        noStroke();
        
        drawPlayer(offsetX, offsetY);
        drawExit(offsetX, offsetY);
    }

    bool hasReachedExit() {
        return (playerX == exitX && playerY == exitY);
    }
    
    void setPlayerPosition(int x, int y) {
        if (x >= 0 && x < cols && y >= 0 && y < rows) {
            playerX = x;
            playerY = y;
        }
    }
    
    bool isExitCell(int x, int y) const {
        return (x == exitX && y == exitY);
    }
    
    int getCols() const { return cols; }
    int getRows() const { return rows; }
    int getPlayerX() const { return playerX; }
    int getPlayerY() const { return playerY; }

    int getOffsetX() {
        int mazeWidth = (cols * 2 + 1) * cellSize;
        return (width - mazeWidth) / 2;
    }

    int getOffsetY() {
        int mazeHeight = (rows * 2 + 1) * cellSize;
        return (height - mazeHeight) / 2;
    }

    void printDimensions() {
        int mazeWidth = (cols * 2 + 1) * cellSize;
        int mazeHeight = (rows * 2 + 1) * cellSize;
        printf("Canvas: %dx%d, Maze: %dx%d, Cells: %dx%d\n", 
               width, height, mazeWidth, mazeHeight, cols, rows);
    }
};