# Source: core/state-chart.js

    /**
     * @module montage/core/state-chart
     * @requires montage
     */
    var Montage = require("./core").Montage;

    /**
     * @class State
     * @extends Montage
     */
    var State = exports.State = Montage.specialize( /** @lends State# */{

        _stateChart: {
            enumerable: false,
            value: null
        },

        /**
         * Initializes a State object with a set of options.
         * @function
         * @param {string} options The options for the new State.
         * @returns {State}
         */
        init: {
            value: function (options) {

                this.substates = {};
                this.enterState = null;
                this.exitState = null;

                var keys = Object.keys(options),
                    i = 0,
                    iKey,
                    iOption;

                for (; (iKey = keys[i]); i++) {

                    iOption = options[iKey];
                    if (typeof iOption === "object" && State.prototype.isPrototypeOf(iOption)) {

                        iOption.name = iKey;
                        iOption.parentState = this;
                        this.substates[iKey] = iOption;
                    }

                    // TODO is it worth guarding against any old property being added to this state?
                    if (typeof iOption === "string" && "initialSubstate" !== iKey) {
                        // this is a simple gotoState transition syntax
                        this[iKey] = this._encloseGotoState(iOption);
                    } else {
                        this[iKey] = iOption;
                    }
                }

                return this;
            }
        },

        /**
         * The name of the state.
         * @type {Property}
         * @default {string} null
         */
        name: {
            enumerable: false,
            value: null
        },

        _initialSubstate: {
            enumerable: false,
            value: null
        },

        /**
         * The intitial substate.
         * @type {Function}
         * @default {string} null
         */
        initialSubstate: {
            get: function () {
                if (typeof this._initialSubstate === "string") {
                    this._initialSubstate = this[this._initialSubstate];
                }
                return this._initialSubstate;
            },
            set: function (value) {
                this._initialSubstate = value;
            }
        },

        /**
         * The set of substates.
         * @type {Property}
         * @default {string} null
        */
        substates: {
            enumerable: false,
            value: null
        },

        /**
         * The state's parent state.
         * @type {Property}
         * @default {string} null
         */
        parentState: {
            enumerable: false,
            value: null
        },

        _path: {
            enumerable: false,
            value: null
        },

        /**
         * @type {Function}
         * @default {string} null
         */
        path: {
            // TODO add dependency on parentState, remember to clear cached value
            enumerable: false,
            get: function () {
                if (!this._path) {

                    if (this.parentState && this.parentState.path) {
                        this._path = this.parentState.path + "." + this.name;
                    } else {
                        this._path = this.name;
                    }
                }
                return this._path;
            }
        },

        /**
         * @type {Property}
         * @default {string} null
         */
        enterState: {
            enumerable: false,
            value: null
        },

        /**
         * @type {Property}
         * @default {string} null
         */
        exitState: {
            enumerable: false,
            value: null
        },

        /**
         * @function
         * @param {string} otherState
         * @returns `!!this.path.match(new RegExp(".?" + otherState + ".?"))`
         */
        // TODO XXX @returns block should be in code
        isInState: {
            enumerable: false,
            value: function (otherState) {

                if (typeof otherState !== "string") {
                    otherState = otherState.name;
                }

                return !!this.path.match(new RegExp(".?" + otherState + ".?"));
            }
        },

        _encloseGotoState: {
            value: function (state) {
                return function (stateChart, owner) {
                    // Not relying on original implementation to save an extra function call, despite duplicated code
                    return this._stateChart._gotoState(state, owner);
                };
            }
        },

        /**
         * @function
         * @param {Property} state
         * @param {Property} owner
         * @returns this._stateChart._gotoState(state, owner)
         */
        gotoState: {
            value: function (state, owner) {
                return this._stateChart._gotoState(state, owner);
            }
        },

        _performAction: {
            enumerable: null,
            value: function (actionName, stateChart, owner) {
                if (this[actionName]) {
                    // TODO what should the context be inside the action function:
                    // state or stateChart?  state makes sense but requires that
                    // authors know that when building a stateChart it's easy to
                    // get annoyed with having to remember which one you're in when
                    // building a relatively complex stateChart
                    // we could just .call(this._stateChart, args) etc...
                    this[actionName](stateChart, owner);

                } else if (this.parentState) {
                    this.parentState._performAction(actionName, stateChart, owner);
                } else {
                    throw "Action '" + actionName + "' not available";
                }
            }
        },

        /**
         * @function
         * @returns "[State " + this.path + " ]"
         */
        toString: {
            enumerable: false,
            value: function () {
                return "[State " + this.path + " ]";
            }
        }

    });

    /**
     * @class StateChart
     */
    var StateChart = exports.StateChart = Montage.specialize(/** @lends StateChart# */ {

        /**
         * @type {Property}
         * @default {string} null
         */
        delegate: {
            enumerable: false,
            value: null
        },

        // When the ownerStateProperty is set, we expect all operations of this
        // stateChart to rely on the owner property.
        // That is, many objects can rely on this stateChart to manage the flow of
        // state but keep track of their own currentState (exposed as
        // ownerStateProperty and recorded at "_" + ownerStateProperty);
        // When this is set, the statechart itself is completely stateless and an
        // object must take ownership of it prior to performing any actions

        /**
         * @type {Property}
         * @default {string} null
         */
        ownerStateProperty: {
            enumerable: false,
            value: null
        },

        /**
         * @type {Property}
         * @default {string} null
         */
        rootState: {
            enumerable: false,
            value: null
        },

        _currentState: {
            enumerable: false,
            value: null
        },

        /**
         * The current state.
         * @function
         * @returns The current state.
         */
        currentState: {
            get: function () {
                return this.ownerStateProperty ? null : this._currentState;
            }
        },

        /**
         * Initializes a StateChart with a State object, and returns the
         * StateChart.
         * @function
         * @param {string} state TODO
         * @returns {StateChart}
        */
        initWithState: {
            value: function (state) {

                this._states = {};

                this.rootState = state;
                this.rootState._stateChart = this;
                this._prepareState(this.rootState);

                this.enterDefaultState();

                return this;
            }
        },

        _defaultState: {
            enumerable: false,
            value: null
        },

        /**
         * The default state.
         * @function
         * @returns this._defaultState
         */
        defaultState: {
            enumerable: false,
            get: function () {
                if (!this._defaultState) {

                    var deepestState, nextState;
                    deepestState = nextState = this.rootState;

                    while ((nextState = nextState.initialSubstate)) {
                        deepestState = nextState;
                    }

                    this._defaultState = deepestState;
                }

                return this._defaultState;
            }
        },

        /**
         * @function
         * @returns this.defaultState
         */
        enterDefaultState: {
            enumerable: false,
            value: function () {
                if (this.ownerStateProperty && !this.owner) {
                    throw "This stateChart has been configured to require an owner to execute this function";
                }

                var owner = this.ownerStateProperty ? this.owner : this,
                    // Using internal ownerStateProperty normally reserved for
                // writing, seeing as many components trigger this
                // enterDefaultState, when their ownerStateProperty.get is invoked
                // the first time. So we need to stop the infinite loop
                    currentState = this.ownerStateProperty ? owner["_" + this.ownerStateProperty] : owner.currentState;

                if (currentState) {
                    throw "Cannot enter default state from '" + currentState.name + "'";
                }

                // We could probably use gotoState for this but I want to minimize
                // the callbacks as we're not really transitioning for the first
                // setup of the state we're just getting there, I don't think we
                // need to make it look like a "transition" to the delegate
                var deepestState, nextState;
                deepestState = nextState = this.rootState;

                while ((nextState = nextState.initialSubstate)) {

                    if (deepestState.enterState) {
                        deepestState.enterState(this, owner);
                    }

                    deepestState = nextState;

                    if (nextState.initialSubstate && deepestState.exitState) {
                        deepestState.exitState(this, owner);
                    }

                }

                if (this.ownerStateProperty) {
                    owner["_" + this.ownerStateProperty] = this.defaultState;
                } else {
                    this._currentState = this.defaultState;
                }

                return this.defaultState;
            }
        },

        _prepareState: {
            enumerable: false,
            value: function (state) {
                state._stateChart = this;

                // Keep a record of all states other than the root
                if (state.name) {
                    this._states[state.name] = state;
                }

                var substateName;
                for (substateName in state.substates) {
                    if (state.substates.hasOwnProperty(substateName)) {
                        this._prepareState(state.substates[substateName]);
                    }
                }
            }
        },

        _states: {
            enumerable: false,
            value: null
        },

        /**
         * @function
         * @param {Property} stateName TODO
         * @returns {Array} this._states[stateName]
         */
        stateWithName: {
            enumerable: false,
            value: function (stateName) {
                return this._states[stateName];
            }
        },

        /**
         * @function
         * @param {string} action TODO
         * @param {string} owner TODO
         */
        performAction: {
            value: function (action, owner) {

                if (this.ownerStateProperty && !owner) {
                    throw "This stateChart has been configured to require an owner to execute this function";
                }

                owner = this.ownerStateProperty ? owner : this;

                var currentState = this.ownerStateProperty ? owner[this.ownerStateProperty] : owner.currentState;

                if (!currentState) {
                    throw "Cannot perform action '" + action + "' without a currentState";
                }

                currentState._performAction(action, this, owner);

                // After performing the action, possibly with state transitions, clear out the owner
                this.owner = null;
            }
        },

        _gotoState: {
            value: function (state, owner) {

                if (this.ownerStateProperty && !owner) {
                    throw "This stateChart has been configured to require an owner to execute this function";
                }

                owner = this.ownerStateProperty ? owner : this;

                var fromState = this.ownerStateProperty ? owner[this.ownerStateProperty] : owner.currentState,
                    fromStateName = fromState.name,
                    stateName = state,
                    currentPath,
                    destinationPath,
                    i,
                    interiorDestinationPath,
                    interiorDestinationPathCount,
                    lastCommonIndex,
                    nextIndex,
                    destinationPathCount,
                    searchCount,
                    iState,
                    oldState,
                    delegateWillExit = false,
                    delegateWillEnter = false,
                    delegateDidExit = false,
                    delegateDidEnter = false;

                if (typeof stateName === "string") {
                    state = this._states[state];
                } else {
                    stateName = state.name;
                }

                //same state
                if (stateName === fromStateName) {
                    return;
                }

                if (this.delegate) {
                    delegateWillExit = typeof this.delegate.stateChartWillExitState === "function";
                    delegateWillEnter = typeof this.delegate.stateChartWillEnterState === "function";
                    delegateDidExit = typeof this.delegate.stateChartDidExitState === "function";
                    delegateDidEnter = typeof this.delegate.stateChartDidEnterState === "function";
                }

                if (this.delegate && typeof this.delegate.stateChartShouldGoFromStateToState === "function") {
                    if (!this.delegate.stateChartShouldGoFromStateToState(this, fromState, state)) {
                        return;
                    }
                }

                if (this.delegate && typeof this.delegate.stateChartWillGoFromStateToState === "function") {
                    this.delegate.stateChartWillGoFromStateToState(this, fromState, state);
                }

                currentPath = fromState.path;
                destinationPath = state.path;

                //state is inside this state
                if ((new RegExp(currentPath)).test(destinationPath)) {

                    interiorDestinationPath = destinationPath.replace(new RegExp(currentPath + ".?"), "").split(".");
                    interiorDestinationPathCount = interiorDestinationPath.length;
                    i = 0;

                    for (; i < interiorDestinationPathCount; i++) {
                        iState = this._states[interiorDestinationPath[i]];

                        if (delegateWillEnter) {
                            this.delegate.stateChartWillEnterState(this, iState);
                        }

                        if (typeof iState.enterState === "function") {
                            iState.enterState(this, owner);
                        }

                        if (delegateDidEnter) {
                            this.delegate.stateChartDidEnterState(this, iState);
                        }
                    }

                }
                // state is outside this state, need to find where they fork
                else {
                    currentPath = currentPath.split(".");
                    destinationPath = destinationPath.split(".");

                    lastCommonIndex = -1;
                    destinationPathCount = destinationPath.length;
                    searchCount = Math.min(currentPath.length, destinationPathCount);

                    while (lastCommonIndex < searchCount) {
                        nextIndex = lastCommonIndex + 1;
                        if (currentPath[nextIndex] !== destinationPath[nextIndex]) {
                            break;
                        }
                        lastCommonIndex++;
                    }

                    // exit from the currentState to just before the common path
                    for (i = currentPath.length - 1; i > lastCommonIndex; i--) {
                        iState = this._states[currentPath[i]];

                        if (delegateWillExit) {
                            this.delegate.stateChartWillExitState(this, iState);
                        }

                        if (typeof iState.exitState === "function") {
                            iState.exitState(this, owner);
                        }

                        if (delegateDidExit) {
                            this.delegate.stateChartDidExitState(this, iState);
                        }
                    }

                    // We don't want to enter the root state
                    lastCommonIndex = lastCommonIndex < 0 ? 0 : lastCommonIndex;

                    // enter from the common path to the new state at the end
                    for (i = lastCommonIndex; i < destinationPathCount; i++) {
                        iState = this._states[destinationPath[i]];

                        if (delegateWillEnter) {
                            this.delegate.stateChartWillEnterState(this, iState);
                        }

                        // Update the currentState as we enter each state
                        // TODO is this really necessary, is it best done at this
                        // point? what about exit?
                        if (this.ownerStateProperty) {
                            owner["_" + this.ownerStateProperty] = iState;
                        } else {
                            this._currentState = iState;
                        }

                        if (typeof iState.enterState === "function") {
                            iState.enterState(this, owner);
                        }

                        if (delegateDidEnter) {
                            this.delegate.stateChartDidEnterState(this, iState);
                        }
                    }
                }

                oldState = fromState;

                if (this.delegate && typeof this.delegate.stateChartDidGoFromStateToState === "function") {
                    this.delegate.stateChartDidGoFromStateToState(this, oldState, state);
                }

                if (typeof owner.transitionedFromStateToState === "function") {
                    owner.transitionedFromStateToState(this, oldState, state);
                }

            }
        }

    });