/*
    Getme Ltd reserve all intellectual property rights of this source code.
    
    EXCEPT AS OTHERWISE EXPRESSLY PROVIDED IN A WRITTEN AGREEMENT BETWEEN YOU 
    AND GETME LTD, THIS SOURCE CODE IS PROVIDED "AS IS" AND WITHOUT ANY 
    WARRANTIES OF ANY KIND, INCLUDING WARRANTIES OF MERCHANTABILITY OR FITNESS 
    FOR A PARTICULAR PURPOSE.

    IN NO EVENT SHALL GETME LTD BE LIABLE FOR ANY DIRECT, INDIRECT, 
    INCIDENTIAL, SPECIAL OR CONSEQUENTIAL DAMAGES OR DAMAGES FROM BUSINESS 
    INTERRUPTION OR LOSS OF PROFITS, REVENUE, DATA OR USE, INCURRED BY YOU OR 
    ANY THIRD PARTY, WHETHER IN CONTRACT OR TORT, ARISING FROM YOUR ACCESS TO, 
    OR USE OF, THIS SOURCE CODE EVEN IF GETME LTD HAS BEEN ADVISED OF THE 
    POSSIBILITY OF SUCH DAMAGES.
    
    Copyright (c)2009 Getme Ltd.
 */

/**
 * @fileoverview    Finite State Machine (FSM) implementation.
 * @version         1.0.0
 * @author          Anthony Blackshaw ant@getme.co.uk
 */


/**
 * The FSM namespace.
 * The Finite State Machine library uses FSM to namespace it's
 * classes and functions.
 * @final
 * @type Namespace
 */  
var FSM = {};


/**
 * Create a new Finite State Machine.
 * @class The Finite State Machine class.
 * @constructor
 * @param {Object} data 
 * Typically an object, the data attribute is used to share data when using
 * callback functions.
 */    
FSM.Machine = function(data) {
        
    /** 
     * A mapping of transitions for action in state.
     * @private
     * @type Object
     */
    this._state_transitions = {};
    
    /** 
     * A mapping of transitions for states.
     * @private
     * @type Object
     */
    this._state_transitions_any = {};
    
    /** 
     * A default transition called when no mapping for action or state can be
     * found.
     * @private
     * @type Object
     */
    this._default_transition = null;
    
    /** 
     * The initial state of the Machine.
     * @private
     * @type String
     * @See #set_initial_state
     */
    this._initial_state = null;
    
    /** 
     * The current state of the Machine.
     * @private
     * @type String
     * @See #get_state
     */
    this._current_state = null;
    
    /**
     * The data attribute is used to share data in callback functions for 
     * transtions.
     * @type Object 
     */
    this.data = data;
}

/** 
 * Set the initial state of the Finite State Machine. NOTE: This method should
 * always be called before calling the {@link #process} method.
 * @param {String} state The state to set the Machine to initially.
 * @see #reset
 */
FSM.Machine.prototype.set_initial_state = function(state) {
    this._initial_state = state;
    if (this._current_state===null) {
        this.reset();
    }
}

/** 
 * Reset the Finite State Machine to the initial state.
 * @see #set_initial_state
 */
FSM.Machine.prototype.reset = function() {
    this._current_state = this._initial_state;
}

/**
 * Add a transition for an action against a state to the Finite State Machine.
 * @param {String} action The action that triggers the transaction.
 * @param {String} state The state under which the action triggers the
 * transaction.
 * @param {String} next_state Optional. If specified the Machine will transform
 * from the current state to the next state.
 * @param {Function} callback Optional. A function called each time the
 * transition occurs.
 * @see #add_transition_any
 * @see #add_transitions
 * @see #get_transition
 * @see #set_default_transition
 */
FSM.Machine.prototype.add_transition = function(action, state, next_state, callback) {
    if (!next_state) {
        next_state = state;
    }
    this._state_transitions[[action,state]] = [callback,next_state];
}

/**
 * Add a transition for multiple actions against a state to the Finite State
 * Machine.
 * @param {String} action_list A list of actions that trigger the transaction.
 * @param {String} state The state under which the action triggers the
 * transaction.
 * @param {String} next_state Optional. If specified the Machine will transform
 * from the current state to the next state.
 * @param {Function} callback Optional. A function called each time the
 * transition occurs.
 * @see #add_transition
 * @see #add_transition_any
 * @see #get_transition
 * @see #set_default_transition
 */
FSM.Machine.prototype.add_transitions = function(action_list, state, next_state, callback) {
    if (!next_state) {
        next_state = state;
    }
    for (var i=0; i<action_list.length; i++) {
        this.add_transition(action_list[i],state,next_state,callback);
    }        
}

/**
 * Add a transition to the Finite State Machine that is supported for the 
 * specified state. Such transitions only  occur if no specific transition for
 * an action can be found.
 * @param {String} state The state for which the default transition will apply.
 * @param {String} next_state Optional. If specified the Machine will transform
 * from the current state to the next state.
 * @param {Function} callback Optional. A function called each time the 
 * transition occurs.
 * @see #add_transition
 * @see #add_transitions
 * @see #get_transition
 * @see #set_default_transition
 */
FSM.Machine.prototype.add_transition_any = function(state, next_state, callback) {
    if (!next_state) {
        next_state = state;
    }
    this._state_transitions_any[state] = [callback,next_state];
}

/**
 * Set the default transition for when no specific transtion can be found.
 * @param {String} state The state to which the Machine will transition.
 * @param {String} next_state Optional. If specified the machine will transform
 * from the current state to the next state.
 * @param {Function} callback Optional. A function called each time the 
 * transition occurs.
 * @see #add_transition
 * @see #add_transition_any
 * @see #add_transitions
 * @see #get_transition
 */
FSM.Machine.prototype.set_default_transition = function(state,callback) {
    this._default_transition = [callback,state];
}

/**
 * Get the transition for the specified action and state.
 * @param {String} action The action under which the transition occurs.
 * @param {String} state Optional. The state under which the transition occurs.
 * @return The transition that occurs based on the action and state.
 * @type Array
 * @see #add_transition
 * @see #add_transition_any
 * @see #add_transitions
 * @see #set_default_transition
 */
FSM.Machine.prototype.get_transition = function(action, state) {
    if (this._state_transitions[[action,state]]) {
        return this._state_transitions[[action,state]];
    } else if (this._state_transitions_any[state]) {
        return this._state_transitions_any[state];
    } else if (this._default_transition) {
        return this._default_transition;
    } else {
        throw Error('Transition is undefined: ('+action+', '+state+')');
    }
}

/**
 * Get the Machines current state.
 * @return The Machines current state.
 * @type String
 */
FSM.Machine.prototype.get_current_state = function() {
    return this._current_state;
}

/**
 * Process an action. NOTE: Before calling process ensure you have set the
 * initial state (see {@link #set_initial_state}).
 * @param {String} action The action to process.
 */
FSM.Machine.prototype.process = function(action) {
    result = this.get_transition(action,this._current_state);
    if (result[0]) {
        result[0].call(this,action);
    }
    this._current_state = result[1];
}
