(function() {

	//set the events
	window.store('hashchange:interval',300);
	window.store('hashchange:ieframe-src','./blank.html');
	window.store('hashchange:implemented',!!('onhashchange' in window));

	Element.Events.hashchange = {
		onAdd:function(fn) {
			//clear the event
			Element.Events.hashchange.onAdd = $empty;

			//check the element
			var self = $(this);
			var checker = $empty;
			if(self != $(window)) {
				return; //the window object only supports this
			}

			//this will prevent the browser from firing the url when the page loads (native onhashchange doesn't do this)
			window.store('hashchange:changed',false);

			//this global method gets called when the hash value changes for all browsers
			var hashchanged = function(hash,tostore) {
				window.store('hashchange:current',tostore || hash);
				if(window.retrieve('hashchange:changed')) {
					hash = hash.trim();
					if(hash.length==0) {
						var url = new String(window.location);
						if(url.indexOf('#')>=0)
						hash = '#';
					}
					window.fireEvent('hashchange',[hash]);
				}
				else {
					window.store('hashchange:changed',true);
				}
			};

			//this is used for when a hash change method has already been defined (futureproof)
			if(typeof window.onhashchange == 'function' && fn !== window.onhashchange) {
				//bind the method to the mootools method stack
				window.addEvent('hashchange',window.onhashchange);

				//remove the event
				window.onhashchange = null;
			}

			//Oldschool IE browsers
			if(Browser.Engine.trident4 || Browser.Engine.trident5) { 

				//IE6 and IE7 require an empty frame to relay the change (back and forward buttons)
				//custom IE method
				checker = function(url,frame) {

					//clear the timer
					var checker = window.retrieve('hashchange:checker');
					var timer = window.retrieve('hashchange:timer');
					$clear(timer); //just incase
					timer = null;

					//IE may give a hash value, a path value or a url
					var isNull = frame && url.length == 0;
					var isEmpty = url == '#';
					var hash, compare, cleanurl = unescape(new String(window.location));

					if(isEmpty) {
						compare = hash = '#';
					}
					else if(isNull) {
						compare = hash = '';	
					}
					else {

						//setup the url
						url = url != null ? url : cleanurl;
						hash = url;

						if(url.length>0) { //not an empty hash
							var index = url.indexOf('#');
							if(index>=0)
							hash = url.substr(index);
						}

						//check the hash
						compare = hash.toLowerCase();
					}

					//if the hash value is different, then it has changed
					var current = window.retrieve('hashchange:current');
					if(current != compare) {

						//update the url
						if(frame) {
							url = cleanurl;
							if(current) {
								url = url.replace(current,hash);
							}
							else {
								url += hash;
							}
							window.location = url;
						}

						//check the flag
						var hasChanged = !frame && window.retrieve('hashchange:changed');

						//change the hash
						hashchanged(hash,compare);

						if(hasChanged) {
							//this will prevent the frame from changing the first time
							window.retrieve('hashchange:ieframe').setPath(hash);
						}
					}

					//reset the timer
					timer = checker.delay(window.retrieve('hashchange:interval'));
					window.store('hashchange:timer',timer);

				};

				//create the frame
				var src = window.retrieve('hashchange:ieframe-src');
				var ieframe = new IFrame({
					'id':'hashchange-ie-frame',
					'src':src+'?start',
					'styles':{
						'width':0,
						'height':0,
						'position':'absolute',
						'top':-9999,
						'left':-9999
					},
					'onload':function() {
						//this shouldn't exist when a hash is changed, if it does then the frame has just loaded
						var self = $('hashchange-ie-frame');
						if(self.retrieve('loaded')) {
							//examine the url
							var url = unescape(new String(self.contentWindow.location));
							var index = url.indexOf('?');
							if(index>=0) {
								var path = '', empty = false;
								if(url.indexOf('?empty')>=0) {
									path = '#';
								}
								else {
									index = url.indexOf('?!');
									if(index>=0) {
										path = url.substr(index+2);
										path = '#' + path;
									}
								}
								window.retrieve('hashchange:checker')(path,true);
							}
						}
						else {
							self.store('loaded',true);
						}
					}.bind(window)
				});

				//save the frame
				window.store('hashchange:ieframe',ieframe);
				ieframe.injectInside(document.body);

				var doc = ieframe.contentWindow;
				ieframe.setPath = function(path) {
					if(path.charAt(0)=='#') {
						path = path.substr(1);
						if(path.length==0) {
							this.contentWindow.location = src + '?empty';
							return;
						}
					}
					this.contentWindow.location = src + '?!' + escape(path);
				}.bind(ieframe);
			}
			else if(window.retrieve('hashchange:implemented')) { //Firefox 3.6, Chrome 5, IE8 all support the event natively

				//check the hashcheck
				checker = window.onhashchange = function(hash) {

					//make sure the hash is a string
					hash = hash && typeof hash == 'string' ? hash : new String(window.location.hash);

					//this is important so that the URL hash has changed BEFORE this is fired
					hashchanged.delay(1,window,[hash]);

				}
			}
			else { //Others
				//opera requires a history mode to be set so that #hash values are recorded in history (back and forward buttons)
				if(Browser.Engine.presto) {
					history.navigationMode='compatible';
				}

				//set the inteval method
				checker = function(hash) {

					//clear the timer
					var checker = window.retrieve('hashchange:checker');
					var timer = window.retrieve('hashchange:timer');
					$clear(timer); //just incase
					timer = null;

					//compare the hash
					var hash = hash || new String(window.location.hash);
					var compare = hash.toLowerCase();
					if(hash.length==0 && new String(window.location).indexOf('#')>=0) {
						compare = '#';
					}
					var current = window.retrieve('hashchange:current');
					if(current != compare) {
						hashchanged(hash,compare);
					}

					//reset the timer
					timer = checker.delay(window.retrieve('hashchange:interval'));
					window.store('hashchange:timer',timer);

				}
			}

			//run the loop
			window.store('hashchange:checker',checker);
			checker();

			//setup a custom go event
			window.sethash = function(hash) {
				if(hash.charAt(0)!='#')
				hash = '#' + hash;
				checker(hash);
			}
		},

		onDelete:function() {
			if($(this) == $(window)) {
				var timer = window.retrieve('hashchange:timer');
				if(timer) {
					$clear(timer); timer = null;
					window.store('hashchange:timer',null);
				}
			}
		}
	}

})();