/** @return the subtaskLoggingOn */
 private boolean isSubtaskLoggingOn() {
   return subtaskLoggingOn || RuntimeProperties.isLogDebugEnabled();
 }
 /** @return the linearLoggingOn */
 private boolean isLinearLoggingOn() {
   return linearLoggingOn || RuntimeProperties.isLogDebugEnabled();
 }
  /**
   * Goal-driven recursive (depth-first, exhaustive) search with backtracking
   *
   * @param problem
   * @param algorithm
   * @param subtaskRelsInPath
   * @param depth
   */
  private boolean subtaskPlanningImpl(
      PlanningContext context,
      Set<Rel> relsWithSubtasks,
      EvaluationAlgorithm algorithm,
      LinkedList<Rel> subtaskRelsInPath,
      int depth) {

    Set<Rel> relsWithSubtasksCopy = new LinkedHashSet<Rel>(relsWithSubtasks);

    Set<Rel> relsWithSubtasksToRemove = new LinkedHashSet<Rel>();

    boolean firstMLB = true;

    // start building Maximal Linear Branch (MLB)
    MLB:
    while (!relsWithSubtasksCopy.isEmpty()) {

      if (isSubtaskLoggingOn()) {
        String print = p(depth) + "Starting new MLB with: ";
        for (Rel rel : relsWithSubtasksCopy) {
          print +=
              "\n" + p(depth) + "  " + rel.getParent().getFullName() + " : " + rel.getDeclaration();
        }
        /*
        print += "\n" + p( depth ) + " All remaining rels in problem:";
        for ( Rel rel : problem.getAllRels() ) {
            print += "\n" + p( depth ) + " " + rel.getParentObjectName() + " : " + rel.getDeclaration();
        }
        print += "\n" + p( depth ) + "All found variables: ";
        for ( Var var : problem.getFoundVars() ) {
            print += "\n" + p( depth ) + " " + var.toString();
        }
        */
        logger.debug(print);
      }

      // if this is a first attempt to construct an MLB to solve a subtask(i.e. depth>0),
      // do not invoke linear planning because it has already been done
      if ((depth == 0) || !firstMLB) {

        boolean solvedIntermediately = linearForwardSearch(context, algorithm, true);

        // Having constructed some MLBs the (sub)problem may be solved
        // and there is no need in wasting precious time planning unnecessary branches
        if (solvedIntermediately
            && ( // on the top level optimize only if computing goals
            (depth == 0 && !computeAll)
                // otherwise (inside subtasks) always optimize
                || (depth != 0))) {
          // If the problem is solved, optimize and return
          if (!isOptDisabled) Optimizer.optimize(context, algorithm);
          return true;
        }
      } else {
        firstMLB = false;
      }

      // or children
      OR:
      for (Iterator<Rel> subtaskRelIterator = relsWithSubtasksCopy.iterator();
          subtaskRelIterator.hasNext(); ) {

        Rel subtaskRel = subtaskRelIterator.next();

        if (isSubtaskLoggingOn())
          logger.debug(
              p(depth)
                  + "OR: depth: "
                  + (depth + 1)
                  + " rel - "
                  + subtaskRel.getParent().getFullName()
                  + " : "
                  + subtaskRel.getDeclaration());

        if (subtaskRel.equals(subtaskRelsInPath.peekLast())
            || (!context.isRelReadyToUse(subtaskRel))
            || context.getFoundVars().containsAll(subtaskRel.getOutputs())
            || (!isSubtaskRepetitionAllowed && subtaskRelsInPath.contains(subtaskRel))) {

          if (isSubtaskLoggingOn()) {
            logger.debug(p(depth) + "skipped");
            if (!context.isRelReadyToUse(subtaskRel)) {
              logger.debug(p(depth) + "because it has unknown inputs"); // TODO print unknown
            } else if (context.getFoundVars().containsAll(subtaskRel.getOutputs())) {
              logger.debug(p(depth) + "because all outputs in FoundVars");
            } else if (subtaskRel.equals(subtaskRelsInPath.peekLast())) {
              logger.debug(p(depth) + "because it is nested in itself");
            } else if (!isSubtaskRepetitionAllowed && subtaskRelsInPath.contains(subtaskRel)) {
              logger.debug(
                  p(depth)
                      + "This rel with subtasks is already in use, path: "
                      + subtaskRelsInPath);
            }
          }
          continue OR;
        }

        LinkedList<Rel> newPath = new LinkedList<Rel>(subtaskRelsInPath);
        newPath.add(subtaskRel);

        PlanningResult result = new PlanningResult(subtaskRel, true);

        // this is true if all subtasks are solvable
        boolean allSolved = true;
        // and children
        AND:
        for (SubtaskRel subtask : subtaskRel.getSubtasks()) {
          if (isSubtaskLoggingOn()) logger.debug(p(depth) + "AND: subtask - " + subtask);

          EvaluationAlgorithm sbtAlgorithm = null;

          ////////////////////// INDEPENDENT SUBTASK////////////////////////////////////////
          if (subtask.isIndependent()) {
            if (isSubtaskLoggingOn()) logger.debug("Independent!!!");

            if (subtask.isSolvable() == null) {
              if (isSubtaskLoggingOn())
                logger.debug("Start solving independent subtask " + subtask.getDeclaration());
              // independent subtask is solved only once
              Problem problemContext = subtask.getContext();
              DepthFirstPlanner planner = new DepthFirstPlanner();
              planner.indSubtasks = indSubtasks;
              planner.nested = true;
              sbtAlgorithm = planner.invokePlaning(problemContext, isOptDisabled);
              PlanningContext indCntx = problemContext.getCurrentContext();
              boolean solved = indCntx.getFoundVars().containsAll(indCntx.getAllGoals());
              if (solved) {
                subtask.setSolvable(Boolean.TRUE);
                indSubtasks.put(subtask, sbtAlgorithm);
                if (isSubtaskLoggingOn()) logger.debug("Solved " + subtask.getDeclaration());
              } else {
                subtask.setSolvable(Boolean.FALSE);
                if (RuntimeProperties.isLogInfoEnabled()) {
                  logger.debug("Unable to solve " + subtask.getDeclaration());
                }
              }
              allSolved &= solved;
            } else if (subtask.isSolvable() == Boolean.TRUE) {
              if (isSubtaskLoggingOn()) logger.debug("Already solved");
              allSolved &= true;
              sbtAlgorithm = indSubtasks.get(subtask);
            } else {
              if (isSubtaskLoggingOn()) logger.debug("Not solvable");
              allSolved &= false;
            }
            if (isSubtaskLoggingOn()) logger.debug("End of independent subtask " + subtask);

            if (!allSolved) {
              continue OR;
            }

            assert sbtAlgorithm != null;

            result.addSubtaskAlgorithm(subtask, sbtAlgorithm);
          }
          ////////////////////// DEPENDENT SUBTASK//////////////////////////////////////
          else {
            // lets clone the environment
            PlanningContext newContext = prepareNewContext(context, subtask);

            sbtAlgorithm = new EvaluationAlgorithm();

            // during linear planning, if some goals are found, they are removed from the set
            // "goals"
            boolean solved =
                linearForwardSearch(
                    newContext,
                    sbtAlgorithm,
                    // do not optimize here, because the solution may require additional rels with
                    // subtasks
                    true);

            if (solved) {
              if (isSubtaskLoggingOn()) logger.debug(p(depth) + "SOLVED subtask: " + subtask);

              if (!isOptDisabled) {
                // if a subtask has been solved, optimize its algorithm
                Optimizer.optimize(newContext, sbtAlgorithm);
              }

              result.addSubtaskAlgorithm(subtask, sbtAlgorithm);
              allSolved &= solved;
              continue AND;
            } else if (!solved && (depth == maxDepth)) {
              if (isSubtaskLoggingOn())
                logger.debug(p(depth) + "NOT SOLVED and cannot go any deeper, subtask: " + subtask);
              continue OR;
            }

            if (isSubtaskLoggingOn()) logger.debug(p(depth) + "Recursing deeper");

            solved =
                subtaskPlanningImpl(newContext, relsWithSubtasks, sbtAlgorithm, newPath, depth + 1);

            if (isSubtaskLoggingOn()) logger.debug(p(depth) + "Back to depth " + (depth + 1));

            // the linear planning has been performed at the end of MLB on the depth+1,
            // if the problem was solved, there is no need to run linear planning again
            if ((solved || (solved = linearForwardSearch(newContext, sbtAlgorithm, true)))
                && !isOptDisabled) {
              // if solved, optimize here with full list of goals in order to get rid of
              // unnecessary subtask instances and other relations
              Optimizer.optimize(newContext, sbtAlgorithm);
            }

            if (isSubtaskLoggingOn())
              logger.debug(p(depth) + (solved ? "" : "NOT") + " SOLVED subtask: " + subtask);

            allSolved &= solved;

            // if at least one subtask is not solvable, try another
            // branch
            if (!allSolved) {
              continue OR;
            }

            result.addSubtaskAlgorithm(subtask, sbtAlgorithm);
          }
        } // AND

        if (allSolved) {
          algorithm.add(result);

          Set<Var> newVars = new LinkedHashSet<Var>();

          unfoldVarsToSet(subtaskRel.getOutputs(), newVars);

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

          subtaskRelIterator.remove();

          if (isSubtaskLoggingOn()) {
            logger.debug(
                p(depth)
                    + "SOLVED ALL SUBTASKS for "
                    + subtaskRel.getParent().getFullName()
                    + " : "
                    + subtaskRel.getDeclaration());
            logger.debug(p(depth) + "Updating the problem graph and continuing building new MLB");
          }

          // this is used for incremental dfs
          if (depth == 0) {
            relsWithSubtasksToRemove.add(subtaskRel);
          }

          continue MLB;
        }
        if (isSubtaskLoggingOn())
          logger.debug(
              p(depth)
                  + "NOT SOLVED ALL subtasks, removing from path "
                  + subtaskRel.getParent().getFullName()
                  + " : "
                  + subtaskRel.getDeclaration());
        newPath.remove(subtaskRel);
      } // end OR

      // exit loop because there are no more rels with subtasks to be
      // applied
      // (i.e. no more rels can introduce new variables into the
      // algorithm)
      if (isSubtaskLoggingOn()) logger.debug(p(depth) + "No more MLB can be constructed");
      break MLB;
    }

    // incremental dfs, remove solved subtasks
    if (depth == 0) {
      relsWithSubtasks.removeAll(relsWithSubtasksToRemove);
    }

    return false;
  }