#include "action.hpp"
#include "character_actor.hpp"
#include "global_world_state.hpp"
#include "utils/godot_macros.h"
#include <godot_cpp/classes/global_constants.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

namespace godot::goap {
void Action::_bind_methods() {
#define CLASSNAME Action
    GDPROPERTY(context_prerequisites, Variant::DICTIONARY);
    GDPROPERTY(prerequisites, Variant::DICTIONARY);
    GDPROPERTY(effects, Variant::DICTIONARY);
    GDPROPERTY_HINTED(apply_state, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "StateArgs");
}

void Action::set_context_prerequisites(Dictionary dict) {
    Action::dict_to_property_map(dict, this->context_prerequisites);
}

Dictionary Action::get_context_prerequisites() const {
    return Action::property_map_to_dict(this->context_prerequisites);
}

void Action::set_prerequisites(Dictionary dict) {
    Action::dict_to_property_map(dict, this->prerequisites);
}

Dictionary Action::get_prerequisites() const {
    return Action::property_map_to_dict(this->prerequisites);
}

void Action::set_effects(Dictionary dict) {
    Action::dict_to_property_map(dict, this->effects);
}

Dictionary Action::get_effects() const {
    return Action::property_map_to_dict(this->effects);
}

void Action::set_apply_state(Ref<StateArgs> state) {
    this->apply_state = state;
}

Ref<StateArgs> Action::get_apply_state() const {
    return this->apply_state;
}

bool Action::get_is_completed(CharacterActor *context) {
    GlobalWorldState *state = GlobalWorldState::get_singleton();
    for(WorldProperty const &prop : this->effects) {
        return (prop.key.begins_with("g_")
            ? state->get_world_property(prop.key)
            : context->call("get_" + prop.key)) == prop.value;
    }
    return true;
}

Dictionary Action::property_map_to_dict(WorldState const &props) {
    Dictionary out{};
    for(KeyValue<StringName, Variant> const &prop : props) {
        out[prop.key] = prop.value;
    }
    return out;
}

void Action::dict_to_property_map(Dictionary const &dict, WorldState &out) {
    out.clear();
    Array keys = dict.keys();
    Array vals = dict.values();
    for(size_t i{0}; i < keys.size(); ++i) {
        if(keys[i].get_type() == Variant::STRING_NAME || keys[i].get_type() == Variant::STRING)
            out.insert(keys[i], dict[keys[i]]);
        else
            UtilityFunctions::push_error("WorldProperty keys have to be string names (expected ", Variant::STRING_NAME, " is ", keys[i].get_type(), ")");
    }
}
}
