if (window.magma == undefined) {
	window.magma = function() {};
}

magma.autoajax = new function() {
	var me = this;
	
	/**
	 * Current state
	 */
	this.state = new Object();
	this.state.elements = new Object();
	
	this.scriptsLoaded = new Object();
	
	/**
	 * Server current timestamp, this avoid
	 * timezone/computer clock offsets.
	 */
	this.serverts = null;
	
	/**
	 * Relative part of the server url to the java context.
	 */
	this.serverurl = "";
	
	this._buildUrl = function(rel, postfix) {
		var ret = "";
		if (this.serverurl.match('/$')) {
			if (rel.match('^/')) {
				ret = this.serverurl + (rel.substr(1));
			} else {
				ret = this.serverurl + rel;
			}
		} else {
			if (rel.match('^/')) {
				ret = this.serverurl + rel;
			} else {
				ret = this.serverurl + '/' + rel;
			}
		}
		if (postfix) ret += postfix;
		return ret;
	}
	
	/**
	 * Initialize the state
	 */
	this.initState = function(acstate) {
		this.state.elements = acstate;
		this.recalcState();
	}
	
	/**
	 * Updates one or more entries of the state.
	 * This method can be called multiple times with minimum
	 * overhead, but recalcState must be called when
	 * one or more updates have been performed to have
	 * other methods work correctly. 
	 */
	this.updateState = function(statepart) {
		for (var id in statepart) {
			var grp = statepart[id];
			var ele = document.getElementById(id);
			if (ele) {
				grp.domElement=ele;
			}
			this.state.elements[id] = grp;
		}
	}
	
	/**
	 * After modifying the state with updateState
	 * or initState, this method must be called
	 * to have the state perform self-check and 
	 * inspect the dom and reset binding with dom 
	 * elements properly.
	 * 
	 */
	this.recalcState = function() {
		this._stateDom();
	}
	
	/**
	 * Finds and connects dom elements and
	 * hierarchy on the state. State elements
	 * for which a corresponding element is not
	 * found will be remove from the state.
	 */
	this._stateDom = function() {
		// Find dom elements
		for (var id in this.state.elements) {
			var stategrp = this.state.elements[id];
			stategrp.stateid = id;
			if (!stategrp.timestamp) {
				stategrp.timestamp = this.serverts;
			}
			if (stategrp.type == 'tpl') {
				this.state.root = stategrp;
				stategrp.domElement=document.body;
				stategrp.children=new Array();
			} else {
				var ele = document.getElementById(id);
				var detached = false;
				
				if (!ele) {
					delete(this.state.elements[id]);
				} else {
					stategrp.domElement=ele;
					stategrp.children=new Array();
					stategrp.parent=null;
				}
			}
		}
		
		function zeropad(str) {
			while (str.length % 3 != 0) str = '0' + str;
			return str;
		}
		
		// Find parents and children
		for (var id in this.state.elements) {
			var stategrp = this.state.elements[id];
			if (stategrp.type == 'tpl') continue;
			var indexstr = '';
			var ele = $(stategrp.domElement);
			indexstr = zeropad(ele.index() + indexstr);
			ele = ele.parent();
			while (ele.length > 0) {
				indexstr = zeropad(ele.index() + indexstr);
				var pid = ele[0].id;
				var ps = this.state.elements[ele[0].id] 
				if (ps) {
					stategrp.parent=ps;
					ps.children.push(stategrp);
					break;
				};
				ele = ele.parent();
			}
			if (stategrp.parent == null) {
				stategrp.parent = this.state.root;
				this.state.root.children.push(stategrp);
			}
			stategrp.order = indexstr;
		}		
		
		for (var id in this.state.elements) {
			var stategrp = this.state.elements[id];
			if (stategrp.children && stategrp.children.length > 0) {
				stategrp.children.sort(function(a,b) {
					if (a.order < b.order) return -1;
					if (b.order < a.order) return 1;
					return 0;
				});
			}
		}		
	}
	
	/**
	 * Find an element given the exact id
	 */
	this.getElement = function(id) {
		return this.state.elements[id];
	}
	
	/**
	 * Find an element given the exact id, but if not found search an element
	 * having the first segment (before the slash) equal to the current, 
	 * provided that it's only one possible element.
	 */
	this.getFuzzyElement = function(id) {
		var ret = this.getElement(id);
		if (ret) return ret;
		var io = id.indexOf('/');
		if (io < 0) return;
		id = id.substr(0,io);
		for (var acid in this.state.elements) {
			if (acid.match("^" + id)) {
				if (ret) return;
				ret = this.state.elements[acid];
			}
		}
		return ret;
	}
	
	this.findStatesByUrl = function(url) {
		var ret = new Array();
		for (id in this.state.elements) {
			var grp = this.state.elements[id];
			if (grp.localurl && grp.localurl.match("^"+url) == url) {
				ret.push(grp);
			}
		}
		return ret;
	}
	
	/**
	 * Dumps an XML representation of the style
	 * suitable for server side processing.
	 */
	this.getState = function() {
		return this._elementState(this.state.root,0);
	}
	
	/**
	 * Recursively dump XML representation of a state element
	 */
	this._elementState = function(stategrp, level) {
		var ret = "<l" + level + " ";
		ret += "id='" + stategrp.stateid + "' ";
		ret += "ty='" + stategrp.type + "' ";
		if (stategrp.handle) {
			ret += "ha='" + stategrp.handle + "' ";
		}
		if (stategrp.localurl) {
			ret += "ur='" + stategrp.localurl + "' ";
		}
		if (stategrp.timestamp) {
			ret += "ti='" + stategrp.timestamp + "' ";
		}
		ret += '>';
		for (var i = 0; i < stategrp.children.length; i++) {
			ret += this._elementState(stategrp.children[i], level + 1);
		}
		ret += '</l' + level + '>';
		return ret;
	}
	
	/**
	 * Performs an in place renew of a piece.
	 * 
	 * This method batches if more than one call is made
	 * in the same thread span.
	 */
	this.renew = function(stateId) {
		var me = this;
		if (!me.torenew || me.torenew.length == 0) {
			setTimeout('magma.autoajax._intrenew()', 200);
			me.torenew = new Array();
		}
		for (var i = 0; i < me.torenew.length; i++) {
			if (me.torenew[i] == stateId) return;
		}
		me.torenew.push(stateId);
	}
	
	this._intrenew = function() {
		var me = this;
		if (!me.torenew || me.torenew.length == 0) {
			delete me.torenew;
			return;
		}
		var stateId = me.torenew[0];
		me.torenew.splice(0,1);
		setTimeout('magma.autoajax._intrenew()', 200);

		var stategrp = this.state.elements[stateId];
		if (stategrp && stategrp.type == 'int') {
			var mainurl = stategrp.localurl; 
			var jqele = jQuery(stategrp.domElement);
			var event = jQuery.Event("renewing");
			event.state = stategrp;
			jqele.trigger(event);
			/* Check if there are others and if they are internal */
			var urls = new Array();
			for (var i = 0; i < me.torenew.length; i++) {
				stategrp = this.state.elements[me.torenew[i]];
				if (stategrp.type == 'int') {
					jqele = jQuery(stategrp.domElement);
					event = jQuery.Event("renewing");
					event.state = stategrp;
					jqele.trigger(event);
					
					urls.push(stategrp.localurl);
					me.torenew.splice(i,1);
					i--;
				}
			}		
			
			var others = {};
			if (urls.length > 0) {
				others = {"others" : urls.join(';')}; 
			}
			
			/* Since they are internals, we can use the batched version */
			jQuery.ajax({
				url: me._buildUrl(mainurl,".ajaxbuint"),
				type: "POST",
				dataType: "html",
				data: others,
				complete: me.parseResponse
			});		
			return;
		}
		
		/* Non-internals go with the old .ajax way */
		var jqele = jQuery(stategrp.domElement);
		var event = jQuery.Event("renewing");
		event.state = stategrp;
		jqele.trigger(event);
		var url = stategrp.localurl;
		var real = me._buildUrl(url,".ajax");
		jQuery.ajax({
			url: real,
			type: "GET",
			dataType: "html",
			complete: function(res, status){
				if ( status == "success" || status == "notmodified" ) {
					var payload = $(res.responseText);
					var wrapperclass = 'AjaxWrapper';
					var headclass = '.AjaxHead';
					// TODO use replaceWith?
					jqele.empty();
					jqele.append($('.' + wrapperclass,payload).children());
					me.parseMagmaHead($(headclass,payload), payload, function() {
						var event = jQuery.Event("renewed");
						event.state = stategrp;
						jqele.trigger(event);
					});
				} else {
					var event = jQuery.Event("notrenewed");
					event.state = stategrp;
					jqele.trigger(event);
				}				
			}
		});		
				
	}
	
	/**
	 * Performs a remove of an element present
	 * in the state.
	 */
	this.performRemove = function(stateId) {
		var stategrp = this.getFuzzyElement(stateId);
		if (!stategrp) {
			console.log("Stategroup (id,grp) is undefined ", stateId, stategrp);
			return;
		}
		var jqele = jQuery(stategrp.domElement);
		var event = jQuery.Event("removing");
		event.state = stategrp;
		jqele.trigger(event);
		if (!event.isDefaultPrevented()) {
			jQuery(magma.autoajax).trigger(event);
		}
		if (!event.isDefaultPrevented()) {
			var parent = jqele.parent(); 
			jqele.remove();
			event = jQuery.Event("removed");
			event.state = stategrp;
			parent.trigger(event);
			jQuery(magma.autoajax).trigger(event);
		}
	}
	
	this.performAdd = function(position, relative, what) {
		var stategrp = this.getFuzzyElement(relative);
		if (!stategrp) {
			console.log("Stategroup (id,grp) is undefined ", relative, stategrp);
			return;
		}
		var jqele = jQuery(stategrp.domElement);
		var payload = $(what).children();
		var event = jQuery.Event("adding");
		event.state = stategrp;
		payload.trigger(event);
		var addtarget = null;
		if (position == 'inside') {
			jqele.append(payload);
			addtarget = jqele;
		} else if (position == 'after') {
			jqele.after(payload);
		} else if (position == 'before') {
			jqele.before(payload);
		}
		event = jQuery.Event("added");
		event.state = stategrp;
		payload.trigger(event);
	}

	this.performRenew = function(which, what) {
		var stategrp = this.getFuzzyElement(which);
		if (!stategrp) {
			console.log("Stategroup (id,grp) is undefined ", which, stategrp);
			return;
		}
		
		var jqele = jQuery(stategrp.domElement);
		var payload = $(what).children();
		jqele.empty();
		jqele.append(payload);
		/*
		jqele.after(payload);
		jqele.remove();
		*/
		var event = jQuery.Event("renewed");
		event.state = stategrp;
		jqele.trigger(event);
	}
	
	
	this.performNewPage = function(ele) {
		var event = jQuery.Event("newpaging");
		$(document).trigger(event);
		var jqe = jQuery(ele);
		var body = jQuery('body');
		var head = jQuery('head');
		body.empty().append(jqe.find('div[type="body"]').children());
		head.empty().append(jqe.find('div[type="head"]').children());
		var event = jQuery.Event("newpaged");
		$(document).trigger(event);
	}

	this.performMove = function(id1, relation, id2) {
		var stategrp1 = this.getFuzzyElement(id1);
		var stategrp2 = this.getFuzzyElement(id2);
		
		if (!stategrp1 || !stategrp2) {
			console.log("One of stategroups (id1,1,id2,2) is undefined ", id1, stategrp1, id2, stategrp2);
			return;
		}
		
		var jqele1 = jQuery(stategrp1.domElement);
		var jqele2 = jQuery(stategrp2.domElement);
		
		var event = jQuery.Event("moving");
		event.state = stategrp1;
		jqele1.trigger(event);
		jqele1.detach();
		if (relation == 'after') {
			jqele2.after(jqele1);
		} else if (relation == 'before') {
			jqele2.before(jqele1);
		}		
		var event = jQuery.Event("moved");
		event.state = stategrp1;
		jqele1.trigger(event);
	}
		
	/**
	 * Performs an Ajax Browser Update. It will
	 * contact the server, asking for the specified
	 * magma-relative url, and send the current state.
	 * Server will then provide change requests needed
	 * to update the page from its current state to its
	 * desired appearance.
	 */
	this.ajaxBu = function(rel, otherparams) {
		var me = this;
		var event = jQuery.Event("ajaxloading");
		$(document).trigger(event);		
		var real = me._buildUrl(rel,".ajaxbu");
		var state = this.getState();
		if (otherparams) {
			otherparams[otherparams.length] = {name:'state',value:state};
		} else {
			otherparams = {state: state};
		}
		jQuery.ajax({
			url: real,
			type: "POST",
			dataType: "html",
			data: otherparams,
			complete: me.parseResponse
		});		
	}
	
	this.parseResponse = function(res, status){
		var event = jQuery.Event("ajaxloaded");
		$(document).trigger(event);		
		if ( status == "success" || status == "notmodified" ) {
			var payload = $(res.responseText);
			var actions = payload.children();
			var newpages = actions.filter(".newpage");
			if (newpages.length > 0) {
				newpages.each(function() {
					me.performNewPage(this);
					var statemod = $(this).attr('state');
					if (statemod != '' && typeof statemod != 'undefined') {
						statemod = jQuery.parseJSON('{' + statemod + '}');
						me.updateState(statemod);
					}
					me.recalcState();
				});
			} else {
				me.parseMagmaHead(payload.find('.ajaxhead'),payload, function() {
					actions.each(function() {
						try { 
							var classes = this.className;
							var acts = classes.split(' ');
							if (acts[0] == 'add') {
								me.performAdd(acts[1], acts[2], this);
							} else if (acts[0] == 'move') {
								me.performMove(acts[1], acts[2], acts[3]);							
							} else if (acts[0] == 'renew') {
								me.performRenew(acts[1], this);
							}
							var statemod = $(this).attr('state');
							if (statemod != '' && typeof statemod != 'undefined') {
								statemod = jQuery.parseJSON('{' + statemod + '}');
								me.updateState(statemod);
							}
						} catch (e) {
							console.log(e);
						}
					});
					actions.filter(".remove").each(function() {
						try {
							var classes = this.className;
							var acts = classes.split(' ');
							me.performRemove(acts[1]);
						} catch (e) {
							console.log(e);
						}
					});
					me.recalcState();
				});
			}
			//alert("Success");
		} else {
			var event = jQuery.Event("ajaxerror");
			$(document).trigger(event);			
		}
	}
	
	/**
	 * Adds dynamically elements to the document head based on the
	 * ajax response.
	 * 
	 * These means : adding missing .css files, loading missing .js
	 * files, executing inline script snippets.
	 * 
	 * @param recvhead jQuery object of the div (or any thing else)
	 * containing head elements from ajax request.
	 */
	this.parseMagmaHead = function(recvhead, fullrecv, callback) {
		var me = this;
		var ajaxhead = recvhead.children();
		var head = $('head')[0];
		var scripts = new Array();
		for (var i = 0; i < ajaxhead.length; i++) {
			var tag = ajaxhead[i].tagName;
			if (tag == 'LINK') {
				var check = tag;
				var url = ajaxhead[i].href;
				url = url.substring(url.lastIndexOf('/'));
				check += "[href$='" + url + "']";
				if ($(check, head).length > 0) continue;
				$(head).prepend(ajaxhead[i]);
			} else {
				head.appendChild(ajaxhead[i]);
			}
		}
		function checkScript() {
			var url = this.src;
			if (url != '') {
				url = url.substring(url.lastIndexOf('/'));
				var check = "script[src$='" + url + "']";
				if ($(check, head).length > 0) return;
				if (me.scriptsLoaded[url]) return;
			}
			scripts.push(this);
		};
		$(fullrecv).filter('script').each(checkScript);
		$('script',fullrecv).each(checkScript);

		if (scripts.length > 0) {
			this.lateLoad(scripts, 0, callback);
		} else {
			callback.call(me);
		}
	}
	/**
	 * Chain progressive loading of script elements
	 * (both inline script and external javascripts)
	 * 
	 * A function is needed cause some loading might be async.
	 */
	this.lateLoad = function(scripts, index, callback) {
		var me = this;
		var ele = scripts[index];
		if (ele.src && ele.src != '') {
			var src = ele.src;
			var inturl = src.substring(src.lastIndexOf('/'));
			me.scriptsLoaded[inturl] = true;
			// Hack for google maps, that require an explicit
			// async parameter when loaded after page load
			if (src.indexOf("google.com/maps") != -1) {
				src += "&async=2";
			}
			if (scripts.length - 1> index) {
				jQuery.getScript(src, function() { me.lateLoad(scripts, index + 1, callback) });
			} else {
				jQuery.getScript(src, function() { callback.call(me) });
			}
		} else {
			var js = ele.text;
			jQuery.globalEval(js);
			if (scripts.length - 1> index) {
				me.lateLoad(scripts, index + 1, callback);
			} else {
				callback.call(me);
			}
		}
	}
	

	
	/**
	 * Loads the page given in the hash. The hash
	 * could arrive from many places, a bookmarked
	 * page, the browser back, or hooking on links.
	 * 
	 * Will call the ajaxBu method.
	 */
	this.loadHash = function(hash) {
		if (hash == '/' && magma.autoajax.skippingHash) return;
		if (hash == '/' && magma.autoajax.serverurl.match('/$')) {
			hash = '';
		}
		magma.autoajax.ajaxBu(hash,magma.autoajax.hashParams);
	}
	
	/**
	 * Navigates to the given url, if possible (if
	 * it is a server local url and hash navigation is
	 * enabled) changing the hash of the page and updating 
	 * it using ajax. Otherwise, will redirect the browser
	 * to the given url. 
	 */
	this.hashNavigate = function(href, params) {
		this.hashParams = params;
		var orighref = href;
		// String protocol and host, as long as they are equal
		if (href.indexOf(window.location.protocol) == 0) {
			href = href.substring(window.location.protocol.length);
		}
		if (href.indexOf("//") == 0) href=href.substring(2);
		if (href.indexOf(window.location.host) == 0) {
			href = href.substring(window.location.host.length);
		}
		if (href.indexOf(this.serverurl) != 0) {
			document.location.href = orighref; 
		}
		var subhref = href.substring(this.serverurl.length);
		if (subhref == '') subhref = '/';
		jQuery.historyLoad(subhref);
	}
	
	/**
	 * Enables hash navigation. If a jQuery element or selector
	 * is passed, only anchors in that element are activated
	 * for hash navigation.
	 * 
	 * If an hash is already present in the url, it is immediately
	 * parsed.
	 * 
	 * @param ele The root element (or selector) on which to activate hash navigation
	 * @param exclude A list of selectors on which NOT to handle navigation 
	 */
	this.hashNavigation = function(ele, exclude) {
		var me = this;
		if (typeof ele === 'undefined') {
			ele = jQuery('body');
		} else {
			ele = jQuery(ele);
		}
		if (exclude) {
			for (var i = 0; i < exclude.length; i++) {
				me.addHashExclude(exclude[i]);
			}
		}
		me.skippingHash = true;
		jQuery.historyInit(me.loadHash);
		me.skippingHash = false;
		// All liniks must go thru hash navigation
		jQuery('a', ele).live('click', function(event) {
			if (me.matchesHashExclude(this)) return true;
			var nevent = jQuery.Event("ajaxclick");
			$(event.currentTarget).trigger(nevent);		
			try {
				me.hashNavigate(event.currentTarget.href);
			} catch (e) {
				alert(e);
			}
			return false;
		});
		// Clicks on button are catched before raw submit,
		// cause otherwise there is no cross browser information 
		// on which button caused the submit
		jQuery('form input:submit', ele).live('click', function(event) {
			if (me.matchesHashExclude(this)) return true;
			try {
				var button = $(event.currentTarget);
				var nevent = jQuery.Event("ajaxclick");
				button.trigger(nevent);		
				var form = button.closest('form');
				var post = form.serializeArray();
				post[post.length] = {name: button.attr('name'), value: button.attr('value') };				
				me.hashNavigate(form.attr('action'), post);
			} catch (e) {
				alert(e);
			}
			return false;
		});
		// default form submits
		jQuery('form', ele).live('submit', function(event) {
			if (me.matchesHashExclude(this)) return true;
			try {
				var nevent = jQuery.Event("ajaxclick");
				$(event.currentTarget).trigger(nevent);		
				me.hashNavigate(event.currentTarget.action, $(event.currentTarget).serializeArray());
			} catch (e) {
				alert(e);
			}
			return false;
		});
	}
	
	this.addHashExclude = function(exclude) {
		var me = this;
		if (!me.hashExclude) me.hashExclude = new Array();
		me.hashExclude.push(exclude);
	}
	
	this.matchesHashExclude = function (ele) {
		if (!this.hashExclude) return false;
		var jqe = $(ele);
		for (var i = 0; i < this.hashExclude.length; i++) {
			if (jqe.is(this.hashExclude[i])) return true;
		}
		return false;
	}
	

	this.findStateFor = function(domele) {
		var me = this;
		var tgt = domele;
		var state = null;
		while (state == null && tgt != null) {
			for (var id in me.state.elements) {
				var stategrp = me.state.elements[id];
				if (stategrp.domElement == tgt) {
					state = stategrp
					break;
				}
			}
			if (state == null) {
				tgt = jQuery(tgt).parent()[0];
			}
		}		
		return state;
	}
	
	this.activateDebugClick = function() {
		var me = this;
		$('body').append($('<div id="debuginfo"></div>').css({position: 'absolute', background:'white', opacity:'0.7',color:'black', border:'2px solid red',width:'200px', height: 'auto', textAlign:'left', padding: '2px'}));
		jQuery("*:not(#debuginfo)").live('mousemove',
			function(event) {
				try {
					var ele = event.target;
					var state = me.findStateFor(ele);
					if (state != null) {
						$('#debuginfo').empty().append(
								$("<span><b>" + state.localurl + "</b><br/>h: " + state.handle + "<br/>t: " + state.type + "<br/>id: " + state.stateid + "<br/>ts: " + state.timestamp + "</span>")
						).css({left: event.pageX + 15, top: event.pageY + 15});
					}
				} catch(e) {
					console.log(e);
				}
			});
		jQuery("*").live('click',function(event){
			var state = me.findStateFor(event.target);
			if (state == null) {
				console.log('Unknown state');
			} else {
				console.log(state.localurl);
				console.log(state);
			}
		});
	}		
	
}




/*
 * jQuery history plugin
 * 
 * sample page: http://www.mikage.to/jquery/jquery_history.html
 *
 * Copyright (c) 2006-2009 Taku Sano (Mikage Sawatari)
 * Licensed under the MIT License:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Modified by Lincoln Cooper to add Safari support and only call the callback once during initialization
 * for msie when no initial hash supplied.
 */


jQuery.extend({
	historyCurrentHash: undefined,
	historyCallback: undefined,
	historyIframeSrc: undefined,
	historyNeedIframe: jQuery.browser.msie && (jQuery.browser.version < 8 || document.documentMode < 8),
	
	historyInit: function(callback, src){
		jQuery.historyCallback = callback;
		if (src) jQuery.historyIframeSrc = src;
		var current_hash = location.hash.replace(/\?.*$/, '');
		
		jQuery.historyCurrentHash = current_hash;
		if (jQuery.historyNeedIframe) {
			// To stop the callback firing twice during initilization if no hash present
			if (jQuery.historyCurrentHash == '') {
				jQuery.historyCurrentHash = '#';
			}
		
			// add hidden iframe for IE
			jQuery("body").prepend('<iframe id="jQuery_history" style="display: none;"'+
				' src="javascript:false;"></iframe>'
			);
			var ihistory = jQuery("#jQuery_history")[0];
			var iframe = ihistory.contentWindow.document;
			iframe.open();
			iframe.close();
			iframe.location.hash = current_hash;
		}
		else if (jQuery.browser.safari) {
			// etablish back/forward stacks
			jQuery.historyBackStack = [];
			jQuery.historyBackStack.length = history.length;
			jQuery.historyForwardStack = [];
			jQuery.lastHistoryLength = history.length;
			
			jQuery.isFirst = true;
		}
		if(current_hash)
			jQuery.historyCallback(current_hash.replace(/^#/, ''));
		setInterval(jQuery.historyCheck, 100);
	},
	
	historyAddHistory: function(hash) {
		// This makes the looping function do something
		jQuery.historyBackStack.push(hash);
		
		jQuery.historyForwardStack.length = 0; // clear forwardStack (true click occured)
		this.isFirst = true;
	},
	
	historyCheck: function(){
		if (jQuery.historyNeedIframe) {
			// On IE, check for location.hash of iframe
			var ihistory = jQuery("#jQuery_history")[0];
			var iframe = ihistory.contentDocument || ihistory.contentWindow.document;
			var current_hash = iframe.location.hash.replace(/\?.*$/, '');
			if(current_hash != jQuery.historyCurrentHash) {
			
				location.hash = current_hash;
				jQuery.historyCurrentHash = current_hash;
				jQuery.historyCallback(current_hash.replace(/^#/, ''));
				
			}
		} else if (jQuery.browser.safari) {
			if(jQuery.lastHistoryLength == history.length && jQuery.historyBackStack.length > jQuery.lastHistoryLength) {
				jQuery.historyBackStack.shift();
			}
			if (!jQuery.dontCheck) {
				var historyDelta = history.length - jQuery.historyBackStack.length;
				jQuery.lastHistoryLength = history.length;
				
				if (historyDelta) { // back or forward button has been pushed
					jQuery.isFirst = false;
					if (historyDelta < 0) { // back button has been pushed
						// move items to forward stack
						for (var i = 0; i < Math.abs(historyDelta); i++) jQuery.historyForwardStack.unshift(jQuery.historyBackStack.pop());
					} else { // forward button has been pushed
						// move items to back stack
						for (var i = 0; i < historyDelta; i++) jQuery.historyBackStack.push(jQuery.historyForwardStack.shift());
					}
					var cachedHash = jQuery.historyBackStack[jQuery.historyBackStack.length - 1];
					if (cachedHash != undefined) {
						jQuery.historyCurrentHash = location.hash.replace(/\?.*$/, '');
						jQuery.historyCallback(cachedHash);
					}
				} else if (jQuery.historyBackStack[jQuery.historyBackStack.length - 1] == undefined && !jQuery.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 (location.hash) {
						var current_hash = location.hash;
						jQuery.historyCallback(location.hash.replace(/^#/, ''));
					} else {
						var current_hash = '';
						jQuery.historyCallback('');
					}
					jQuery.isFirst = true;
				}
			}
		} else {
			// otherwise, check for location.hash
			var current_hash = location.hash.replace(/\?.*$/, '');
			if(current_hash != jQuery.historyCurrentHash) {
				jQuery.historyCurrentHash = current_hash;
				jQuery.historyCallback(current_hash.replace(/^#/, ''));
			}
		}
	},
	historyLoad: function(hash){
		var newhash;
		hash = decodeURIComponent(hash.replace(/\?.*$/, ''));
		
		if (jQuery.browser.safari) {
			newhash = hash;
		}
		else {
			newhash = '#' + hash;
			location.hash = newhash;
		}
		jQuery.historyCurrentHash = newhash;
		
		if (jQuery.historyNeedIframe) {
			var ihistory = jQuery("#jQuery_history")[0];
			var iframe = ihistory.contentWindow.document;
			iframe.open();
			iframe.close();
			iframe.location.hash = newhash;
			jQuery.lastHistoryLength = history.length;
			jQuery.historyCallback(hash);
		}
		else if (jQuery.browser.safari) {
			jQuery.dontCheck = true;
			// Manually keep track of the history values for Safari
			this.historyAddHistory(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).
			var fn = function() {jQuery.dontCheck = false;};
			window.setTimeout(fn, 200);
			jQuery.historyCallback(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 = newhash;
		}
		else {
		  jQuery.historyCallback(hash);
		}
	}
});





