/******************************************************************************
*	File		-	window.h
*	Author		-	Joey Pollack
*	Date		-	2021/09/01 (y/m/d)
*	Mod Date	-	2021/09/01 (y/m/d)
*	Description	-	Manages the window system using GLFW. This is where GLFW
*                   is initialized. I think this file can completely hide GLFW
*                   but I'm not yet sure...
******************************************************************************/

#include "window.h"
#include <LunariumConfig.h>
#include <utils/logger.h>
#include <utils/helpers.h>
#include <sstream>

namespace lunarium
{
    bool Window::mbIsInit = false;
    Window::Window()
    {
        
    }

    OpRes Window::Initialize(const State& state)
    {
        if (mbIsInit)
        {
            return OpRes::Fail("A window is already initialized. Multiple windows is not yet supported");
        }

        if (state.Display.RenderFramework == Renderer::VULKAN)
        {
            return OpRes::Fail("Render Framework VULKAN is not yet implemented");
        }

        if (state.Display.RenderFramework == Renderer::OPENGL)
        {
            int width, height;

            if( !glfwInit() )
            {
                char buffer[1024];
                glfwGetError((const char**)&buffer);
                std::ostringstream oss;
                oss << "Failed to initialize GLFW: " << buffer;
                return OpRes::Fail(oss.str().c_str());
            }

            glfwWindowHint(GLFW_DEPTH_BITS, 16);
            glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);    
            
            glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_MAJOR_VERSION);
            glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_MINOR_VERSION);

            Logger::Log(LogCategory::GRAPHICS, LogLevel::INFO, 
                            "Creating OpenGL Context version %d, %d and glsl %s", 
                                OPENGL_MAJOR_VERSION, OPENGL_MINOR_VERSION, System::GetGLSLVersionString().c_str());

            mpWindow = glfwCreateWindow( state.Display.WindowedSize.Width, state.Display.WindowedSize.Height, "Lunarium", NULL, NULL );
            if (!mpWindow)
            {
                glfwTerminate();
                char buffer[1024];
                glfwGetError((const char**)&buffer);
                std::ostringstream oss;
                oss << "Failed to initialize GLFWFailed to open GLFW window: " << buffer;
                return OpRes::Fail(oss.str().c_str());
            }

            glfwSetWindowPos(mpWindow, state.Display.WindowStartPosition.X, state.Display.WindowStartPosition.Y);

            glfwMakeContextCurrent(mpWindow);

            if (state.Display.VSyncEnabled)
            {
                glfwSwapInterval(1); // Enable vsync
            }

            // Use glad2 to load extensions
            int version = gladLoadGL(glfwGetProcAddress);
            Logger::Log(LogCategory::GRAPHICS, LogLevel::INFO, 
                            "Glad2 Loaded version: %d.%d", GLAD_VERSION_MAJOR(version), GLAD_VERSION_MINOR(version));
        }
        else
        {
            return OpRes::Fail("No Render Framework selected");
        }

        glfwSetWindowUserPointer(mpWindow, this);
        mbIsInit = true;
        return OpRes::OK();
    }

    bool Window::IsInit() const
    {
        return mbIsInit;
    }

    GLFWwindow* Window::GetWindow()
    {
        return mpWindow;
    }

    glm::vec2 Window::GetCursorPos() const
    {
        double x, y;
        glfwGetCursorPos(mpWindow, &x, &y);
        return { (float)x, (float)y };
    }

    void Window::SetCursorPos(glm::vec2 pos)
    {
        glfwSetCursorPos(mpWindow, pos.x, pos.y);
    }

    bool Window::IsKeyDown(int key) const
    {
        return glfwGetKey(mpWindow, key) == GLFW_PRESS;
    }

    void Window::Resize(int width, int height)
    {
        glfwSetWindowSize(mpWindow, width, height);
    }

    void Window::SetShouldCloseFlag(bool should_close)
    {
        glfwSetWindowShouldClose(mpWindow, should_close);
    }
    
    // If fullscreen is true but we're already in fullscreen mode this function will change the
    // resolution. If fullscreen is false but we're not fullscreen this function will change
    // the window position and size.
    void Window::ChangeDisplayMode(bool fullscreen, int xPos, int yPos, int width, int height)
    {
        // TODO: Allow the user to select which monitor to use
        //       https://www.glfw.org/docs/latest/monitor_guide.html
        //       For now just use the primary monitor

        GLFWmonitor* pMonitor = glfwGetWindowMonitor(mpWindow);

        if (fullscreen)
        {
            if (pMonitor)
            {
                // Requsting fullscreen but we're already fullscreen so
                // just set the size
                glfwSetWindowSize(mpWindow, width, height);
                return;
            }
            else
            {
                // Otherwise we need to switch to fullscreen mode
                // TODO: Allow the user to select the refresh rate
                glfwSetWindowMonitor(mpWindow, glfwGetPrimaryMonitor(), 0, 0, width, height, 60);
            }
        }
        else
        {
            if (pMonitor)
            {
                // Go to windowed mode
                glfwSetWindowMonitor(mpWindow, nullptr, xPos, yPos, width, height, 60);
            }
            else
            {
                // Already in windowed mode so just change the size and position
                glfwSetWindowSize(mpWindow, width, height);
                glfwSetWindowPos(mpWindow, xPos, yPos);
            }
        }
    }

    void Window::GetFramebufferSize(int* width, int* height) const
    {
        glfwGetFramebufferSize(mpWindow, width, height);
    }

    void Window::GetPosition(int* x, int* y) const
    {
        glfwGetWindowPos(mpWindow, x, y);
    }

    bool Window::ShouldWindowClose() const
    {
        return glfwWindowShouldClose(mpWindow);
    }

    void Window::PollEvents()
    {
        glfwPollEvents();
    }

    void Window::SwapBuffers()
    {
        glfwSwapBuffers(mpWindow);
    }


    void Window::Shutdown()
    {
        glfwDestroyWindow(mpWindow);
        glfwTerminate();
        mbIsInit = false;
    }

}