#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <unistd.h>
#include "render/render.h"
#include <render/core/texture/raw.h>
#include <config.h>
#include "window.h"
#include "input.h"
#include "log.h"

GLFWwindow *handle = 0;

uchar inputKeys[GLFW_KEY_LAST+1] = {0};
const float* joystickAxes[GLFW_JOYSTICK_LAST+1] = {0};
const uchar* joystickButtons[GLFW_JOYSTICK_LAST+1] = {0};
int joystickAxesCount[GLFW_JOYSTICK_LAST+1] = {0};
int joystickButtonCount[GLFW_JOYSTICK_LAST+1] = {0};

int lastChar, scroll;
vec2d cursorPos, oldCursor;
double _oldTime;

namespace window_fps
{
    float counter[8];
    uint pos = 0;
}


namespace window
{

double deltaTime;
float fps;

cursor defaultCursor, handCursor;

void createWindow(const vec2i &size, const std::string &name)
{
    defaultCursor._createDefault(GLFW_ARROW_CURSOR);
    handCursor._createDefault(GLFW_HAND_CURSOR);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);

    #if DEBUG
    glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1);
    #endif // DEBUG

    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);


    glfwWindowHint(GLFW_SAMPLES, config::window::samples);
    _log::out("creating window(%dx%d)\nOpenGL ver = 3.3; MSAA level = %d\n", size.x, size.y, config::window::samples);
    handle = glfwCreateWindow(size.x, size.y, name.c_str(), 0, 0);
    glfwMakeContextCurrent(handle);
    //glfwSwapInterval(1);
    glewInit();
    glfwSetScrollCallback(handle, [](GLFWwindow*, double, double s){scroll = s;});
    glfwSetCharCallback(handle, [](GLFWwindow*, uint key){lastChar = key;});
    glfwSetKeyCallback(handle, [](GLFWwindow*, int key, int, int act, int){inputKeys[key] = act; if(key>=GLFW_KEY_ESCAPE && act) lastChar = key;});
    glfwSetWindowSizeCallback(handle, [](GLFWwindow*, int x, int y)
    {
      render::viewport(vec2u(x, y));
      config::window::size = vec2i(x, y);
      config::save();
    });
    glfwSetCursorPosCallback(handle, [](GLFWwindow*, double x, double y){cursorPos = vec2d(x, y);});
    glfwSetMouseButtonCallback(handle, [](GLFWwindow*, int key, int act, int)
    {
        static float oldTime = 0;
        if(key == 0)
        {
            if(act == GLFW_RELEASE)
            {
                inputKeys[key] = 0;
                oldTime = glfwGetTime();
            }else{
                float diff = glfwGetTime() - oldTime;
                if(diff > 0.01 && diff < 0.25)
                    inputKeys[key] = 2;
                else
                    inputKeys[key] = 1;
            }
        }else
            inputKeys[key] = act;
    });
    _oldTime = 0;
    deltaTime = 0;
    _log::out("%s / %s / GLSL version: %s\n", glGetString(GL_VENDOR), glGetString(GL_VERSION), glGetString(GL_SHADING_LANGUAGE_VERSION));
}

void setSizeBounds(const vec2i &minSize, const vec2i &maxSize)
{
    glfwSetWindowSizeLimits(handle, minSize.x, minSize.y, maxSize.x, maxSize.y);
}

void setAspectRatio(const vec2i &aspect)
{
    glfwSetWindowAspectRatio(handle, aspect.x, aspect.y);
}

void swapBuffers()
{
    deltaTime = glfwGetTime()-_oldTime;
    _oldTime = glfwGetTime();

    window_fps::counter[window_fps::pos++] = 1.0/deltaTime;
    if(window_fps::pos == 8)
        window_fps::pos = 0;

    fps = 0;
    for(uint i = 0; i < 8; i++)
        fps += window_fps::counter[i];
    fps /= 8;


    /*if(deltaTime < 0.016667)
        usleep(1667-int((deltaTime)*1000));*/
    glfwSwapBuffers(handle);
}

void closeWindow()
{
    glfwDestroyWindow(handle);
}

GLFWwindow *getHandler()
{
    return handle;
}

bool shouldClose()
{
    return glfwWindowShouldClose(handle);
}

void setShouldClose(bool val)
{
    return glfwSetWindowShouldClose(handle, val);
}

vec2i getSize()
{
    int w, h;
    glfwGetWindowSize(handle, &w, &h);
    return vec2i(w, h);
}

void getSize(const vec2i &size)
{
    glfwSetWindowSize(handle, size.x, size.y);
}

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

void setWindowed()
{
    glfwSetWindowMonitor(handle, 0, 50, 50, config::window::size.x, config::window::size.y, 0);
}

void pollEvents()
{
    for(uint i = 0; i <= GLFW_MOUSE_BUTTON_LAST; i++)
        if(inputKeys[i] == 2)
            inputKeys[i] = 0;
    scroll = 0;
    lastChar = 0;
    oldCursor = cursorPos;
    glfwPollEvents();
    for(uint i = 0; i <= GLFW_JOYSTICK_LAST; i++)
    {
        if(!glfwJoystickPresent(i))
            continue;
        joystickAxes[i] = glfwGetJoystickAxes(i, &joystickAxesCount[i]);
        joystickButtons[i] = glfwGetJoystickButtons(i, &joystickButtonCount[i]);
    }
}

cursor::cursor()
{
    _cursor = 0;
}

cursor::~cursor()
{
    if(_cursor)
        glfwDestroyCursor((GLFWcursor*)_cursor);
}

void cursor::create(const std::string &path, const vec2i &offset)
{
    render::rawImage img;
    if(!img.load(path))
        return;
    GLFWimage cimg;
    cimg.width = img.size.x;
    cimg.height = img.size.y;
    cimg.pixels = img.pixeles;
    _cursor = glfwCreateCursor(&cimg, offset.x, offset.y);
    img.free();
}

void cursor::_createDefault(uint n)
{
    _cursor = glfwCreateStandardCursor(n);
}

void cursor::bind()
{
    if(_cursor)
        glfwSetCursor(handle, (GLFWcursor*)_cursor);
}

//=========================================================

std::vector<monitor> _monitors;

const std::vector<monitor> &monitors = _monitors;

void initMonitors()
{
    int monitorsCount;
    GLFWmonitor **_ms = glfwGetMonitors(&monitorsCount);
    _monitors.reserve(monitorsCount);
    for(int i = 0; i < monitorsCount; i++)
    {
        _monitors.emplace_back(_ms[i]);
    }
}

monitor::monitor(void *m)
{
    GLFWmonitor *mon = (GLFWmonitor*)m;
    handle = m;
    const GLFWvidmode *nativeMode = glfwGetVideoMode(mon);
    _nativeMode.x = nativeMode->width;
    _nativeMode.y = nativeMode->height;
    int count;

    const GLFWvidmode *_vm = glfwGetVideoModes(mon, &count);
    for(int i = 0; i < count; i++)
        _modes.push_back(vec2u(_vm[i].width, _vm[i].height));
    name = glfwGetMonitorName(mon);
}

void monitor::bind() const
{
    glfwSetWindowMonitor(::handle, (GLFWmonitor*)handle, 0, 0, _nativeMode.x, _nativeMode.y, 0);
}

void monitor::unbind() const
{
    glfwSetWindowMonitor(::handle, 0, 0, 0, _nativeMode.x, _nativeMode.y, 0);
}

}

namespace input
{

uchar getKey(uint key)
{
    if(key > GLFW_KEY_LAST)
        return 0;
    return inputKeys[key];
}

const vec2d &getCursor()
{
    return cursorPos;
}

vec2d getCursorDiff()
{
    return cursorPos-oldCursor;
}

float getScroll()
{
    return scroll;
}

uint getSymbol()
{
    return lastChar;
}

void setKey(uint key, uchar val)
{
    if(key > GLFW_KEY_LAST)
        return;
    inputKeys[key] = val;
}

void setCursor(const vec2d &val)
{
    cursorPos = val;
}

float getJoystickAxis(uint axis)
{
    if(axis >= joystickAxesCount[0])
        return 0;
    return joystickAxes[0][axis];
}

bool getJoystickButton(uint button)
{
    if(button >= joystickButtonCount[0])
        return 0;
    return joystickButtons[0][button];
}

bool isJoystickPresent(uint joy)
{
    return glfwJoystickPresent(joy);
}

}


