/**
* History.js HTML4 Support
* Depends on the HTML5 Support
* @author Benjamin Arthur Lupton <contact@balupton.com>
* @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
* @license New BSD License <http://creativecommons.org/licenses/BSD/>
*/

(function (window, undefined) {
    "use strict";

    // ========================================================================
    // Initialise

    // Localise Globals
    var 
		document = window.document, // Make sure we are using the correct document
		setTimeout = window.setTimeout || setTimeout,
		clearTimeout = window.clearTimeout || clearTimeout,
		setInterval = window.setInterval || setInterval,
		History = window.History = window.History || {}; // Public History Object

    // Check Existence
    if (typeof History.initHtml4 !== 'undefined') {
        throw new Error('History.js HTML4 Support has already been loaded...');
    }


    // ========================================================================
    // Initialise HTML4 Support

    // Initialise HTML4 Support
    History.initHtml4 = function () {
        // Initialise
        if (typeof History.initHtml4.initialized !== 'undefined') {
            // Already Loaded
            return false;
        }
        else {
            History.initHtml4.initialized = true;
        }


        // ====================================================================
        // Properties

        /**
        * History.enabled
        * Is History enabled?
        */
        History.enabled = true;


        // ====================================================================
        // Hash Storage

        /**
        * History.savedHashes
        * Store the hashes in an array
        */
        History.savedHashes = [];

        /**
        * History.isLastHash(newHash)
        * Checks if the hash is the last hash
        * @param {string} newHash
        * @return {boolean} true
        */
        History.isLastHash = function (newHash) {
            // Prepare
            var oldHash = History.getHashByIndex(),
				isLast;

            // Check
            isLast = newHash === oldHash;

            // Return isLast
            return isLast;
        };

        /**
        * History.saveHash(newHash)
        * Push a Hash
        * @param {string} newHash
        * @return {boolean} true
        */
        History.saveHash = function (newHash) {
            // Check Hash
            if (History.isLastHash(newHash)) {
                return false;
            }

            // Push the Hash
            History.savedHashes.push(newHash);

            // Return true
            return true;
        };

        /**
        * History.getHashByIndex()
        * Gets a hash by the index
        * @param {integer} index
        * @return {string}
        */
        History.getHashByIndex = function (index) {
            // Prepare
            var hash = null;

            // Handle
            if (typeof index === 'undefined') {
                // Get the last inserted
                hash = History.savedHashes[History.savedHashes.length - 1];
            }
            else if (index < 0) {
                // Get from the end
                hash = History.savedHashes[History.savedHashes.length + index];
            }
            else {
                // Get from the beginning
                hash = History.savedHashes[index];
            }

            // Return hash
            return hash;
        };


        // ====================================================================
        // Discarded States

        /**
        * History.discardedHashes
        * A hashed array of discarded hashes
        */
        History.discardedHashes = {};

        /**
        * History.discardedStates
        * A hashed array of discarded states
        */
        History.discardedStates = {};

        /**
        * History.discardState(State)
        * Discards the state by ignoring it through History
        * @param {object} State
        * @return {true}
        */
        History.discardState = function (discardedState, forwardState, backState) {
            //History.debug('History.discardState', arguments);
            // Prepare
            var discardedStateHash = History.getHashByState(discardedState),
				discardObject;

            // Create Discard Object
            discardObject = {
                'discardedState': discardedState,
                'backState': backState,
                'forwardState': forwardState
            };

            // Add to DiscardedStates
            History.discardedStates[discardedStateHash] = discardObject;

            // Return true
            return true;
        };

        /**
        * History.discardHash(hash)
        * Discards the hash by ignoring it through History
        * @param {string} hash
        * @return {true}
        */
        History.discardHash = function (discardedHash, forwardState, backState) {
            //History.debug('History.discardState', arguments);
            // Create Discard Object
            var discardObject = {
                'discardedHash': discardedHash,
                'backState': backState,
                'forwardState': forwardState
            };

            // Add to discardedHash
            History.discardedHashes[discardedHash] = discardObject;

            // Return true
            return true;
        };

        /**
        * History.discardState(State)
        * Checks to see if the state is discarded
        * @param {object} State
        * @return {bool}
        */
        History.discardedState = function (State) {
            // Prepare
            var StateHash = History.getHashByState(State),
				discarded;

            // Check
            discarded = History.discardedStates[StateHash] || false;

            // Return true
            return discarded;
        };

        /**
        * History.discardedHash(hash)
        * Checks to see if the state is discarded
        * @param {string} State
        * @return {bool}
        */
        History.discardedHash = function (hash) {
            // Check
            var discarded = History.discardedHashes[hash] || false;

            // Return true
            return discarded;
        };

        /**
        * History.recycleState(State)
        * Allows a discarded state to be used again
        * @param {object} data
        * @param {string} title
        * @param {string} url
        * @return {true}
        */
        History.recycleState = function (State) {
            //History.debug('History.recycleState', arguments);
            // Prepare
            var StateHash = History.getHashByState(State);

            // Remove from DiscardedStates
            if (History.discardedState(State)) {
                delete History.discardedStates[StateHash];
            }

            // Return true
            return true;
        };


        // ====================================================================
        // HTML4 HashChange Support

        if (History.emulated.hashChange) {
            /*
            * We must emulate the HTML4 HashChange Support by manually checking for hash changes
            */

            /**
            * History.hashChangeInit()
            * Init the HashChange Emulation
            */
            History.hashChangeInit = function () {
                // Define our Checker Function
                History.checkerFunction = null;

                // Define some variables that will help in our checker function
                var lastDocumentHash = '',
					iframeId, iframe,
					lastIframeHash, checkerRunning;

                // Handle depending on the browser
                if (History.isInternetExplorer()) {
                    // IE6 and IE7
                    // We need to use an iframe to emulate the back and forward buttons

                    // Create iFrame
                    iframeId = 'historyjs-iframe';
                    iframe = document.createElement('iframe');

                    // Adjust iFarme
                    iframe.setAttribute('id', iframeId);
                    iframe.style.display = 'none';

                    // Append iFrame
                    document.body.appendChild(iframe);

                    // Create initial history entry
                    iframe.contentWindow.document.open();
                    iframe.contentWindow.document.close();

                    // Define some variables that will help in our checker function
                    lastIframeHash = '';
                    checkerRunning = false;

                    // Define the checker function
                    History.checkerFunction = function () {
                        // Check Running
                        if (checkerRunning) {
                            return false;
                        }

                        // Update Running
                        checkerRunning = true;

                        // Fetch
                        var documentHash = History.getHash() || '',
							iframeHash = History.unescapeHash(iframe.contentWindow.document.location.hash) || '';

                        // The Document Hash has changed (application caused)
                        if (documentHash !== lastDocumentHash) {
                            // Equalise
                            lastDocumentHash = documentHash;

                            // Create a history entry in the iframe
                            if (iframeHash !== documentHash) {
                                //History.debug('hashchange.checker: iframe hash change', 'documentHash (new):', documentHash, 'iframeHash (old):', iframeHash);

                                // Equalise
                                lastIframeHash = iframeHash = documentHash;

                                // Create History Entry
                                iframe.contentWindow.document.open();
                                iframe.contentWindow.document.close();

                                // Update the iframe's hash
                                iframe.contentWindow.document.location.hash = History.escapeHash(documentHash);
                            }

                            // Trigger Hashchange Event
                            History.Adapter.trigger(window, 'hashchange');
                        }

                        // The iFrame Hash has changed (back button caused)
                        else if (iframeHash !== lastIframeHash) {
                            //History.debug('hashchange.checker: iframe hash out of sync', 'iframeHash (new):', iframeHash, 'documentHash (old):', documentHash);

                            // Equalise
                            lastIframeHash = iframeHash;

                            // Update the Hash
                            History.setHash(iframeHash, false);
                        }

                        // Reset Running
                        checkerRunning = false;

                        // Return true
                        return true;
                    };
                }
                else {
                    // We are not IE
                    // Firefox 1 or 2, Opera

                    // Define the checker function
                    History.checkerFunction = function () {
                        // Prepare
                        var documentHash = History.getHash();

                        // The Document Hash has changed (application caused)
                        if (documentHash !== lastDocumentHash) {
                            // Equalise
                            lastDocumentHash = documentHash;

                            // Trigger Hashchange Event
                            History.Adapter.trigger(window, 'hashchange');
                        }

                        // Return true
                        return true;
                    };
                }

                // Apply the checker function
                History.intervalList.push(setInterval(History.checkerFunction, History.options.hashChangeInterval));

                // Done
                return true;
            }; // History.hashChangeInit

            // Bind hashChangeInit
            History.Adapter.onDomLoad(History.hashChangeInit);

        } // History.emulated.hashChange


        // ====================================================================
        // HTML5 State Support

        // Non-Native pushState Implementation
        if (History.emulated.pushState) {
            /*
            * We must emulate the HTML5 State Management by using HTML4 HashChange
            */

            /**
            * History.onHashChange(event)
            * Trigger HTML5's window.onpopstate via HTML4 HashChange Support
            */
            History.onHashChange = function (event) {
                //History.debug('History.onHashChange', arguments);

                // Prepare
                var currentUrl = ((event && event.newURL) || document.location.href),
					currentHash = History.getHashByUrl(currentUrl),
					currentState = null,
					currentStateHash = null,
					currentStateHashExits = null,
					discardObject;

                // Check if we are the same state
                if (History.isLastHash(currentHash)) {
                    // There has been no change (just the page's hash has finally propagated)
                    //History.debug('History.onHashChange: no change');
                    History.busy(false);
                    return false;
                }

                // Reset the double check
                History.doubleCheckComplete();

                // Store our location for use in detecting back/forward direction
                History.saveHash(currentHash);

                // Expand Hash
                if (currentHash && History.isTraditionalAnchor(currentHash)) {
                    //History.debug('History.onHashChange: traditional anchor', currentHash);
                    // Traditional Anchor Hash
                    History.Adapter.trigger(window, 'anchorchange');
                    History.busy(false);
                    return false;
                }

                // Create State
                currentState = History.extractState(History.getFullUrl(currentHash || document.location.href, false), true);

                // Check if we are the same state
                if (History.isLastSavedState(currentState)) {
                    //History.debug('History.onHashChange: no change');
                    // There has been no change (just the page's hash has finally propagated)
                    History.busy(false);
                    return false;
                }

                // Create the state Hash
                currentStateHash = History.getHashByState(currentState);

                // Check if we are DiscardedState
                discardObject = History.discardedState(currentState);
                if (discardObject) {
                    // Ignore this state as it has been discarded and go back to the state before it
                    if (History.getHashByIndex(-2) === History.getHashByState(discardObject.forwardState)) {
                        // We are going backwards
                        //History.debug('History.onHashChange: go backwards');
                        History.back(false);
                    } else {
                        // We are going forwards
                        //History.debug('History.onHashChange: go forwards');
                        History.forward(false);
                    }
                    return false;
                }

                // Push the new HTML5 State
                //History.debug('History.onHashChange: success hashchange');
                History.pushState(currentState.data, currentState.title, currentState.url, false);

                // End onHashChange closure
                return true;
            };
            History.Adapter.bind(window, 'hashchange', History.onHashChange);

            /**
            * History.pushState(data,title,url)
            * Add a new State to the history object, become it, and trigger onpopstate
            * We have to trigger for HTML4 compatibility
            * @param {object} data
            * @param {string} title
            * @param {string} url
            * @return {true}
            */
            History.pushState = function (data, title, url, queue) {
                //History.debug('History.pushState: called', arguments);

                // Check the State
                if (History.getHashByUrl(url)) {
                    throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
                }

                // Handle Queueing
                if (queue !== false && History.busy()) {
                    // Wait + Push to Queue
                    //History.debug('History.pushState: we must wait', arguments);
                    History.pushQueue({
                        scope: History,
                        callback: History.pushState,
                        args: arguments,
                        queue: queue
                    });
                    return false;
                }

                // Make Busy
                History.busy(true);

                // Fetch the State Object
                var newState = History.createStateObject(data, title, url),
					newStateHash = History.getHashByState(newState),
					oldState = History.getState(false),
					oldStateHash = History.getHashByState(oldState),
					html4Hash = History.getHash();

                // Store the newState
                History.storeState(newState);
                History.expectedStateId = newState.id;

                // Recycle the State
                History.recycleState(newState);

                // Force update of the title
                History.setTitle(newState);

                // Check if we are the same State
                if (newStateHash === oldStateHash) {
                    //History.debug('History.pushState: no change', newStateHash);
                    History.busy(false);
                    return false;
                }

                // Update HTML4 Hash
                if (newStateHash !== html4Hash && newStateHash !== History.getShortUrl(document.location.href)) {
                    //History.debug('History.pushState: update hash', newStateHash, html4Hash);
                    History.setHash(newStateHash, false);
                    return false;
                }

                // Update HTML5 State
                History.saveState(newState);

                // Fire HTML5 Event
                //History.debug('History.pushState: trigger popstate');
                History.Adapter.trigger(window, 'statechange');
                History.busy(false);

                // End pushState closure
                return true;
            };

            /**
            * History.replaceState(data,title,url)
            * Replace the State and trigger onpopstate
            * We have to trigger for HTML4 compatibility
            * @param {object} data
            * @param {string} title
            * @param {string} url
            * @return {true}
            */
            History.replaceState = function (data, title, url, queue) {
                //History.debug('History.replaceState: called', arguments);

                // Check the State
                if (History.getHashByUrl(url)) {
                    throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
                }

                // Handle Queueing
                if (queue !== false && History.busy()) {
                    // Wait + Push to Queue
                    //History.debug('History.replaceState: we must wait', arguments);
                    History.pushQueue({
                        scope: History,
                        callback: History.replaceState,
                        args: arguments,
                        queue: queue
                    });
                    return false;
                }

                // Make Busy
                History.busy(true);

                // Fetch the State Objects
                var newState = History.createStateObject(data, title, url),
					oldState = History.getState(false),
					previousState = History.getStateByIndex(-2);

                // Discard Old State
                History.discardState(oldState, newState, previousState);

                // Alias to PushState
                History.pushState(newState.data, newState.title, newState.url, false);

                // End replaceState closure
                return true;
            };

        } // History.emulated.pushState



        // ====================================================================
        // Initialise

        // Non-Native pushState Implementation
        if (History.emulated.pushState) {
            /**
            * Ensure initial state is handled correctly
            */
            if (History.getHash() && !History.emulated.hashChange) {
                History.Adapter.onDomLoad(function () {
                    History.Adapter.trigger(window, 'hashchange');
                });
            }

        } // History.emulated.pushState

    }; // History.initHtml4

    // Try and Initialise History
    if (typeof History.init !== 'undefined') {
        History.init();
    }

})(window);

