#ifndef TURRET_HPP
#define TURRET_HPP

#include <godot_cpp/classes/area3d.hpp>
#include <godot_cpp/classes/packed_scene.hpp>
#include <godot_cpp/classes/rigid_body3d.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/templates/vector.hpp>

namespace godot {
//! Current state of a turret
enum class TurretState {
    //! No known valid targets
    WAITING = 0x0,
    //! Aim in front of an enemy
    LOCKING = 0x1,
    //! Hold still
    CHARGING,
    //! Wait for beam to end
    FIRING
};

//! Generic turret class with the ability to target any type of node as configured. Used for both the player's roof turret pickup and the enemy turrets.
class Turret : public RigidBody3D {
    GDCLASS(Turret, RigidBody3D);
    //! register editor properties
    static void _bind_methods();
public:
    //! Freeze physics and connect awareness observers. Fetch child nodes and initialize them. Update current awareness.
    virtual void _enter_tree() override;
    //! Update state machine. Update gun node position.
    virtual void _process(double delta_time) override;

    //! Check if a new target needs to be found. Find a new target if required.
    void awareness_changed();
    //! Select a target from the currently known nodes.
    Node3D *select_target();

    //! Aim ahead of the current target.
    void lead_target(double delta_time);

    //! Become aware of a node in the awareness area.
    void detect_node(Node3D *node);
    //! Lose track of a node exiting the awareness area.
    void lose_node(Node3D *node);
    //! Try transition to the next state if possible.
    void try_next_state();
    //! Create a damaging beam.
    void create_beam();
    //! Create a non-damaging guiding beam.
    void create_guiding_beam();

    //! End the fire state.
    void beam_ended();

    //! The duration of the damaging lazer.
    void set_fire_time(float val);
    //! The duration of the damaging lazer.
    float get_fire_time() const;
    //! The duration of the guiding lazer.
    void set_charge_time(float val);
    //! The duration of the guiding lazer.
    float get_charge_time() const;
    //! The delay between firing and charging the next shot.
    void set_lock_time(float val);
    //! The delay between firing and charging the next shot.
    float get_lock_time() const;

    //! Set to true to target everything but the attack_classes
    void set_invert_targets(bool value);
    //! Set the node classes to attack while invert_targets is false.
    void set_attack_classes(Array array);
    //! Set the node classes to attack while invert_targets is false.
    Array get_attack_classes() const;
    //! Set the scene to spawn when firing
    void set_beam_scene(Ref<PackedScene> scene);
    //! Set the scene to spawn when firing
    Ref<PackedScene> get_beam_scene() const;
    //! Set the scene to spawn when charging
    void set_guiding_beam_scene(Ref<PackedScene> scene);
    //! Set the scene to spawn when charging
    Ref<PackedScene> get_guiding_beam_scene() const;
private:
    //! The current target node.
    Node3D *current_target{nullptr};
    //! If true, target classes not in attack_classes
    bool invert_attack_classes{false};
    //! Position currently being aimed at.
    Vector3 aim_position{0.f, 0.f, 0.f};
    //! Position of the current_target on the last frame.
    Vector3 last_target_position{0.f, 0.f, 0.f};
    //! Last time the state was changed.
    float last_state_switch{0.f};
    //! Distance ahead of target to aim.
    float lead_distance{1.2f};

    //! The child node representing the gun.
    Node3D *gun_node{nullptr};
    //! Position offset of the gun.
    Vector3 gun_node_offset{0.f, 0.f, 0.f};

    //! Current state-machine state of the turret.
    TurretState state{TurretState::WAITING};

    //! Names of classes to attack.
    Vector<StringName> attack_classes{};
    //! Set of all nodes the turret is currently aware of.
    HashSet<Node3D*> awareness{};

    //! Scene to spawn when firing.
    Ref<PackedScene> beam_scene{};
    //! Scene to spawn when charging.
    Ref<PackedScene> guiding_beam_scene{};

    float fire_time{1.0f};
    float charge_time{1.f};
    float lock_time{1.3f};
};
}

#endif // !TURRET_HPP
