rules/condition-builder.js

const operations = require('./operation-builder');
const items = require('../items/items');

/**
 * Condition that wraps a function to determine whether if passes
 *
 * @hideconstructor
 */
class ConditionBuilder {
  constructor (builder, fn) {
    /** @private */
    this._builder = builder;
    /** @private */
    this._fn = fn;
  }

  /** @private */
  _then (condition, fn) {
    this._builder.setCondition(condition);
    return new operations.OperationBuilder(this._builder, fn);
  }

  /**
   * Move to the rule operations
   *
   * @param {*} [fn] the optional function to execute
   * @returns {operations.OperationBuilder}
   */
  then (fn) {
    if (this._fn) {
      this._builder.setCondition(new FunctionConditionConf(this._fn));
    } else {
      throw new Error("'then' can only be called when 'if' is passed a function");
    }
    return new operations.OperationBuilder(this._builder, fn);
  }

  /**
    * Condition of an item in determining whether to process rule.
    *
    * @param {string} itemName the name of the item to assess the state
    * @returns {ItemStateConditionConf} the operation config
    */
  stateOfItem (itemName) {
    this.condition = new ItemStateConditionConf(itemName, this);
    return this.condition;
  }
}

/**
 * {@link RuleBuilder} RuleBuilder conditions
 */
class ConditionConf {
  constructor (conditionBuilder) {
    /** @private */
    this.conditionBuilder = conditionBuilder;
  }

  /**
    * @param {*} [fn] an optional function
    * @returns {operations.OperationBuilder}
    */
  then (fn) {
    return this.conditionBuilder._then(this, fn);
  }
}

/**
 * Condition that wraps a function to determine whether if passes
 *
 * @extends ConditionConf
 * @hideconstructor
 */
class FunctionConditionConf extends ConditionConf {
  /**
     * Creates a new function condition. Don't call directly.
     *
     * @param {*} fn callback which determines whether the condition passes
     */
  constructor (fn, conditionBuilder) {
    super(conditionBuilder);
    /** @private */
    this.fn = fn;
  }

  /**
     * Checks whether the rule operations should be run
     *
     * @private
     * @param  {...any} args rule trigger arguments
     * @returns {boolean} true only if the operations should be run
     */
  check (...args) {
    return this.fn(args);
  }
}

/**
 * Condition that wraps a function to determine whether if passes
 *
 * @extends ConditionConf
 * @hideconstructor
 */
class ItemStateConditionConf extends ConditionConf {
  constructor (itemName, conditionBuilder) {
    super(conditionBuilder);
    /** @private */
    this.item_name = itemName;
  }

  /**
    * Checks if Item state is equal to given value.
   *
    * @param {string} value
    * @return {ItemStateConditionConf}
    */
  is (value) {
    this.values = [value];
    return this;
  }

  /**
   * Checks if the Item state is ON.
   *
   * @return {ItemStateConditionConf}
   */
  isOn () {
    this.is('ON');
    return this;
  }

  /**
   * Checks if the Item state is OFF.
   *
   * @return {ItemStateConditionConf}
   */
  isOff () {
    this.is('OFF');
    return this;
  }

  /**
     * Checks if item state matches any array of values
     * @param  {...any} values
     * @return {ItemStateConditionConf}
     */
  in (...values) {
    this.values = values;
    return this;
  }

  /** @private */
  check (...args) {
    const item = items.getItem(this.item_name);
    if (typeof item === 'undefined' || item === null) {
      throw Error(`Cannot find item: ${this.item_name}`);
    }
    return this.values.includes(item.state);
  }
}

module.exports = {
  FunctionConditionConf,
  ItemStateConditionConf,
  ConditionBuilder
};