#include "Window.hpp"

#include <GL/gl3w.h>
#include <GLFW/glfw3.h>
#include <exception>
#include <thread>
#include <chrono>
#include "../core/utils/logger.hpp"
#include "logo.cxx"

/// Default floating window width
constexpr auto DEFAULT_WIDTH = 1280;
/// Default floating window height
constexpr auto DEFAULT_HEIGHT = 720;

constexpr auto MIN_WIDTH = 854;
constexpr auto MIN_HEIGHT = 480;

constexpr auto APP_NAME = "Univerxel";

static void glfw_error_callback(int error, const char* description) {
    LOG_E("[GLFW] " << error << ": " << description);
}

Window::Window() : ptr(nullptr), targetFPS(60) {
    glfwSetErrorCallback(glfw_error_callback);

    if (!glfwInit()) {
        FATAL("Failed to initialize GLFW");
    }
}
Window::~Window() {
    glfwTerminate();
}

bool Window::create(const CreateInfo &opt) {
    if(isReady()) {
        LOG_W("Previous window in use");
        return false;
    }

    glfwWindowHint(GLFW_SAMPLES, opt.samples);
    switch (opt.client.type) {
        case CreateInfo::Client::Type::GL:
            glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
            glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, opt.client.major);
            glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, opt.client.minor);
            glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
            glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
            break;

        case CreateInfo::Client::Type::VK:
            glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
            break;

        default:
            FATAL("Unsupported client type");
            break;
    }

#if FIXED_WINDOW
    glfwWindowHint(GLFW_RESIZABLE, false); //Note: Dev-only: Force floating on i3
#endif

    ptr = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, APP_NAME, NULL, NULL);
    if (ptr == NULL) {
        LOG_E("Failed to open GLFW window");
        return false;
    }
    if (opt.client.type == CreateInfo::Client::Type::GL)
        glfwMakeContextCurrent(ptr);

    // Hide the mouse and enable unlimited mouvement
    // glfwSetInputMode(ptr, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // Set the mouse at the center of the screen
    // TODO: move to input manager
    glfwPollEvents();
    glfwSetCursorPos(ptr, DEFAULT_WIDTH / 2, DEFAULT_HEIGHT / 2);

    // Force aspect ratio
    glfwSetWindowSizeLimits(ptr, MIN_WIDTH, MIN_HEIGHT, GLFW_DONT_CARE, GLFW_DONT_CARE);
    glfwSetWindowAspectRatio(ptr, RATIO_WIDTH, RATIO_HEIGHT);

    glfwSetFramebufferSizeCallback(ptr, opt.pfnResize);

    glfwSetWindowTitle(ptr, APP_NAME);
    glfwSetWindowIcon(ptr, 1, &LOGO);
    glfwSetWindowAttrib(ptr, GLFW_RESIZABLE, true);

    setTargetFPS(opt.fps);
    setFullscreen(opt.fullscreen);

    return true;
}

bool Window::isReady() const { return ptr != nullptr; }

void Window::setTargetFPS(int val) {
    targetFPS = val;
}

double Window::getTime() const {
    return glfwGetTime();
}
void Window::startFrame() {
    frameStart = getTime();
}
void Window::wait(int microseconds) {
    std::this_thread::sleep_for(std::chrono::microseconds(microseconds));
}
void Window::waitTargetFPS() {
    if (targetFPS >= MIN_FPS && targetFPS <= MAX_FPS) {
        while (glfwGetTime() < frameStart + 1.0 / targetFPS) {
            wait();
        }
    }
}
void Window::destroy() {
    glfwDestroyWindow(ptr);
    ptr = nullptr;
}

bool Window::shouldClose() { return glfwWindowShouldClose(ptr); }

void Window::setFullscreen(bool val) {
    // MAYBE: real fullscreen
    if(fullscreen != val) {
        fullscreen = val;
        if (val) {
            GLFWmonitor *monitor = glfwGetPrimaryMonitor();
            const GLFWvidmode *mode = glfwGetVideoMode(monitor);
            glfwSetWindowMonitor(ptr, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
        } else {
            glfwSetWindowMonitor(ptr, nullptr, 0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT, GLFW_DONT_CARE);
        }
    }
}