// LevelEditor.cpp — FIXED

#include "Engine.h"
#include <GLFW/glfw3.h>
#include <iostream>
#include <vector>
#include <cmath>
#include <cstring>

class LevelEditor : public Engine {
private:
    std::vector<std::shared_ptr<GameObject>> objects_;
    std::shared_ptr<Primitive>  monkeyPrim_;
    std::shared_ptr<GameObject> selected_;

    // FIX: Button stores its own pressed state so DrawButton can be
    //      called for multiple buttons without a shared static variable
    struct Button {
        float x, y, w, h;
        bool  pressed = false;
    };

    // Free-cam state
    bool   firstMouse_ = true;
    double lastX_ = 0.0, lastY_ = 0.0;
    float  yaw_ = -90.0f, pitch_ = 0.0f;
    float  camSpeed_ = 10.0f, mouseSens_ = 0.1f;

    // Light sliders
    float lightR = 1.0f, lightG = 1.0f, lightB = 1.0f, lightI = 1.0f;
    bool  dragR = false, dragG = false, dragB = false, dragI = false;

    Button btnOrtho{ 560.0f, 150.0f, 80.0f, 20.0f, false };
    bool   useOrtho_ = false;

    // FIX: per-key previous-state for edge detection (one-shot placement)
    bool prevKey1_ = false, prevKey2_ = false, prevKey3_ = false,
         prevKey4_ = false, prevKey5_ = false;

    // ---------------------------------------------------------------
    // Input helpers
    // ---------------------------------------------------------------
    bool KeyDown(int k) const { return glfwGetKey(window_, k) == GLFW_PRESS; }

    // Returns true only on the frame the key transitions low→high [web:61]
    bool KeyJustPressed(int k, bool& prev) const {
        bool cur  = KeyDown(k);
        bool just = cur && !prev;
        prev = cur;
        return just;
    }

    void ProcessMouse(double xpos, double ypos) {
        if (firstMouse_) {
            lastX_ = xpos; lastY_ = ypos;
            firstMouse_ = false;
            return; // FIX: skip first frame to avoid a jump
        }
        float xoff = static_cast<float>(xpos - lastX_) * mouseSens_;
        float yoff = static_cast<float>(lastY_ - ypos) * mouseSens_;
        lastX_ = xpos; lastY_ = ypos;
        yaw_  += xoff;
        pitch_ = glm::clamp(pitch_ + yoff, -89.0f, 89.0f);
        glm::vec3 front;
        front.x = cos(glm::radians(yaw_)) * cos(glm::radians(pitch_));
        front.y = sin(glm::radians(pitch_));
        front.z = sin(glm::radians(yaw_)) * cos(glm::radians(pitch_));
        camera_target_ = camera_position_ + glm::normalize(front);
    }

    // FIX: overload — explicit position for use before camera is set up
    void Place(std::shared_ptr<Primitive> prim, const glm::vec3& pos) {
        auto go = CreateGameObject(prim, pos);
        objects_.push_back(go);
        AddGameObject(go);
    }
    void Place(std::shared_ptr<Primitive> prim) { Place(prim, camera_target_); }

    std::shared_ptr<GameObject> Pick(double mx, double my) {
        int w, h; glfwGetWindowSize(window_, &w, &h);
        float nx =  (2.0f * mx) / w - 1.0f;
        float ny = 1.0f - (2.0f * my) / h;
        glm::vec4 ray_clip{ nx, ny, -1.0f, 1.0f };
        glm::mat4 proj = glm::perspective(glm::radians(60.0f), float(w)/h, 0.1f, 1000.0f);
        glm::mat4 view = glm::lookAt(camera_position_, camera_target_, camera_up_);
        glm::vec4 ray_eye = glm::inverse(proj) * ray_clip;
        ray_eye.z = -1.0f; ray_eye.w = 0.0f;
        glm::vec3 dir = glm::normalize(glm::vec3(glm::inverse(view) * ray_eye));

        std::shared_ptr<GameObject> best; float bd = FLT_MAX;
        for (auto& obj : objects_) {
            glm::vec3 oc = camera_position_ - obj->GetPosition();
            float b    = glm::dot(oc, dir);
            float c    = glm::dot(oc, oc)
                       - obj->GetBoundingRadius() * obj->GetBoundingRadius();
            float disc = b * b - c;
            if (disc < 0.0f) continue;
            float t = -b - std::sqrt(disc);
            if (t > 0.0f && t < bd) { bd = t; best = obj; }
        }
        return best;
    }

    // ---------------------------------------------------------------
    // 2D drawing — unified vertex type + helper to avoid repeating
    // glBufferData / glVertexAttribPointer for every draw call
    // ---------------------------------------------------------------
    struct V2D { float x, y, r, g, b; };

    // FIX: single upload-and-draw helper used everywhere — the original
    //      code was missing glVertexAttribPointer calls before several
    //      glDrawArrays calls (e.g. the button fill triangles)
    void UploadAndDraw(const std::vector<V2D>& v, GLenum mode) {
        if (v.empty()) return;
        glBufferData(GL_ARRAY_BUFFER,
                     static_cast<GLsizeiptr>(v.size() * sizeof(V2D)),
                     v.data(), GL_DYNAMIC_DRAW);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
                              sizeof(V2D), reinterpret_cast<void*>(0));
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
                              sizeof(V2D), reinterpret_cast<void*>(2 * sizeof(float)));
        glDrawArrays(mode, 0, static_cast<GLsizei>(v.size()));
    }

    // ---------------------------------------------------------------
    // Segment-display font — digits AND letters
    // FIX: original DrawChar only handled '0'–'9'; calling it with
    //      'R','G','B','I' (and button labels) produced no output.
    //      Merged into one function covering A–Z and 0–9.
    // ---------------------------------------------------------------
    void DrawChar(char c, float x, float y, float s,
                  float r = 1.0f, float g = 1.0f, float b = 1.0f)
    {
        using P = std::pair<float,float>;
        std::vector<P> pts;

        if (c >= '0' && c <= '9') {
            static const std::vector<std::vector<P>> D = {
                {{0,0},{1,0},{1,1},{0,1},{0,0}},
                {{0.5f,0},{0.5f,1}},
                {{0,1},{1,1},{1,0.5f},{0,0.5f},{0,0},{1,0}},
                {{0,1},{1,1},{1,0},{0,0},{1,0.5f}},
                {{0,1},{0,0.5f},{1,0.5f},{1,1},{1,0}},
                {{1,1},{0,1},{0,0.5f},{1,0.5f},{1,0},{0,0}},
                {{1,1},{0,1},{0,0},{1,0},{1,0.5f},{0,0.5f}},
                {{0,1},{1,1},{0.5f,0}},
                {{0,0},{0,1},{1,1},{1,0},{0,0},{0,0.5f},{1,0.5f}},
                {{1,0},{1,1},{0,1},{0,0.5f},{1,0.5f}}
            };
            pts = D[c - '0'];
        } else {
            switch (c) {
            case 'A': pts={{0,0},{0.5f,1},{1,0},{0.25f,0.5f},{0.75f,0.5f}};            break;
            case 'B': pts={{0,0},{0,1},{0.7f,1},{0.7f,0.5f},{0,0.5f},{0.7f,0.5f},{0.7f,0},{0,0}};  break;
            case 'C': pts={{1,1},{0,1},{0,0},{1,0}};                                  break;
            case 'D': pts={{0,0},{0,1},{0.7f,0.8f},{0.7f,0.2f},{0,0}};               break;
            case 'E': pts={{1,1},{0,1},{0,0.5f},{0.7f,0.5f},{0,0.5f},{0,0},{1,0}};   break;
            case 'F': pts={{0,0},{0,1},{1,1},{0,1},{0,0.5f},{0.7f,0.5f}};            break;
            case 'G': pts={{1,1},{0,1},{0,0},{1,0},{1,0.5f},{0.5f,0.5f}};            break;
            case 'H': pts={{0,0},{0,1},{0,0.5f},{1,0.5f},{1,1},{1,0}};               break;
            case 'I': pts={{0,1},{1,1},{0.5f,1},{0.5f,0},{0,0},{1,0}};               break;
            case 'J': pts={{0,1},{1,1},{1,0},{0,0},{0,0.3f}};                        break;
            case 'K': pts={{0,0},{0,1},{0,0.5f},{1,1},{0,0.5f},{1,0}};               break;
            case 'L': pts={{0,1},{0,0},{1,0}};                                        break;
            case 'M': pts={{0,0},{0,1},{0.5f,0.5f},{1,1},{1,0}};                     break;
            case 'N': pts={{0,0},{0,1},{1,0},{1,1}};                                  break;
            case 'O': pts={{0,0},{0,1},{1,1},{1,0},{0,0}};                            break;
            case 'P': pts={{0,0},{0,1},{1,1},{1,0.5f},{0,0.5f}};                     break;
            case 'Q': pts={{1,0},{0,0},{0,1},{1,1},{1,0},{0.6f,0.2f}};               break;
            case 'R': pts={{0,0},{0,1},{1,1},{1,0.5f},{0,0.5f},{1,0}};               break;
            case 'S': pts={{1,1},{0,1},{0,0.5f},{1,0.5f},{1,0},{0,0}};               break;
            case 'T': pts={{0,1},{1,1},{0.5f,1},{0.5f,0}};                           break;
            case 'U': pts={{0,1},{0,0},{1,0},{1,1}};                                  break;
            case 'V': pts={{0,1},{0.5f,0},{1,1}};                                     break;
            case 'W': pts={{0,1},{0.25f,0},{0.5f,0.5f},{0.75f,0},{1,1}};             break;
            case 'X': pts={{0,0},{1,1},{0.5f,0.5f},{0,1},{1,0}};                     break;
            case 'Y': pts={{0,1},{0.5f,0.5f},{1,1},{0.5f,0.5f},{0.5f,0}};           break;
            case 'Z': pts={{0,1},{1,1},{0,0},{1,0}};                                  break;
            default:  return; // skip spaces / unknown
            }
        }

        std::vector<V2D> v;
        v.reserve(pts.size());
        for (auto& p : pts)
            v.push_back({ x + p.first * s, y + p.second * s, r, g, b });
        UploadAndDraw(v, GL_LINE_STRIP);
    }

    // Draw a string left-to-right with fixed character advance
    void DrawString(const char* text, float x, float y, float charSize,
                    float r = 1.0f, float g = 1.0f, float b = 1.0f)
    {
        const float advance = charSize * 0.75f + 2.0f;
        for (int i = 0; text[i] != '\0'; ++i)
            if (text[i] != ' ')
                DrawChar(text[i], x + i * advance, y, charSize * 0.7f, r, g, b);
    }

    float MeasureTextWidth(const char* text, float size) {
        return strlen(text) * (size * 0.7f + 2.0f);
    }

    // FIX: takes Button& (not const) so pressed state is stored per-button,
    //      replacing the broken shared static bool in the original
    bool DrawButton(Button& btn, const char* label,
                    double mx, double my, bool mb)
    {
        bool hovered = mx >= btn.x && mx <= btn.x + btn.w
                    && my >= btn.y && my <= btn.y + btn.h;
        bool prevPressed = btn.pressed;
        btn.pressed      = mb && hovered;
        bool clicked     = btn.pressed && !prevPressed;

        glm::vec3 top = hovered ? glm::vec3(0.3f,0.5f,0.8f)
                                : glm::vec3(0.2f,0.4f,0.7f);
        glm::vec3 bot = hovered ? glm::vec3(0.1f,0.3f,0.6f)
                                : glm::vec3(0.0f,0.2f,0.5f);
        if (btn.pressed) { top *= 0.8f; bot *= 0.8f; }

        // FIX: UploadAndDraw now sets glVertexAttribPointer — the original
        //      triangle draw was missing those calls entirely
        UploadAndDraw({
            {btn.x,          btn.y,          bot.r, bot.g, bot.b},
            {btn.x + btn.w,  btn.y,          bot.r, bot.g, bot.b},
            {btn.x + btn.w,  btn.y + btn.h,  top.r, top.g, top.b},
            {btn.x,          btn.y,          bot.r, bot.g, bot.b},
            {btn.x + btn.w,  btn.y + btn.h,  top.r, top.g, top.b},
            {btn.x,          btn.y + btn.h,  top.r, top.g, top.b},
        }, GL_TRIANGLES);

        UploadAndDraw({
            {btn.x,          btn.y,          0,0,0},
            {btn.x + btn.w,  btn.y,          0,0,0},
            {btn.x + btn.w,  btn.y + btn.h,  0,0,0},
            {btn.x,          btn.y + btn.h,  0,0,0},
            {btn.x,          btn.y,          0,0,0},
        }, GL_LINE_STRIP);

        // Centred label using DrawString (supports full A–Z)
        float tw = MeasureTextWidth(label, 10.0f);
        float tx = btn.x + (btn.w - tw)   * 0.5f;
        float ty = btn.y + (btn.h - 10.0f) * 0.5f;
        DrawString(label, tx, ty, 10.0f);

        return clicked;
    }

    void DrawSlider(float x, float y, float w, float h,
                    float& val, bool& drag,
                    double mx, double my, bool mb)
    {
        if (mb && mx >= x && mx <= x+w && my >= y && my <= y+h) drag = true;
        if (!mb) drag = false;
        if (drag) val = glm::clamp(float((mx - x) / w), 0.0f, 1.0f);

        // Track background
        UploadAndDraw({
            {x,   y,   0.2f,0.2f,0.2f}, {x+w, y,   0.2f,0.2f,0.2f},
            {x+w, y+h, 0.2f,0.2f,0.2f}, {x,   y+h, 0.2f,0.2f,0.2f}
        }, GL_TRIANGLE_FAN);

        // Filled portion
        float fw = w * val;
        UploadAndDraw({
            {x,    y,   0.8f,0.4f,0.4f}, {x+fw, y,   0.8f,0.4f,0.4f},
            {x+fw, y+h, 0.8f,0.4f,0.4f}, {x,    y+h, 0.8f,0.4f,0.4f}
        }, GL_TRIANGLE_FAN);

        // Knob
        float kx = x + fw - 2.0f, ky = y - 2.0f, ks = h + 4.0f;
        UploadAndDraw({
            {kx,    ky,    1,1,1}, {kx+ks, ky,    1,1,1},
            {kx+ks, ky+ks, 1,1,1}, {kx,    ky+ks, 1,1,1}
        }, GL_TRIANGLE_FAN);
    }

public:
    // FIX 1 — constructor: enable_vsync set in body, not on base ctor call
    LevelEditor() : Engine(3840, 2160, "Level Editor") {
        
    }

    bool OnInitialize() override {
        try   { monkeyPrim_ = CreateOBJModel("monkey.obj"); }
        catch (...) { monkeyPrim_ = CreateCube(glm::vec3(1.0f, 0.5f, 0.2f)); }

        // FIX: use explicit-position overload — camera_target_ is not yet
        //      set at this point, so passing it would place the grid at (0,0,0)
        //      only by accident if Engine zero-inits it. Be explicit.
        Place(CreateGrid(20, 1.0f, glm::vec3(0.4f)), glm::vec3(0.0f));

        camera_position_ = glm::vec3(0.0f, 20.0f, 20.0f);
        camera_target_   = camera_position_
                         + glm::normalize(glm::vec3(0.0f) - camera_position_);
        return true;
    }

    void OnUpdate(float dt) override {
        glm::vec3 front = glm::normalize(camera_target_ - camera_position_);
        glm::vec3 right = glm::normalize(glm::cross(front, camera_up_));

        if (KeyDown(GLFW_KEY_W)) camera_position_ += front  * camSpeed_ * dt;
        if (KeyDown(GLFW_KEY_S)) camera_position_ -= front  * camSpeed_ * dt;
        if (KeyDown(GLFW_KEY_A)) camera_position_ -= right  * camSpeed_ * dt;
        if (KeyDown(GLFW_KEY_D)) camera_position_ += right  * camSpeed_ * dt;
        if (KeyDown(GLFW_KEY_Q)) camera_position_ -= camera_up_ * camSpeed_ * dt;
        if (KeyDown(GLFW_KEY_E)) camera_position_ += camera_up_ * camSpeed_ * dt;
        camera_target_ = camera_position_ + front;

        double mx, my; glfwGetCursorPos(window_, &mx, &my);
        if (glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS)
            ProcessMouse(mx, my);
        else
            firstMouse_ = true;

        // FIX: KeyJustPressed — one object per key-press, not one per frame [web:61]
        if (KeyJustPressed(GLFW_KEY_1, prevKey1_)) Place(CreateCube(glm::vec3(0.8f,0.2f,0.2f)));
        if (KeyJustPressed(GLFW_KEY_2, prevKey2_)) Place(CreateSphere(16, glm::vec3(0.2f,0.8f,0.2f)));
        if (KeyJustPressed(GLFW_KEY_3, prevKey3_)) Place(CreatePlane(5.0f, 5.0f, glm::vec3(0.2f,0.2f,0.8f)));
        if (KeyJustPressed(GLFW_KEY_4, prevKey4_)) Place(CreateGrid(5, 1.0f, glm::vec3(0.5f)));
        if (KeyJustPressed(GLFW_KEY_5, prevKey5_)) Place(monkeyPrim_);

        if (glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS)
            selected_ = Pick(mx, my);

        if (selected_) {
            glm::vec3 p = selected_->GetPosition();
            if (KeyDown(GLFW_KEY_UP))    p.z -= dt * 5.0f;
            if (KeyDown(GLFW_KEY_DOWN))  p.z += dt * 5.0f;
            if (KeyDown(GLFW_KEY_LEFT))  p.x -= dt * 5.0f;
            if (KeyDown(GLFW_KEY_RIGHT)) p.x += dt * 5.0f;
            selected_->SetPosition(p);
        }

        Engine::OnUpdate(dt);
    }

    void OnRender() override {
        glUseProgram(shader_program_);
        glm::mat4 proj = useOrtho_
            ? glm::ortho(-30.0f * float(width_)/height_, 30.0f * float(width_)/height_,
                         -30.0f, 30.0f, 0.1f, 1000.0f)
            : glm::perspective(glm::radians(60.0f),
                               float(width_) / float(height_), 0.1f, 1000.0f);
        glUniformMatrix4fv(glGetUniformLocation(shader_program_, "uProjection"),
                           1, GL_FALSE, &proj[0][0]);

        Engine::OnRender();
        RenderOverlay();

        // ── 2D overlay ───────────────────────────────────────────────
        glDisable(GL_DEPTH_TEST);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        glUseProgram(overlay_program);
        glm::mat4 ortho2D = glm::ortho(0.0f, float(width_), 0.0f, float(height_));
        glUniformMatrix4fv(glGetUniformLocation(overlay_program, "uOrtho"),
                           1, GL_FALSE, &ortho2D[0][0]);
        glBindVertexArray(overlay_vao);
        glBindBuffer(GL_ARRAY_BUFFER, overlay_vbo);

        double mx, my; glfwGetCursorPos(window_, &mx, &my);
        my = height_ - my; // flip Y to match OpenGL bottom-left origin
        bool mb = glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS;

        // Sliders
        DrawSlider(50, 50,  200, 10, lightR, dragR, mx, my, mb);
        DrawSlider(50, 70,  200, 10, lightG, dragG, mx, my, mb);
        DrawSlider(50, 90,  200, 10, lightB, dragB, mx, my, mb);
        DrawSlider(50, 110, 200, 10, lightI, dragI, mx, my, mb);

        // FIX: DrawButton now takes Button& — state is stored per button
        // FIX: labels use uppercase A–Z which DrawChar now supports
        if (DrawButton(btnOrtho, useOrtho_ ? "ORTHO" : "PERSP", mx, my, mb))
            useOrtho_ = !useOrtho_;

        // FIX: was DrawChar('R',...) etc. — DrawChar now handles letters;
        //      coloured to match channel (red/green/blue/white)
        DrawChar('R', 30, 52,  10, 1.0f, 0.3f, 0.3f);
        DrawChar('G', 30, 72,  10, 0.3f, 1.0f, 0.3f);
        DrawChar('B', 30, 92,  10, 0.3f, 0.5f, 1.0f);
        DrawChar('I', 30, 112, 10, 1.0f, 1.0f, 1.0f);

        // Push updated light values to 3D shader
        glUseProgram(shader_program_);
        glUniform3f(glGetUniformLocation(shader_program_, "uLightColor"),
                    lightR, lightG, lightB);
        glUniform1f(glGetUniformLocation(shader_program_, "uAmbientStrength"),
                    0.1f * lightI);

        glBindVertexArray(0);
        glUseProgram(0);
        glDisable(GL_BLEND);
        glEnable(GL_DEPTH_TEST);
    }

    void OnShutdown() override { Engine::OnShutdown(); }
};
