const items = require('../items/items');
const rules = require('./rules');
const triggers = require('./trigger-builder');
const conditions = require('./condition-builder');
/**
* Creates rules in a fluent style.
* @param {boolean} toggleable if this builder is toggleable
*/
class RuleBuilder {
constructor (toggleable) {
/** @private */
this._triggerConfs = [];
/** @private */
this.toggleable = toggleable || false;
}
/**
* Specifies when the rule should occur. Will create a standard rule.
*
* @returns {triggers.TriggerBuilder} rule builder
*/
when () {
return new triggers.TriggerBuilder(this);
}
/** @private */
addTrigger (triggerConf) {
if (!triggerConf._complete()) {
throw Error('Trigger is not complete!');
}
this._triggerConfs.push(triggerConf);
return this;
}
/** @private */
setCondition (condition) {
if (typeof condition === 'function') {
condition = new conditions.FunctionConditionConf(condition);
}
/** @private */
this.condition = condition;
return this;
}
/** @private */
setOperation (operation, optionalRuleGroup) {
if (typeof operation === 'function') {
const operationFunction = operation;
operation = {
_complete: () => true,
_run: x => operationFunction(x),
describe: () => 'custom function'
};
} else {
// first check complete
if (!operation._complete()) {
throw Error('Operation is not complete!');
}
}
/** @private */
this.operation = operation;
/** @private */
this.optionalRuleGroup = optionalRuleGroup;
const generatedTriggers = this._triggerConfs.flatMap(x => x._toOHTriggers());
const ruleClass = this.toggleable ? rules.SwitchableJSRule : rules.JSRule;
let fnToExecute = operation._run.bind(operation); // bind the function to it's instance
// chain (of responsibility for) the execute hooks
for (const triggerConf of this._triggerConfs) {
const next = fnToExecute;
if (typeof triggerConf._executeHook === 'function') {
const maybeHook = triggerConf._executeHook();
if (maybeHook) {
const hook = maybeHook.bind(triggerConf); // bind the function to it's instance
fnToExecute = function (args) {
return hook(next, args);
};
}
}
}
if (typeof this.condition !== 'undefined') { // if conditional, check it first
const fnWithoutCheck = fnToExecute;
fnToExecute = (x) => this.condition.check(x) && fnWithoutCheck(x);
}
return ruleClass({
name: this.name || items.safeItemName(this.describe(true)),
description: this.description || this.describe(true),
triggers: generatedTriggers,
ruleGroup: optionalRuleGroup,
execute: function (data) {
fnToExecute(data);
},
tags: this.tags || [],
id: this.id
});
}
describe (compact) {
return (compact ? '' : 'When ') +
this._triggerConfs.map(t => t.describe(compact)).join(' or ') +
(compact ? '→' : ' then ') +
this.operation.describe(compact) +
((!compact && this.optionalRuleGroup) ? ` (in group ${this.optionalRuleGroup})` : '');
}
}
module.exports = {
RuleBuilder,
/**
* Create a new {@link RuleBuilder} chain for easily creating rules.
*
* @example <caption>Basic rule</caption>
* rules.when().item("F1_Light").changed().then().send("changed").toItem("F2_Light").build("My Rule", "My First Rule");
*
* @example <caption>Rule with function</caption>
* rules.when().item("F1_light").changed().to("100").then(event => {
* console.log(event)
* }).build("Test Rule", "My Test Rule");
*
* @memberof rules
* @param {boolean} [withToggle=false] rule can be toggled on or off (optional)
* @returns {triggers.TriggerBuilder} rule builder
*/
when: (withToggle) => new RuleBuilder(withToggle).when()
};