#include "pw/system/window.hpp"

#include "glad/glad.h"
#include "GLFW/glfw3.h"

#include "pw/visual/context.hpp"
#include "pw/system/input.hpp"
#include "pw/system/display.hpp"

#include "pw/core/debug.hpp"

#include <cmath>
#include <locale>
#include <codecvt>

#include <iostream>

namespace pw {

//struct window_context : context
//{
//    virtual bool make_current() override;
//    virtual void resize() override;
//    //	virtual context::size size() override;
//    virtual void flush() override;
//};

//bool window_context::make_current()
//{
//}

//void window_context::resize()
//{
//}

//void window_context::flush()
//{
//}

struct window::impl {

    window& _parent;

    GLFWwindow *_window = nullptr;

    sizei _old_size;
    pointi _old_pos;

    //	window_context _context;

    static void error_callback(int error, const char* description)
    {
        debug::e() <<  "GLFW error: " <<  description;
    }

    static void drop_callback(GLFWwindow* window, int count, const char** paths)
    {
        //        std::cout << __FUNCTION__ << std::endl;
        //		for (int i = 0; i < count; i++)
        //			std::cout << "\t" << paths[i] << std::endl;
    }

    static void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
    {
        std::cout << __FUNCTION__ << std::endl;
    }

    static void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
    {
        input::get()._mouse_button = button;

        std::cout << __FUNCTION__ << " " << button << " " << action << " " << mods << std::endl;
        //        input::get()._mouse_position
    }

    static void cursor_pos_callback(GLFWwindow* window, double xpos, double ypos)
    {
        input::get()._mouse_position = pointd(xpos,ypos).cast<float>();
    }


    static void key_callback(GLFWwindow *window,int key, int scancode, int action, int mods)
    {
        input::get()._key_code = scancode;
        input::get()._key_pressed = action;
        // action 0,1,2
        //        std::cout << __FUNCTION__ << action <<  std::endl;
    }

    //    static void character_callback(GLFWwindow* window, unsigned int codepoint)
    //    {
    //        std::cout << __FUNCTION__ << std::endl;
    //    }

    static void charmods_callback(GLFWwindow* window, unsigned int codepoint, int mods)
    {
        // build the string from a Unicode code point
        std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;
        std::string u8str = converter.to_bytes(codepoint);

        input::get()._input_string = u8str;
    }

    static void framebuffer_size_callback(GLFWwindow* window, int width, int height)
    {
        window::impl* impl = static_cast<window::impl*>(glfwGetWindowUserPointer(window));

        debug::d() << "window::frame_buffer_size_callback " << width << "x" << height;

        //        impl->on_resize(width,height);

        //		std::cout << "framebuffer " << width << "x" << height << std::endl;
        //	glViewport(0, 0, width, height);
    }

    void update_display_list()
    {
        display::_displays.clear();

        // fetch all monitors
        int monitor_count = 0;
        GLFWmonitor** monitors = glfwGetMonitors(&monitor_count);

        for (int i = 0; i < monitor_count;i++) {
            display d;
            d._name = std::string(glfwGetMonitorName(monitors[i]));

            display::_displays.push_back(d);

            //			debug::d() <<
        }

        //		GLFWvidmode
        //		glfwGetVideoModes() ... get all
        //		glfwGetVideoMode( . get current
        //		glfwGetMonitorPos(
        //		glfwGetMonitorPhysicalSize(
        //		glfwGetMonitorName();

    }

    impl(window& w)
        : _parent(w)
    {
        // initialize
        if (!glfwInit())
        {
            debug::e() << "Initalization error";

        }

        int glfw_major, glfx_minor,glfw_rev;
        glfwGetVersion(&glfw_major,&glfx_minor,&glfw_rev);


        debug::d() << "GLFW " << glfw_major << "." << glfx_minor << "." << glfw_rev;
        debug::d() << "GLFW header " << GLFW_VERSION_MAJOR << "." << GLFW_VERSION_MINOR << "." << GLFW_VERSION_REVISION;

        update_display_list();

        // request specific version 3.3
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

        //
        _window = glfwCreateWindow(640, 480, "pixwerx", nullptr, nullptr);

        // make window current
        glfwMakeContextCurrent(_window);

        // load opengl
        if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
        {
            debug::e() << "glad couldn't get OpenGL API";
        }

        // check Version
        int major, minor, rev;
        major = glfwGetWindowAttrib(_window, GLFW_CONTEXT_VERSION_MAJOR);
        minor = glfwGetWindowAttrib(_window, GLFW_CONTEXT_VERSION_MINOR);
        rev = glfwGetWindowAttrib(_window, GLFW_CONTEXT_REVISION);

        // maybe something to pass to the outside
        debug::d() << "OpenGL " << major << "." << minor << "." << rev;

        glfwSetWindowUserPointer(_window,this);

        //glfwSetFramebufferSizeCallback(_window, window::impl::framebuffer_size_callback);
        glfwSetKeyCallback(_window, window::impl::key_callback);
        //        glfwSetCharCallback(_window, character_callback);
        glfwSetCharModsCallback(_window, charmods_callback);
        glfwSetScrollCallback(_window, scroll_callback);

        glfwSetDropCallback(_window, drop_callback);

        glfwSetCursorPosCallback(_window, cursor_pos_callback);
        glfwSetMouseButtonCallback(_window, mouse_button_callback);
        glfwSetScrollCallback(_window, scroll_callback);


        glfwSetErrorCallback(error_callback);

        //glfwSetWindowCloseCallback(_window,close_callback);
    }

    ~impl()
    {
        glfwDestroyWindow(_window);
    }

    bool update()
    {
        if (_window && !glfwWindowShouldClose(_window))
        {
            input::get().reset();

            // get new events
            glfwPollEvents();

            _parent._on_update(_parent);

            glfwSwapBuffers(_window);

            return true;
        }

        return false;
    }

    void set_title(const std::string& title)
    {
        glfwSetWindowTitle(_window,title.c_str());
    }

    void set_size(const ::pw::sizei& s)
    {
        glfwSetWindowSize(_window,s.width,s.height);
    }

    ::pw::size size() const
    {
        int w,h;
        glfwGetWindowSize(_window,&w,&h);
        return ::pw::size(w,h);
    }


    ::pw::size client_size() const
    {
        int w,h;
        glfwGetFramebufferSize(_window,&w,&h);
        return ::pw::size(w,h);
    }

    ::pw::point position() const
    {
        int x,y;
        glfwGetWindowPos(_window,&x,&y);
        return ::pw::point(x,y);
    }

    void set_position(const pointi& p)
    {
        glfwSetWindowPos(_window,p.x,p.y);
    }

    void set_fullscreen(bool use_fullscreen)
    {
        if (fullscreen() == use_fullscreen)
            return;

        if (use_fullscreen)
        {
            glfwGetWindowPos( _window, &_old_pos.x, &_old_pos.y );
            glfwGetWindowSize( _window, &_old_size.width, &_old_size.height );

            GLFWmonitor * monitor = glfwGetPrimaryMonitor();
            const GLFWvidmode * mode = glfwGetVideoMode(monitor);

            glfwSetWindowMonitor( _window, monitor, 0, 0, mode->width, mode->height, 0 );

        } else
        {

            glfwSetWindowMonitor( _window, nullptr, _old_pos.x,_old_pos.y, _old_size.width,_old_size.height,0);
        }

        //		glfwSetWindow
    }

    bool fullscreen() const {
        return glfwGetWindowMonitor(_window) != nullptr;
    }

    void set_visible(bool show) {
        (show) ? glfwShowWindow(_window) : glfwHideWindow(_window);
    }

    bool visible() const {
        return glfwGetWindowAttrib(_window, GLFW_VISIBLE) > 0;
    }
};


//
//
//
window::window()
    : _impl(std::make_unique<window::impl>(*this))
    , _on_update([](window&){})
{
}

window::~window()
{
}

bool window::update()
{
    return _impl->update();
}

void window::set_title(const std::string& title)
{
    _impl->set_title(title);
}

void window::set_size(const ::pw::size& s)
{
    _impl->set_size(s.cast<int>());
}

size window::size() const
{
    return _impl->size();
}

size window::client_size() const
{
    return _impl->client_size();
}

void window::set_position(const point &p)
{
    _impl->set_position(p.cast<int>());
}

point window::position() const
{
    return _impl->position();
}

bool window::fullscreen() const
{
    return _impl->fullscreen();
}

void window::set_fullscreen(bool use_fullscreen)
{
    _impl->set_fullscreen(use_fullscreen);
}

bool window::visible() const
{
    return _impl->visible();
}

void window::set_visible(bool is_visible)
{
    _impl->set_visible(is_visible);
}




}
