#ifndef RTS_PLAYER_HPP
#define RTS_PLAYER_HPP

#include "godot_cpp/classes/camera3d.hpp"
#include "godot_cpp/classes/input_event.hpp"
#include "godot_cpp/classes/packed_scene.hpp"
#include "unit.hpp"
#include "utils/player.hpp"
#include "utils/player_input.hpp"
#include <cstdint>
#include <godot_cpp/classes/node3d.hpp>

namespace gd = godot;

class RTSPlayer : public gd::Node3D,
                  public utils::IPlayer {
    GDCLASS(RTSPlayer, godot::Node3D);
    static void _bind_methods();
public:
    virtual void _ready() override;
    virtual void _process(double delta_time) override;

    virtual void setup_player_input(utils::PlayerInput *input) override;
    virtual Node *to_node() override;
    virtual void spawn_at_position(gd::Transform3D const &at) override;
private:
    //! process any changes to mouse position
    void process_mouse();
    //! move the camera along the ground
    void move(gd::Vector2 motion);
    gd::Dictionary raycast_from_cursor(uint32_t layers = UINT32_MAX) const;
    //! \return the first node from the camera under the cursor
    gd::Node3D *node_under_cursor(uint32_t layers = UINT32_MAX) const;
    //! add units under the cursor to this->selected_units
    void select_unit_under_cursor();
    void order_activate_object_under_cursor();
    void clear_selected_unit();
    void select_unit(Unit *unit);

    // input functions
    void on_mouse_horizontal(gd::Ref<gd::InputEvent> event, float value);
    void on_mouse_vertical(gd::Ref<gd::InputEvent> event, float value);
    void on_direction_horizontal(gd::Ref<gd::InputEvent> event, float value);
    void on_direction_vertical(gd::Ref<gd::InputEvent> event, float value);
    void on_rotate_horizontal(gd::Ref<gd::InputEvent> event, float value);
    void on_lclick(gd::Ref<gd::InputEvent> event, float value);
    void on_rclick(gd::Ref<gd::InputEvent> event, float value);
    void on_mclick(gd::Ref<gd::InputEvent> event, float value);

    // setters & getters
    gd::Vector3 get_forward_direction() const;
    gd::Vector3 get_left_direction() const;
    bool lmb_is_held();
    bool rmb_is_held();
    gd::Callable unit_destroyed_callable();
    void set_ground_marker_scene(gd::Ref<gd::PackedScene> scene);
    gd::Ref<gd::PackedScene> get_ground_marker_scene() const;
private:
    Unit* selected_unit{nullptr};

    bool mmb_down{false};
    bool lmb_down{false};
    float lmb_last_change{0.f};
    bool rmb_down{false};
    float rmb_last_change{0.f};

    gd::Vector2 camera_mouse_motion{0.f, 0.f};
    float camera_mouse_rotation{0.f};
    gd::Vector2 camera_keys_motion{0.f, 0.f};
    float camera_keys_rotation{0.f};

    gd::Vector3 cursor_camera_normal{0.f, 0.f, 0.f};

    gd::Camera3D *camera{nullptr};

    gd::Ref<gd::PackedScene> ground_marker_scene{nullptr};

    float const camera_keys_speed{10.f};
    float const camera_keys_rotation_speed{1.f};
    float const camera_mouse_speed{0.01f};
    float const camera_mouse_rotation_speed{-0.003f};
    double const time_to_held{0.1};
};

#endif // !RTS_PLAYER_HPP
