Source: managers/ActionManager.js

import { Utils } from '../common/Utils';

const __ = {
	private: Symbol('private'),
}

// #region Private Functions

// #endregion

/**
 * @class ActionManager
 * The action manager.
 * @memberof THING
 * @public
 */
class ActionManager {

	constructor() {
		this[__.private] = {};
		let _private = this[__.private];

		_private.objects = [];

		_private.actions = new Map();

		_private.app = Utils.getCurrentApp();
	}

	processAction(action, candidateMode, target, options, delay) {
		let _private = this[__.private];
		delay = Utils.parseValue(delay, 0);
		return new Promise((resolve, reject) => {
			if (delay) {
				Utils.setTimeout(_doAction, delay);
			}
			else {
				_doAction();
			}

			function _doAction() {
				let promises = [];
				// Use target to filter objects
				if (target) {
					let objects = candidateMode ? _private.objects : _private.app.objectManager.objects;
					for (let i = 0, l = objects.length; i < l; i++) {
						let object = objects[i];

						if (object.test(target)) {
							let promise = action.onRun({
								object,
								options
							});
							promises.push(promise);
						}
					}
				}
				// Use app root without any target
				else {
					let promise = action.onRun({
						// object: _private.app.root,
						options
					});
					promises.push(promise);
				}
				Promise.all(promises).then(resolve, reject);
			}
		});
	}

	dispose() {
		let _private = this[__.private];

		_private.app = null;

		_private.objects.length = 0;

		_private.actions.clear();
	}

	async _execute(messages) {
		let _private = this[__.private];

		for (let msg of messages) {
			let { type, candidateMode, target, params, delay, waitForComplete } = msg;

			let action = _private.actions.get(type);
			if (!action) {
				continue;
			}

			// process action
			if (waitForComplete) {
				await this.processAction(action, candidateMode, target, params, delay);
			}
			else {
				this.processAction(action, candidateMode, target, params, delay);
			}
		}
	}

	/**
	 * Register action.
	 * @param {String} name The action unique name.
	 * @param {THING.BaseAction} action The action.
	 * @public
	 */
	register(name, action) {
		let _private = this[__.private];

		_private.actions.set(name, action);
	}

	/**
	 * Register action.
	 * @param {String} name The action unique name.
	 * @param {THING.BaseAction} action The action.
	 * @deprecated 2.7
	 * @private
	 */
	registerAction(name, action) {
		this.register(name, action);
	}

	/**
	 * Unregister action.
	 * @param {String} name The action unique name.
	 * @public
	 */
	unregister(name) {
		let _private = this[__.private];

		_private.actions.delete(name);
	}

	/**
	 * Unregister action.
	 * @param {String} name The action unique name.
	 * @deprecated 2.7
	 * @private
	 */
	unregisterAction(name) {
		this.unregister(name);
	}

	addListener(object) {
		if (!object) {
			return;
		}

		let _private = this[__.private];

		if (object.isSelector) {
			object.forEach(obj => {
				_private.objects.push(obj);
			});
		}
		else {
			_private.objects.push(object);
		}
	}

	removeListener(object) {
		if (!object) {
			return;
		}

		let _private = this[__.private];

		if (object.isSelector) {
			object.forEach(obj => {
				let index = _private.objects.indexOf(obj);
				if (index !== -1) {
					_private.objects.splice(index, 1);
				}
			});
		}
		else {
			let index = _private.objects.indexOf(object);
			if (index !== -1) {
				_private.objects.splice(index, 1);
			}
		}
	}

	/**
	 * The action message data.
	 * @typedef {Object} ActionMessageData
	 * @property {String} type The action type.
	 * @property {String} target The target to filer object what to process this action, if not provide then indicates use app's root as target object.
	 * @property {Object} params The parameters what pass to action.
	 * @property {Number} delay The delay to start performing the current action.
	 * @property {Boolean} waitForComplete Wait for the current action to complete before executing the next action, The default is false.
	 */

	/**
	 * Run actions.
	 * @param {Array<ActionMessageData>} messages The messages data.
	 * @example
	 * 	app.actionManager.run([
	 * 		{
	 * 			type: 'CameraFlyTo',
	 * 			params: {
	 * 				position: [10, 10, 5],
	 * 				target: [0, 0, 0],
	 * 				time: 1000
	 * 			},
	 * 			delay: 1000,
	 * 			waitForComplete: true
	 * 		},
	 * 		{
	 * 			type: 'CreateObject',
	 * 			params: {
	 * 				type: 'Box',
	 * 				name: 'box01'
	 * 			},
	 * 			delay: 1000,
	 * 			waitForComplete: true
	 * 		},
	 * 		{
	 * 			type: 'ObjectSetColor',
	 * 			target: 'box01',
	 * 			params: {
	 * 				color: 'red'
	 * 			},
	 * 			delay: 1000,
	 * 			waitForComplete: true
	 * 		}
	 * 	]);
	 * @public
	 */
	run(messages) {
		if (!messages) {
			return;
		}

		if (Utils.isArray(messages)) {
			this._execute(messages.slice(0));
		}
		else {
			this._execute([messages]);
		}
	}

	/**
	 * Run actions.
	 * @param {Array<ActionMessageData>} messages The messages data.
	 * @deprecated 2.7
	 * @private
	 */
	push(messages) {
		this.run(messages);
	}

	getActionByName(name) {
		let _private = this[__.private];

		return _private.actions.get(name);
	}

}

export { ActionManager }