#include "unit.hpp"
#include "goap/goal.hpp"
#include "godot_cpp/variant/callable_method_pointer.hpp"
#include "godot_cpp/variant/utility_functions.hpp"
#include "utils/godot_macros.hpp"
#include <godot_cpp/classes/navigation_agent3d.hpp>

void Unit::_bind_methods() {
#define CLASSNAME Unit
}

void Unit::_enter_tree() { GDGAMEONLY();
    this->agent = this->get_node<gd::NavigationAgent3D>("NavigationAgent3D");
    this->planner = this->get_node<goap::Planner>("Planner");
    this->world_state = this->get_node<UnitWorldState>("ActorWorldState");
    this->world_state->connect("attention_changed", callable_mp(this, &Unit::stop_plan));
}

void Unit::_process(double delta_time) {
}

void Unit::stop_plan() {
    if(this->is_queued_for_deletion())
        return;
    this->current_plan.clear();
    if(this->state && !this->state->is_queued_for_deletion())
        this->destroy_state();
    this->state = nullptr;
}

void Unit::plan_for_marker(GoalMarker *marker) {
    gd::UtilityFunctions::print("plan_for_marker on ", this->get_path());
    this->destroy_state();
    this->world_state->set_target_node(marker);
    this->plan_for_goal(marker->get_goal());
}

void Unit::plan_for_goal(gd::Ref<goap::Goal> goal) {
    gd::UtilityFunctions::print("plan_for_goal on ", this->get_path());
    this->current_plan = this->planner->plan_for_goal(goal);
    this->next_action();
}

UnitWorldState *Unit::get_world_state() const {
    return this->world_state;
}

void Unit::destroy_state() {
    if(this->state == nullptr || this->state->is_queued_for_deletion() || !this->state->is_inside_tree())
        return;
    this->state->queue_free();
    this->remove_child(this->state);
    this->state = nullptr;
}

void Unit::next_action() {
    if(this->state != nullptr && !this->state->is_queued_for_deletion())
        this->destroy_state();
    this->state = nullptr;
    if(this->current_plan.is_empty()) return;
    this->state = this->current_plan.get(0)->get_apply_state(this->world_state);
    if(state == nullptr) {
        this->stop_plan();
        gd::UtilityFunctions::push_error("Plan failed to be executed, abandoning");
        return;
    }
    this->current_plan.remove_at(0);
    this->add_child(this->state);
    this->state->connect("end_state", this->on_end_state);
}
