#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <runtime/runtime.h>

#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>

#include "meshlet_generator.hpp"
#include "meshlet_renderer.hpp"

static void GlfwErrorCallback(int errnum, const char *desc) {
    rtReportError("GLFW", "Error %d: %s", errnum, desc);
}

static void GLDebugCallback(GLenum source,
                            GLenum type,
                            GLuint id,
                            GLenum severity,
                            GLsizei length,
                            const GLchar *message,
                            const void *userparam) {
    if (id == 131169 || id == 131185 || id == 131218 || id == 131204)
        return;

    rtLog("gl", "---------------");
    rtLog("gl", "Debug message (%u): %s", id, message);

    const char *source_str = "<undefined>";
    switch (source) {
    case GL_DEBUG_SOURCE_API:
        source_str = "Source: API";
        break;
    case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
        source_str = "Source: Window System";
        break;
    case GL_DEBUG_SOURCE_SHADER_COMPILER:
        source_str = "Source: Shader Compiler";
        break;
    case GL_DEBUG_SOURCE_THIRD_PARTY:
        source_str = "Source: Third Party";
        break;
    case GL_DEBUG_SOURCE_APPLICATION:
        source_str = "Source: Application";
        break;
    case GL_DEBUG_SOURCE_OTHER:
        source_str = "Source: Other";
        break;
    }
    rtLog("gl", "%s", source_str);

    const char *type_str = "<undefined>";
    switch (type) {
    case GL_DEBUG_TYPE_ERROR:
        type_str = "Type: Error";
        break;
    case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
        type_str = "Type: Deprecated Behaviour";
        break;
    case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
        type_str = "Type: Undefined Behaviour";
        break;
    case GL_DEBUG_TYPE_PORTABILITY:
        type_str = "Type: Portability";
        break;
    case GL_DEBUG_TYPE_PERFORMANCE:
        type_str = "Type: Performance";
        break;
    case GL_DEBUG_TYPE_MARKER:
        type_str = "Type: Marker";
        break;
    case GL_DEBUG_TYPE_PUSH_GROUP:
        type_str = "Type: Push Group";
        break;
    case GL_DEBUG_TYPE_POP_GROUP:
        type_str = "Type: Pop Group";
        break;
    case GL_DEBUG_TYPE_OTHER:
        type_str = "Type: Other";
        break;
    }
    rtLog("gl", "%s", type_str);

    const char *severity_str = "<undefined>";
    switch (severity) {
    case GL_DEBUG_SEVERITY_HIGH:
        severity_str = "Severity: high";
        break;
    case GL_DEBUG_SEVERITY_MEDIUM:
        severity_str = "Severity: medium";
        break;
    case GL_DEBUG_SEVERITY_LOW:
        severity_str = "Severity: low";
        break;
    case GL_DEBUG_SEVERITY_NOTIFICATION:
        severity_str = "Severity: notification";
        break;
    }
    rtLog("gl", "%s", severity_str);

    if (type == GL_DEBUG_TYPE_ERROR)
        RT_DEBUGBREAK;
}

struct file_picker {

    file_picker() {
        memset(m_input_buf[0], 0, sizeof(m_input_buf[0]));
        memset(m_input_buf[1], 0, sizeof(m_input_buf[1]));

        strcpy(m_input_buf[0], "assets/stanford-bunny.obj");
    }

    /* Returns true if a new file was selected */
    bool RunFlat() {
        bool opened=false;
        if (ImGui::Begin("File Selection", &m_open)) {
            ImGui::InputTextWithHint("Path",
                                     "Path to the OBJ file",
                                     &m_input_buf[m_active_input][0],
                                     RT_ARRAY_COUNT(m_input_buf[0]));
            if (ImGui::Button("Open")) {
                m_picked = m_input_buf[m_active_input];
                m_active_input = (m_active_input + 1) & 1;
                strcpy(m_input_buf[m_active_input], m_picked);
                opened         = true;
            }
            ImGui::End();
        }
        return opened;
    }

    RT_INLINE const char *GetPicked() {
        RT_VERIFY(m_picked);
        return m_picked;
    }
    RT_INLINE bool HasPickedFile() const {
        return m_picked != nullptr;
    }
    
    const char *m_picked=nullptr;
    bool m_open = true;
    char m_input_buf[2][260];
    int m_active_input = 0;
};

int main() {
    if (rtInitRuntime() != RT_SUCCESS)
        return -1;
    glfwSetErrorCallback(GlfwErrorCallback);
    if (!glfwInit())
        return -1;

    rtLog("EXP", "Meshlets experiment starting.");

    glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
    GLFWwindow *window = glfwCreateWindow(1280, 720, "MESHLETS!", NULL, NULL);
    if (!window)
        return -1;
    glfwMakeContextCurrent(window);
    if (!gladLoadGL())
        return -1;
    glfwSwapInterval(1);

    int flags;
    glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
    if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) {
        glEnable(GL_DEBUG_OUTPUT);
        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
        glDebugMessageCallback(GLDebugCallback, nullptr);
        glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
    }

    // Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO &io = ImGui::GetIO();
    (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;  // Enable Gamepad Controls
    ImGui::StyleColorsDark();
    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init("#version 130");

    file_picker picker;
    meshlet_generator gen;
    meshlet_renderer ren;
    ren.Initialize();

    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();
        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();

        if (picker.RunFlat()) {
            gen.LoadObj(picker.GetPicked());
            gen.RunFlat();
        }
        ren.SettingMenu();

        ImGui::Render();
        int display_w, display_h;
        glfwGetFramebufferSize(window, &display_w, &display_h);
        glViewport(0, 0, display_w, display_h);
        glClearColor(0.f, 0.f, 0.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        if (picker.HasPickedFile()) {
            ren.m_aspect = (float)display_w / (float)display_h;
            ren.RenderFlat(gen.m_meshlets, gen.m_num_meshlets);
        }

        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
        glfwSwapBuffers(window);
    }

    glfwDestroyWindow(window);
    glfwTerminate();
    rtShutdownRuntime();
    return 0;
}
