// Get the namespace.
window.EXANIMO = window.EXANIMO || {};




/**
 *
 * Adapted from the YAHOO Global Object.
 * <http://developer.yahoo.com/yui/docs/yahoo/YAHOO.html>
 *
 *  Returns the namespace specified and creates it if it doesn't exist
 *
 *     @param ns    the name of the namespace
 *     @return      a reference to the namespace object
 *
 */
EXANIMO.namespace = function(ns)
{
    if (!ns || !ns.length)
        return;

    var levels = ns.split('.');
    var nsobj = EXANIMO;

    // EXANIMO is implied, so it is ignored if it is included
    for (var i = (levels[0] == 'EXANIMO') ? 1 : 0; i < levels.length; i++)
    {
        nsobj[levels[i]] = nsobj[levels[i]] || {};
        nsobj = nsobj[levels[i]];
    }

    return nsobj;
}




/**
 *
 * EXANIMO.utils.getBrowserInfo
 *
 *     Stupid crappy browser sniffing crap.
 *
 *     @author     matthew at exanimo dot com
 *     @version    2006.07.25
 *
 */

    EXANIMO.namespace('EXANIMO.utils');

    /**
     *
     * Gets the browser information.
     *
     *     @return    an object with a browser and a version property
     *
     */
    EXANIMO.utils.getBrowserInfo = function()
    {
        if (navigator.appName.indexOf('Microsoft Internet') != -1)
            return {browser: 'MSIE'};
        if (navigator.userAgent.indexOf('Safari') != -1)
            return {browser: 'SAFARI'};
        else
            return {browser: 'OTHER'};
    }




/**
 *
 * EXANIMO.utils.getSWFObject
 *
 *     From <http://www.permadi.com/tutorial/flashjscommand/>
 *
 */

    EXANIMO.namespace('EXANIMO.utils');

    /**
     *
     * Gets a SWF from the DOM.
     *
     *     @param id    the name/id of the movie
     *     @return      the swf object
     *
     */
    EXANIMO.utils.getSWFObject = function(id)
    {
        if (window.document[id])
            return window.document[id];
        if (navigator.appName.indexOf('Microsoft Internet') == -1)
            if (document.embeds && document.embeds[id])
                return document.embeds[id];
        else
            return document.getElementById(id);
    }




/**
 *
 * StateManager
 *
 *     Adds state management (back button and deep-linking) to your single-page
 *     web application.
 *
 *     For usage instructions, see
 *     <http://exanimo.com/javascript/using-the-statemanager-in-ajax-apps>.
 *
 *     copyright 2006 matthew john tretter.  available under the MIT License.
 *     (<http://www.opensource.org/licenses/mit-license.php>)
 *
 *     todo:
 *         - pick one of _cancelLoad or _refreshPage
 *         - clean up code
 *             - look at all the stuff that hasn't been looked at since July
 *               (_updateIFrame, etc.) and make sure it's the best way.
 *         - OO changes
 *             - reduce redundancy (less "EXANIMO.managers.StateManager")
 *             - use Function instead of Object.
 *
 *     version history:
 *         2006.11.16
 *             - fixed bug introduced in last version that broke deeplinks for
 *               Safari.
 *         2006.11.12
 *             - added AJAX autoInit
 *             - logState is now setState
 *             - added new events 'stateRegistered', 'stateSet'
 *         2006.11.10
 *             - major updates. loadState is now logState. there was no reason
 *               to load a state without logging it -- that's the same as
 *               calling the stateChange handler.
 *         2006.11.09
 *             - stateChange event now tells you if the state was changed
 *               directly or indirectly (by back, forward, or a deeplink)
 *         2006.11.08
 *             - fixed a bug that caused safari to load states twice
 *         2006.11.07
 *             - some misc. bugs fixes / tons of cleanup
 *             - registerState is gone in favor of setTitle
 *         2006.11.06
 *             - renamed to StateManager
 *             - now supports AJAX
 *         2006.11.05
 *             - FULL Safari support!!!
 *         2006.10.31
 *             - reworked hash method to eliminate FP9 bugs.
 *             - defineState is now registerState
 *         2006.10.30
 *             - added defineState to fix a problem with deep linking
 *         2006.09.25
 *             - added initialize function
 *             - now uses object literal
 *         2006.09.14
 *             - BACK BUTTON FIXED FOR SAFARI!! unfortunately, hash no longer
 *               updates, though you can still deep-link if you know the hash.
 *         2006.09.13
 *             - if movie is not provided, checks for variable filled by
 *               JSInterface's locateSelf.
 *         2006.07.27
 *             - renamed from BB5K
 *             - got rid of #lookup= in hash.
 *         2006.07.20
 *
 *     @author     matthew at exanimo dot com
 *     @version    2006.11.12
 *
 */

EXANIMO.namespace('EXANIMO.managers');
if (!EXANIMO.managers.StateManager)
{
    EXANIMO.managers.StateManager = {


        CHECK_RATE: 100,
        stateChangeHandler: null,
        event: null,

        _initialized: false,
        _autoInitInterval: setInterval(
            function()
            {
                if (document.body && !EXANIMO.managers.StateManager._initialized)
                {
                    EXANIMO.managers.StateManager.initialize();
                }
            },
            10
        ),
        _checkInterval: null,
        _method: null,
        _swf: null,

        // Browser specific variables.
        _oldStateID: null,
        _refreshPage: null,
        _cancelLoad: false,


        /**
         *
         * Does all the behind-the-scenes startup work for the StateManager.
         *
         *     @param swf    (optional) the SWF to add state management to. if
         *                   not present, the function will use the swf in
         *                   EXANIMO.utils.JSInterface.swfObject. if that is
         *                   also empty, it goes into AJAX mode.
         *
         */
        initialize: function(swf)
        {
            // Stop trying to auto-initialize and reset StateManager.
            clearInterval(EXANIMO.managers.StateManager._autoInitInterval);
            clearInterval(EXANIMO.managers.StateManager._checkInterval);
            EXANIMO.managers.StateManager._initialized = true;

            // If a SWF was provided, remember it.
            if (typeof swf != 'undefined')
            {
                EXANIMO.managers.StateManager._swf = typeof(swf) == 'string' ? EXANIMO.utils.getSWFObject(swf) : swf || null;
            }

            // Decide which method to use by (blech!) browser sniffing.
            var browserInfo = EXANIMO.utils.getBrowserInfo();
            switch(browserInfo.browser)
            {
                case 'MSIE':
                    EXANIMO.managers.StateManager._method = 'IFRAME';
                    break;
                case 'SAFARI':
                    EXANIMO.managers.StateManager._method = 'LINK';
                    break;
                default:
                    EXANIMO.managers.StateManager._method = 'HASH';
                    break;
            }

            switch(EXANIMO.managers.StateManager._method)
            {
                case 'HASH':

                    var initialState = EXANIMO.managers.StateManager._getStateID();
                    EXANIMO.managers.StateManager._oldStateID = initialState == 'home' ? 'home' : null;

                    // Watch to see if the hash changes.
                    var checkForHashChange = function()
                    {
                        var stateID = EXANIMO.managers.StateManager._getStateID();

                        if (stateID != EXANIMO.managers.StateManager._oldStateID)
                        {
                            EXANIMO.managers.StateManager._oldStateID = stateID;
                            EXANIMO.managers.StateManager._dispatchEvent(stateID)
                        }
                    }

                    EXANIMO.managers.StateManager._checkInterval = setInterval(checkForHashChange, EXANIMO.managers.StateManager.CHECK_RATE);

                    break;

                //
                // Do the actual initialization, based on the method determined
                // above.
                //
                case 'IFRAME':

                    //
                    // Make sure the iframe knows that, when it loads, it should
                    // not refresh this page.
                    //
                    EXANIMO.managers.StateManager._refreshPage = false;

                    // Create and attach the iframe.
                    var iframe = document.createElement('iframe');
                    iframe.setAttribute('src', 'about:blank');
                    iframe.setAttribute('name', 'EXANIMO-managers-StateManager-iFrame');
                    iframe.setAttribute('id', 'EXANIMO-managers-StateManager-iFrame');
                    iframe.style.visibility = 'hidden';
                    iframe.style.width = '0';
                    iframe.style.height = '0';
                    iframe.style.position = 'absolute';
                    iframe.style.overflow = 'hidden';
                    document.body.appendChild(iframe);

                    // If a state id is already present in the hash, go to it.
                    var stateID = EXANIMO.managers.StateManager._getStateID();
                    if (stateID != 'home')
                    {
                        setTimeout(
                            function()
                            {
                                EXANIMO.managers.StateManager._dispatchEvent(stateID)
                            },
                            0
                        );
                    }

                    // Update the page and hash from the iframe.
                    frames['EXANIMO-managers-StateManager-iFrame'].document.open();
                    if (stateID)
                        frames['EXANIMO-managers-StateManager-iFrame'].document.write('<script>parent.document.location.hash = "' + (stateID == 'home' ? '' : stateID) + '"; parent.EXANIMO.managers.StateManager._updateIFrame("' + stateID + '");</script>');
                    else
                        frames['EXANIMO-managers-StateManager-iFrame'].document.write('<script>parent.document.location.hash = ""; parent.EXANIMO.managers.StateManager._updateIFrame();</script>');
                    frames['EXANIMO-managers-StateManager-iFrame'].document.close();

                    break;

                case 'LINK':

                    document.location.EXANIMO = document.location.EXANIMO || {};
                    document.location.EXANIMO.managers = document.location.EXANIMO.managers || {};
                    document.location.EXANIMO.managers.StateManager = document.location.EXANIMO.managers.StateManager || {};

                    var loc = document.location.EXANIMO.managers.StateManager;

                    // Make sure the last state is loaded when you come back to
                    // this page after navigating away.
                    window.onunload = function()
                    {
                        loc.oldHistoryLength = -1;
                    }

                    if (loc.deepLink && loc.deepLink != 'home')
                    {
                    	loc.oldHistoryLength = -1;
						loc.deepLink = null;
                    }

                    // Create a list of the states we click through.
                    if (typeof loc.stateList == 'undefined')
                    {
                        loc.stateList = [EXANIMO.managers.StateManager._getStateID() || 'home'];
                        loc.deepLink = loc.stateList[0];

                        loc.offset = history.length - 1;
                        while (loc.offset)
                        {
                            loc.stateList.unshift(null);
                            loc.offset--;
                        }
                        delete loc.offset;

                        loc.oldHistoryLength = document.location.hash ? -1 : history.length;

                    }

                    // Watch to see if the length of the history object changes.
                    var checkForHistoryLengthChange = function()
                    {
                        var loc = document.location.EXANIMO.managers.StateManager;

                        if (EXANIMO.managers.StateManager._cancelLoad)
                        {
                            EXANIMO.managers.StateManager._cancelLoad = false;
                            loc.oldHistoryLength = history.length;
                            return;
                        }

                        if (history.length != loc.oldHistoryLength)
                        {
                            var stateID = loc.stateList[history.length - 1];

                            EXANIMO.managers.StateManager._dispatchEvent(stateID)
                            loc.oldHistoryLength = history.length;
                        }
                    }

                    EXANIMO.managers.StateManager._checkInterval = setInterval(checkForHistoryLengthChange, EXANIMO.managers.StateManager.CHECK_RATE);

                    break;

            }
        },


        /**
         *
         * Adds an entry to the history.
         *
         *     @param stateID       a unique identifier corresponding to a
         *                          specific state
         *
         */
        setState: function(stateID, title)
        {
            // Set the title.
            if (title)
            {
                EXANIMO.managers.StateManager.setTitle(title);
            }

            // Block infinite loops.
            if (EXANIMO.managers.StateManager.event) return;

            switch(EXANIMO.managers.StateManager._method)
            {
                case 'HASH':

                    document.location.hash = stateID == 'home' ? '#' : stateID;
                    EXANIMO.managers.StateManager._oldStateID = stateID;

                    break;

                case 'IFRAME':

                    EXANIMO.managers.StateManager._setIFrame(stateID);

                    break;

                case 'LINK':

                    EXANIMO.managers.StateManager._cancelLoad = true;

                    var a = document.createElement('a');
                    a.setAttribute('href', stateID == 'home' ? '#' : '#' + stateID);

                    var evt = document.createEvent('MouseEvents');
                    evt.initEvent('click', true, true);
                    a.dispatchEvent(evt);

                    document.location.EXANIMO.managers.StateManager.stateList.push(stateID);

                    break;

            }

            // Dispatch the stateChange events
            EXANIMO.managers.StateManager._dispatchEvent(stateID, true);

        },


        /**
         *
         * Sets the window's title.
         *
         *     @param title    the string to put in the browser's title bar
         *
         */
        setTitle: function(title)
        {
            window.document.title = title || ' ';
        },


        /**
         *
         * Dispatches a stateChange event.
         *
         *     @param stateID    the id of the state to load
         *     @param manual     was the stateChange manual? manual changes
         *                       are the result of calls to setState. (automatic
         *                       changes are the result of the back / forward
         *                       buttons or deep-linking.)
         *
         */
        _dispatchEvent: function(stateID, manual)
        {
            stateID = stateID || 'home';

            // If a SWF reference has been stored using the JSInterface's
            // locateSelf (and the StateManager wasn't initialized with a swf
            // argument), use that.
            if ((typeof EXANIMO.utils.JSInterface != 'undefined') && (EXANIMO.utils.JSInterface.swfObject))
            {
                EXANIMO.managers.StateManager._swf = EXANIMO.managers.StateManager._swf || EXANIMO.utils.JSInterface.swfObject;
            }

            // Flash: call the SWF's loadState function..
            if (EXANIMO.managers.StateManager._swf)
            {
                // ..but only if
                //     1) it hasn't been caused by navigating within flash. (if
                //        it has, we don't want to navigate again.)
                //     2) a handler has been set
                if (!manual && EXANIMO.managers.StateManager._swf.dispatchStateChangeEvents)
                {
                    EXANIMO.managers.StateManager._swf.dispatchStateChangeEvents(stateID);
                }
            }

            // AJAX: dispatch a stateChange event.
            else
            {
                EXANIMO.managers.StateManager.event = {id: stateID};

                // If a handler is set, call it.
                if (EXANIMO.managers.StateManager.stateChangeHandler)
                {
                    EXANIMO.managers.StateManager.stateChangeHandler(EXANIMO.managers.StateManager.event);
                }

                if (manual)
                {
                    if (EXANIMO.managers.StateManager.stateSetHandler)
                    {
                        EXANIMO.managers.StateManager.stateSetHandler(EXANIMO.managers.StateManager.event);
                    }
                }
                else
                {
                    if (EXANIMO.managers.StateManager.stateRevisitedHandler)
                    {
                        EXANIMO.managers.StateManager.stateRevisitedHandler(EXANIMO.managers.StateManager.event);
                    }
                }

                EXANIMO.managers.StateManager.event = null;
            }
         },


        /**
         *
         * Retrieves state ID.
         *
         *     @return    the id of the current state
         *
         */
        _getStateID: function()
        {
            return document.location.href.split('#')[1] || 'home';
        },


        /**
         *
         * Sets the iframe to a specific lookup.
         *
         *     @param lookup    the lookup to use
         *
         */
        _setIFrame: function(stateID)
        {
            EXANIMO.managers.StateManager._refreshPage = false;
            var iframe = document.getElementById('EXANIMO-managers-StateManager-iFrame');

            switch(EXANIMO.managers.StateManager._method)
            {
                case 'IFRAME':

                    frames['EXANIMO-managers-StateManager-iFrame'].document.open();
                    frames['EXANIMO-managers-StateManager-iFrame'].document.write('<script>parent.document.location.hash = "' + (stateID == 'home' ? '#' : stateID) + '"; /* Wait for IE to impose its title before setting ours. */ setTimeout( function(){ parent.EXANIMO.managers.StateManager._updateIFrame("' + stateID + '"); }, 0);</script>');
                    frames['EXANIMO-managers-StateManager-iFrame'].document.close();

                    break;
            }
        },


        /**
         *
         * Updates the iframe
         *
         *     @param stateID    the id of the state to record
         *
         */
        _updateIFrame: function(stateID)
        {
            if (EXANIMO.managers.StateManager._refreshPage)
            {
                EXANIMO.managers.StateManager._dispatchEvent(stateID);
            }
            else
            {
                EXANIMO.managers.StateManager._refreshPage = true;
            }
        }




    }




}




// Copyright (c) 2006 matthew john tretter.  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
