// dear imgui: standalone example application for GLFW + OpenGL 3, using programmable pipeline
// If you are new to dear imgui, see examples/README.txt and documentation at the top of imgui.cpp.
// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.)

#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <stdio.h>
#include <glm/vec3.hpp>

// About Desktop OpenGL function loaders:
//  Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers.
//  Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad).
//  You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own.
#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W)
#include <GL/gl3w.h>    // Initialize with gl3wInit()
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW)
#include <GL/glew.h>    // Initialize with glewInit()
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD)
#include <glad/glad.h>  // Initialize with gladLoadGL()
#else
#include IMGUI_IMPL_OPENGL_LOADER_CUSTOM
#endif

// Include glfw3.h after our OpenGL definitions
#include <GLFW/glfw3.h>

#include "engineClass/scene.h"
#include "engineClass/mainRenderer.h"
#include "engineClass/UI.h"
#include "engineClass/InputManager.h"
// #include "engineClass/var_global.h"

#include <chrono>
#include <thread>
#include <iostream>

// [Win32] Our example includes a copy of glfw3.lib pre-compiled with VS2010 to maximize ease of testing and compatibility with old VS compilers.
// To link with VS2010-era libraries, VS2015+ requires linking with legacy_stdio_definitions.lib, which we do using this pragma.
// Your own project should not be affected, as you are likely to link with a newer binary of GLFW that is adequate for your version of Visual Studio.
#if defined(_MSC_VER) && (_MSC_VER >= 1900) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
#pragma comment(lib, "legacy_stdio_definitions")
#endif

float global_limitFramerate;

std::chrono::microseconds inputUpdateTime, updateTime, renderingTime, rendererUpdate, physicsUpdate;



void display(int display_w, int display_h, MainRenderer *renderer, UI *ui){
    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();

    // CREATE UI //
    ui->drawUI();
    //ImGui::ShowDemoWindow();
    if(ui->hasToDisplayed()){
        ImGui::Begin("Execution time");
        ImGui::Text("Application average %.3f ms/frame", 1000.0f / ImGui::GetIO().Framerate);
        ImGui::Text("(%.1f FPS)", ImGui::GetIO().Framerate);
        ImGui::Text("Input update : %u microseconds", inputUpdateTime);
        ImGui::Text("Physics update: %u microseconds", physicsUpdate);
        ImGui::Text("scene update : %u microseconds", updateTime);
        ImGui::Text("rendering : %u microseconds", renderingTime);
        ImGui::Text("renderer update : %u microseconds", rendererUpdate);
        ImGui::End();
    }
    
    // Rendering
    ImGui::Render();
    renderer->displaySceneOnTheScreen(display_w, display_h);
    // draw UI
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}

void rendering(int display_w, int display_h, MainRenderer *renderer, Scene *scene){
    const ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);

    glViewport(0, 0, display_w, display_h);
    glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);
    glClear(GL_COLOR_BUFFER_BIT);
    renderer->paintGL(scene, display_w, display_h);
}


static void glfw_error_callback(int error, const char* description)
{
    fprintf(stderr, "Glfw Error %d: %s\n", error, description);
}


int main(int, char**)
{
    // Setup window
    glfwSetErrorCallback(glfw_error_callback);
    if (!glfwInit())
        return 1;

    // Decide GL+GLSL versions
#if __APPLE__
    // GL 3.2 + GLSL 150
    const char* glsl_version = "#version 150";
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 3.2+ only
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);            // Required on Mac
#else
    // GL 3.0 + GLSL 130
    const char* glsl_version = "#version 130";
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
    //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 3.2+ only
    //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);            // 3.0+ only
#endif

    // Create window with graphics context
    GLFWwindow* window = glfwCreateWindow(1920, 1080, "Voxel-Engine", NULL, NULL);
    if (window == NULL)
        return 1;
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1); // Enable vsync

    // Initialize OpenGL loader
#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W)
    bool err = gl3wInit() != 0;
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW)
    bool err = glewInit() != GLEW_OK;
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD)
    bool err = gladLoadGL() == 0;
#else
    bool err = false; // If you use IMGUI_IMPL_OPENGL_LOADER_CUSTOM, your loader is likely to requires some form of initialization.
#endif
    if (err)
    {
        fprintf(stderr, "Failed to initialize OpenGL loader!\n");
        return 1;
    }

    // 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

    // Setup Dear ImGui style
    ImGui::StyleColorsDark();

    // Setup Platform/Renderer bindings
    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init(glsl_version);

    glfwSwapInterval(1);

    // glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);



    MainRenderer *renderer = new MainRenderer();
    renderer->initializeGL();

    Scene *scene = new Scene();

    UI *ui = new UI();
    InputManager inputManager;

    ui->set(scene);
    ui->set(renderer);
    ui->set(window);
    
    inputManager.setUI(ui);
    inputManager.setScene(scene);
    inputManager.setRenderer(renderer);



    int display_w, display_h;
    double lasttime = glfwGetTime();
    global_limitFramerate = 60.f;

    // Main loop
    while (!glfwWindowShouldClose(window)){
        

        glfwPollEvents();
        glfwGetFramebufferSize(window, &display_w, &display_h);

        scene->cleanDestroy();


        auto start = std::chrono::high_resolution_clock::now();
        scene->inputUpdate();
        auto stop = std::chrono::high_resolution_clock::now(); 
        inputUpdateTime = std::chrono::duration_cast<std::chrono::microseconds>(stop - start); 

        /// UPDATE
        inputManager.update();

        start = std::chrono::high_resolution_clock::now();
        scene->physicsUpdate();
        stop = std::chrono::high_resolution_clock::now(); 
        physicsUpdate = std::chrono::duration_cast<std::chrono::microseconds>(stop - start); 

        start = std::chrono::high_resolution_clock::now();
        scene->update();
        stop = std::chrono::high_resolution_clock::now(); 
        updateTime = std::chrono::duration_cast<std::chrono::microseconds>(stop - start); 
        
        start = std::chrono::high_resolution_clock::now();
        renderer->update();
        stop = std::chrono::high_resolution_clock::now(); 
        rendererUpdate = std::chrono::duration_cast<std::chrono::microseconds>(stop - start); 
        
        // std::thread threadSceneUpdate(&Scene::update, scene);
        // std::thread threadRendererUpdate(&MainRenderer::update, renderer);
        // renderer->update();

        //threadInput.join();
        // threadSceneUpdate.join();
        // threadRendererUpdate.join();

        // RENDERING
        start = std::chrono::high_resolution_clock::now();
        rendering(display_w, display_h, renderer, scene);
        stop = std::chrono::high_resolution_clock::now(); 
        renderingTime = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);

        // DISPLAY ON THE SCREEN
        display(display_w, display_h, renderer, ui);

        // wait for refresh rate
        while (glfwGetTime() < lasttime + 1.0/global_limitFramerate) {
            // sleep for x milliseconds
            std::this_thread::sleep_for(std::chrono::milliseconds(1));   
        }
        lasttime += 1.0/global_limitFramerate;

        glfwSwapBuffers(window);
        // glfwSetCursorPos(window, display_w/2,display_h/2);   
    }

    delete(scene);
    delete(renderer);
    delete(ui);
    // delete(inputManager);

    // Cleanup
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();

    glfwDestroyWindow(window);
    glfwTerminate();

    return 0;
}
