/**
 * History/Remote - jQuery plugin for enabling history support and bookmarking
 * @requires jQuery v1.0.3
 *
 * http://stilbuero.de/jquery/history/
 *
 * Copyright (c) 2006 Klaus Hartl (stilbuero.de)
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 * Version: 0.2.3
 */

(function($) { // block scope

/**
 * Initialize the history manager. Subsequent calls will not result in additional history state change 
 * listeners. Should be called soonest when the DOM is ready, because in IE an iframe needs to be added
 * to the body to enable history support.
 *
 * @example $.ajaxHistory.initialize();
 *
 * @param Function callback A single function that will be executed in case there is no fragment
 *                          identifier in the URL, for example after navigating back to the initial
 *                          state. Use to restore such an initial application state.
 *                          Optional. If specified it will overwrite the default action of 
 *                          emptying all containers that are used to load content into.
 * @type undefined
 *
 * @name $.ajaxHistory.initialize()
 * @cat Plugins/History
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */
$.ajaxHistory = new function() {
    var RESET_EVENT = 'historyReset';

    var _currentHash = location.hash.replace('#','');
    var _intervalId = null;
    var _observeHistory; // define outside if/else required by Opera

    this.update = function() { }; // empty function body for graceful degradation
		this.updating = '';

		
    // create custom event for state reset
    var _defaultReset = function() {
        $('.remote-output').empty();
    };

    $(document).bind(RESET_EVENT, _defaultReset);
	
	this.clickLink = function(currentHash, previousHash) {
	    if (typeof currentHash !== 'undefined') {
	        currentHash = currentHash.replace('#', '');
	    } else {
	        currentHash = '';
	    }
	    if (typeof previousHash !== 'undefined') {
	        previousHash = previousHash.replace('#', '');
	    } else {
	        previousHash = '';
	    }
	    
	    if (currentHash === '') {
	        return false;
	    }
	    
	    // get current and previous tab and item id's
		var previousTab = '', currentTab = '';
		if (previousHash.indexOf('/') >= 0) {
			previousTab = previousHash.split('/')[0];
			previousItem = previousHash.split('/')[1];
		} else {
			previousTab = previousHash;
			previousItem = '';
		}
		if (currentHash.indexOf('/') >= 0) {
			currentTab = currentHash.split('/')[0];
			currentItem = currentHash.split('/')[1];
		} else {
			currentTab = currentHash;
			currentItem = '';
		}

        // 
		if (previousTab === currentTab && previousItem !== '' && currentItem === '') {
			if (NGC.carousel) NGC.carousel.update(-1); // -1 is a special case to set a carousel'ed tab to the base tab
			return false;
		}
        
		// if <a> doesn't exist (previous 'page' was item in carousel, then went to another tab)
		// go to tab first, then select item
		if (previousTab !== currentTab && currentItem !== '' && previousTab !== '') {
			$('a[@href$="' + currentTab + '"]').click(); // the tab render behavior will pick up the item from the hash and select that item
			return false;
		}
		
		// otherwise, try to do whatever the hash wants to do
		$('a[@href$="#' + currentHash + '"]').click();
		return false;
	}
	
    if ($.browser.msie) {
	
        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) {
					if (_currentHash != hash) {
            var iframe = _historyIframe.contentWindow.document;
            iframe.open();
            iframe.close();
            _currentHash = hash.replace('#', '');
            iframe.location.hash = hash.replace('#', '');
						$.ajaxHistory.updating = '';
					}
        };

        _observeHistory = function() {
            var iframe = _historyIframe.contentWindow.document;
            var iframeHash = iframe.location.hash.replace('#', '');
            if (iframeHash != _currentHash) {
				_previousHash = _currentHash;
                _currentHash = iframeHash;
                if (iframeHash && iframeHash != '#') {
                    // order does matter, set location.hash after triggering the click...
					$.ajaxHistory.clickLink(iframeHash, _previousHash);
                    location.hash = iframeHash;
                } else if (initialized) {
                    location.hash = '';
                    $(document).trigger(RESET_EVENT);
                }
            }
            initialized = true;
        };

    } else if ($.browser.mozilla || $.browser.opera) {
        this.update = function(hash) {
            _currentHash = hash.replace('#', '');
        };

        _observeHistory = function() {
            if (location.hash) {
                if (_currentHash != location.hash.replace('#', '')) {
					_previousHash = _currentHash;
					_currentHash = location.hash.replace('#', '');
					$.ajaxHistory.clickLink(_currentHash, _previousHash);
                }
            } else if (_currentHash) {
                _currentHash = '';
                $(document).trigger(RESET_EVENT);
            }
        };

    } else if ($.browser.safari) {

        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);
			$.ajaxHistory.updating = ''; 
        };

        _observeHistory = function() {
			if ($.ajaxHistory.updating != 'true') {
                var historyDelta = history.length - _backStack.length;
                if (historyDelta && (cachedHash != location.hash)) { // back or forward button has been pushed
                    isFirst = false;

                    if (historyDelta < 0) { // back button has been pushed
                        var cachedHash = _backStack[_backStack.length-1];
                        // move items to forward stack
                        for (var i = 0; i < Math.abs(historyDelta); i++) _forwardStack.unshift(_backStack.pop());
                        //var cachedHash = _forwardStack[_forwardStack.length-1];
                    } else { // forward button has been pushed
                        // move items to back stack
                        for (var i = 0; i < historyDelta; i++) _backStack.push(_forwardStack.shift());
                        var cachedHash = _forwardStack[_forwardStack.length-1];
                    }
                    
                    if (cachedHash === location.hash) {
                        return false;
                    }
                    // $('a[@href$="' + cachedHash + '"]').click();
                    
                    _currentHash = location.hash;

                    $.ajaxHistory.clickLink(_currentHash, 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
                    if (document.URL.indexOf('#') >= 0) {
                        $('a[@href$="' + '#' + document.URL.split('#')[1] + '"]').click();
                    } else if (initialized) {
                        $(document).trigger(RESET_EVENT);
                    }
                    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') {
            $('a[@href$="' + location.hash + '"]').trigger('click');
        }
        // start observer
        if (_observeHistory && _intervalId == null) {
            _intervalId = setInterval(_observeHistory, 200); // Safari needs at least 200 ms
        }
    };

};

/**
 * Implement Ajax driven links in a completely unobtrusive and accessible manner (also known as "Hijax")
 * with support for the browser's back/forward navigation buttons and bookmarking.
 *
 * The link's href attribute gets altered to a fragment identifier, such as "#remote-1", so that the browser's
 * URL gets updated on each click, whereas the former value of that attribute is used to load content via
 * XmlHttpRequest from and update the specified element. If no target element is found, a new div element will be
 * created and appended to the body to load the content into. The link informs the history manager of the 
 * state change on click and adds an entry to the browser's history.
 *
 * jQuery's Ajax implementation adds a custom request header of the form "X-Requested-With: XmlHttpRequest"
 * to any Ajax request so that the called page can distinguish between a standard and an Ajax (XmlHttpRequest)
 * request.
 *
 * @example $('a.remote').remote('#output');
 * @before <a class="remote" href="/path/to/content.html">Update</a>
 * @result <a class="remote" href="#remote-1">Update</a>
 * @desc Alter a link of the class "remote" to an Ajax-enhanced link and let it load content from
 *       "/path/to/content.html" via XmlHttpRequest into an element with the id "output".
 * @example $('a.remote').remote('#output', {hashPrefix: 'chapter'});
 * @before <a class="remote" href="/path/to/content.html">Update</a>
 * @result <a class="remote" href="#chapter-1">Update</a>
 * @desc Alter a link of the class "remote" to an Ajax-enhanced link and let it load content from
 *       "/path/to/content.html" via XmlHttpRequest into an element with the id "output".
 *
 * @param String expr A string containing a CSS selector or basic XPath specifying the element to load
 *                    content into via XmlHttpRequest.
 * @param Object settings An object literal containing key/value pairs to provide optional settings.
 * @option String hashPrefix A String that is used for constructing the hash the link's href attribute
 *                           gets altered to, such as "#remote-1". Default value: "remote-".
 * @param Function callback A single function that will be executed when the request is complete. 
 * @type jQuery
 *
 * @name remote
 * @cat Plugins/Remote
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */

/**
 * Implement Ajax driven links in a completely unobtrusive and accessible manner (also known as "Hijax")
 * with support for the browser's back/forward navigation buttons and bookmarking.
 *
 * The link's href attribute gets altered to a fragment identifier, such as "#remote-1", so that the browser's
 * URL gets updated on each click, whereas the former value of that attribute is used to load content via
 * XmlHttpRequest from and update the specified element. If no target element is found, a new div element will be
 * created and appended to the body to load the content into. The link informs the history manager of the 
 * state change on click and adds an entry to the browser's history.
 *
 * jQuery's Ajax implementation adds a custom request header of the form "X-Requested-With: XmlHttpRequest"
 * to any Ajax request so that the called page can distinguish between a standard and an Ajax (XmlHttpRequest)
 * request.
 *
 * @example $('a.remote').remote( $('#output > div')[0] );
 * @before <a class="remote" href="/path/to/content.html">Update</a>
 * @result <a class="remote" href="#remote-1">Update</a>
 * @desc Alter a link of the class "remote" to an Ajax-enhanced link and let it load content from
 *       "/path/to/content.html" via XmlHttpRequest into an element with the id "output".
 * @example $('a.remote').remote('#output', {hashPrefix: 'chapter'});
 * @before <a class="remote" href="/path/to/content.html">Update</a>
 * @result <a class="remote" href="#chapter-1">Update</a>
 * @desc Alter a link of the class "remote" to an Ajax-enhanced link and let it load content from
 *       "/path/to/content.html" via XmlHttpRequest into an element with the id "output".
 *
 * @param Element elem A DOM element to load content into via XmlHttpRequest.
 * @param Object settings An object literal containing key/value pairs to provide optional settings.
 * @option String hashPrefix A String that is used for constructing the hash the link's href attribute
 *                           gets altered to, such as "#remote-1". Default value: "remote-".
 * @param Function callback A single function that will be executed when the request is complete. 
 * @type jQuery
 *
 * @name remote
 * @cat Plugins/Remote
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */
$.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;
        var hash = '#' + (this.title && this.title.replace(/\s/g, '_') || settings.hashPrefix + (i + 1));
        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();
                });
            }
        });
    });
};

/**
 * Provides the ability to use the back/forward navigation buttons in a DHTML application.
 * A change of the application state is reflected by a change of the URL fragment identifier.
 *
 * The link's href attribute needs to point to a fragment identifier within the same resource,
 * although that fragment id does not need to exist. On click the link changes the URL fragment
 * identifier, informs the history manager of the state change and adds an entry to the browser's
 * history.
 *
 * @param Function callback A single function that will be executed as the click handler of the 
 *                          matched element. It will be executed on click (adding an entry to 
 *                          the history) as well as in case the history manager needs to trigger 
 *                          it depending on the value of the URL fragment identifier, e.g. if its 
 *                          current value matches the href attribute of the matched element.
 *                           
 * @type jQuery
 *
 * @name history
 * @cat Plugins/History
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */
$.fn.history = function(callback) {
    return this.click(function(e) {    
        // add to history only if true click occured, not a triggered click
        if (e.clientX) {
            $.ajaxHistory.update(this.hash);
        }
        typeof callback == 'function' && callback();
    });
};

})(jQuery);