/**
   * This method is the access point to the planning procedure. Initially, it adds all variables
   * from axioms to the set of found vars, then does the linear planning. If lp does not solve the
   * problem and there are subtasks, goal-driven recursive planning with backtracking is invoked.
   * Planning is performed until no new variables are introduced into the algorithm.
   */
  public EvaluationAlgorithm invokePlaning(Problem problem, boolean _computeAll) {
    long startTime = System.currentTimeMillis();

    computeAll = _computeAll;
    EvaluationAlgorithm algorithm = new EvaluationAlgorithm();

    PlanningContext context = problem.getCurrentContext();

    // add all axioms at the beginning of an algorithm
    Collection<Var> flattened = new HashSet<Var>();
    for (Iterator<Rel> axiomIter = problem.getAxioms().iterator(); axiomIter.hasNext(); ) {
      Rel rel = axiomIter.next();

      unfoldVarsToSet(rel.getOutputs(), flattened);

      // do not overwrite values of variables that come via args of compute() or as inputs of
      // independent subtasks
      if (!problem.getAssumptions().containsAll(flattened)
      // do not overwrite values of already known variables.
      // typically this is the case when a value of a variable
      // is given in a scheme via a properties window
      //                    && !problem.getKnownVars().containsAll( flattened )
      ) {
        algorithm.addRel(rel);
      }
      axiomIter.remove();
      context.getKnownVars().addAll(flattened);
      flattened.clear();
    }

    context.getFoundVars().addAll(context.getKnownVars());

    // remove all known vars with no relations
    for (Iterator<Var> varIter = context.getKnownVars().iterator(); varIter.hasNext(); ) {
      if (varIter.next().getRels().isEmpty()) {
        varIter.remove();
      }
    }

    // start planning
    if (problem.getRelsWithSubtasks().isEmpty()
        && linearForwardSearch(context, algorithm, computeAll)) {
      if (isLinearLoggingOn()) logger.debug("Problem solved without subtasks");
    } else if (!problem.getRelsWithSubtasks().isEmpty() && subtaskPlanning(problem, algorithm)) {
      if (isLinearLoggingOn()) logger.debug("Problem solved with subtasks");
    } else if (!computeAll) {
      if (isLinearLoggingOn()) logger.debug("Problem not solved");
    }

    if (!nested) {
      logger.info("Planning time: " + (System.currentTimeMillis() - startTime) + "ms.");
    }
    return algorithm;
  }
  /**
   * Linear forward search algorithm
   *
   * @param p
   * @param algorithm
   * @param targetVars
   * @param _computeAll
   * @return
   */
  private boolean linearForwardSearch(
      PlanningContext context, EvaluationAlgorithm algorithm, boolean _computeAll) {

    /*
     * while iterating through hashset, items cant be removed from/added to
     * that set. Theyre collected into these sets and added/removedall
     * together after iteration is finished
     */
    Set<Var> newVars = new LinkedHashSet<Var>();
    Set<Var> relOutputs = new LinkedHashSet<Var>();
    Set<Var> removableVars = new LinkedHashSet<Var>();

    boolean changed = true;

    if (isLinearLoggingOn())
      logger.debug(
          "------Starting linear planning with (sub)goals: "
              + context.getRemainingGoals()
              + "--------");

    if (isLinearLoggingOn()) logger.debug("Algorithm " + algorithm);

    int counter = 1;

    while ((!_computeAll && changed && !context.getRemainingGoals().isEmpty())
        || (changed && _computeAll)) {

      if (isLinearLoggingOn()) logger.debug("----Iteration " + counter + " ----");

      counter++;
      changed = false;

      // iterate through all knownvars
      if (isLinearLoggingOn()) logger.debug("Known:" + context.getKnownVars());

      for (Var var : context.getKnownVars()) {

        if (isLinearLoggingOn()) logger.debug("Current Known: " + var);

        // Check the relations of all components
        for (Rel rel : var.getRels()) {
          if (isLinearLoggingOn()) logger.debug("And its rel: " + rel);
          if (context.isAvailableRel(rel)) {
            context.removeUnknownInput(rel, var);

            if (isLinearLoggingOn()) logger.debug("problem contains it " + rel);

            removableVars.add(var);

            if (context.isRelReadyToUse(rel) && rel.getType() != RelType.TYPE_METHOD_WITH_SUBTASK) {

              if (isLinearLoggingOn()) logger.debug("rel is ready to be used " + rel);

              boolean relIsNeeded = false;

              if (isLinearLoggingOn()) logger.debug("its outputs " + rel.getOutputs());

              for (Var relVar : rel.getOutputs()) {

                if (!context.getFoundVars().contains(relVar)) {
                  relIsNeeded = true;
                }
              }

              if (rel.getOutputs().isEmpty()) {
                relIsNeeded = true;
              }
              if (isLinearLoggingOn()) logger.debug("relIsNeeded " + relIsNeeded);

              if (relIsNeeded) {

                if (isLinearLoggingOn()) logger.debug("needed rel:  " + rel);

                if (!rel.getOutputs().isEmpty()) {
                  relOutputs.clear();
                  unfoldVarsToSet(rel.getOutputs(), relOutputs);
                  newVars.addAll(relOutputs);
                  context.getFoundVars().addAll(relOutputs);
                }
                algorithm.addRel(rel);
                if (isLinearLoggingOn()) logger.debug("algorithm " + algorithm);
              }

              context.removeRel(rel);
              changed = true;
            }
          }
        }
      }

      // remove targets if they have already been found
      for (Iterator<Var> targetIter = context.getRemainingGoals().iterator();
          targetIter.hasNext(); ) {
        Var targetVar = targetIter.next();
        if (context.getFoundVars().contains(targetVar)) {
          targetIter.remove();
        }
      }

      if (isLinearLoggingOn()) logger.debug("foundvars " + context.getFoundVars());

      context.getKnownVars().addAll(newVars);
      context.getKnownVars().removeAll(removableVars);
      newVars.clear();
    }
    if (isLinearLoggingOn()) logger.debug("algorithm " + algorithm);

    if (!_computeAll) {
      Optimizer.optimize(context, algorithm);

      if (isLinearLoggingOn()) logger.debug("optimized algorithm " + algorithm);
    }

    if (isLinearLoggingOn()) logger.debug("\n---!!!Finished linear planning!!!---\n");

    return context.getRemainingGoals().isEmpty()
        || context.getFoundVars().containsAll(context.getAllGoals());
  }