#include "drone.hpp"
#include "godot_cpp/classes/animation_player.hpp"
#include "godot_cpp/classes/global_constants.hpp"
#include "rally_rush_game_mode.hpp"
#include "utils/game_root.hpp"
#include "utils/godot_macros.h"
#include <godot_cpp/classes/navigation_agent3d.hpp>

namespace godot {
void Drone::_bind_methods() {
#define CLASSNAME Drone
    GDPROPERTY_HINTED(explosion_scene, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
}

void Drone::_ready() { GDGAMEONLY();
    this->agent = this->get_node<NavigationAgent3D>("NavigationAgent3D");
    this->anim = this->get_node<AnimationPlayer>("AnimationPlayer");
    this->player = Object::cast_to<CarPlayer>(GameRoot3D::get_singleton()->get_players()[0]->to_node());
    RallyRushGameMode *game_mode = Ref<RallyRushGameMode>(GameRoot3D::get_singleton()->get_game_mode()).ptr();
    game_mode->connect("key_found", callable_mp(this, &Drone::key_found));
    game_mode->connect("turret_pickup", callable_mp(this, &Drone::start_flee));
    game_mode->connect("end_turret_pickup", callable_mp(this, &Drone::end_flee));
    this->recalculate_navigation();
}

void Drone::_process(double delta_time) { GDGAMEONLY();
    switch(this->state) {
    case DroneState::CHASE:
        this->process_chase(delta_time);
        break;
    case DroneState::FLEE:
        this->process_flee(delta_time);
        break;
    case DroneState::DROP:
        if(this->anim->get_current_animation_position() >= this->anim->get_current_animation_length())
            this->spawn_explosion();
        break;
    }
}

void Drone::process_chase(double delta_time) {
    this->process_navigate(delta_time);
    if(this->get_global_position().distance_to(this->agent->get_target_position()) < this->target_distance) {
        this->anim->play("drop");
        this->state = DroneState::DROP;
    }
}

void Drone::process_flee(double delta_time) {
    this->process_navigate(delta_time);
}

void Drone::process_navigate(double delta_time) {
    this->next_recalc--;
    if(this->next_recalc == 0) this->recalculate_navigation();
    Vector3 path_node{this->agent->get_next_path_position()};
    Vector3 forward = (path_node - this->get_global_position()).normalized();
    this->set_global_position(this->get_global_position() + forward * float(delta_time) * this->speed);
    Vector3 left = forward.cross({0.f, 1.f, 0.f});
    this->set_global_basis({left, left.cross(forward), forward});
}

void Drone::recalculate_navigation() {
    this->next_recalc = this->recalc_frame_interval;
    Vector3 player_pos = this->player->get_global_position();
    Vector3 target = player_pos + (this->player->local_to_world_velocity() * lead_time);
    if(this->state == DroneState::CHASE)
        this->agent->set_target_position(target);
    else if(this->state == DroneState::FLEE)
        this->agent->set_target_position(target + (this->get_global_position() - target).normalized() * 100.f);
}

void Drone::spawn_explosion() {
    this->set_process(false);
    Node3D *node = Object::cast_to<Node3D>(this->explosion_scene->instantiate());
    this->add_sibling(node);
    node->set_global_position(this->get_global_position());
    this->queue_free();
}

void Drone::key_found(int total) {
    if(total == 2) {
        this->speed *= 2;
    }
}

void Drone::start_flee() {
    if(this->state == DroneState::CHASE)
        this->state = DroneState::FLEE;
}

void Drone::end_flee() {
    if(this->state == DroneState::FLEE)
        this->state = DroneState::CHASE;
}

void Drone::set_explosion_scene(Ref<PackedScene> scene) {
    this->explosion_scene = scene;
}
Ref<PackedScene> Drone::get_explosion_scene() const {
    return this->explosion_scene;
}
}
