#include "rendering/Window.h"

static int glfwCounter = 0;
static int glewCounter = 0;

Window::Window() : window(nullptr), size({0, 0}), textInput(nullptr) {
    if(glfwCounter == 0 && !glfwInit()) {
        return;
    }
    glfwCounter++;
}

Window::~Window() {
    if(window != nullptr) {
        glfwDestroyWindow(window);
    }
    if(glfwCounter > 0 && --glfwCounter == 0) {
        glfwTerminate();
    }
}

Error Window::open(const WindowOptions& options) {
    if(glfwCounter <= 0) {
        return {"could not initialize GLFW"};
    }

    glfwDefaultWindowHints();
    glfwWindowHint(GLFW_VISIBLE, false);
    glfwWindowHint(GLFW_RESIZABLE, true);
    glfwWindowHint(GLFW_DECORATED, !options.fullscreen);
    glfwWindowHint(GLFW_DOUBLEBUFFER, true);

    if(options.es) {
        glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
    } else {
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, options.majorVersion);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, options.minorVersion);

    GLFWmonitor* monitor =
        options.fullscreen ? glfwGetPrimaryMonitor() : nullptr;
    window = glfwCreateWindow(options.size.width, options.size.height,
                              options.name, monitor, nullptr);
    if(window == nullptr) {
        return {"could not create window"};
    }
    size = options.size;
    glfwSetWindowUserPointer(window, this);
    glfwSetKeyCallback(window, onKey);
    glfwSetCharCallback(window, onChar);
    glfwSetFramebufferSizeCallback(window, onResize);
    glfwSetMouseButtonCallback(window, onMouse);
    glfwSetCursorPosCallback(window, onMouseMove);

    glfwMakeContextCurrent(window);
    glfwSwapInterval(options.vsync);

    if(glewCounter == 0) {
        GLenum err = glewInit();
        if(err != GLEW_OK) {
            Error error;
            error.message.clear()
                .append("could not initialize GLEW: ")
                .append(glewGetErrorString(err));
            return error;
        }
    }
    glewCounter++;
    return {};
}

const Clock& Window::getFrameClock() const {
    return fps;
}

const Clock& Window::getTickClock() const {
    return tps;
}

const Size& Window::getSize() const {
    return size;
}

void Window::trapCursor(bool trap) {
    glfwSetInputMode(window, GLFW_CURSOR,
                     trap ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL);
}

void Window::onKey(GLFWwindow* w, int key, int scancode, int action, int mods) {
    void* p = glfwGetWindowUserPointer(w);
    if(p != nullptr) {
        Window* rw = static_cast<Window*>(p);
        TextInput* input = rw->textInput;
        if(input != nullptr) {
            input->onKeyEvent(key, scancode, action, mods);
        }
        rw->buttons.onKey(key, scancode, action, mods);
    }
}
void Window::onChar(GLFWwindow* w, unsigned int codepoint) {
    void* p = glfwGetWindowUserPointer(w);
    if(p != nullptr) {
        TextInput* input = static_cast<Window*>(p)->textInput;
        if(input != nullptr) {
            input->onCharEvent(codepoint);
        }
    }
}

void Window::onResize(GLFWwindow* w, int width, int height) {
    void* p = glfwGetWindowUserPointer(w);
    if(p != nullptr) {
        Window* rw = static_cast<Window*>(p);
        rw->size.width = width;
        rw->size.height = height;
    }
}

void Window::onMouse(GLFWwindow* w, int button, int action, int mods) {
    void* p = glfwGetWindowUserPointer(w);
    if(p != nullptr) {
        static_cast<Window*>(p)->buttons.onMouse(button, action, mods);
    }
}

void Window::onMouseMove(GLFWwindow* w, double x, double y) {
    void* p = glfwGetWindowUserPointer(w);
    if(p != nullptr) {
        static_cast<Window*>(p)->buttons.onMouseMove(x, y);
    }
}