/**
 * @library BeaPortalClientStateManager
 *
 * This JavaScript library implements a simple client-side state manager.  The state persisted by this library is
 * intended to survive, when possible, browser refreshes and possibly even browser sessions, depending on the expiration
 * setting for the state manager and the browser's/user's willingness to accept cookies.
 *
 * Note that state persistence may not be possible if the browser is in any way set to deny cookies.
 *
 * @todo
 *      Support a state entry count limit, pruning state entries as the state entry count grows above a threshold
 * @todo
 *      Support a cumulative state entry size limit, pruning state entries as state grows above a size threshold
 * @todo
 *      Support size-based threshold overflow into additional cookies, up to a limit of additional cookies
 *
 * All comments in this file may be removed when optimizing for production.
 */

/**
 * This global variable tracks the number of instances of the state manager class.  This value should not be modified
 * externally!
 *
 * @access private
 */
var beaPortalClientStateManagerCount = 0;

/**
 * Creates a new BeaPortalClientStateManager instance.
 *
 * @constructor
 * @access public
 *
 * @param (namespace)
 *      The namespace to which all state stored via the current instance should be scoped;
 *      the namespace should be unique, as otherwise two managers created with the same namespace will share a state
 *      pool (i.e. a single cookie);
 *      defaults to "beaPortalClientStateManagerX", where "X" is the instance count for this class
 * @param (expires)
 *      The expiration date/time for cookie used to persist all state stored via this manager instance;
 *      defaults to the end of the current browser session
 */
function BeaPortalClientStateManager(namespace, expires)
{
    // "Public" methods
    this.setState = beaPortalClientStateManagerSetState;
    this.getState = beaPortalClientStateManagerGetState;
    this.containsState = beaPortalClientStateManagerContainsState;
    this.deleteState = beaPortalClientStateManagerDeleteState;
    this.listStates = beaPortalClientStateManagerListStates;
    this.clear = beaPortalClientStateManagerClear;

    // "Private" methods
    this.bundleState = beaPortalClientStateManagerBundleState;

    // "Private" member variables
    this.instanceId = ++beaPortalClientStateManagerCount;
    this.namespace = (namespace ? namespace : "beaPortalClientStateManager" + this.instanceId);
    this.expires = expires;
    this.equals = "->";
    // Switch the second ctor arg to false if the master cookie is being inited elsewhere in the app
    // (e.g. the initial app page)
    this.cookies = new BeaPortalCookieManager(true, true);
}

/**
 * Sets a state value with the given name.  The name is namespaced based on the value of the current object's namespace
 * property value.
 *
 * @method BeaPortalClientStateManager.setState
 * @access public
 *
 * @param state
 *      The name of the state property to set; the strings "][", ";", or "->" are not allowed in the state name
 * @param (value)
 *      The value of the state property to set; the strings "][" or ";" are not allowed in the state value;
 *      defaults to null (in which case the state name alone is stored)
 *
 * @see #getCookieName(...)
 */
function beaPortalClientStateManagerSetState(state, value)
{
    this.deleteState(state);
    var cookie = this.cookies.getCookie(this.namespace);
    cookie = (cookie ? cookie : "") + this.bundleState(state, value);
    this.cookies.setCookie(this.namespace, cookie, this.expires);
}

/**
 * Fetches a persistent state value, if it exists and can be read from the persistent store.  The name is namespaced
 * based on the value of the current object's namespace property value.
 *
 * @method BeaPortalClientStateManager.getState
 * @access public
 *
 * @param state
 *      The name of the state property value to return
 *
 * @return The state's value, or null if the state does not exist or exists but has no value
 */
function beaPortalClientStateManagerGetState(state)
{
    var value = null;
    var states = this.listStates();

    for (var index = 0; index < states.length; index++)
    {
        if (states[index][0] == state)
        {
            value = states[index][1];
            break;
        }
    }

    return value;
}

/**
 * Checks to see if a given state has been set for this manager.
 *
 * @method BeaPortalClientStateManager.containsState
 * @access public
 *
 * @param state
 *      The state name setting to check for
 *
 * @return
 *      true if the state is set (with or without a value) in the cookie; false otherwise
 *
 * @see #getState(...)
 */
function beaPortalClientStateManagerContainsState(state)
{
    var result = false;
    var states = this.listStates();

    for (var index = 0; index < states.length; index++)
    {
        if (states[index][0] == state)
        {
            result = true;
            break;
        }
    }

    return result;
}

/**
 * Deletes a persistent state value if it exists.  The name is namespaced based on the value of the current object's
 * namespace property value.
 *
 * @method BeaPortalClientStateManager.deleteState
 * @access public
 *
 * @param name
 *      The name of the state property to delete
 */
function beaPortalClientStateManagerDeleteState(state)
{
    var cookie = "";
    var states = this.listStates();

    for (var index = 0; index < states.length; index++)
    {
        if (states[index][0] != state)
        {
            cookie += this.bundleState(states[index][0], states[index][1]);
        }
    }

    if (cookie.length > 0)
    {
        this.cookies.setCookie(this.namespace, cookie, this.expires);
    }
    else
    {
        this.cookies.deleteCookie(this.namespace);
    }
}

/**
 * Returns a list of the currently set states for this manager instance.  The returned list is a 2-dimensional array
 * where the first dimension is the list of states, and the second represents name/values pairs (size-2 in all cases).
 *
 * @method BeaPortalClientStateManager.listStates
 * @access public
 *
 * @return
 *      A list of all states; the format is a two-dimensional array where the first dimension is lists pairs of states
 *      and values in the second dimension; states without associated values provide null in the value position of the
 *      second dimension
 */
function beaPortalClientStateManagerListStates()
{
    var states = new Array();
    var cookie = this.cookies.getCookie(this.namespace);

    if (cookie)
    {
        cookie = cookie.substring(1, cookie.length - 1);
        var properties = cookie.split("][");

        for (var index = 0; index < properties.length; index++)
        {
            var property = properties[index];
            var equalsIndex = property.indexOf(this.equals);

            if (equalsIndex >= 1 && property.length > (equalsIndex + this.equals.length))
            {
                var key = property.substring(0, equalsIndex);
                var value = property.substring(equalsIndex + this.equals.length);
                states[index] = new Array(key, value);
            }
            else
            {
                states[index] = new Array(property, null);
            }
        }
    }

    return states;
}

/**
 * Clears all state associated with this state manager.  More precisely, the cookie used to store this managers state is
 * permenantly deleted.  Only use this method if you wish to delete all state entries associated with this manager
 * instance.
 *
 * @method BeaPortalClientStateManager.clear
 * @access public
 */
function beaPortalClientStateManagerClear()
{
    this.cookies.deleteCookie(this.namespace);
}

/**
 * Bundles a name and state into a writeable packet ready for insertion into the backing storage cookie.
 *
 * @method BeaPortalClientStateManager.bundleState
 * @access private
 *
 * @param state
 *      The name of the state property to bundle
 * @param (value)
 *      The value of the state property to set; defaults to nul (not included in the bundle)
 *
 * @return
 *      A string containing the state and, if it was specified, it's value formatted appropriately for insertion into
 *      overall state cookie
 */
function beaPortalClientStateManagerBundleState(state, value)
{
    return "[" + state + (value ? this.equals + value : "") + "]";
}
