/*
 * --------------------------------------------------------------------------
 *
 *    ___________                      ____   ____.__                    
 *    \__    ___/______________________\   \ /   /|__| _________________ 
 *      |    |_/ __ \_  __ \_  __ \__  \\   Y   / |  |/  ___/  _ \_  __ \
 *      |    |\  ___/|  | \/|  | \// __ \\     /  |  |\___ (  <_> )  | \/
 *      |____| \___  >__|   |__|  (____  /\___/   |__/____  >____/|__|   
 *                 \/                  \/                 \/             
 * 
 *                               TerraVisor
 *                     Dynamic Terrain Visualization
 * 
 * --------------------------------------------------------------------------
 * Author: Jack Christensen
 * Version: 0.0.1
 *
 * Description:
 * TerraVisor is a dynamic terrain visualization tool that uses real-world elevation
 * data to render high-fidelity 3D landscapes. It leverages tessellation shaders
 * to provide detailed and adaptive terrain rendering.
 *
 * License:
 * This project is licensed under the GNU General Public License v3.0.
 * See the COPYING file for more details.
 */

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <chrono>
#include <thread>

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

#include "callbacks.h"
#include "scene.h"

namespace {

const int kInitWindowWidth = 1280;
const int kInitWindowHeight = 720;

const int kTargetFps = 60;
const auto kFrameDuration = std::chrono::milliseconds(1000 / kTargetFps);

bool InitializeGlfw() {
    if (!glfwInit()) {
        std::cerr << "Failed to initialize GLFW" << std::endl;
        return false;
    }
    return true;
}

GLFWwindow* CreateGlfwWindow() {
#ifdef _DEBUG
    glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
#endif

    GLFWwindow* window = glfwCreateWindow(kInitWindowWidth, kInitWindowHeight, "TerraVisor", nullptr, nullptr);
    if (!window) {
        std::cerr << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return nullptr;
    }

    glfwMakeContextCurrent(window);
    return window;
}

bool InitializeGlew() {
    glewExperimental = GL_TRUE;
    if (glewInit() != GLEW_OK) {
        std::cerr << "Failed to initialize GLEW" << std::endl;
        return false;
    }
    return true;
}

void InitializeImGui(GLFWwindow* window) {
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init("#version 150");
}

void CleanupImGui() {
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();
}

void MainLoop(GLFWwindow* window, Scene& scene) {
    auto last_frame_time = std::chrono::high_resolution_clock::now();

    while (!glfwWindowShouldClose(window)) {
        auto frame_start = std::chrono::high_resolution_clock::now();

        scene.Idle();
        scene.Display(window);
        
        glfwPollEvents();

        auto frame_end = std::chrono::high_resolution_clock::now();
        auto frame_duration = frame_end - frame_start;

        if (frame_duration < kFrameDuration) {
        std::this_thread::sleep_for(kFrameDuration - frame_duration);
        }

        auto current_frame_time = std::chrono::high_resolution_clock::now();
        auto actual_frame_duration = std::chrono::duration_cast<std::chrono::milliseconds>(current_frame_time - last_frame_time).count();

        last_frame_time = current_frame_time;
    }
} 

} // namespace

int main() {
    if (!InitializeGlfw()) return -1;

    GLFWwindow* window = CreateGlfwWindow();
    if (!window) return -1;

    if (!InitializeGlew()) return -1;

    Scene scene(kInitWindowWidth, kInitWindowHeight);
    Callbacks::Register(window, &scene);
    scene.Init();
    InitializeImGui(window);

    MainLoop(window, scene);

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