/// Guards

#ifdef  GLBACKEND_GLFW
#ifndef GLBACKEND_GLFW_HPP_
#define GLBACKEND_GLFW_HPP_


/// Includes

#include <array>
#include <string>

#include <glbackend.hpp>

#include <GLFW/glfw3.h>


/// Class definition

class GLBackendGLFW final : public GLBackend
{

public:

    //// Special member functions

    explicit GLBackendGLFW(
        std::string const & title,
        std::array<int, 2>  size        = {0, 0},
        std::array<int, 2>  version     = {0, 0},
        int                 samples     = 0,
        bool                fullscreen  = false,
        bool                transparent = false
    );
    ~GLBackendGLFW();
    GLBackendGLFW(GLBackendGLFW &&) noexcept;
    GLBackendGLFW(GLBackendGLFW const &) = delete;
    GLBackendGLFW & operator=(GLBackendGLFW &&) = delete;
    GLBackendGLFW & operator=(GLBackendGLFW const &) = delete;

    //// Context

    void current() override;

    //// Render loop

    void swap() override;

    void events() override;

    bool running() const override;
    bool running(bool running) override;

    float time() const override;
    float time(float time) override;

    //// Input and output

    void lock(bool lock) override;

    bool key   (std::string const & key)    const override;
    bool button(int                 button) const override;

    //// Debug

    std::string debug_info() const override;

protected:

    //// Special member functions

    void destroy_();

    //// Debug

    void static debug_glfw_error_callback_(
        int          error,
        char const * message
    );

protected:

    //// Context

    int static thread_local init_count_;

    GLFWwindow * window_;

};


/// Inline definitions

inline void GLBackendGLFW::current()
{
    glfwMakeContextCurrent(window_);
}

inline void GLBackendGLFW::events()
{
    glfwPollEvents();
}

inline void GLBackendGLFW::swap()
{
    glfwSwapBuffers(window_);
}

inline bool GLBackendGLFW::running(bool running)
{
    glfwSetWindowShouldClose(window_, !running);
    return running;
}

inline bool GLBackendGLFW::running() const
{
    return !glfwWindowShouldClose(window_);
}

inline float GLBackendGLFW::time() const
{
    return (float)glfwGetTime();
}

inline float GLBackendGLFW::time(float time)
{
    glfwSetTime((double)time);
    return time;
}

inline void GLBackendGLFW::lock(bool lock)
{
    glfwSetInputMode(
        window_, GLFW_CURSOR, lock ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL
    );
}

inline bool GLBackendGLFW::key(std::string const & key) const
{
    if (key == "Left")
        return glfwGetKey(window_, GLFW_KEY_LEFT) == GLFW_PRESS;
    else if (key == "Right")
        return glfwGetKey(window_, GLFW_KEY_RIGHT) == GLFW_PRESS;
    else if (key == "Up")
        return glfwGetKey(window_, GLFW_KEY_UP) == GLFW_PRESS;
    else if (key == "Down")
        return glfwGetKey(window_, GLFW_KEY_DOWN) == GLFW_PRESS;
    else if (key == "Enter")
        return glfwGetKey(window_, GLFW_KEY_ENTER) == GLFW_PRESS;
    else if (key == "Escape")
        return glfwGetKey(window_, GLFW_KEY_ESCAPE) == GLFW_PRESS;
    else if (key == "Tab")
        return glfwGetKey(window_, GLFW_KEY_TAB) == GLFW_PRESS;
    else if (key == "Backspace")
        return glfwGetKey(window_, GLFW_KEY_BACKSPACE) == GLFW_PRESS;
    else if (key == "Control")
        return
            glfwGetKey(window_, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS ||
            glfwGetKey(window_, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS;
    else if (key == "Shift")
        return
            glfwGetKey(window_, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS ||
            glfwGetKey(window_, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS;
    else if (key == "Alt")
        return
            glfwGetKey(window_, GLFW_KEY_LEFT_ALT) == GLFW_PRESS ||
            glfwGetKey(window_, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS;
    else if (key.length() == 1)
        return glfwGetKey(window_, key[0]) == GLFW_PRESS;
    return false;
}

inline bool GLBackendGLFW::button(int button) const
{
    return glfwGetMouseButton(window_, button - 1) == GLFW_PRESS;
}


/// Guards

#endif
#endif