/**
   * @param deployment the deployment from which create the jobs
   * @param jobgraph the graph of the jobs
   * @param templateTopologicalOrderIterator the topological order iterator of the jobs
   * @param client the Chronos client to use
   * @return <tt>true</tt> if the currently checked node is ready, <tt>false</tt> if still in
   *     progress.
   * @throws DeploymentException if the currently node failed.
   */
  protected boolean checkJobsOnChronosIteratively(
      Deployment deployment,
      Map<String, IndigoJob> jobgraph,
      TemplateTopologicalOrderIterator templateTopologicalOrderIterator,
      Chronos client)
      throws DeploymentException {

    // Get current job
    Resource currentNode = templateTopologicalOrderIterator.getCurrent();
    IndigoJob job = jobgraph.get(currentNode.getToscaNodeName());

    String jobName = job.getChronosJob().getName();
    Job updatedJob = getJobStatus(client, jobName);
    if (updatedJob == null) {
      String errorMsg =
          String.format(
              "Failed to deploy deployment <%s>. Chronos job <%s> (id: <%s>) does not exist",
              deployment.getId(), job.getToscaNodeName(), jobName);
      LOG.error(errorMsg);
      // Update job status
      updateResource(deployment, job, NodeStates.ERROR);
      throw new DeploymentException(errorMsg);
    }

    JobState jobState = getLastState(updatedJob);
    LOG.debug(
        "Status of Chronos job <{}> for deployment <{}> is <{}> ({}/{})",
        jobName,
        deployment.getId(),
        jobState,
        templateTopologicalOrderIterator.getPosition() + 1,
        templateTopologicalOrderIterator.getNodeSize());

    // Go ahead only if the job succeeded
    if (jobState != JobState.SUCCESS) {
      if (jobState != JobState.FAILURE) {
        // Job still in progress
        LOG.debug("Polling again job <{}> for deployment <{}>", jobName, deployment.getId());
        return false;
      } else {
        // Job failed -> Deployment failed!
        String errorMsg =
            String.format(
                "Failed to deploy deployment <%s>. Chronos job <%s> (id: <%s>) status is <%s>",
                deployment.getId(), job.getToscaNodeName(), jobName, jobState);
        LOG.error(errorMsg);
        // Update job status
        updateResource(deployment, job, NodeStates.ERROR);
        currentNode.setState(NodeStates.ERROR);
        throw new DeploymentException(errorMsg);
      }
    } else {
      // Job finished -> Update job status
      updateResource(deployment, job, NodeStates.STARTED);
      currentNode.setState(NodeStates.STARTED);
      return true;
    }
  }
  private void updateResource(Deployment deployment, IndigoJob job, NodeStates state) {

    // Find the Resource from DB
    Resource resource =
        resourceRepository.findByToscaNodeNameAndDeployment_id(
            job.getToscaNodeName(), deployment.getId());
    resource.setState(state);
    // resourceRepository.save(resource);
  }
  /**
   * Deletes all the jobs from Chronos.
   *
   * @param jobgraph the job graph.
   * @param client the {@link Chronos} client.
   * @param failAtFirst if <tt>true</tt> throws an exception at the first job deletion error,
   *     otherwise it tries to delete every other job.
   * @return <tt>true</tt> if all jobs have been deleted, <tt>false</tt> otherwise.
   */
  protected boolean deleteJobsOnChronosIteratively(
      Deployment deployment,
      Map<String, IndigoJob> jobgraph,
      TemplateTopologicalOrderIterator templateTopologicalOrderIterator,
      Chronos client,
      boolean failAtFirst) {

    Resource currentNode = templateTopologicalOrderIterator.getCurrent();
    if (currentNode == null) {
      return false;
    }

    IndigoJob currentJob = jobgraph.get(currentNode.getToscaNodeName());
    boolean failed = false;

    // Delete current job (all jobs iteratively)
    try {
      try {
        updateResource(deployment, currentJob, NodeStates.DELETING);
        currentNode.setState(NodeStates.DELETING);

        String jobName = currentJob.getChronosJob().getName();

        client.deleteJob(jobName);

        LOG.debug(
            "Deleted Chronos job <{}> for deployment <{}> ({}/{})",
            jobName,
            deployment.getId(),
            templateTopologicalOrderIterator.getPosition() + 1,
            templateTopologicalOrderIterator.getNodeSize());
      } catch (ChronosException ce) {
        // Chronos API hack (to avoid error 400 if the job to delete does not exist)
        if (ce.getStatus() != 400 && ce.getStatus() != 404) {
          throw new RuntimeException(String.format("Status Code: <%s>", ce.getStatus()));
        }
      }
    } catch (Exception ex) {
      // Just log the error
      String errorMsg = String.format("Failed to delete job <%s> on Chronos: %s", ex.getMessage());
      LOG.error(errorMsg);

      failed = true;
      // Update job status
      updateResource(deployment, currentJob, NodeStates.ERROR);
      currentNode.setState(NodeStates.ERROR);

      // Only throw exception if required
      if (failAtFirst) {
        // TODO use a custom exception ?
        throw new RuntimeException(errorMsg);
      }
    }

    return !failed;
  }
  /**
   * @param deployment the deployment from which create the jobs
   * @param jobgraph the graph of the jobs
   * @param templateTopologicalOrderIterator the topological order iterator of the jobs
   * @param client the Chronos client to use
   * @return <tt>true</tt> if there are more nodes to create, <tt>false</tt> otherwise.
   */
  protected boolean createJobsOnChronosIteratively(
      Deployment deployment,
      Map<String, IndigoJob> jobgraph,
      TemplateTopologicalOrderIterator templateTopologicalOrderIterator,
      Chronos client) {

    // Create Jobs in the required order on Chronos
    Resource currentNode = templateTopologicalOrderIterator.getCurrent();
    if (currentNode == null) {
      return false;
    }

    IndigoJob currentJob = jobgraph.get(currentNode.getToscaNodeName());

    // Create jobs based on the topological order
    try {
      String nodeTypeMsg;
      if (currentJob.getParents().isEmpty()) {
        // Scheduled job (not dependent)
        nodeTypeMsg = "scheduled";
        client.createJob(currentJob.getChronosJob());
      } else {
        // Dependent job
        nodeTypeMsg = String.format("parents <%s>", currentJob.getChronosJob().getParents());
        client.createDependentJob(currentJob.getChronosJob());
      }

      LOG.debug(
          "Created job for deployment <{}> on Chronos: name <{}>, {} ({}/{})",
          deployment.getId(),
          currentJob.getChronosJob().getName(),
          nodeTypeMsg,
          templateTopologicalOrderIterator.getPosition() + 1,
          templateTopologicalOrderIterator.getNodeSize());
      // Update job status
      updateResource(deployment, currentJob, NodeStates.CREATED);
      // The node in the iterator is not actually an entity (serialization issues)
      currentNode.setState(NodeStates.CREATED);

    } catch (ChronosException exception) { // Chronos job launch error
      // Update job status
      updateResource(deployment, currentJob, NodeStates.ERROR);
      currentNode.setState(NodeStates.ERROR);
      // TODO use a custom exception ?
      throw new RuntimeException(
          String.format(
              "Failed to launch job <%s> on Chronos. Status Code: <%s>",
              currentJob.getChronosJob().getName(), exception.getStatus()));
    }

    return templateTopologicalOrderIterator.getNext() != null;
  }
  protected Job createJob(
      Map<String, NodeTemplate> nodes,
      String deploymentId,
      String nodeName,
      NodeTemplate nodeTemplate,
      Map<String, Resource> resources) {
    try {
      Job chronosJob = new Job();
      // Init job infos

      // Get the generated UUID for the node (in DB resource ?)
      // FIXME This is just for prototyping... Otherwise is madness!!
      Resource resourceJob = resources.get(nodeName);

      chronosJob.setName(resourceJob.getId());

      // TODO Validation
      chronosJob.setRetries(
          Integer.parseInt(
              (String)
                  toscaService.getNodePropertyValueByName(nodeTemplate, "retries").getValue()));

      chronosJob.setCommand(
          (String) toscaService.getNodePropertyValueByName(nodeTemplate, "command").getValue());

      // TODO Enable epsilon setting in TOSCA tplt ?
      chronosJob.setEpsilon("PT10S");

      ListPropertyValue inputUris =
          (ListPropertyValue) toscaService.getNodePropertyValueByName(nodeTemplate, "uris");
      if (inputUris != null && !inputUris.getValue().isEmpty()) {
        // Convert List<Object> to List<String>
        chronosJob.setUris(
            inputUris
                .getValue()
                .stream()
                .map(e -> ((PropertyValue<?>) e).getValue().toString())
                .collect(Collectors.toList()));
      }

      List<EnvironmentVariable> envs = new ArrayList<>();
      ComplexPropertyValue inputEnvVars =
          ((ComplexPropertyValue)
              toscaService.getNodePropertyValueByName(nodeTemplate, "environment_variables"));
      if (inputEnvVars != null) {
        for (Map.Entry<String, Object> var : inputEnvVars.getValue().entrySet()) {
          EnvironmentVariable envVar = new EnvironmentVariable();
          envVar.setName(var.getKey());
          envVar.setValue(((PropertyValue<?>) var.getValue()).getValue().toString());
          envs.add(envVar);
        }
        chronosJob.setEnvironmentVariables(envs);
      }

      // Docker image
      // TODO Remove hard-coded?
      String supportedType = "tosca.artifacts.Deployment.Image.Container.Docker";
      DeploymentArtifact image;
      // <image> artifact available
      if (nodeTemplate.getArtifacts() == null
          || (image = nodeTemplate.getArtifacts().get("image")) == null) {
        throw new IllegalArgumentException(
            String.format(
                "<image> artifact not found in node <%s> of type <%s>",
                nodeName, nodeTemplate.getType()));
      }

      // <image> artifact type check
      if (!image.getArtifactType().equals(supportedType)) {
        throw new IllegalArgumentException(
            String.format(
                "Unsupported artifact type for <image> artifact in node <%s> of type <%s>. "
                    + "Given <%s>, supported <%s>",
                nodeName,
                nodeTemplate.getType(),
                nodeTemplate.getArtifacts().get("image").getArtifactType(),
                supportedType));
      }

      // Requirements

      // Get Docker host dependency
      String dockerCapabilityName = "host";
      Map<String, NodeTemplate> dockerRelationships =
          toscaService.getAssociatedNodesByCapability(nodes, nodeTemplate, dockerCapabilityName);
      Double dockerNumCpus = null;
      Double dockerMemSize = null;
      if (!dockerRelationships.isEmpty()) {
        /*
         * WARNING: The TOSCA validation should already check the limits (currently Alien4Cloud does
         * not...)
         */
        NodeTemplate dockerNode = dockerRelationships.values().iterator().next();
        Capability dockerCapability = dockerNode.getCapabilities().get(dockerCapabilityName);
        dockerNumCpus =
            Double.parseDouble(
                (String)
                    toscaService
                        .getCapabilityPropertyValueByName(dockerCapability, "num_cpus")
                        .getValue());

        // Converting Memory Size (as TOSCA scalar-unit.size)
        SizeType tmp = new SizeType();
        String memSizeRaw =
            (String)
                toscaService
                    .getCapabilityPropertyValueByName(dockerCapability, "mem_size")
                    .getValue();
        dockerMemSize = tmp.parse(memSizeRaw).convert("MB"); // Chronos wants MB
      }

      Container container = new Container();
      container.setType("DOCKER");

      // Run the container in Privileged Mode
      Parameters param = new Parameters();
      param.setKey("privileged");
      param.setValue("true");
      Collection<Parameters> parameters = new ArrayList<Parameters>();
      parameters.add(param);
      container.setParameters(parameters);

      // FIXME ForcePullImage must be parametrizable by tosca template
      container.setForcePullImage(true);
      ////////////////////////////////////////////////////////////////

      container.setImage(
          (String)
              ((PropertyValue<?>) nodeTemplate.getArtifacts().get("image").getFile()).getValue());
      if (dockerNumCpus != null) {
        chronosJob.setCpus(dockerNumCpus);
      }
      if (dockerMemSize != null) {
        chronosJob.setMem(dockerMemSize);
      }

      chronosJob.setContainer(container);

      return chronosJob;
    } catch (Exception ex) {
      throw new RuntimeException(
          String.format(
              "Failed to parse node <%s> of type <%s>: %s",
              nodeName, nodeTemplate.getType(), ex.getMessage()),
          ex);
    }
  }