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

GLFWwindow *handle = 0;

uint8_t inputKeys[GLFW_KEY_LAST+1] = {0};
const float* joystickAxes[GLFW_JOYSTICK_LAST+1] = {0};
const uint8_t* 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];
    uint32_t pos = 0;
}


namespace window
{

double deltaTime;
float fps;

cursor defaultCursor, handCursor;

void createWindow(const vec2i32 &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, 4);
    glfwWindowHint(GLFW_RESIZABLE, false);

    Logger::Debug("Creating window (%d x %d)", size.x, size.y);

    handle = glfwCreateWindow(size.x, size.y, name.c_str(), 0, 0);
    glfwMakeContextCurrent(handle);
    glewInit();

    _oldTime = 0;
    deltaTime = 0;
    Logger::Debug("ogl vendor %s", glGetString(GL_VENDOR));
    Logger::Debug("ogl version %s", glGetString(GL_VERSION));
    Logger::Debug("glsl version %s", glGetString(GL_SHADING_LANGUAGE_VERSION));
}

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

void setAspectRatio(const vec2i32 &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(uint32_t i = 0; i < 8; i++)
        fps += window_fps::counter[i];
    fps /= 8;

    glfwSwapBuffers(handle);
}

void closeWindow()
{
    glfwDestroyWindow(handle);
}

GLFWwindow *getHandler()
{
    return handle;
}

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

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

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

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

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

void pollEvents()
{
    glfwPollEvents();
}

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

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

void cursor::create(const std::string &path, const vec2i32 &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(uint32_t 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(vec2u32(_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);
}

}
