/**
   * Starts an instance (prerequisite: DEPLOYED_STOPPED).
   *
   * @param instance the instance
   * @param plugin the associated plug-in
   * @throws IOException if something went wrong
   */
  void start(Instance instance, PluginInterface plugin) throws IOException {

    String instancePath = InstanceHelpers.computeInstancePath(instance);
    if (instance.getStatus() != InstanceStatus.DEPLOYED_STOPPED
        && instance.getStatus() != InstanceStatus.WAITING_FOR_ANCESTOR) {
      this.logger.fine(
          instancePath
              + " cannot be started. Prerequisite status: DEPLOYED_STOPPED or WAITING_FOR_ANCESTOR (but was "
              + instance.getStatus()
              + ").");

    } else if (instance.getParent() != null
        && instance.getParent().getStatus() != InstanceStatus.DEPLOYED_STARTED) {

      // An element cannot start if its parent is not started.
      // However, if the parent is unresolved (or waiting for its parent to start),
      // then we can mark this instance as ready to start with its parent.
      this.logger.fine(
          instancePath
              + " cannot be started because its parent is not started. Parent status: "
              + instance.getParent().getStatus()
              + ".");
      if (instance.getParent().getStatus() == InstanceStatus.UNRESOLVED
          || instance.getParent().getStatus() == InstanceStatus.WAITING_FOR_ANCESTOR) {

        instance.setStatus(InstanceStatus.WAITING_FOR_ANCESTOR);
        this.logger.fine(instancePath + " will start as soon as its parent starts.");
      }

    } else {
      // Otherwise, start it
      try {
        if (ImportHelpers.hasAllRequiredImports(instance, this.logger)) {
          instance.data.put(FORCE, "whatever");
          updateStateFromImports(instance, plugin, null, null);

        } else {
          this.logger.fine(
              "Instance "
                  + InstanceHelpers.computeInstancePath(instance)
                  + " cannot be started, dependencies are missing. Requesting exports from other agents.");

          instance.setStatus(InstanceStatus.UNRESOLVED);
          this.messagingClient.sendMessageToTheDm(
              new MsgNotifInstanceChanged(this.appName, instance));
          this.messagingClient.requestExportsFromOtherAgents(instance);
        }

      } catch (PluginException e) {
        this.logger.severe(
            "An error occured while starting " + InstanceHelpers.computeInstancePath(instance));
        Utils.logException(this.logger, e);

        instance.setStatus(InstanceStatus.DEPLOYED_STOPPED);
        this.messagingClient.sendMessageToTheDm(
            new MsgNotifInstanceChanged(this.appName, instance));
      }
    }
  }
  /**
   * Updates the status of an instance based on the imports.
   *
   * @param impactedInstance the instance whose imports may have changed
   * @param plugin the plug-in to use to apply a concrete modification
   * @param statusChanged The changed status of the instance that changed (e.g. that provided new
   *     imports)
   * @param importChanged The individual imports that changed
   */
  public void updateStateFromImports(
      Instance impactedInstance,
      PluginInterface plugin,
      Import importChanged,
      InstanceStatus statusChanged)
      throws IOException, PluginException {

    // Do we have all the imports we need?
    boolean haveAllImports = ImportHelpers.hasAllRequiredImports(impactedInstance, this.logger);

    // Update the life cycle of this instance if necessary
    // Maybe we have something to start
    if (haveAllImports) {
      if (impactedInstance.getStatus() == InstanceStatus.UNRESOLVED
          || impactedInstance.data.remove(FORCE) != null) {

        InstanceStatus oldState = impactedInstance.getStatus();
        impactedInstance.setStatus(InstanceStatus.STARTING);
        try {
          this.messagingClient.sendMessageToTheDm(
              new MsgNotifInstanceChanged(this.appName, impactedInstance));
          plugin.start(impactedInstance);
          impactedInstance.setStatus(InstanceStatus.DEPLOYED_STARTED);

          this.messagingClient.sendMessageToTheDm(
              new MsgNotifInstanceChanged(this.appName, impactedInstance));
          this.messagingClient.publishExports(impactedInstance);
          this.messagingClient.listenToRequestsFromOtherAgents(
              ListenerCommand.START, impactedInstance);

        } catch (Exception e) {
          this.logger.severe(
              "An error occured while starting "
                  + InstanceHelpers.computeInstancePath(impactedInstance));
          Utils.logException(this.logger, e);
          impactedInstance.setStatus(oldState);
          this.messagingClient.sendMessageToTheDm(
              new MsgNotifInstanceChanged(this.appName, impactedInstance));
        }

      } else if (impactedInstance.getStatus() == InstanceStatus.DEPLOYED_STARTED) {
        plugin.update(impactedInstance, importChanged, statusChanged);

      } else {
        this.logger.fine(
            InstanceHelpers.computeInstancePath(impactedInstance)
                + " checked import changes but has nothing to update (1).");
      }
    }

    // Or maybe we have something to stop
    else {
      if (impactedInstance.getStatus() == InstanceStatus.DEPLOYED_STARTED) {
        stopInstance(impactedInstance, plugin, true);

      } else {
        this.logger.fine(
            InstanceHelpers.computeInstancePath(impactedInstance)
                + " checked import changes but has nothing to update (2).");
      }
    }
  }