#pragma once

#include "graphics/opengl/framebuffer.hpp"
#include "window/input_system.hpp"
#include "window/window.hpp"

#include <GLFW/glfw3.h>

#include <glm/vec2.hpp>

#include <cstdint>
#include <memory>
#include <string_view>

namespace kuiper
{

class glfw_window : public window {
  public:
    glfw_window(
        std::uint32_t width, std::uint32_t height, std::u8string_view title, GLFWmonitor* monitor, GLFWwindow* share);
    ~glfw_window() override;

    // Only allow moving
    glfw_window(const glfw_window& other)                = delete;
    glfw_window& operator=(const glfw_window& other)     = delete;
    glfw_window(glfw_window&& other) noexcept            = default;
    glfw_window& operator=(glfw_window&& other) noexcept = default;

  public:
    // Implement window

    virtual void*                  get_native_handle() const noexcept override;
    virtual void                   set_title(std::u8string_view title) noexcept override;
    virtual void                   poll_events() noexcept override;
    virtual void                   swap_buffers() noexcept override;
    virtual void                   set_swap_interval(std::uint32_t interval) noexcept override;
    virtual void                   bind_context() noexcept override;
    virtual gl::framebuffer::s_ptr back_buffer() const noexcept override;

    virtual bool should_close() const noexcept override;
    virtual bool is_minimised() const noexcept override;
    virtual bool is_cursor_locked() const noexcept override;

    virtual void lock_cursor() noexcept override;
    virtual void unlock_cursor() noexcept override;
    virtual void enable_raw_input() noexcept override;
    virtual void disable_raw_input() noexcept override;

    virtual glm::uvec2 get_size() const noexcept override;
    virtual glm::uvec2 get_framebuffer_size() const noexcept override; // should this go in render context?
    virtual glm::vec2  get_content_scale() const noexcept override;

    input_system& input() noexcept {
        return m_input_state;
    }

  private:
    void resize_back_buffer() noexcept;

  public:
    static void initialise_glfw(bool prefer_x11);

  private:
    static GLFWwindow* create_glfw_window(std::uint32_t      width,
                                          std::uint32_t      height,
                                          std::u8string_view title,
                                          GLFWmonitor*       monitor,
                                          GLFWwindow*        share) noexcept;
    static void        push_window_hints() noexcept;

    // GLFW Callbacks

    static void glfw_error_callback(int error_code, const char* description);
    static void glfw_framebuffer_size_callback(GLFWwindow* window, int width, int height);
    static void glfw_content_scale_callback(GLFWwindow* window, float xscale, float yscale);
    static void glfw_key_event_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
    static void glfw_cursor_pos_callback(GLFWwindow* window, double xpos, double ypos);
    static void glfw_scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
    static void glfw_dragdrop_callback(GLFWwindow* window, int path_count, const char* paths[]);

  private:
    GLFWwindow*            m_window_handle = nullptr;
    input_system           m_input_state {};
    bool                   m_cursor_locked       = false;
    gl::framebuffer::s_ptr m_default_framebuffer = nullptr;
};

} // namespace kuiper