#include "unit_world_state.hpp"
#include "unit.hpp"
#include "utils/godot_macros.hpp"
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
#include <godot_cpp/classes/world3d.hpp>
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
#include <godot_cpp/variant/callable_method_pointer.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

void UnitWorldState::_bind_methods() {
#define CLASSNAME UnitWorldState
    GDSIGNAL("attention_changed");
    GDFUNCTION(get_can_see_target);
    GDFUNCTION(get_is_target_dead);
    GDFUNCTION(get_is_at_target);
    GDFUNCTION(get_target_node);
}

void UnitWorldState::_enter_tree() { GDGAMEONLY();
    this->parent_unit = gd::Object::cast_to<Unit>(this->get_parent());
    this->agent = this->get_node<gd::NavigationAgent3D>("../NavigationAgent3D");
    if(this->parent_unit == nullptr)
        gd::UtilityFunctions::push_warning("UnitWorldState needs to be a child node of a Unit");
}

bool UnitWorldState::get_can_see_target() const {
    gd::Transform3D const eyes = this->parent_unit->get_eye_transform();
    gd::Transform3D const target = this->target_node->get_global_transform();
    gd::Ref<gd::PhysicsRayQueryParameters3D> const query{gd::PhysicsRayQueryParameters3D::create(eyes.origin, target.origin, 0x1 | 0x4, {this->target_node})};
    return this->parent_unit->get_world_3d()->get_direct_space_state()->intersect_ray(query).is_empty();
}

bool UnitWorldState::get_is_at_target() const {
    if(this->target_node == nullptr) return true;
    gd::Vector3 const target = this->target_node->get_global_position();
    gd::Vector3 const current = this->parent_unit->get_global_position();
    float const min_dist = this->agent->get_target_desired_distance();
    return (target - current).length_squared() <= min_dist * min_dist;
}

bool UnitWorldState::get_is_target_dead() const {
    return false;
}

void UnitWorldState::set_target_node(gd::Node3D *node) {
    this->target_node = node;
    this->emit_signal("attention_changed");
}

gd::Node3D *UnitWorldState::get_target_node() const {
    return this->target_node;
}
