#ifndef GOAP_PLANNER_HPP
#define GOAP_PLANNER_HPP

#include "action.hpp"
#include "actor_world_state.hpp"
#include "goal.hpp"
#include <cstdint>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/templates/vector.hpp>

namespace gd = godot;

namespace goap {
typedef gd::Vector<Action const *> Plan;

/*! Wrapper for WorldState to be used to represent nodes in the A* implementation.
 * Mainly required to let a HashMap of HashMaps work.
 */
struct WorldStateNode {
    WorldStateNode() = default;
    //! Root node constructor
    WorldStateNode(WorldState const &goal, ActorWorldState *context);
    //! Path node constructor
    WorldStateNode(WorldStateNode const &last_state, Action const *last_action);
    ~WorldStateNode() = default;
    int requirements_unmet() const;
    WorldState state{}; // the only part considered in hashing
    WorldState open_requirements{};
    Action const *last_action{nullptr};
    ActorWorldState *context;
};

struct WorldStateNodeHasher {
    static uint32_t hash(WorldStateNode const &state);
};

extern bool operator==(WorldStateNode const &lhs, WorldStateNode const &rhs);
extern bool operator!=(WorldStateNode const &lhs, WorldStateNode const &rhs);
extern bool operator<(WorldStateNode const &lhs, WorldStateNode const &rhs);
extern bool operator<=(WorldStateNode const &lhs, WorldStateNode const &rhs);
extern bool operator>(WorldStateNode const &lhs, WorldStateNode const &rhs);
extern bool operator>=(WorldStateNode const &lhs, WorldStateNode const &rhs);

typedef gd::HashMap<WorldStateNode, WorldStateNode, WorldStateNodeHasher> NodeMap;
typedef gd::HashMap<WorldStateNode, float, WorldStateNodeHasher> NodeScoreMap;

/*! Uses A* to compute `Action` sequences for an actor to achieve `Goal`s.
 *
 * Set up available actions from the editor and call plan_for_goal with a desired goal.
 * Requires a sibling node inheriting from ActorWorldState called "ActorWorldState".
 */
class Planner : public gd::Node {
    GDCLASS(Planner, gd::Node);
    static void _bind_methods();
public:
    virtual void _enter_tree() override;

    //! Compute a plan to achieve the passed `Goal`.
    Plan plan_for_goal(gd::Ref<Goal> goal);

    void set_actions(gd::Array array);
    gd::Array get_actions() const;
    void set_test_goal(gd::Ref<Goal> goal);
    gd::Ref<Goal> get_test_goal() const;
private:
    //! \returns A vector of all actions that satisfy any of the requirements in `unsatisfied`
    gd::Vector<Action const *> get_neighbours(WorldStateNode const &from) const;
    //! \returns True if the passed action contributes to any of the open requirements in `node`.
    bool does_action_contribute(Action const *action, WorldStateNode const &node) const;
    //! \returns A plan starting with `start_node` traced backwards through the `from` map.
    Plan unroll_plan(WorldStateNode const &start_node, NodeMap const &from);
private:
    gd::Ref<Goal> test_goal{};
    ActorWorldState *world_state{nullptr};
    gd::Vector<Action const *> actions;
};
}

#endif // !GOAP_PLANNER_HPP
