#include "Engine.h"
#include "LevelEditor.cpp"
#include <iostream>
#include <random>
#include <deque>
#include <chrono>


// ---------------------------------------------
// SceneDemo: existing complex scene demo
// ---------------------------------------------
class SceneDemo : public Engine {
private:
    float time_ = 0.0f;
    std::vector<std::shared_ptr<GameObject>> rotating_cubes_;
    std::vector<std::shared_ptr<GameObject>> floating_spheres_;
    std::shared_ptr<GameObject> central_sphere_;
    std::shared_ptr<GameObject> ground_plane_;
    std::shared_ptr<GameObject> grid_;
    
    std::mt19937 rng_;
    std::uniform_real_distribution<float> color_dist_{0.3f, 1.0f};
    
public:
    SceneDemo()
      : Engine(1920, 1080, "Modern C++ Engine - Complex Scene Demo"),
        rng_(std::random_device{}()) {}
    
    bool OnInitialize() override {
        std::cout << "Creating complex scene with primitives..." << std::endl;
        
        auto grid_primitive = CreateGrid(25, 1.0f, glm::vec3(0.4f));
        grid_ = CreateGameObject(grid_primitive, glm::vec3(0,0,0));
        AddGameObject(grid_);
        
        auto plane_primitive = CreatePlane(30.0f, 30.0f, glm::vec3(0.2f,0.2f,0.3f));
        ground_plane_ = CreateGameObject(plane_primitive, glm::vec3(0,-0.01f,0));
        AddGameObject(ground_plane_);
        
        auto central_sphere_primitive = CreateSphere(64, glm::vec3(0.9f,0.3f,0.1f));
        central_sphere_ = CreateGameObject(central_sphere_primitive, glm::vec3(0,3,0));
        central_sphere_->SetScale(glm::vec3(2.0f));
        AddGameObject(central_sphere_);
        
        for (int i = 0; i < 8; ++i) {
            glm::vec3 color{color_dist_(rng_), color_dist_(rng_), color_dist_(rng_)};
            auto cube = CreateGameObject(CreateCube(color), glm::vec3(0.0f));
            rotating_cubes_.push_back(cube);
            AddGameObject(cube);
        }
        
        for (int i = 0; i < 15; ++i) {
            glm::vec3 color{color_dist_(rng_), color_dist_(rng_), color_dist_(rng_)};
            float x = (rng_() % 20) - 10.0f;
            float z = (rng_() % 20) - 10.0f;
            float y = 1.0f + (rng_() % 5);
            auto sphere = CreateGameObject(CreateSphere(32, color),
                                           glm::vec3(x,y,z));
            sphere->SetScale(glm::vec3(0.5f + (rng_()%10)*0.1f));
            floating_spheres_.push_back(sphere);
            AddGameObject(sphere);
        }
        
        for (int i = 0; i < 5; ++i) {
            glm::vec3 color{1.0f - i*0.15f, 0.2f + i*0.15f, 0.5f};
            auto cube = CreateGameObject(CreateCube(color),
                                         glm::vec3(-8, i*2.1f+1, 8));
            cube->SetScale(glm::vec3(0.8f + i*0.1f));
            AddGameObject(cube);
        }
        
        for (int i = 0; i < 10; ++i) {
            float hue = i/10.0f;
            glm::vec3 color{hue, 1.0f-hue, 0.5f};
            auto sphere = CreateGameObject(CreateSphere(24, color),
                                           glm::vec3(i*2-9,1,-10));
            sphere->SetScale(glm::vec3(0.3f));
            AddGameObject(sphere);
        }
        
        std::cout << "Scene created with " << game_objects_.size() << " objects!" << std::endl;
        return true;
    }
    
    void OnUpdate(float dt) override {
        time_ += dt;
        if (central_sphere_)
            central_sphere_->SetRotation(glm::vec3(0, time_*30, 0));
        
        for (size_t i = 0; i < rotating_cubes_.size(); ++i) {
            float angle = time_*60.0f + i*(360.0f/rotating_cubes_.size());
            float radius=8.0f, height=2.0f+std::sin(time_*2.0f+i)*1.5f;
            glm::vec3 pos{std::cos(glm::radians(angle))*radius,
                          height,
                          std::sin(glm::radians(angle))*radius};
            rotating_cubes_[i]->SetPosition(pos);
            rotating_cubes_[i]->SetRotation(glm::vec3(angle,angle*1.5f,0));
        }
        
        for (size_t i = 0; i < floating_spheres_.size(); ++i) {
            auto sphere = floating_spheres_[i];
            glm::vec3 p = sphere->GetPosition();
            float bob = std::sin(time_*3.0f + i*0.5f)*0.3f;
            sphere->SetPosition(glm::vec3(p.x, p.y+bob, p.z));
            sphere->SetRotation(glm::vec3(0, time_*20.0f + i*30.0f, 0));
        }
        
        float camAng = time_*15.0f;
        SetCameraOrbit(20.0f, camAng, 12.0f);
        SetCameraTarget(glm::vec3(0,2,0));
        
        static float status_timer=0.0f;
        status_timer += dt;
        if (status_timer >= 3.0f) {
            std::cout<<"Scene running: "<<int(time_)<<"s | "
                     <<game_objects_.size()<<" objects | Cam angle: "
                     <<int(camAng)%360<<"°"<<std::endl;
            status_timer=0.0f;
        }
    }
};



// ---------------------------------------------
// StressTest: incremental spawn until quit
// ---------------------------------------------
class StressTest : public Engine {
public:
    StressTest() : Engine(1920, 1080, "Zoom Stresser") {}


    bool OnInitialize() override {
        last_report_ = std::chrono::high_resolution_clock::now();
        
        // Add a ground plane for visual reference
        auto plane = CreateGameObject(CreatePlane(50.0f, 50.0f, glm::vec3(0.2f, 0.3f, 0.5f)),
                                      glm::vec3(0, -1, 0));
        AddGameObject(plane);
        
        return true;
    }


    void OnUpdate(float dt) override {
        time_ += dt;
        
        // Spawn new objects every second
        if (game_objects_.size() < 10000) {
            spawn_timer_ += dt;
            if (spawn_timer_ >= 1.0f) {
                spawn_timer_ = 0.0f;
                AddRow();
            }
        }
        
        // Animate existing objects
        AnimateObjects();
        
        // Orbit camera around the scene
        float camera_angle = time_ * 20.0f; // 20 degrees per second
        float radius = 30.0f + std::sin(time_ * 0.5f) * 10.0f; // Varying radius
        float height = 15.0f + std::cos(time_ * 0.3f) * 5.0f;  // Varying height
        SetCameraOrbit(radius, camera_angle, height);
        SetCameraTarget(glm::vec3(0, 5, 0));
        
        // Report performance every 2 seconds
        auto now = std::chrono::high_resolution_clock::now();
        float elapsed = std::chrono::duration<float>(now - last_report_).count();
        if (elapsed >= 2.0f) {
            float fps = 1.0f/dt;
            std::cout << "Objects: " << game_objects_.size()
                      << " | FPS: " << fps 
                      << " | Cam angle: " << int(camera_angle) % 360 << "°" << std::endl;
            last_report_ = now;
        }
    }


private:
    void AddRow() {
        int row = int(stress_objects_.size()) / columns_;
        for (int i = 0; i < columns_; ++i) {
            // Colorful gradient based on position
            float hue = (i + row * columns_) / 100.0f;
            glm::vec3 color{
                0.5f + 0.5f * std::sin(hue * 6.28f),
                0.5f + 0.5f * std::sin(hue * 6.28f + 2.09f),
                0.5f + 0.5f * std::sin(hue * 6.28f + 4.19f)
            };
            
            glm::vec3 pos{
                i * spacing_ - columns_ * spacing_ / 2.0f,
                0.5f + row * spacing_,
                -20.0f + row * 2.0f  // Spread them out in depth too
            };
            
            // Alternate between cubes and spheres
            std::shared_ptr<GameObject> obj;
            if ((i + row) % 2 == 0) {
                obj = CreateGameObject(CreateCube(color), pos);
            } else {
                obj = CreateGameObject(CreateSphere(16, color), pos);
            }
            
            obj->SetScale(glm::vec3(0.4f + (i % 5) * 0.1f)); // Varying sizes
            stress_objects_.push_back(obj);
            AddGameObject(obj);
        }
    }
    
    void AnimateObjects() {
        for (size_t i = 0; i < stress_objects_.size(); ++i) {
            auto& obj = stress_objects_[i];
            glm::vec3 pos = obj->GetPosition();
            
            // Floating/bobbing motion
            float bob_speed = 2.0f + (i % 10) * 0.2f;
            float bob_height = 0.3f + (i % 7) * 0.1f;
            float bob = std::sin(time_ * bob_speed + i * 0.1f) * bob_height;
            obj->SetPosition(glm::vec3(pos.x, pos.y + bob, pos.z));
            
            // Rotation animation
            float rot_speed = 30.0f + (i % 13) * 10.0f;
            glm::vec3 rotation{
                time_ * rot_speed + i * 10.0f,
                time_ * (rot_speed * 0.7f) + i * 15.0f,
                time_ * (rot_speed * 0.4f) + i * 20.0f
            };
            obj->SetRotation(rotation);
        }
    }


    float time_{0.0f};
    float spawn_timer_{0.0f};
    std::chrono::high_resolution_clock::time_point last_report_;
    std::vector<std::shared_ptr<GameObject>> stress_objects_;
    static constexpr int columns_{500}; // Reduced for better performance
    static constexpr float spacing_{1.0f};
};


// ---------------------------------------------
// SpinningMonkey: Loads OBJ monkey and spins it
// ---------------------------------------------
class SpinningMonkey : public Engine {
private:
    float time_ = 0.0f;
    std::shared_ptr<GameObject> monkey_;
    std::shared_ptr<GameObject> ground_plane_;
    std::shared_ptr<GameObject> grid_;
    
    // Animation parameters
    float spin_speed_ = 45.0f;  // degrees per second
    float bob_height_ = 2.0f;   // vertical bobbing range
    float bob_speed_ = 1.5f;    // bobbing frequency
    float scale_pulse_ = 0.2f;  // scale pulsing amount
    
public:
    SpinningMonkey()
      : Engine(1920, 1080, "Spinning Monkey Demo - OBJ Model Loader") {}
    
    bool OnInitialize() override {
        std::cout << "Loading Suzanne (Blender Monkey) model..." << std::endl;
        
        // Create ground reference
        auto grid_primitive = CreateGrid(15, 1.0f, glm::vec3(0.3f, 0.3f, 0.4f));
        grid_ = CreateGameObject(grid_primitive, glm::vec3(0, 0, 0));
        AddGameObject(grid_);
        
        auto plane_primitive = CreatePlane(20.0f, 20.0f, glm::vec3(0.15f, 0.25f, 0.35f));
        ground_plane_ = CreateGameObject(plane_primitive, glm::vec3(0, -0.01f, 0));
        AddGameObject(ground_plane_);
        
        // Try to load monkey model (with optional texture)
        try {
            // First try with texture
            auto monkey_primitive = CreateOBJModel("monkey.obj", "monkey.jpg");
            if (monkey_primitive) {
                monkey_ = CreateGameObject(monkey_primitive, glm::vec3(0, 3, 0));
                monkey_->SetScale(glm::vec3(2.0f));
                AddGameObject(monkey_);
                std::cout << "✓ Loaded monkey with texture!" << std::endl;
            }
        } catch (...) {
            std::cout << "! Texture loading failed, trying without texture..." << std::endl;
            try {
                // Fallback: load without texture
                auto monkey_primitive = CreateOBJModel("monkey.obj");
                if (monkey_primitive) {
                    monkey_ = CreateGameObject(monkey_primitive, glm::vec3(0, 3, 0));
                    monkey_->SetScale(glm::vec3(2.0f));
                    AddGameObject(monkey_);
                    std::cout << "✓ Loaded monkey without texture!" << std::endl;
                }
            } catch (...) {
                std::cout << "✗ Failed to load monkey.obj, using fallback cube..." << std::endl;
                // Ultimate fallback: colored cube
                auto fallback_primitive = CreateCube(glm::vec3(1.0f, 0.5f, 0.2f));
                monkey_ = CreateGameObject(fallback_primitive, glm::vec3(0, 3, 0));
                monkey_->SetScale(glm::vec3(2.0f));
                AddGameObject(monkey_);
            }
        }
        
        // Set initial camera position
        SetCameraPosition(glm::vec3(8, 6, 8));
        SetCameraTarget(glm::vec3(0, 3, 0));
        
        std::cout << "Scene initialized with " << game_objects_.size() << " objects!" << std::endl;
        std::cout << "Controls:" << std::endl;
        std::cout << "  - Camera orbits automatically" << std::endl;
        std::cout << "  - Monkey spins and bobs up and down" << std::endl;
        std::cout << "  - Scale pulses with time" << std::endl;
        
        return true;
    }
    
    void OnUpdate(float dt) override {
        time_ += dt;
        
        float spin_y = time_ * spin_speed_;

        if (monkey_) {
            // Spinning animation
           
            float spin_x = std::sin(time_ * 0.3f) * 15.0f;  // Gentle x-axis wobble
            float spin_z = std::cos(time_ * 0.7f) * 10.0f;  // Gentle z-axis wobble
            monkey_->SetRotation(glm::vec3(spin_x, spin_y, spin_z));
            
            // Vertical bobbing
            float base_height = 3.0f;
            float bob_offset = std::sin(time_ * bob_speed_) * bob_height_;
            monkey_->SetPosition(glm::vec3(0, base_height + bob_offset, 0));
            
            // Scale pulsing
            float base_scale = 2.0f;
            float scale_offset = std::sin(time_ * 2.0f) * scale_pulse_;
            float current_scale = base_scale + scale_offset;
            monkey_->SetScale(glm::vec3(current_scale));
        }
        
        // Orbiting camera
        float camera_angle = time_ * 25.0f;  // 25 degrees per second
        float camera_radius = 10.0f + std::sin(time_ * 0.8f) * 3.0f;  // Varying distance
        float camera_height = 6.0f + std::cos(time_ * 0.5f) * 2.0f;   // Varying height
        
        SetCameraOrbit(camera_radius, camera_angle, camera_height);
        SetCameraTarget(glm::vec3(0, 3, 0));  // Always look at monkey
        
        // Status updates every 5 seconds
        static float status_timer = 0.0f;
        status_timer += dt;
        if (status_timer >= 5.0f) {
            std::cout << "🐵 Monkey spinning: " << int(time_) << "s | "
                      << "Rotation: " << int(spin_y) % 360 << "° | "
                      << "Camera: " << int(camera_angle) % 360 << "°" << std::endl;
            status_timer = 0.0f;
        }
    }
};

// ---------------------------------------------
// MonkeyGridDemo: 50 jiggling monkeys on grid
// ---------------------------------------------
class MonkeyGridDemo : public Engine {
private:
    float time_ = 0.0f;
    std::vector<std::shared_ptr<GameObject>> monkeys_;
    std::shared_ptr<GameObject> ground_plane_;
    std::shared_ptr<GameObject> grid_;
    
    static constexpr int MONKEY_COUNT = 50;
    static constexpr int GRID_COLS = 7;
    static constexpr int GRID_ROWS = 8;
    static constexpr float SPACING = 4.0f;
    std::mt19937 rng_{ std::random_device{}() };
    std::uniform_real_distribution<float> jiggle_dist_{ -0.1f, 0.1f };
    
public:
    MonkeyGridDemo()
      : Engine(1920, 1080,  "Monkey Grid Demo") {}
    
    bool OnInitialize() override {
        std::cout << "Spawning " << MONKEY_COUNT << " miniature monkeys on grid..." << std::endl;
        
        // Grid and plane for reference
        auto grid_primitive = CreateGrid(15, 1.0f, glm::vec3(0.3f));
        grid_ = CreateGameObject(grid_primitive, glm::vec3(0, 0, 0));
        AddGameObject(grid_);
        
        auto plane_primitive = CreatePlane(60.0f, 60.0f, glm::vec3(0.2f, 0.25f, 0.3f));
        ground_plane_ = CreateGameObject(plane_primitive, glm::vec3(0, -0.01f, 0));
        AddGameObject(ground_plane_);
        
        // Load OBJ monkey primitive once
        std::shared_ptr<Primitive> monkey_primitive;
        try {
            monkey_primitive = CreateOBJModel("monkey.obj", "monkey.jpg");
            if (!monkey_primitive)
                monkey_primitive = CreateOBJModel("monkey.obj");
        } catch (...) {
            monkey_primitive = CreateCube(glm::vec3(1.0f, 0.5f, 0.2f));
        }
        
        // Spawn monkeys on GRID_COLS×GRID_ROWS grid
        int spawned = 0;
        for (int r = 0; r < GRID_ROWS && spawned < MONKEY_COUNT; ++r) {
            for (int c = 0; c < GRID_COLS && spawned < MONKEY_COUNT; ++c) {
                float x = (c - (GRID_COLS - 1) * 0.5f) * SPACING;
                float z = (r - (GRID_ROWS - 1) * 0.5f) * SPACING;
                auto monkey = CreateGameObject(monkey_primitive, glm::vec3(x, 1.0f, z));
                monkey->SetScale(glm::vec3(0.5f));  // shrink to half size
                monkeys_.push_back(monkey);
                AddGameObject(monkey);
                ++spawned;
            }
        }
        
        // Setup camera
        SetCameraOrbit(50.0f, 45.0f, 30.0f);
        SetCameraTarget(glm::vec3(0, 1.0f, 0));
        
        return true;
    }
    
    void OnUpdate(float dt) override {
        time_ += dt;
        
        // Jiggle and slight bob for each monkey
        for (auto& monkey : monkeys_) {
            glm::vec3 basePos = monkey->GetPosition();
            float jigX = jiggle_dist_(rng_);
            float jigZ = jiggle_dist_(rng_);
            float bobY = std::sin(time_ * 3.0f + basePos.x + basePos.z) * 0.2f;
            
            monkey->SetPosition(glm::vec3(
                basePos.x + jigX,
                1.0f + bobY,
                basePos.z + jigZ
            ));
            
            // Optional subtle rotation
            float angle = std::sin(time_ * 2.0f + basePos.x - basePos.z) * 15.0f;
            monkey->SetRotation(glm::vec3(0, angle, 0));
        }
        
        // Slow orbiting camera
        float camAng = time_ * 10.0f;
        SetCameraOrbit(50.0f, camAng, 30.0f);
        SetCameraTarget(glm::vec3(0, 1.0f, 0));
    }
};



// ---------------------------------------------
// main: choose demo, stress, or monkey by arg
// ---------------------------------------------
int main(int argc, char** argv) {
    try {
        std::string mode = (argc > 1) ? std::string(argv[1]) : "demo";
        
        if (mode == "stress") {
            std::cout << "Running Stress Test..." << std::endl;
            StressTest demo;
            demo.Run();
            return 0;

        } 
        else if (mode == "monkeygrid") {
            std::cout << "Starting Monkey Grid Demo..." << std::endl;
            MonkeyGridDemo demo;
            demo.Run();
            return 0;
        }
        else if (mode == "edit") {
            std::cout << "Starting Zoom Level Editor ..." << std::endl;
            LevelEditor edit;
            edit.Run();
            return 0;
        }
        else if (mode == "monkey") {
            std::cout << "🐵 Starting Spinning Monkey Demo..." << std::endl;
            std::cout << "====================================" << std::endl;
            std::cout << "Place your monkey.obj in models/ folder" << std::endl;
            std::cout << "Optionally add monkey.bmp in textures/ folder" << std::endl;
            std::cout << "====================================" << std::endl;
            SpinningMonkey demo;
            demo.Run();
            std::cout << "====================================" << std::endl;
            std::cout << "Monkey demo completed successfully! 🐵" << std::endl;
            return 0;
        } else {
            std::cout << "Starting Modern C++ Engine Scene Demo..." << std::endl;
            std::cout << "=========================================" << std::endl;
            SceneDemo demo;
            demo.Run();
            std::cout << "=========================================" << std::endl;
            std::cout << "Demo completed successfully!" << std::endl;
            return 0;
        
        } 
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return -1;
    }
}
