/**
   * Create an internalTaskFlow job with the given task flow job (user)
   *
   * @param userJob the user job that will be used to create the internal job.
   * @return the created internal job.
   * @throws JobCreationException an exception if the factory cannot create the given job.
   */
  private static InternalJob createJob(TaskFlowJob userJob) throws JobCreationException {
    if (userJob.getTasks().size() == 0) {
      logger.info("Job '" + userJob.getName() + "' must contain tasks !");
      throw new JobCreationException("This job must contains tasks !");
    }

    // validate taskflow
    List<FlowChecker.Block> blocks = new ArrayList<>();
    FlowError err = FlowChecker.validate(userJob, blocks);
    if (err != null) {
      String e = "";

      e += "Invalid taskflow: " + err.getMessage() + "; context: " + err.getTask();
      logger.error(e);
      throw new JobCreationException(e, err);
    }

    InternalJob job = new InternalTaskFlowJob();
    // keep an initial job content
    job.setTaskFlowJob(userJob);
    Map<Task, InternalTask> tasksList = new LinkedHashMap<>();
    boolean hasPreciousResult = false;

    for (Task t : userJob.getTasks()) {
      tasksList.put(t, createTask(userJob, t));

      if (!hasPreciousResult) {
        hasPreciousResult = t.isPreciousResult();
      }
    }

    for (Entry<Task, InternalTask> entry : tasksList.entrySet()) {
      if (entry.getKey().getDependencesList() != null) {
        for (Task t : entry.getKey().getDependencesList()) {
          entry.getValue().addDependence(tasksList.get(t));
        }
      }

      job.addTask(entry.getValue());
    }

    // tag matching blocks in InternalTasks
    for (InternalTask it : tasksList.values()) {
      for (FlowChecker.Block block : blocks) {
        if (it.getName().equals(block.start.element.getName())) {
          it.setMatchingBlock(block.end.element.getName());
        }
        if (it.getName().equals(block.end.element.getName())) {
          it.setMatchingBlock(block.start.element.getName());
        }
      }
    }

    // create if/else/join weak dependencies
    for (InternalTask it : tasksList.values()) {

      // it performs an IF action
      if (it.getFlowScript() != null
          && it.getFlowScript().getActionType().equals(FlowActionType.IF.toString())) {
        String ifBranch = it.getFlowScript().getActionTarget();
        String elseBranch = it.getFlowScript().getActionTargetElse();
        String join = it.getFlowScript().getActionContinuation();
        List<InternalTask> joinedBranches = new ArrayList<>();

        // find the ifBranch task
        for (InternalTask it2 : tasksList.values()) {
          if (it2.getName().equals(ifBranch)) {
            it2.setIfBranch(it);
            String match = it2.getMatchingBlock();
            // find its matching block task
            if (match == null) {
              // no match: single task
              joinedBranches.add(it2);
            } else {
              for (InternalTask it3 : tasksList.values()) {
                if (it3.getName().equals(match)) {
                  joinedBranches.add(it3);
                  break;
                }
              }
            }
            break;
          }
        }

        // find the elseBranch task
        for (InternalTask it2 : tasksList.values()) {
          if (it2.getName().equals(elseBranch)) {
            it2.setIfBranch(it);

            String match = it2.getMatchingBlock();
            // find its matching block task
            if (match == null) {
              // no match: single task
              joinedBranches.add(it2);
            } else {
              for (InternalTask it3 : tasksList.values()) {
                if (it3.getName().equals(match)) {
                  joinedBranches.add(it3);
                  break;
                }
              }
            }
            break;
          }
        }

        // find the joinBranch task
        for (InternalTask it2 : tasksList.values()) {
          if (it2.getName().equals(join)) {
            it2.setJoinedBranches(joinedBranches);
          }
        }
      }
    }
    return job;
  }
  /**
   * Creates a flow control element for the given task <element name="controlFlow">
   *
   * @return the xml Element corresponding to the flow control, if the task contains a flow control,
   *     null otherwise
   */
  private Element createFlowControlElement(Document doc, Task task) {
    Element controlFlowE = null;

    // <ref name="block"/>
    if (task.getFlowBlock() != FlowBlock.NONE) {
      controlFlowE =
          doc.createElementNS(Schemas.SCHEMA_LATEST.namespace, XMLTags.FLOW.getXMLName());
      setAttribute(controlFlowE, XMLAttributes.FLOW_BLOCK, task.getFlowBlock().toString());
    }

    FlowScript flowScript = task.getFlowScript();
    if (flowScript != null) {

      Element flowActionE = null;

      // flowActionE can be if, loop, replicate or null.
      // if not null, it contains a script element

      // <ref name="actionIf"/>
      // <ref name="actionReplicate"/>
      // <ref name="actionLoop"/>
      // </choice>

      // *** if ***
      // <element name="if">
      if (flowScript.getActionType().equals(FlowActionType.IF.toString())) {
        flowActionE =
            doc.createElementNS(Schemas.SCHEMA_LATEST.namespace, XMLTags.FLOW_IF.getXMLName());
        setAttribute(flowActionE, XMLAttributes.FLOW_TARGET, flowScript.getActionTarget(), true);
        setAttribute(flowActionE, XMLAttributes.FLOW_ELSE, flowScript.getActionTargetElse(), true);
        setAttribute(
            flowActionE, XMLAttributes.FLOW_CONTINUATION, flowScript.getActionContinuation(), true);
      }

      // *** loop ***
      // <element name="loop">
      if (flowScript.getActionType().equals(FlowActionType.LOOP.toString())) {
        flowActionE =
            doc.createElementNS(Schemas.SCHEMA_LATEST.namespace, XMLTags.FLOW_LOOP.getXMLName());
        setAttribute(flowActionE, XMLAttributes.FLOW_TARGET, flowScript.getActionTarget(), true);
      }

      // *** replicate ***
      // <element name="replicate">
      if (flowScript.getActionType().equals(FlowActionType.REPLICATE.toString())) {
        flowActionE =
            doc.createElementNS(
                Schemas.SCHEMA_LATEST.namespace, XMLTags.FLOW_REPLICATE.getXMLName());
      }

      if (flowActionE != null) {
        if (controlFlowE == null) {
          controlFlowE =
              doc.createElementNS(Schemas.SCHEMA_LATEST.namespace, XMLTags.FLOW.getXMLName());
        }
        Element scriptE = createScriptElement(doc, flowScript);
        flowActionE.appendChild(scriptE);
        controlFlowE.appendChild(flowActionE);
      }
    } // flowScript !=null

    return controlFlowE;
  }