﻿
(function($) { // block scope

    $.ajaxHistory = new function() {

        var RESET_EVENT = 'historyReset';

        var _currentHash = location.hash;
        var _intervalId = null;
        var _observeHistory; // define outside if/else required by Opera

        this.update = function() { }; // empty function body for graceful degradation

        // create custom event for state reset
        var _defaultReset = function() {
            $('.remote-output').empty();
        };
        $(document).bind(RESET_EVENT, _defaultReset);

        // TODO fix for Safari 3
        // if ($.browser.msie)
        // else if hash != _currentHash
        // else check history length

        if ($.browser.msie && $.browser.version < 7.0) {
            var _historyIframe, initialized = false; // for IE

            // add hidden iframe
            $(function() {
                _historyIframe = $('<iframe style="display: none;"></iframe>').appendTo(document.body).get(0);
                var iframe = _historyIframe.contentWindow.document;
                // create initial history entry
                iframe.open();
                iframe.close();
                if (_currentHash && _currentHash != '#') {
                    iframe.location.hash = _currentHash.replace('#', '');
                }
            });

            this.update = function(hash) {
                _currentHash = hash;
                var iframe = _historyIframe.contentWindow.document;
                iframe.open();
                iframe.close();
                iframe.location.hash = hash.replace('#', '');
            };

            _observeHistory = function() {
                var iframe = _historyIframe.contentWindow.document;
                var iframeHash = iframe.location.hash;
                if (iframeHash != _currentHash) {
                    _currentHash = iframeHash;
                    if (iframeHash && iframeHash != '#') {
                        // order does matter, set location.hash after triggering the click...

                        if (ajaxHistory && ajaxHistory.hashCallback) {
                            ajaxHistory.runHashCallback(iframeHash);
                        }
                        else {
                            $('a[@href$="' + iframeHash + '"]').click();
                        }

                        location.hash = iframeHash;
                    } else if (initialized) {
                        location.hash = '';
                        if (ajaxHistory && ajaxHistory.hashCallback) {
                            ajaxHistory.runHashCallback("#");
                        }
                        $(document).trigger(RESET_EVENT);
                    }
                }
                initialized = true;
            };

        } else if ($.browser.mozilla || $.browser.opera || ($.browser.msie && $.browser.version >= 7.0) || ($.browser.safari && $.browser.version >= 4)) {
            this.update = function(hash) {
                _currentHash = hash;
            };

            _observeHistory = function() {
                if (location.hash) {
                    if (_currentHash != location.hash) {
                        _currentHash = location.hash;
                        if (ajaxHistory && ajaxHistory.hashCallback) {
                            ajaxHistory.runHashCallback(_currentHash);
                        }
                        else {
                            $('a[@href$="' + _currentHash + '"]').click();
                        }
                    }
                } else if (_currentHash) {
                    _currentHash = '';
                    $(document).trigger(RESET_EVENT);
                    if (ajaxHistory && ajaxHistory.hashCallback) {
                        ajaxHistory.runHashCallback("#");
                    }
                }
            };

        } else if ($.browser.safari && $.browser.version < 4) {
            var _backStack, _forwardStack, _addHistory; // for Safari

            // etablish back/forward stacks
            $(function() {
                _backStack = [];
                _backStack.length = history.length;
                _forwardStack = [];

            });
            var isFirst = false, initialized = false;
            _addHistory = function(hash) {
                _backStack.push(hash);
                _forwardStack.length = 0; // clear forwardStack (true click occured)
                isFirst = false;
            };

            this.update = function(hash) {
                _currentHash = hash;
                _addHistory(_currentHash);
            };

            _observeHistory = function() {
                var historyDelta = history.length - _backStack.length;
                if (historyDelta) { // 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 (ajaxHistory && ajaxHistory.hashCallback) {
                        ajaxHistory.runHashCallback(cachedHash);
                    }
                    else {
                        $('a[@href$="' + cachedHash + '"]').click();
                    }

                    _currentHash = location.hash;
                } 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
                    if (document.URL.indexOf('#') >= 0) {
                        if (ajaxHistory && ajaxHistory.hashCallback) {
                            ajaxHistory.runHashCallback('#' + document.URL.split('#')[1]);
                        }
                        else {
                            $('a[@href$="' + '#' + document.URL.split('#')[1] + '"]').click();
                        }


                    } else if (initialized) {
                        $(document).trigger(RESET_EVENT);
                        if (ajaxHistory && ajaxHistory.hashCallback) {
                            ajaxHistory.runHashCallback('#');
                        }
                    }
                    isFirst = true;
                }
                initialized = true;
            };

        }

        this.initialize = function(callback) {
            // custom callback to reset app state (no hash in url)
            if (typeof callback == 'function') {
                $(document).unbind(RESET_EVENT, _defaultReset).bind(RESET_EVENT, callback);
            }
            // look for hash in current URL (not Safari)
            if (location.hash && typeof _addHistory == 'undefined') {
                if (ajaxHistory && ajaxHistory.hashCallback) {
                    ajaxHistory.runHashCallback(location.hash);
                }
                else {
                    try {
                        $('a[@href$="' + location.hash + '"]').trigger('click');
                    }
                    catch (exc) { }
                }
            }
            // start observer
            if (_observeHistory && _intervalId == null) {
                _intervalId = setInterval(_observeHistory, 200); // Safari needs at least 200 ms
            }
        };

    };


    $.fn.remote = function(output, settings, callback) {

        callback = callback || function() { };
        if (typeof settings == 'function') { // shift arguments
            callback = settings;
        }

        settings = $.extend({
            hashPrefix: 'remote-'
        }, settings || {});

        var target = $(output).size() && $(output) || $('<div></div>').appendTo('body');
        target.addClass('remote-output');

        return this.each(function(i) {
            var href = this.href, hash = '#' + (this.title && this.title.replace(/\s/g, '_') || settings.hashPrefix + (i + 1)),
            a = this;
            this.href = hash;
            $(this).click(function(e) {
                // lock target to prevent double loading in Firefox
                if (!target['locked']) {
                    // add to history only if true click occured, not a triggered click
                    if (e.clientX) {
                        $.ajaxHistory.update(hash);
                    }
                    target.load(href, function() {
                        target['locked'] = null;
                        callback.apply(a);
                    });
                }
            });
        });

    };


    $.fn.history = function(callback) {
        return this.click(function(e) {
            // add to history only if true click occured,
            // not a triggered click...
            if (e.clientX) {
                // ...and die if already active
                if (this.hash == location.hash) {
                    return false;
                }
                $.ajaxHistory.update(this.hash);
            }
            if (typeof callback == 'function') {
                callback.call(this);
            }
        });
    };

})(jQuery);


var ajaxHistory = {
    iframe: null,
    hashCallback: null,
    _hash: "",
    runHashCallback: function(hashData) {
        ajaxHistory.hashCallback(ajaxHistory.getURLHash(hashData));
    },
    setURLHash: function(obj) {
        var hash = [];
        var first = true;
        for (key in obj) {
            if (obj[key]) { hash.push(key + "=" + obj[key]); }
        }
        if (hash.length != 0) {
            window.location.hash = "#" + hash.join("&");
        }
        else { window.location.hash = ""; }
        ajaxHistory.historyPush();
    },
    getURLHash: function(str) {
        var obj = {}, data;
        if (!str) { data = window.location.hash.split("#"); }
        else { data = str.split("#"); }
        if (data && data.length > 1) { data = data[1].split("&"); }
        else { return obj; }
        for (var k = 0; k < data.length; ++k) {
            var paramVal = data[k].split("=");
            if (paramVal.length > 1) { obj[paramVal[0]] = paramVal[1]; }
            else { obj[paramVal[0]] = null; }
        }
        return obj;
    },

    attrFromAnchor: function(id) {
        var obj = ajaxHistory.getURLHash();
        return obj[id];
    },
    attrToAnchor: function(id, val) {
        var obj = ajaxHistory.getURLHash();
        obj[id] = val;
        ajaxHistory.setURLHash(obj);
    },
    attrsToAnchor: function(newAttrs) {
        var obj = ajaxHistory.getURLHash();
        for (key in newAttrs) {
            obj[key] = newAttrs[key];
        }
        ajaxHistory.setURLHash(obj);
    },
    historyPush: function() {
        //console.log(window.location.hash);
        //console.log(ajaxHistory._hash);
        if (ajaxHistory._hash != window.location.hash) {
            $.ajaxHistory.update(window.location.hash);
        }
    },
    init: function() {
        $($.ajaxHistory.initialize());
    }
};


$(function() {
    ajaxHistory.init();
});

