#include "planner.hpp"
#include "action.hpp"
#include "character_actor.hpp"
#include "global_world_state.hpp"
#include "state.hpp"
#include "utils/godot_macros.h"
#include <cstdlib>
#include <godot_cpp/templates/pair.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/templates/hash_map.hpp>
#include <godot_cpp/templates/hashfuncs.hpp>
#include <godot_cpp/variant/vector3.hpp>

namespace godot::goap {
typedef HashMap<PlannerNode, PlannerNode, PlannerNodeHasher> NodeNodeMap;
typedef HashMap<PlannerNode, float, PlannerNodeHasher> NodeScoreMap;
typedef HashSet<PlannerNode, PlannerNodeHasher> NodeSet;

void Goal::_bind_methods() {
#define CLASSNAME Goal
    GDPROPERTY(goal_state, Variant::DICTIONARY);
    GDPROPERTY(prerequisites, Variant::DICTIONARY);
}

void Goal::set_goal_state(Dictionary dict) {
    Action::dict_to_property_map(dict, this->goal_state);
}

Dictionary Goal::get_goal_state() const {
    return Action::property_map_to_dict(this->goal_state);
}

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

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

#undef CLASSNAME // !Goal

PlannerNode PlannerNode::goal_node(WorldState const &goal)  {
    return PlannerNode{goal};
}

PlannerNode PlannerNode::new_node_along(Ref<Action> action) const {
    PlannerNode new_node{
        action->prerequisites,
        action->effects,
        action,
    };
    for(WorldProperty const &prop : this->state)
        new_node.state.insert(prop.key, prop.value);
    for(WorldProperty const &prop : this->open_requirements)
        new_node.open_requirements.insert(prop.key, prop.value);
    return new_node;
}

void Planner::_bind_methods() {
#define CLASSNAME Planner
    GDPROPERTY_HINTED(actions, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE(Action));
    GDPROPERTY_HINTED(goals, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE(Goal));
}

void Planner::_enter_tree() {
    this->global_world_state = GlobalWorldState::get_singleton();
    this->actor = Object::cast_to<CharacterActor>(this->get_parent());
}

static Vector<Ref<Action>> trace_path(NodeNodeMap &map, PlannerNode &end) {
    Vector<Ref<Action>> edges{};
    PlannerNode node{end};
    while(node.last_edge.is_valid()) {
        edges.push_back(node.last_edge);
        node = map.get(node);
    }
    return edges;
}

Vector<Ref<Action>> Planner::make_plan() {
    // clear cache every planning phase
    this->cached_world_state.clear();
    // select the most desirable goal available
    Ref<Goal> goal = this->select_goal();
    if(!goal.is_valid()) {
        this->plan = {};
        return this->plan;
    }
    // ordered list of all nodes still being considered
    Vector<PlannerNode> open{PlannerNode::goal_node(goal->goal_state)};
    PlannerNode first = open.get(0);
    NodeNodeMap from{}; // mapping states to the previous in the path
    NodeScoreMap dist_traveled{}; // mapping states to the shortest found distance from start
    dist_traveled.insert(first, 0);
    NodeScoreMap best_guess{}; // mapping states to the best guess of the distance to the goal
    best_guess.insert(first, first.open_requirements.size());
    PlannerNode current{}; // state we're checking for neighbours or completion
    while(!open.is_empty()) {
        // current is the top of the ordered list
        current = open.get(0);
        // check if we've reached the goal
        if(current.open_requirements.is_empty()) {
            this->plan = trace_path(from, current);
            return this->plan;
        }
        // current is no longer considered as it cannot be the end
        open.erase(current);
        // find all neighbours of this state
        Vector<PlannerNode> neighbours = this->find_neighbours_of(current);
        for(PlannerNode const& node : neighbours) {
            float const new_dist = dist_traveled.get(current) + 1.f; // unweighed distance traveled to neighbour
            if(!dist_traveled.has(node) || new_dist < dist_traveled.get(node)) {
                // store distances
                dist_traveled[node] = new_dist;
                best_guess[node] = new_dist + node.open_requirements.size();
                from[node] = current;
                int i = open.find(node);
                if(i != -1)
                    open.remove_at(i);
                open.ordered_insert(node);
            }
        }
    }
    UtilityFunctions::push_warning("Failed to find a plan satisfying goal");
    this->plan = {};
    return this->plan;
}

Ref<Goal> Planner::select_goal() {
    for(Ref<Goal> const &goal : this->goals) {
        if(this->can_do(goal))
            return goal;
    }
    return {};
}

Variant Planner::get_world_property(StringName prop_key) {
    if(prop_key.begins_with("g_")) {
        return this->global_world_state->get_world_property(prop_key);
    } else if(this->cached_world_state.has(prop_key)) {
        return this->cached_world_state.get(prop_key);
    } else if(this->actor->has_method("get_" + prop_key)) {
        Variant val = this->actor->call("get_" + prop_key);
        this->cached_world_state.insert(prop_key, val);
        return val;
    } else return nullptr;
}

bool Planner::can_do(Ref<Action> action) {
    for(WorldProperty &prop : action->context_prerequisites)
        if(this->get_world_property(prop.key) != prop.value)
            return false;
    return true;
}

bool Planner::can_do(Ref<Goal> goal) {
    for(WorldProperty const &prop : goal->prerequisites)
        if(this->get_world_property(prop.key) != prop.value)
            return false;
    return true;
}

Vector<PlannerNode> Planner::find_neighbours_of(PlannerNode &node) {
    Vector<PlannerNode> neighbours{};
    for(Ref<Action> const &action : this->find_actions_satisfying(node.open_requirements)) {
        PlannerNode new_node = node.new_node_along(action);
        // remove all satisfied requirements
        for(WorldProperty const &delta : action->effects) {
            if(new_node.open_requirements.has(delta.key)
            && new_node.open_requirements.get(delta.key) == delta.value) {
                new_node.open_requirements.erase(delta.key);
            }
        }
        neighbours.push_back(new_node);
    }
    return neighbours;
}

Vector<Ref<Action>> Planner::find_actions_satisfying(WorldState requirements) {
    Vector<Ref<Action>> found_actions{};
    for(Ref<Action> &act : this->actions) {
        for(WorldProperty &prop : requirements) {
            if(act->effects.has(prop.key)
            && act->effects.get(prop.key) == prop.value
            && this->can_do(act)) {
                found_actions.push_back(act);
            }
        }
    }
    return found_actions;
}

bool Planner::is_action_complete() {
    return this->plan.get(0)->get_is_completed(this->actor);
}

State Planner::get_next_state() {
    if(!this->plan.is_empty())
        this->plan.remove_at(0);
    if(this->plan.is_empty())
        this->plan = this->make_plan();
    if(this->plan.is_empty())
        return State::new_invalid();
    return this->plan.get(0)->apply_state->construct(this);
}

void Planner::set_actions(Array value) {
    this->actions.clear();
    this->actions.resize(value.size());
    for(size_t i{0}; i < value.size(); ++i) {
        Ref<Action> act = value[i];
        if(act.is_valid())
            this->actions.set(i, act);
    }
}

Array Planner::get_actions() const {
    Array array{};
    for(Ref<Action> const &act : this->actions) {
        array.push_back(act);
    }
    return array;
}

void Planner::set_goals(Array value) {
    this->goals.clear();
    this->goals.resize(value.size());
    for(size_t i{0}; i < value.size(); ++i) {
        Ref<Goal> goal = value[i];
        if(goal.is_valid())
            this->goals.set(i, goal);
    }
}

Array Planner::get_goals() const {
    Array array{};
    for(Ref<Goal> const &goal : this->goals) {
        array.push_back(goal);
    }
    return array;
}

bool Planner::add_goal(Ref<Goal> goal) {
    bool can_do = this->can_do(goal);
    this->goals.insert(0, goal);
    return can_do;
}

void Planner::remove_goal(Ref<Goal> goal) {
    this->goals.erase(goal);
}
}