#include "WindowGlfw.hpp"
#include "utils/Time.hpp"
#include "TimeInterfaceGlfw.hpp"
#include "Debug.hpp"
#include "InputGlfw.hpp"

TSE::GLFW::WindowGlfw::WindowGlfw(string _title, int _width, int _height, IRenderingBackend* backend)
: WindowGlfw(_title, _width, _height, backend, WindowType::Windowed, 32){ }

TSE::GLFW::WindowGlfw::WindowGlfw(string _title, int _width, int _height, IRenderingBackend* backend, TSE::WindowType type)
: WindowGlfw(_title, _width, _height, backend, type, 32){ }

TSE::GLFW::WindowGlfw::WindowGlfw(string _title, int _width, int _height, IRenderingBackend* backend, TSE::WindowType type, int ppu)
{
    width = _width;
    height = _height;
    title = _title;
    windowType = type;
    renderingBackend = backend;
    renderingBackend->window = this;
    IWindow::lastWindow = this;
    if(!Init())
    {
        glfwTerminate();
    }
}

TSE::GLFW::WindowGlfw::~WindowGlfw()
{
    glfwDestroyWindow(window);
    glfwTerminate();
}

bool TSE::GLFW::WindowGlfw::Init()
{
    if(!BaseInit() || !glfwInit())
    {
        Debug::Log("GLFW Error");
        return false;
    }

    InputGlfw::Init();
    Time::Init(new TimeInterfaceGlfw());

    renderingBackend->InitPreWindow();
    glfwWindowHint(GLFW_FOCUSED, true);

    window = NULL;

    if(windowType == WindowType::Maximized)
    {
        glfwWindowHint(GLFW_MAXIMIZED, true);
        window = glfwCreateWindow(width, height, title.c_str(), NULL, NULL);
        int w,h;
        glfwGetWindowSize(window, &w, &h);
        width = w;
        height = h;
    }
    else if(windowType == WindowType::Fullscreen)
    {
        auto monitor = glfwGetPrimaryMonitor();
        int w,h;
        glfwGetMonitorWorkarea(monitor, nullptr, nullptr, &w, &h);
        width = w;
        height = h;
        window = glfwCreateWindow(width, height, title.c_str(), monitor, NULL);
    }
    else if(windowType == WindowType::FullscreenWindowed)
    {
        auto monitor = glfwGetPrimaryMonitor();
        auto mode = glfwGetVideoMode(monitor);
        glfwWindowHint(GLFW_RED_BITS, mode->redBits);
        glfwWindowHint(GLFW_GREEN_BITS, mode->redBits);
        glfwWindowHint(GLFW_BLUE_BITS, mode->redBits);
        glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
        width = mode->width;
        height = mode->height;
        window = glfwCreateWindow(width, height, title.c_str(), monitor, NULL);
    }
    else
    {
        window = glfwCreateWindow(width, height, title.c_str(), NULL, NULL);
    }

    if(!window)
    {
        Debug::Log("Failed to create window. Maybe try another WindowType.");
        return false;
    }

    glfwSetWindowUserPointer(window, this);

    if(!renderingBackend->InitPostWindow())
    {
        return false;
    }

    //callbacks
    glfwSetWindowSizeCallback(window, ResizeWindow);
    glfwSetKeyCallback(window, InputGlfw::KeyCallback);
    glfwSetMouseButtonCallback(window, InputGlfw::MouseButtonCallback);
    glfwSetCursorPosCallback(window, InputGlfw::CursorPosCallback);
    glfwSetScrollCallback(window, InputGlfw::ScrollCallback);
    glfwSetCharCallback(window, InputGlfw::CharCallback);

    Debug::Log("GLFW:" + std::string(glfwGetVersionString()));

    if(!renderingBackend->InitEnd())
    {
        return false;
    }

    return true;
}

void TSE::GLFW::WindowGlfw::ResizeWindow(GLFWwindow *wnd, int width, int height)
{
    WindowGlfw* window = (WindowGlfw*)glfwGetWindowUserPointer(wnd);
    window->width = width;
    window->height = height;

    window->renderingBackend->onResize(width, height);

    for (auto const& i : window->objectsToResize)
    {
        i->OnResize(width, height, window);
    }
}

void TSE::GLFW::WindowGlfw::Clear() const
{
    renderingBackend->onClear();
}

void TSE::GLFW::WindowGlfw::Update()
{
    BaseUpdate();
    glfwPollEvents();

    renderingBackend->onUpdate();
}

void TSE::GLFW::WindowGlfw::ClearDepthBuffer() const
{
    renderingBackend->onClearDepthBuffer();
}

bool TSE::GLFW::WindowGlfw::ShouldClose() const
{
    return glfwWindowShouldClose(window);
}

void TSE::GLFW::WindowGlfw::DoneSetup()
{
    renderingBackend->onResize(width, height);

    for (auto const& i : objectsToResize)
    {
        i->OnResize(width, height, this);
    }
}
