Source: engine/universal/capabilities/module.js

const _ = require('lodash');

const NativeCapability = require('@proceed/system').capability;
const capabilitiesRoute = require('./routes/capabilitesRoute');

const parameterAndOutputParser = require('./parser/parameterAndOutputParser');
const createExpandedList = require('./parser/listParser/listParser');
const processDescriptionParser = require('./parser/listParser/processDescriptionParser');
const itemExists = require('./parser/listParser/itemExists');
const potentialAction = require('./parser/potentialActionParser');
const { ConfigurationError } = require('./error/configurationError');
const { NotFoundError } = require('./error/notFoundError');

/**
 * @module @proceed/capabilities
 */
const capabilities = {
  /**
   * For testing purposes init() function is used, so that the capabilities module
   * can be tested with several capability lists.
   *
   * @param {*} capabilityList
   * @memberof module:@proceed/capabilities
   * @private
   */
  init(capabilityList) {
    this.capabilityList = capabilityList;
    // this.capability = new NativeCapability();
  },

  /**
   * An async function that starts the capability module, gets the capability list
   * of the engine, checks for double occurences and calls the endpoint in order to
   * see all the capabilities of the engine
   *
   * @memberof module:@proceed/capabilities
   */
  async start() {
    this.capabilityList = await NativeCapability.getAllCapabilities();
    await this.checkDoubleOccurences();
    // eslint-disable-next-line max-len
    // new NativeCapability().registerForNewOrRemovedCapabilities(await this.capabilityListUpdate());
    capabilitiesRoute(this);
  },

  /**
   * A registered callback for registerForNewOrRemovedCapabilities which check if an item is
   * included or removed to the capability list
   * of the engine, if an item is tried to be added during runtime, a message is raised.
   *
   * @memberof module:@proceed/capabilities
   */

  async capabilityListUpdate(err, capabilityItem, status) {
    if (err) return;
    if (
      (await Promise.resolve(itemExists(capabilityItem, this.capabilityList))) &&
      status === 'new'
    ) {
      // eslint-disable-next-line no-console
      console.log('You cannnot add a new capability during runtime');
    }
    if (status === 'new') {
      this.capabilityList.push(capabilityItem);
    } else {
      this.capabilityList.splice(this.capabilityList.indexOf(capabilityItem), 1);
    }
  },

  /**
    A Function that checks for multiple occurences of the same capability item
    @param {list} capabilityList
    throw a configuration error and stops the engine in case there are multiple occurences

    @memberof module:@proceed/capabilities
  */

  async checkDoubleOccurences() {
    const expandedList = await Promise.all(createExpandedList(this.capabilityList));
    const flattenedList = _.flatten(expandedList.map((capabilityItem) => capabilityItem.expanded));
    const potentialActions = potentialAction.getPotentialActions(flattenedList);
    const uniqActions = _.uniq(potentialActions);
    if (potentialActions.length !== uniqActions.length) {
      throw new ConfigurationError(
        'The engine needs to stop! One machine can have only one capability for the same concept'
      );
    }
  },

  /**
    A Function that receives an capabilityName, arguments for the native function and a callback
    @param {string} capabilityName the URI of the capability name or only the action name
    @param {object} args the arguments needed for native function, parameter names
    are given as described in the semantic description
    @returns {object} the return item of the native function in the parsed form as described
    in the semantic description if there is there a callback from the native function then
    the function returns null

    @memberof module:@proceed/capabilities
  */

  async startCapability(capabilityName, args, callback) {
    const capObject = await potentialAction.findIdAndDesc(capabilityName, this.capabilityList);
    if (_.isEmpty(capObject)) {
      throw new NotFoundError(`${capabilityName} is not found. Please check your script task!`);
    }
    const mappedArgs = parameterAndOutputParser.parseArguments(args, capObject.expanded);
    const { identifier } = capObject;
    const output = await NativeCapability.executeNativeCapability(identifier, mappedArgs, callback);
    if (callback) {
      return null;
    }
    const { expanded } = capObject;
    return parameterAndOutputParser.parseOutput(expanded, output);
  },

  /**
      A Function that receives a processDescription and a capability desciption of an engine
      and checks whether the process description can be executed in the engine with the given
      capability description
      @param {list} processDescription
      @param {list} capabilityDescription
      @return boolean

      @memberof module:@proceed/capabilities
    */

  async isCapabilityExecutable(processDescription, capabilityDescription) {
    const expandedProcessDesc = await Promise.resolve(processDescriptionParser(processDescription));
    const processAction = potentialAction.getPotentialActions(expandedProcessDesc).find(() => true);
    const capObject = await potentialAction.findIdAndDesc(processAction, capabilityDescription);
    if (_.isEmpty(capObject)) {
      return false;
    }
    const allProcessParams = parameterAndOutputParser.getParams(expandedProcessDesc, false);
    const allCapObjectParams = parameterAndOutputParser.getParams(capObject.expanded, false);
    const reqProcessParams = parameterAndOutputParser.getParams(expandedProcessDesc, true);
    const reqCapObjectParams = parameterAndOutputParser.getParams(capObject.expanded, true);
    const processParamsCheck = _.difference(reqProcessParams, allCapObjectParams).length === 0;
    const capObjectParamsCheck = _.difference(reqCapObjectParams, allProcessParams).length === 0;
    if (processParamsCheck && capObjectParamsCheck) {
      if (parameterAndOutputParser.checkOutput(expandedProcessDesc, capObject.expanded)) {
        return true;
      }
    }
    return false;
  },

  /**
    A Function that receives a processDescription and checks whether the process
    is executable locally
    @param {list} processDescription
    @return boolean

    @memberof module:@proceed/capabilities
  */
  async isCapabilityLocallyExecutable(processDescription) {
    const executable = await Promise.resolve(
      this.isCapabilityExecutable(processDescription, this.capabilityList)
    );
    return executable;
  },
};

/*
 * not marked as module, because only the methods of the class are exported
 */
module.exports = capabilities;