#include "spawner.hpp"
#include "enemy_spawnpoint.hpp"
#include "godot_cpp/classes/time.hpp"
#include "rally_rush_game_mode.hpp"
#include "utils/game_root.hpp"
#include "utils/godot_macros.h"

namespace godot {
void Spawner::_bind_methods() {
#define CLASSNAME Spawner
    GDPROPERTY_HINTED(drone_scene, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
    GDPROPERTY_HINTED(car_scene, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
    GDPROPERTY_HINTED(key_scene, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
}

void Spawner::_enter_tree() { GDGAMEONLY();
    this->game_mode = Object::cast_to<RallyRushGameMode>(GameRoot3D::get_singleton()->get_game_mode().ptr());
    Callable spawn_key_mp = callable_mp(this, &Spawner::spawn_key);
    spawn_key_mp.call_deferred();
    spawn_key_mp.call_deferred();
    spawn_key_mp.call_deferred();
}

void Spawner::_process(double delta_time) { GDGAMEONLY();
    double time = Time::get_singleton()->get_ticks_msec() * 0.001;
    if(time > next) {
        next = time + 5.f / (1 + this->game_mode->get_num_found_keys());
        if(num_cars < this->max_enemy_cars() && tick % 2 == 0) this->spawn_enemy_car();
        else if(num_drones < this->max_enemy_drones()) this->spawn_enemy_drone();
    }
}

int Spawner::max_enemy_cars() {
    return (this->game_mode->get_num_found_keys() + 1) * 3;
}

int Spawner::max_enemy_drones() {
    return (this->game_mode->get_num_found_keys() + 1) * 3;
}

void Spawner::spawn_enemy_car() {
    int const child_count{this->get_child_count()};
    int iter_count{0};
    int child_index{int(this->rng.randi()) % child_count};
    while(iter_count++ < child_count) {
        EnemySpawnpoint *spawnpoint = Object::cast_to<EnemySpawnpoint>(this->get_child(child_index));
        child_index++;
        if(!spawnpoint) continue;
        if(!spawnpoint->get_cars()) continue;
        if(spawnpoint->player_can_see()) continue;
        Node3D *node = Object::cast_to<Node3D>(this->car_scene->instantiate());
        this->add_sibling(node);
        node->set_global_transform(spawnpoint->get_global_transform());
        this->num_cars++;
        node->connect("tree_exited", callable_mp(this, &Spawner::decrement_cars));
        return;
    }
}

void Spawner::spawn_enemy_drone() {
    int const child_count{this->get_child_count()};
    int iter_count{0};
    int child_index{int(this->rng.randi()) % child_count};
    while(iter_count++ < child_count) {
        EnemySpawnpoint *spawnpoint = Object::cast_to<EnemySpawnpoint>(this->get_child(child_index));
        child_index++;
        if(!spawnpoint) continue;
        if(!spawnpoint->get_drones()) continue;
        if(spawnpoint->player_can_see()) continue;
        Node3D *node = Object::cast_to<Node3D>(this->drone_scene->instantiate());
        this->add_sibling(node);
        node->set_global_transform(spawnpoint->get_global_transform());
        this->num_drones++;
        node->connect("tree_exited", callable_mp(this, &Spawner::decrement_drones));
        return;
    }
}

void Spawner::spawn_key() {
    int const child_count{this->get_child_count()};
    int iter_count{0};
    int child_index{int(this->rng.randi()) % child_count};
    while(iter_count++ < child_count) {
        EnemySpawnpoint *spawnpoint = Object::cast_to<EnemySpawnpoint>(this->get_child(child_index));
        child_index++;
        if(!spawnpoint) continue;
        if(!spawnpoint->get_keys()) continue;
        Node3D *node = Object::cast_to<Node3D>(this->key_scene->instantiate());
        this->add_sibling(node);
        node->set_global_transform(spawnpoint->get_global_transform());
        return;
    }
}

void Spawner::decrement_cars() {
    this->num_cars--;
}

void Spawner::decrement_drones() {
    this->num_drones--;
}

void Spawner::set_drone_scene(Ref<PackedScene> scene) {
    this->drone_scene = scene;
}

Ref<PackedScene> Spawner::get_drone_scene() const {
    return this->drone_scene;
}

void Spawner::set_car_scene(Ref<PackedScene> scene) {
    this->car_scene = scene;
}

Ref<PackedScene> Spawner::get_car_scene() const {
    return this->car_scene;
}

void Spawner::set_key_scene(Ref<PackedScene> scene) {
    this->key_scene = scene;
}

Ref<PackedScene> Spawner::get_key_scene() const {
    return this->key_scene;
}
}
