//
// Created by marvin on 16.08.18.
//

#include "../option_parser.h"
#include "../plugin.h"

#include "backward_task.h"
#include "../tasks/root_task.h"
#include "../utils/logging.h"
#include "../utils/collections.h"

using namespace std;

namespace backward_tasks {

// ----------------------- Helper Structs -----------------------

    ExplicitBackwardOperator::ExplicitBackwardOperator(const vector<FactPair> pre,
                                                       const vector<FactPair> eff,
                                                       int cost,
                                                       string name,
                                                       int parent_id) :
            preconditions(pre), effects(eff),
            cost(cost), name(move(name)), parent_operator_id(parent_id){
    }

    ExplicitBackwardVariable::ExplicitBackwardVariable(int domain,
                                                       string name,
                                                       vector<string> fact_names,
                                                       int initial_value,
                                                       int goal_value) :
            domain_size(domain), name(move(name)),
            fact_names(move(fact_names)), axiom_layer(0),
            axiom_default_value(0), initial_value(initial_value),
            goal_value(goal_value){
    }

    const string artificial_action_name = "to_artificial_goal";
    const string recursive_appendix = " -R";


// ----------------------- The Backward Task -----------------------

    BackwardTask::BackwardTask(const std::shared_ptr<AbstractTask> &parent)
            : DelegatingTask(parent),
              extra_variables(get_backward_variables()),
              goals(get_goal_from_initial()),
              initial_state_values(get_initial_from_goal()),
              operators(get_operators()){
        cout << "Initializing Backward Task..." << endl;
    }

    BackwardTask::BackwardTask(const options::Options &)
            : BackwardTask(tasks::g_root_task){

    }

// ----------------------- Initializer Methods -----------------------

    const vector<FactPair> BackwardTask::get_goal_from_initial() const{
        vector<int> values = parent->get_initial_state_values();
        vector<FactPair> new_goals;
        for(size_t i = 0; i < values.size(); i++) {
            new_goals.emplace_back(i,values[i]);
        }

        for (size_t i = 0; i < extra_variables.size(); i++){
            new_goals.emplace_back(i+values.size(),extra_variables[i].goal_value);
        }

        return new_goals;
    }

    vector<ExplicitBackwardOperator> BackwardTask::get_operators() const{
        vector<ExplicitBackwardOperator> ops;
        int count = parent->get_num_operators();
        //go over every operator in the task
        for (int op_ID = 0; op_ID < count; op_ID++){
            vector<FactPair> pre;
            vector<FactPair> eff;
            int cost = parent->get_operator_cost(op_ID, false);
            string name = parent->get_operator_name(op_ID, false);

            //gather all preconditions of an operator as effect
            for (int pre_ID = 0; pre_ID < parent->get_num_operator_preconditions(op_ID, false); pre_ID++){
                eff.push_back(parent->get_operator_precondition(op_ID, pre_ID, false));
            }
            //gather all effects of an operator as precondition
            for (int eff_ID = 0; eff_ID < parent->get_num_operator_effects(op_ID, false); eff_ID++){
                pre.push_back(parent->get_operator_effect(op_ID, eff_ID, false));
            }

            //check if all effects have a correspondent condition.
            for (auto & effect : eff){
                bool has_correspondent = false;
                for (auto & condition : pre){
                    if (condition.var == effect.var){
                        has_correspondent = true;
                        goto found_correspondent2;
                    }
                }
                found_correspondent2:
                if (!has_correspondent){
                    pre.push_back(effect);
                    // Add effect to condition, as we could cheat otherwise.
                }
            }

            //check if all conditions have a correspondent effect.
            vector<FactPair> single_conditions;
            for (auto & condition : pre){
                bool has_correspondent = false;
                for (auto & effect : eff){
                    if (condition.var == effect.var){
                        has_correspondent = true;
                        goto found_correspondent;
                    }
                }
                found_correspondent:
                if (!has_correspondent){
                    single_conditions.push_back(condition);
                }
            }

            //add goal variable as precondition
            pre.emplace_back(parent->get_num_variables(),extra_variables[0].goal_value);


            if (single_conditions.empty()){
                if (!is_mutex(eff)){
                    ops.emplace_back(pre,eff,cost,name,op_ID);
                }
            } else {
                //create multiple vectors for eff.
                ExplicitBackwardOperator op(pre,eff,cost,name,op_ID);
                create_operators_recursively(ops, op, eff, single_conditions);
            }
        }

        // Add operators from artificial goal to goal states.
        vector<FactPair> pre;
        vector<FactPair> eff;
        int cost = 0;
        vector<bool> checked_variables((unsigned)parent->get_num_variables(),false);

        for (int i = 0; i < parent->get_num_goals(); i++){
            const FactPair &fact = parent->get_goal_fact(i);
            checked_variables[fact.var] = true;
            pre.push_back(fact);
            eff.push_back(fact);
        }

        pre.emplace_back(parent->get_num_variables(),extra_variables[0].initial_value);
        eff.emplace_back(parent->get_num_variables(),extra_variables[0].goal_value);

        vector<FactPair> single_conditions;
        for (size_t i = 0 ; i < checked_variables.size(); i++){
            if (!checked_variables[i]){
                single_conditions.emplace_back(i,0);
            }
        }

        if (single_conditions.empty()){
            if (!is_mutex(eff)){
                ops.emplace_back(pre,eff,cost,artificial_action_name,-1);
            }
        } else {
            ExplicitBackwardOperator op(pre,eff,cost,artificial_action_name,-1);
            create_operators_recursively(ops, op, eff, single_conditions);
        }

        return ops;
    }

    /**
     * We set the initial state to hold all goal facts and the other
     * variables are set to 0. However, the only important variable is
     * the artificial goal variable.
     * @return The initial state values.
     */
    vector<int> BackwardTask::get_initial_from_goal() const{
        int parent_size = parent->get_num_variables();
        vector<int> new_initial(
                parent_size+extra_variables.size(), 0);
        for (int i = 0; i < parent->get_num_goals(); i++){
            const FactPair &fact = parent->get_goal_fact(i);
            new_initial[fact.var] = fact.value;
        }

        for (size_t i = 0; i < extra_variables.size(); i++){
            new_initial[parent_size+i] = extra_variables[i].initial_value;
        }

        return new_initial;
    }

    vector<ExplicitBackwardVariable> BackwardTask::get_backward_variables() const{
        vector<ExplicitBackwardVariable> vars;

        //Add a goal variable
        vector<string> facts = {"is_artificial_goal(false)","is_artificial_goal(true)"};
        vars.emplace_back(2, "artificial_goal", facts, 1, 0);

        return vars;
    }

// ----------------------- Backward Specific Methods -----------------------

    void BackwardTask::create_operators_recursively(
            vector<ExplicitBackwardOperator> &ops,
            const ExplicitBackwardOperator &op,
            vector<FactPair> eff,
            vector<FactPair> singles) const{
        if (singles.empty()){
            if (!is_mutex(eff)){//check for mutex violation
                ops.emplace_back(op.preconditions,eff,op.cost,op.name+recursive_appendix,op.parent_operator_id);
            }
        } else {
            FactPair n = singles.back();
            singles.pop_back();

            int size = parent->get_variable_domain_size(n.var);
            for (int i = 0; i < size; i++){
                vector<FactPair> new_eff = eff;
                new_eff.emplace_back(n.var,i);
                create_operators_recursively(ops, op, new_eff, singles);
            }
        }

    }

    /**
   * Returns a bool to indicate whether the pre_id points
   * to a preexisting variable (= true) or
   * to an extra backward one (= false).
   * The int is equal to the input for preexisting variables and
   * adjusted for backward variables.
   * @param pre_id The id the variable has accessed from the outside.
   * @return <bool,int>
   */
    pair<bool,int> BackwardTask::get_correct_variable_id(int pre_id) const{
        int parent_size = parent->get_num_variables();
        if (parent_size > pre_id){
            return make_pair(true, pre_id);
        } else {
            return make_pair(false, pre_id - parent_size);
        }
    }

    /**
     * Counts the number of actions within the domain
     * without the specific ones for creating the artificial goals.
     * @return number of actions
     */
    int BackwardTask::get_num_operators_without_artificial() const{
        int count = 0;

        for (const auto &op : operators){
            if (op.name.compare(artificial_action_name) != 0
                && op.name.compare(artificial_action_name + recursive_appendix) != 0){
                count ++;
            }
        }
        return count;
    }

    bool BackwardTask::is_mutex(vector<FactPair> effects) const{
        for (auto &fact1 : effects){
            for (auto &fact2 : effects){
                if (fact1 != fact2 && are_facts_mutex(fact1,fact2)){
                    return true;
                }
            }
        }
        return false;
    }

// ----------------------- Overridden Methods -----------------------

    int BackwardTask::get_num_goals() const {
        return goals.size();
    }

    FactPair BackwardTask::get_goal_fact(int index) const {
        return goals[index];
    }

    vector<int> BackwardTask::get_initial_state_values() const {
        return initial_state_values;
    }

    int BackwardTask::get_operator_cost(int op_index, bool) const{
        assert(utils::in_bounds(op_index, operators));
        return operators[op_index].cost;
    }
    std::string BackwardTask::get_operator_name(int op_index, bool) const{
        assert(utils::in_bounds(op_index, operators));
        return operators[op_index].name;
    }
    int BackwardTask::get_num_operators() const{
        return operators.size();
    }

    OperatorID BackwardTask::get_forward_op_id(OperatorID op_index) const {
        assert(utils::in_bounds(op_index.get_index(), operators));
        return OperatorID(operators[op_index.get_index()].parent_operator_id);
    }

    int BackwardTask::get_num_operator_preconditions(int op_index, bool) const {
        assert(utils::in_bounds(op_index, operators));
        return operators[op_index].preconditions.size();
    }

    FactPair BackwardTask::get_operator_precondition(int op_index, int fact_index, bool) const {
        assert(utils::in_bounds(op_index, operators));
        assert(utils::in_bounds(fact_index,operators[op_index].preconditions));
        return operators[op_index].preconditions[fact_index];
    }

    int BackwardTask::get_num_operator_effects(int op_index, bool) const {
        assert(utils::in_bounds(op_index, operators));
        return operators[op_index].effects.size();
    }

    FactPair BackwardTask::get_operator_effect(int op_index, int eff_index, bool) const {
        assert(utils::in_bounds(op_index, operators));
        assert(utils::in_bounds(eff_index, operators[op_index].effects));
        return operators[op_index].effects[eff_index];
    }

    int BackwardTask::get_num_operator_effect_conditions(int, int, bool) const {
        return 0;
    }

    FactPair
    BackwardTask::get_operator_effect_condition(int, int, int ,bool ) const {
        cout << "Effect conditions are not supported!" << endl;
        utils::exit_with(utils::ExitCode::SEARCH_INPUT_ERROR);
    }

    int BackwardTask::get_num_variables() const {
        return parent->get_num_variables() + extra_variables.size();
    }

    std::string BackwardTask::get_variable_name(int var) const {
        pair<bool,int> id = get_correct_variable_id(var);
        if (id.first){
            return parent->get_variable_name(id.second);
        } else {
            return extra_variables[id.second].name;
        }
    }

    int BackwardTask::get_variable_domain_size(int var) const {
        pair<bool,int> id = get_correct_variable_id(var);
        if (id.first){
            return parent->get_variable_domain_size(id.second);
        } else {
            return extra_variables[id.second].domain_size;
        }
    }

    int BackwardTask::get_variable_axiom_layer(int var) const {
        pair<bool,int> id = get_correct_variable_id(var);
        if (id.first){
            return parent->get_variable_axiom_layer(id.second);
        } else {
            return extra_variables[id.second].axiom_layer;
        }
    }

    int BackwardTask::get_variable_default_axiom_value(int var) const {
        pair<bool,int> id = get_correct_variable_id(var);
        if (id.first){
            return parent->get_variable_default_axiom_value(id.second);
        } else {
            return extra_variables[id.second].axiom_default_value;
        }
    }

    std::string BackwardTask::get_fact_name(const FactPair &fact) const{
        pair<bool,int> id = get_correct_variable_id(fact.var);
        if (id.first){
            return parent->get_fact_name(fact);
        } else {
            return extra_variables[id.second].fact_names[fact.value];
        }
    }

    bool BackwardTask::are_facts_mutex(const FactPair &fact1, const FactPair &fact2) const{
        if (fact1.var == fact2.var) {
            return fact1.value != fact2.value;
        }
        if (fact1.var < parent->get_num_variables() &&
            fact2.var < parent->get_num_variables()){
            return parent->are_facts_mutex(fact1,fact2);
        }
        return false;
    }

// ----------------------- Plugin -----------------------

    static shared_ptr<AbstractTask> _parse(OptionParser &parser) {
        Options opts = parser.parse();
        if (parser.dry_run())
            return nullptr;
        else
            return make_shared<BackwardTask>(opts);
    }

    static Plugin<AbstractTask> _plugin("backward", _parse);
}

