(function ($) {

$.history = {
    currentHash:   ''
,   length:        0
,   lastLength:    0
,   backStack:     []
,   forwardStack:  []
,   isFirst:       true
,   skipCheck:     false        // true = temporarily skip history checks
,   callback:      undefined    // required parameter
,   needIframe:    $.browser.msie && ($.browser.version < 8 || document.documentMode < 8)

,   init: function (callback) {
        var _ = $.history;
        var cur_hash = location.hash.replace(/\?.*$/, '');
        _.currentHash = cur_hash;
        _.callback = callback;

        if (_.needIframe) {
            // To stop the callback firing twice during initilization if no hash present
            if (_.currentHash == '') {
                _.currentHash = '#';
            }
            // add hidden iframe for IE
            $("body").prepend('<iframe id="jQuery_history" style="display: none;" src="javascript:false;"></iframe>');
            var ihistory = $("#jQuery_history")[0];
            var iframe = ihistory.contentWindow.document;
            iframe.open();
            iframe.close();
            iframe.location.hash = cur_hash;
        }
        else if ($.browser.safari) {
            _.backStack.length = history.length;
            _.lastLength = history.length;
        }
        if (cur_hash)
            _.callback(cur_hash.replace(/^#/, ''));

        setInterval(jQuery.history.check, 100);
    }

,   add: function (hash) {
        var _ = $.history;
        // This makes the looping function do something
        _.backStack.push(hash);
        _.forwardStack.length = 0; // clear forwardStack (true click occured)
        _.isFirst = true;
    }

,   check: function () {
        var _ = $.history;
        if (_.needIframe) {
            // On IE, check for location.hash of iframe
            var ihistory = $("#jQuery_history")[0];
            var iframe = ihistory.contentDocument || ihistory.contentWindow.document;
            var cur_hash = iframe.location.hash.replace(/\?.*$/, '');
            if (cur_hash != _.currentHash) {
                location.hash = cur_hash;
                _.currentHash = cur_hash;
                _.callback(cur_hash.replace(/^#/, ''));
            }
        }
        else if ($.browser.safari) {
            if (_.lastLength == history.length && _.backStack.length > _.lastLength) {
                _.backStack.shift();
            }
            if (!_.skipCheck) {
                var historyDelta = history.length - _.backStack.length;
                _.lastLength = history.length;

                if (historyDelta != 0) { // back or forward button has been pushed
                    _.isFirst = false;
                    if (historyDelta < 0) { // back button has been pushed
                        // move items to forward stack
                        for (var i = 0; i < Math.abs(historyDelta); i++)
                            _.forwardStack.unshift(_.backStack.pop());
                    }
                    else { // forward button has been pushed
                        // move items to back stack
                        for (var i = 0; i < historyDelta; i++)
                            _.backStack.push(_.forwardStack.shift());
                    }
                    var cachedHash = _.backStack[_.backStack.length - 1];
                    if (cachedHash != undefined) {
                        _.currentHash = location.hash.replace(/\?.*$/, '');
                        _.callback(cachedHash);
                    }
                }
                else if (_.backStack[_.backStack.length - 1] == undefined && !_.isFirst) {
                    // back button has been pushed to beginning and URL already pointed to hash (e.g. a bookmark)
                    // document.URL doesn't change in Safari
                    _.callback(location.hash ? location.hash.replace(/^#/, '') : '');
                    _.isFirst = true;
                }
            }
        }
        else {
            // otherwise, check for location.hash
            var cur_hash = location.hash.replace(/\?.*$/, '');
            if (cur_hash != _.currentHash) {
                _.currentHash = cur_hash;
                _.callback(cur_hash.replace(/^#/, ''));
            }
        }
    }

,   load: function (hash) {
        hash = decodeURIComponent(hash.replace(/\?.*$/, ''));

        var _ = $.history;
        var new_hash;

        if ($.browser.safari) {
            new_hash = hash;
        }
        else {
            new_hash = '#'+ hash;
            location.hash = new_hash;
        }
        _.currentHash = new_hash;

        if (_.needIframe) {
            var iframe = $("#jQuery_history")[0].contentWindow.document;
            iframe.open();
            iframe.close();
            iframe.location.hash = new_hash;
            _.lastLength = history.length;
            _.callback(hash);
        }
        else if ($.browser.safari) {
            _.skipCheck = true;
            // Manually keep track of the history values for Safari
            _.add(hash);

            // Wait a while before allowing checking so that Safari has time to update the "history" object
            // correctly (otherwise the check loop would detect a false change in hash).
            window.setTimeout(function(){jQuery.history.skipCheck = false;}, 200);
            _.callback(hash);
            // N.B. "location.hash=" must be the last line of code for Safari as execution stops afterwards.
            // By explicitly using the "location.hash" command (instead of using a variable set to "location.hash") the
            // URL in the browser and the "history" object are both updated correctly.
            location.hash = new_hash;
        }
        else {
          _.callback(hash);
        }
    }

};
})( jQuery );

