#ifndef GOAP_PLANNER_HPP
#define GOAP_PLANNER_HPP

#include "action.hpp"
#include "goal_marker.hpp"
#include "godot_cpp/variant/variant.hpp"
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/resource.hpp>
#include <godot_cpp/templates/vector.hpp>
#include <godot_cpp/variant/string_name.hpp>

namespace godot {
class CharacterActor;

namespace goap {
class GlobalWorldState;

class Goal : public Resource {
    GDCLASS(Goal, Resource);
    static void _bind_methods();

    void set_goal_state(Dictionary dict);
    Dictionary get_goal_state() const;
    void set_prerequisites(Dictionary dict);
    Dictionary get_prerequisites() const;
public:
    WorldState goal_state{};
    WorldState prerequisites{};
};

struct PlannerNode {
    WorldState open_requirements{};
    WorldState state{};
    Ref<Action> last_edge{};

    PlannerNode() = default;
    PlannerNode(PlannerNode const &src) = default;

    static PlannerNode goal_node(WorldState const &goal);

    PlannerNode new_node_along(Ref<Action> action) const;
};

class Planner : public Node {
    GDCLASS(Planner, Node);
    static void _bind_methods();
public:
    virtual void _enter_tree() override;

    Vector<Ref<Action>> make_plan();
    Ref<Goal> select_goal();

    Variant get_world_property(StringName prop_key);

    bool can_do(Ref<Action> action);
    bool can_do(Ref<Goal> goal);
    Vector<PlannerNode> find_neighbours_of(PlannerNode &node);
    Vector<Ref<Action>> find_actions_satisfying(WorldState requirements);

    bool is_action_complete();
    State get_next_state();

    void set_actions(Array actions);
    Array get_actions() const;
    void set_goals(Array goals);
    Array get_goals() const;
    bool add_goal(Ref<Goal> goal);
    void remove_goal(Ref<Goal> goal);
private:
    CharacterActor *actor{nullptr}; // the parent actor of this planner
    WorldState cached_world_state{}; // the cached worldstate, cleared for every make_plan call
    GlobalWorldState *global_world_state{nullptr}; // cached singleton instance
    // configured settings
    Vector<Ref<Action>> actions{}; // available actions
    Vector<Ref<Goal>> goals{}; // available goals
    Vector<Ref<Action>> plan{};
};

struct PlannerNodeHasher {
    static _FORCE_INLINE_
    uint32_t hash(godot::goap::PlannerNode const &node) {
        uint32_t hash{1};
        for(KeyValue<StringName, Variant> const &kvp : node.state) {
            hash = hash_murmur3_one_32(kvp.key.hash(), hash);
            hash = hash_murmur3_one_32(kvp.value.hash(), hash);
        }
        return hash_fmix32(hash);
    }
};

static _FORCE_INLINE_ bool operator==(PlannerNode const &lhs, PlannerNode const &rhs) {
    return PlannerNodeHasher::hash(lhs) == PlannerNodeHasher::hash(rhs);
}
static _FORCE_INLINE_ bool operator!=(PlannerNode const &lhs, PlannerNode const &rhs) {
    return !(lhs == rhs);
}
static _FORCE_INLINE_ bool operator<(PlannerNode const &lhs, PlannerNode const &rhs) {
    return lhs.open_requirements.size() < rhs.open_requirements.size();
}
static _FORCE_INLINE_ bool operator>=(PlannerNode const &lhs, PlannerNode const &rhs) {
    return !(lhs < rhs);
}
static _FORCE_INLINE_ bool operator>(PlannerNode const &lhs, PlannerNode const &rhs) {
    return lhs.open_requirements.size() > rhs.open_requirements.size();
}
static _FORCE_INLINE_ bool operator<=(PlannerNode const &lhs, PlannerNode const &rhs) {
    return !(lhs > rhs);
}
}
}


#endif // !GOAP_PLANNER_HPP
