Source: mvc/StateMachine.js

const collect = require('collect.js');
const moment = require('moment');

/**
 * The class implements a classic statemachine which allow state to change
 * when the {@link Transition|transtion guard function} is true.
 * Upon the change the {@link Transition|transition action function} is called
 */
class StateMachine {
	/**
	 * The construtcor
	 * @param {Object} handler
	 * 	The object to control.
	 */
	constructor(handler) {
		if (handler == null) {
			throw Error('Illegal arguments');
		}
		this._transitions = collect({});
		this._state = 'start';
		this._handler = handler;
	}

	/**
	 * Get current state
	 * @returns {string}
	 * 	The current state
	 */
	get state() {
		return this._state;
	}

	/**
	 * Add a single transition
	 * @param {Transition} transition
	 * 	The transition
	 */
	add(transition) {
		let transitions = this._transitions.get(transition.from);
		if (transitions == null) {
			transitions = collect([]);
			this._transitions.put(transition.from, transitions)
		}
		transitions.push(transition);
	}

	/**
	 * Run the statemachine
	 * @param {Event} event
	 * 	The event.
	 */
	run(event) {
		//console.log(`${this._handler.constructor.name} ${event.toString()}`);

		let transitions = this._transitions.get(this._state);
		if (transitions == null) {
			throw Error(`No transitions found from state: ${this._state}`);
		}
		transitions.each(transtion => {
			if (transtion.canChange === undefined || transtion.canChange.call(this._handler, event)) {
				let ts = moment().format('hh:mm:ss:SSS');
				try {
					this._state = transtion.to;
					if (transtion.action != null) {
						console.log(`${ts} ${this._handler.constructor.name} State from: ${transtion.from} to: ${this._state} ${event.toString()}`);
						transtion.action.call(this._handler, event);
						return false;
					} else {
						console.warn(`${ts} ${this._handler.constructor.name} State from: ${transtion.from} to: ${this._state} ${event.toString()} has an empty action`)
					}
				} catch (e) {
					this._state = transtion.from;
					throw e;
				}
			}
		});
	}
}
exports.StateMachine = StateMachine;