Source: jsfsa.js

var jsfsa;
/**
 * @author Camille Reynders
 * @version @@version
 */
(function (pkg) {
    "use strict";

    //--( Dispatcher )--//

    var Dispatcher = function () {
        this._listeners = {};
    };

    /**
     * Registers a <code>handler</code> function to be called when an event with <code>eventName</code> is dispatched.
     * The <code>handler</code> is called within the scope of the dispatcher.
     * @param {String} eventName
     * @param {Function} handler
     * @return {Object} the dispatcher
     */
    Dispatcher.prototype.addListener = function (eventName, handler) {
        if (!this._listeners.hasOwnProperty(eventName)) {
            this._listeners[eventName] = [];
        }
        this._listeners[eventName].push(handler);

        return this;
    };

    /**
     * Registers several <code>handlers</code> for an event with name <code>eventName</code>
     * @param {String} eventName
     * @param {Function} handlers
     * @return {Object} the dispatcher
     */
    Dispatcher.prototype.addListeners = function (eventName, handlers) {
        if (typeof handlers === "function") {
            return this.addListener(eventName, handlers);
        }

        var arr;
        if (!this._listeners.hasOwnProperty(eventName)) {
            arr = [];
        } else {
            arr = this._listeners[eventName];
        }

        this._listeners[eventName] = arr.concat(handlers);

        return this;
    };

    /**
     * Checks whether the dispatcher has a specific <code>handler</code> registered for <code>eventName</code>
     * @param {String} eventName
     * @param {Function} handler
     * @return {Boolean}
     */
    Dispatcher.prototype.hasListener = function (eventName, handler) {
        if (this._listeners.hasOwnProperty(eventName)) {
            var index = this._listeners[eventName].indexOf(handler);
            return index >= 0;
        }
        return false;
    };
    /**
     * Unregisters a <code>handler</code> for <code>eventName</code>
     * @param {String} eventName
     * @param {Function} handler
     * @return {Object} the dispatcher
     */
    Dispatcher.prototype.removeListener = function (eventName, handler) {
        if (this._listeners.hasOwnProperty(eventName)) {
            var index = this._listeners[eventName].indexOf(handler);
            if (index >= 0) {
                this._listeners[eventName].splice(index, 1);
            }
        }
        return this;
    };

    /**
     * Dispatches an event.
     * @param {Object} e
     * @param {String} e.type
     * @return {Boolean} the result of the registered handlers
     */
    Dispatcher.prototype.dispatch = function (e) {
        var eventName = e.type;
        var result = true;
        if (this._listeners.hasOwnProperty(eventName)) {
            var args = Array.prototype.slice.call(arguments);
            for (var i = 0, n = this._listeners[eventName].length; i < n; i++) {
                var handler = this._listeners[eventName][i];
                result = handler.apply(this, args) && result;
            }
        }
        return result;
    };

    //--( DispatcherProxy )--//

    var DispatcherProxy = function () {};
    DispatcherProxy.prototype = Dispatcher.prototype;

    //--( StateEvent )--//

    /**
     * @class
     * @constructor
     * @param {String} type
     * @param {String} from name of the exiting/exited state
     * @param {String} to name of the entering/entered state
     * @param {transition} transition name of the transition
     */
    var StateEvent = function (type, from, to, transition) {

        /**
         * @type String
         */
        this.fqn = 'jsfsa.StateEvent';

        /**
         * The name of the dispatched event.
         * @see jsfsa.StateEvent.ENTERED
         * @see jsfsa.StateEvent.EXITED
         * @see jsfsa.StateEvent.ENTRY_DENIED
         * @see jsfsa.StateEvent.EXIT_DENIED
         * @see jsfsa.StateEvent.TRANSITION_DENIED
         * @see jsfsa.StateEvent.CHANGED
         * @type String
         */
        this.type = type;

        /**
         * The name of the state we're (trying to) transition from.
         * @type String
         */
        this.from = from;

        /**
         * The name of the state we're (trying to) transition to.
         * @type String
         */
        this.to = to;

        /**
         * The name of the transition.
         * @type String
         */
        this.transition = transition;
    };

    /**
     * Dispatched when a state has been entered
     * @static
     * @const
     * @default 'entered'
     */
    StateEvent.ENTERED = 'entered';

    /**
     * Dispatched when a state has been exited
     * @static
     * @const
     * @default 'exit'
     */
    StateEvent.EXITED = 'exited';

    /**
     * Dispatched when entry to a state has been denied by a guard
     * @static
     * @const
     * @default 'entryDenied'
     */
    StateEvent.ENTRY_DENIED = 'entryDenied';

    /**
     * Dispatched when exit from a state has been denied by a guard.
     * @static
     * @const
     * @default 'exitDenied'
     */
    StateEvent.EXIT_DENIED = 'exitDenied';

    /**
     * Dispatched when the current state has no transition with the specified name
     * or if the <code>to</code> state was not found.</br>
     * This event is only dispatched by {@link jsfsa.Automaton}
     * @static
     * @const
     * @default 'transitionDenied'
     */
    StateEvent.TRANSITION_DENIED = 'transitionDenied';

    /**
     * Dispatched after transitioning has completely finished.
     * @static
     * @const
     * @default 'changed'
     */
    StateEvent.CHANGED = 'changed';

    /**
     * @return {jsfsa.StateEvent}
     */
    StateEvent.prototype.clone= function () {
        var result = new StateEvent(this.type, this.from, this.to, this.transition);
        return result;
    };

    /**
     * @private
     * @param type
     */
    StateEvent.prototype._setType= function (type) {
        this.type = type;
        return this;
    };

    //--( Action )--//

    /**
     * @class
     * @constructor
     */
    var Action = function () {};

    /**
     * @static
     * @const
     * @default 'entry'
     */
    Action.ENTRY = 'entry';

    /**
     * @static
     * @const
     * @default 'exit'
     */
    Action.EXIT = 'exit';

    //--( State )--//

    /**
     * @class
     * @constructor
     * @borrows Dispatcher#addListener as this.addListener
     * @borrows Dispatcher#addListeners as this.addListeners
     * @borrows Dispatcher#removeListener as this.removeListener
     * @borrows Dispatcher#hasListener as this.hasListener
     * @borrows Dispatcher#dispatch as this.dispatch
     * @param {String} name
     * @param {Object} [data] object with configuration data, see {@link jsfsa.State#parseData} for syntax
     */
    var State = function (name, data) {
        Dispatcher.call(this);

        /**
         * @type String
         */
        this.fqn = 'jsfsa.State';

        /**
         * The name of the state
         * @type String
         */
        this.name = '';

        /**
         * Name of the parent state
         * @type String
         */
        this.parent = undefined;

        /**
         * Whether this state will be used as the initial state of the {@link jsfsa.State#parent}
         * @type Boolean
         */
        this.isInitial = false;

        /**
         * @private
         * @type Dispatcher
         */
        this._guardian = undefined;

        /**
         * @private
         * @type {Object}
         */
        this._transitions = {};

        this._parseName(name);
        this.parseData(data);
    };

    State.prototype = new DispatcherProxy();
    State.prototype.constructor = State;

    /**
     * @private
     * @type {Array}
     */
    State._configMembers = ['isInitial', 'guards', 'listeners', 'parent', 'transitions'];

    /**
     * Tells this state to allow a transition with name <code>transitionName</code> from this state to <code>stateName</code></br>
     * Overwrites transitions with the same name already registered with this state.
     * @see jsfsa.State#removeTransition
     * @see jsfsa.State#hasTransition
     * @see jsfsa.State#getTransition
     * @param {String} transitionName
     * @param {String|Function} stateName The name of the state to transition to -or- a function which returns that name
     * @return {jsfsa.State} the instance of {@link jsfsa.State} that is acted upon
     */
    State.prototype.addTransition = function (transitionName, stateName) {
        this._transitions[transitionName] = stateName;
        return this;
    };

    /**
     * Removes a registered transition with name <code>transitionName</code>. Fails silently if such a transition was not found.
     * @see jsfsa.State#addTransition
     * @see jsfsa.State#hasTransition
     * @see jsfsa.State#getTransition
     * @param {String} transitionName
     * @return {jsfsa.State} the instance of {@link jsfsa.State} that is acted upon
     */
    State.prototype.removeTransition = function (transitionName) {
        delete this._transitions[transitionName];
        return this;
    };

    /**
     * Retrieves the name of the state that will be transitioned with <code>transitionName</code>
     * @see jsfsa.State#addTransition
     * @see jsfsa.State#hasTransition
     * @see jsfsa.State#removeTransition
     * @param {String} transitionName
     * @return {String} target state name
     */
    State.prototype.getTransition = function (transitionName) {
        return this._transitions[transitionName];
    };

    /**
     * Checks whether a transition with <code>transitionName</code> has been registered for this state.
     * @see jsfsa.State#addTransition
     * @see jsfsa.State#getTransition
     * @see jsfsa.State#removeTransition
     * @param {String} transitionName
     * @return {Boolean}
     */
    State.prototype.hasTransition = function (transitionName) {
        return this._transitions.hasOwnProperty(transitionName);
    };

    /**
     * Sets up <code>guard</code> to be called if entry/exit of a state has been requested.</br>
     * The <code>guard</code> should return <code>true</code> to allow the <code>action</code> to happen.
     * Anything <code>falsy</code> will deny transition to/from this state.</br>
     * The <code>guard</code> receives a {@link jsfsa.StateEvent} object as a first parameter.
     * @see jsfsa.State#addGuards
     * @see jsfsa.State#hasGuard
     * @see jsfsa.State#removeGuard
     * @param {String} action One of the static property values of {@link jsfsa.Action}
     * @param {Function} guard
     * @return {jsfsa.State} the instance of {@link jsfsa.State} that is acted upon
     */
    State.prototype.addGuard = function (action, guard) {
        this._getGuardian()
            .addListener(action, guard);
        return this;
    };

    /**
     * Registers multiple <code>guards</code> to be called on <code>action</code>
     * @see jsfsa.State#addGuard
     * @see jsfsa.State#hasGuard
     * @see jsfsa.State#removeGuard
     * @param {String} action One of the static property values of {@link jsfsa.Action}
     * @param {Function[]} guards
     * @return {jsfsa.State} the instance of {@link jsfsa.State} that is acted upon
     */
    State.prototype.addGuards = function (action, guards) {
        this._getGuardian()
            .addListeners(action, guards);
        return this;
    };

    /**
     * Checks if a <code>guard</code> has been registered to control transitioning to/from this state.
     * @see jsfsa.State#addGuard
     * @see jsfsa.State#addGuards
     * @see jsfsa.State#removeGuard
     * @param {String} action One of the static property values of {@link jsfsa.Action}
     * @param {Function} guard
     * @return {Boolean}
     */
    State.prototype.hasGuard = function (action, guard) {
        return this._guardian && this._guardian.hasListener(action, guard);
    };

    /**
     * Unregisters a <code>guard</code> for <code>action</code>
     * @see jsfsa.State#addGuard
     * @see jsfsa.State#addGuards
     * @see jsfsa.State#hasGuard
     * @param {String} action One of the static property values of {@link jsfsa.Action}
     * @param {Function} guard
     * @return {jsfsa.State} the instance of {@link jsfsa.State} that is acted upon
     */
    State.prototype.removeGuard = function (action, guard) {
        if (this._guardian) {
            this._guardian.removeListener(action, guard);
        }
        return this;
    };

    /**
     * Parses the <code>data</code> to configure the state.
     * @example //Strict syntax
    var guard = function( e ){
        return false;
    };
    var listener = function( e ){
        console.log( e );
    }
    var config = {
        isInitial : true,
        transitions : {
            'ignite' : 'on', //transitionName : targetStateName
            'fail' : 'broken'
        },
        guards : {
            entry : [ guard ],
            exit : [ guard ]
        },
        listeners : {
            entryDenied : [ listener ],
            entered : [ listener ],
            exitDenied : [ listener ],
            exited : [ listener ]
        }
    }
     * @example //Loose syntax
    var guard = function( e ){
        return false;
    };
    var listener = function( e ){
        console.log( e );
    }
    var config = {
        isInitial : true,
        'ignite' : 'on', //transitionName : targetStateName
        'fail' : 'broken'
        guards : {
            entry : guard,
            exit : guard
        },
        listeners : {
            entryDenied : listener,
            entered : listener,
            exitDenied : listener,
            exited : listener
        }
    }
     * @param {Object} data
     */
    State.prototype.parseData = function (data) {
        if (data) {
            if (data.isInitial) {
                this.isInitial = true;
            }
            if (data.guards) {
                if (data.guards[Action.ENTRY]) {
                    this.addGuards(Action.ENTRY, data.guards[Action.ENTRY]);
                }
                if (data.guards[Action.EXIT]) {
                    this.addGuards(Action.EXIT, data.guards[Action.EXIT]);
                }
            }
            for (var eventName in data.listeners) {
                if (data.listeners.hasOwnProperty(eventName)) {
                    this.addListeners(eventName, data.listeners[eventName]);
                }
            }
            if (data.parent) {
                this.parent = data.parent;
            }
            if (data.transitions) {
                this._addTransitions(data.transitions);
            }

            this._addTransitions(data, State._configMembers);
        }
    };

    /**
     * Releases all resources. After calling this method, the State instance can/should no longer be used.
     */
    State.prototype.destroy = function () {
        this._guardian = undefined;
        this._transitions = undefined;
        this._listeners = undefined;
        this.name = undefined;
    };

    /**
     *
     * @return {Dispatcher}
     * @private
     */
    State.prototype._getGuardian = function () {
        if (this._guardian === undefined) {
            this._guardian = new Dispatcher();
        }
        return this._guardian;
    };

    State.prototype._parseName = function (name) {
        var index = name.lastIndexOf('/');
        if (index >= 0) {
            this.parent = name.substring(0, index);
        }
        this.name = name;
    };

    State.prototype._executeGuards = function (args) {
        var result = true;
        if (this._guardian) {
            result = this._guardian.dispatch.apply(this._guardian, args);
            if (!result) {
                args = args.slice(0);
                var e = args[0].clone();
                e.type += 'Denied';
                args[0] = e;
                this.dispatch.apply(this, args);
            }
        }

        return result;
    };

    /**
     * @private
     * @param {Object} data
     * @param {String[]} [skip]
     */
    State.prototype._addTransitions = function (data, skip) {
        for (var transitionName in data) {
            if (data.hasOwnProperty(transitionName)) {
                if (skip && skip.indexOf(transitionName) >= 0) {
                    continue;
                }
                this.addTransition(transitionName, data[transitionName]);
            }
        }
    };

    /**
     * @private
     * @param {Array} args
     */
    State.prototype._dispatchArgs = function (args) {
        this.dispatch.apply(this, args);
    };

    //--( Node )--//

    /**
     * @ignore
     * @class
     * @constructor
     * @param state
     */
    var Node = function (state) {
        this.state = state;
        this.parent = undefined;
        this.children = undefined;
        this.initialChild = undefined;
    };

    Node.prototype = {

        /**
         * @ignore
         * @param node
         */
        addChild: function (node) {
            if (!this.children) {
                this.children = {};
            }
            node.parent = this;
            if (node.state.isInitial) {
                this.initialChild = node;
            }
            this.children[node.state.name] = node;
        },
        /**
         * @ignore
         * @param node
         */
        removeChild: function (node) {
            if (this.initialChild === node) {
                this.initialChild = undefined;
            }
            delete this.children[node.state.name];
        },
        /**
         * @ignore
         */
        destroy: function () {
            for (var stateName in this.children) {
                if (this.children.hasOwnProperty(stateName)) {
                    this.children[stateName].destroy();
                }
            }
            this.state = undefined;
            this.parent = undefined;
            this.children = undefined;
        },
        /**
         * @ignore
         */
        getInitialBranch: function () {
            var result = [];
            var initial = this.initialChild;
            while (initial) {
                result.push(initial);
                initial = initial.initialChild;
            }

            return result;
        }
    };

    //--( StateEventFactory )--//

    /**
     * @ignore
     * @class
     * @constructor
     * @param {String} transition
     * @param {String} from
     * @param {String} to
     */
    var StateEventFactory = function (payload, transition, from, to) {
        this.payload = payload;
        this.from = from;
        this.to = to;
        this.transition = transition;
    };

    StateEventFactory.prototype = {
        /**
         * @ignore
         * @param type
         * @return {jsfsa.StateEvent}
         */
        createEvent: function (type) {
            return new StateEvent(type, this.from, this.to, this.transition);
        },

        createArgsArray: function (type) {
            var result = this.payload.slice(0);
            result.unshift(this.createEvent(type));
            return result;
        }
    };

    //--( Automaton )--//

    /**
     * @class
     * @constructor
     * @borrows Dispatcher#addListener as this.addListener
     * @borrows Dispatcher#addListeners as this.addListeners
     * @borrows Dispatcher#removeListener as this.removeListener
     * @borrows Dispatcher#hasListener as this.hasListener
     * @borrows Dispatcher#dispatch as this.dispatch
     */
    var Automaton = function (data, name) {
        Dispatcher.call(this);
        /**
         * @type String
         */
        this.fqn = 'jsfsa.Automaton';

        this.name = name || '';

        this._nodes = {};
        this._rootNode = new Node(new State('root'));
        this._currentBranch = [this._rootNode];
        this._internalState = 'ready';
        this._queue = [];
        this._newBranch = undefined;
        if (data) {
            this.parse(data);
        }
    };

    Automaton.prototype = new DispatcherProxy();
    Automaton.prototype.constructor = Automaton;

    /**
     * Returns whether the Automaton is currently transitioning from one state to the other
     * @return {Boolean}
     */
    Automaton.prototype.isTransitioning = function () {
        return this._internalState !== 'ready';
    };

    /**
     * @private
     * @return {jsfsa.State}
     */
    Automaton.prototype.getRootState = function () {
        return this._rootNode.state;
    };

    /**
     * Returns the lowest state that is currently active
     * @return {jsfsa.State}
     */
    Automaton.prototype.getCurrentState = function () {
        return this._currentBranch[this._currentBranch.length - 1].state;
    };

    /**
     * Returns a top-down list of the currently active hierarchical states
     * @return {jsfsa.State[]}
     */
    Automaton.prototype.getCurrentBranch = function () {
        var branch = this._currentBranch;
        var output = [];
        //we're skipping the root node!
        for (var i = 1; i < branch.length; i++) {
            var node = branch[i];
            output.push(node.state);
        }
        return output;
    };

    /**
     * Checks whether a state with name <code>statename</code> is currently active
     * @param stateName
     * @return {Boolean}
     */
    Automaton.prototype.isInCurrentBranch = function (stateName) {
        var node = this._nodes[stateName];
        if (node) {
            return this._currentBranch.indexOf(node) > -1;
        }
        return false;
    };

    /**
     * Creates a state and adds it to the Automaton
     * @param {String} stateName
     * @param {Object} stateData
     * @return {jsfsa.Automaton} the instance of {@link jsfsa.Automaton} that is acted upon
     */
    Automaton.prototype.createState = function (stateName, stateData) {
        var state = new State(stateName, stateData);
        this.addState(state);

        return this;
    };

    /**
     * Adds a state to the Automaton
     * @param {jsfsa.State} state
     * @return {jsfsa.Automaton} the instance of {@link jsfsa.Automaton} that is acted upon
     */
    Automaton.prototype.addState = function (state) {
        if (!this.hasState(state.name)) {
            var node = new Node(state);
            var parentNode = (state.parent) ? this._nodes[state.parent] : this._rootNode;
            parentNode.addChild(node);
            if (state.isInitial && parentNode.state === this.getCurrentState()) {
                this._currentBranch.push(node);
            }
            this._nodes[state.name] = node;
        }

        return this;
    };

    /**
     * Checks whether the a state with name <code>stateName</code> was added to the Automaton
     * @param {String} stateName
     * @return {Boolean}
     */
    Automaton.prototype.hasState = function (stateName) {
        return this._nodes.hasOwnProperty(stateName);
    };

    /**
     * Retrieves a state
     * @param {String} stateName
     * @return {jsfsa.State} <code>undefined</code> if no state with name <code>stateName</code> was found.
     */
    Automaton.prototype.getState = function (stateName) {
        if (this.hasState(stateName)) {
            return this._nodes[stateName].state;
        }

        return undefined;
    };

    /**
     * Removes the state with name <code>stateName</code> from the Automaton
     * @param {String} stateName
     * @return {jsfsa.Automaton} the instance of {@link jsfsa.Automaton} that is acted upon
     */
    Automaton.prototype.removeState = function (stateName) {
        if (this.hasState(stateName)) {
            var node = this._nodes[stateName];
            var parentNode = node.parent;
            parentNode.removeChild(node);
            var index = this._currentBranch.indexOf(node);
            if (index >= 0) {
                this._currentBranch.splice(index, this._currentBranch.length - index);
            }
            node.destroy();
            delete this._nodes[stateName];
        }

        return this;
    };

    /**
     * Starts transitioning from the current state to the state as defined by the transition
     * Accepts any number of arguments after <code>transitionName</code> that will be passed on to the
     * guards and actions
     * @param {String} transitionName
     * @return {jsfsa.Automaton} the instance of {@link jsfsa.Automaton} that is acted upon
     */
    Automaton.prototype.doTransition = function (transitionName) {
        if (this._internalState === 'ready') {
            var payload;
            if (arguments.length > 1) {
                payload = Array.prototype.slice.call(arguments);
                payload.shift(); //drop transitionname
            } else {
                payload = [];
            }

            this._newBranch = this._currentBranch;
            var eventFactory = new StateEventFactory(payload, transitionName, this.getCurrentState().name);
            var sourceNode = this._hasTransitionInCurrentBranch(transitionName);

            if (sourceNode === undefined) {
                //there's no transition with that name in the current state branch
                this._finishTransition(eventFactory.createArgsArray(StateEvent.TRANSITION_DENIED));
            } else {
                //transition found somewhere in the _currentStateBranch
                this._attemptTransition(sourceNode, eventFactory);
            }
        }
        return this;
    };

    /**
     * Parses a JSON formatted data object and translates it to states and transitions
     * @param {Object} data JSON formatted data object
     * @return {jsfsa.Automaton} the instance of {@link jsfsa.Automaton} that is acted upon
     */
    Automaton.prototype.parse = function (data) {
        for (var stateName in data) {
            if (data.hasOwnProperty(stateName)) {
                this.createState(stateName, data[stateName]);
            }
        }

        return this;
    };

    /**
     * Resumes the transitioning between states
     * @return {jsfsa.Automaton} the instance of {@link jsfsa.Automaton} that is acted upon
     */
    Automaton.prototype.proceed = function () {
        if (this._internalState === 'transitioning' || this._internalState === 'paused') {
            if (this._queue.length > 0) {
                this._internalState = 'transitioning';
                var o = this._queue.shift();
                var item = o.obj.state;
                item[o.method].call(item, o.args);
                if (this._internalState !== "paused") {
                    this.proceed();
                }
            }
        }

        return this;
    };

    /**
     * Pauses the transitioning between states
     * @return {jsfsa.Automaton} the instance of {@link jsfsa.Automaton} that is acted upon
     */
    Automaton.prototype.pause = function () {
        if (this._internalState === 'transitioning') {
            this._internalState = 'paused';
        }

        return this;
    };

    /**
     * Destroys all internal states and prepares the Automaton to be dismantled by
     * -- the horrible GARBAGE COLLECTOR --
     *
     * Sorry, couldn't resist.
     */
    Automaton.prototype.destroy = function () {
        this._rootNode.destroy();
        this._rootNode = undefined;
        this._nodes = undefined;
        this._currentBranch = undefined;
    };

    Automaton.prototype._hasTransitionInCurrentBranch = function (transitionName) {
        var runner;
        var found = false;
        for (var i = this._currentBranch.length - 1, n = 0; i >= n; i--) {
            runner = this._currentBranch[i].state;
            if (runner.hasTransition(transitionName)) {
                found = true;
                break;
            }
        }
        return found ? runner : undefined;
    };

    Automaton.prototype._attemptTransition = function (sourceNode, eventFactory) {
        var targetSpec = sourceNode.getTransition( eventFactory.transition );
        var targetName;
        if (typeof targetSpec === 'function') {
            var args = eventFactory.createArgsArray();
            targetName = targetSpec.apply(this, args);
        } else {
            targetName = targetSpec;
        }
        var targetNode = null;
        if ( targetName ) {
            targetNode = this._nodes[ targetName ];
        }
        if ( !targetNode ) {
            //state doesn't exist
            this._finishTransition(eventFactory.createArgsArray(StateEvent.TRANSITION_DENIED));
        } else {
            eventFactory.to = targetNode.state.name;
            var initialNodes = targetNode.getInitialBranch();
            this._newBranch = this._getBranchFromRoot(targetNode)
                .concat(initialNodes);
            var streams = this._getShortestRoute(this._currentBranch, this._newBranch);
            this._doExitGuardPhase(streams, eventFactory);
        }
    };

    /**
     * @private
     * @param {Node} node
     * @return {Node[]}
     */
    Automaton.prototype._getBranchFromRoot = function (node) {
        var branch = [];
        while (node) {
            branch.unshift(node);
            node = node.parent;
        }
        return branch;
    };

    /**
     * @private
     * @param {Node[]} rootToBegin
     * @param {Node[]} rootToEnd
     * @return {Object}
     */
    Automaton.prototype._getShortestRoute = function (rootToBegin, rootToEnd) {
        var i, n = Math.min(rootToBegin.length, rootToEnd.length);
        for (i = 0; i < n; i++) {
            if (rootToBegin[i] !== rootToEnd[i]) {
                break;
            }
        }

        var up = rootToBegin.slice(i)
            .reverse();
        var down = rootToEnd.slice(i);

        return {
            up: up,
            down: down
        };
    };
    Automaton.prototype._doExitGuardPhase = function (streams, eventFactory) {
        this._internalState = 'guarding';
        var proceed = this._executeGuards(streams.up, eventFactory.createArgsArray(Action.EXIT));
        if (!proceed) {
            this._newBranch = undefined;
            this._finishTransition(eventFactory.createArgsArray(StateEvent.EXIT_DENIED));
        } else {
            proceed = this._doEntryGuardPhase(streams, eventFactory);
        }
        return proceed;
    };
    Automaton.prototype._doEntryGuardPhase = function (streams, eventFactory) {
        var proceed = this._executeGuards(streams.down, eventFactory.createArgsArray(Action.ENTRY));
        if (!proceed) {
            this._newBranch = undefined;
            this._finishTransition(eventFactory.createArgsArray(StateEvent.ENTRY_DENIED));
        } else {
            this._startTransition(streams, eventFactory);
        }
        return proceed;
    };
    Automaton.prototype._executeGuards = function (nodesList, args) {
        var result = true;
        for (var i = 0, n = nodesList.length; i < n; i++) {
            var state = nodesList[i].state;
            result = state._executeGuards(args) && result;
        }

        return result;
    };

    Automaton.prototype._startTransition = function (streams, eventFactory) {
        this._internalState = 'transitioning';
        this._currentBranch = undefined;
        var referer = [
        {
            state: this
        }
        ];
        var args = eventFactory.createArgsArray(StateEvent.EXITED);
        this._queue = [];
        this._addToQueue(streams.up, '_dispatchArgs', args);
        this._addToQueue(referer, '_dispatchArgs', args);
        args = eventFactory.createArgsArray(StateEvent.ENTERED);
        this._addToQueue(streams.down, '_dispatchArgs', args);
        this._addToQueue(referer, '_dispatchArgs', args);
        this._addToQueue(referer, '_finishTransition', eventFactory.createArgsArray(StateEvent.CHANGED));
        this.proceed();
    };

    Automaton.prototype._addToQueue = function (list, method, args) {
        for (var i = 0, n = list.length; i < n; i++) {
            this._queue.push({
                obj: list[i],
                args: args,
                method: method
            });
        }
    };

    Automaton.prototype._finishTransition = function (args) {
        if (this._newBranch) {
            this._currentBranch = this._newBranch;
        } else {
            this._newBranch = undefined;
        }
        this._internalState = 'ready';
        this._dispatchArgs(args);
    };

    /**
     *
     * @type {Function}
     * @private
     */
    Automaton.prototype._dispatchArgs = State.prototype._dispatchArgs;

    /**
     * framework version number
     * @constant
     * @type String
     */
    pkg.VERSION= '@@version';

    pkg.Action = Action;
    pkg.StateEvent = StateEvent;
    pkg.State = State;
    pkg.Automaton = Automaton;

})(jsfsa || (jsfsa = {}));