#pragma once

#include <GLFW/glfw3.h>

#include <glm/vec2.hpp>

#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <vector>

namespace kuiper
{

using action_fn_t = std::function<void()>;

enum action_type : std::uint8_t {
    none = 0,
    escape,
};

constexpr std::size_t max_actions = 1 << (sizeof(action_type) * 8);

struct key_state {
    int state = GLFW_RELEASE;
};

struct mouse_state {
    glm::vec2 pos;
    glm::vec2 delta_pos;
};

class input_system {
  public:
    input_system() {
        // Default engine action keymappings
        m_action_keymap[GLFW_KEY_ESCAPE] = action_type::escape;
        return;
    }
    ~input_system() = default;

    // Keyboard

    void set_key_state(int key, int state);
    int  get_key_state(int key) const;

    bool is_key_pressed(int key) const;
    bool is_key_released(int key) const;
    bool is_key_held(int key) const;
    bool is_key_down(int key) const;

    // Mouse

    void set_mouse_pos(const glm::vec2& pos);
    void set_mouse_delta(const glm::vec2& pos);

    /// Get mouse position (top-left corner is the origin)
    glm::vec2 get_mouse_pos() const;
    /// Get change in mouse position since last frame (top-left corner is the origin)
    glm::vec2 get_mouse_delta() const;

    // Actions
    // TODO: user can add a new custom action of their own

    void subscribe_to_action(action_type type, const action_fn_t& callback);

  private:
    std::array<key_state, GLFW_KEY_LAST>              m_key_states {};
    std::array<action_type, GLFW_KEY_LAST>            m_action_keymap {};
    std::array<std::vector<action_fn_t>, max_actions> m_action_listeners {};
    mouse_state                                       m_mouse_state {};
    glm::vec2                                         m_old_mouse_pos {};
};

} // namespace kuiper
