/*
---
description: A Class that provides a cross-browser history-management functionaility, using the browser hash to store the application's state

license: MIT-style

authors:
- Arieh Glazer
- Dave De Vos
- Digitarald

requires:
- core/1.2.4: Class
- core/1.2.4: Class.Extras
- core/1.2.4: Element

provides: [HashListner]

...
*/

/*
 * modified by Robert.Schulze@dotless.de :
 *  - return `this` in [[HashListener#start]] and [[HashListener#stop]] for a fluent API
 *  - adding an mechanism, to let async processes (event driven) block und unblock the (real) startup of the  lister
 *  - adds an option `pollInterval` for the default 100ms to poll the hash state
 *  - fix some jsLint concerns
 */
var HashListener = new Class({
	Implements : [Options,Events],
	options : {
		blank_page : 'blank.html',
		start : false,
		pollInterval: 100
	},
	iframe : null,
	currentHash : '',
	firstLoad : true,
	handle : false,
	useIframe : (Browser.Engine.trident && (typeof(document.documentMode)=='undefined' || document.documentMode < 8)),
	ignoreLocationChange : false,
	startBlockers: null,
	initialize : function(options){
		var s_f, 
			self=this,
			cached_storage;
			
		this.setOptions(options);
		
		this.startBlockers = new Hash();
		
		// Disable Opera's fast back/forward navigation mode
		if (Browser.Engine.presto && window.history.navigationMode) {
			window.history.navigationMode = 'compatible';
		}

		
		 // IE8 in IE7 mode defines window.onhashchange, but never fires it...
        if (window.onhashchange && (typeof(document.documentMode) == 'undefined' || document.documentMode > 7)) {
				// The HTML5 way of handling DHTML history...
				window.onhashchange = function () {
					var hash = self.getHash();
					if (hash == self.currentHash) {
						return;
					}
					self.fireEvent('hash-changed',hash);
				};
        } else  {
			if (this.useIframe){
				this.initializeHistoryIframe();
			} 
        } 
		
		window.addEvent('unload', function(event) {
			self.firstLoad = null;
		});
		
		if (this.options.start) this.start();
	},
	initializeHistoryIframe : function(){
		var hash = this.getHash(), self = this, doc;
		this.iframe = new IFrame({
			src		: this.options.blank_page,
			styles	: { 
				'position'	: 'absolute',
				'top'		: 0,
				'left'		: 0,
				'width'		: '1px', 
				'height'	: '1px',
				'visibility': 'hidden'
			}
		}).inject(document.body);
    	
		doc	= (this.iframe.contentDocument) ? this.iframe.contentDocumnet  : this.iframe.contentWindow.document;
		doc.open();
		doc.write('<html><body id="state">' + hash + '</body></html>');
		doc.close();
		return;
	},
	checkHash : function(){
		var hash = this.getHash(), ie_state, doc;
		if (this.ignoreLocationChange) {
			this.ignoreLocationChange = false;
			return;
		}

		if (this.useIframe){
			doc	= (this.iframe.contentDocument) ? this.iframe.contentDocumnet  : this.iframe.contentWindow.document;
			ie_state = doc.body.innerHTML;
			
			if (ie_state!=hash){
				this.setHash(ie_state);
				hash = ie_state;
			} 
		}		
		
		if (this.currentLocation == hash) {
			return;
		}
		
		this.currentLocation = hash;
		
		this.fireEvent('hash-changed',hash);
	},
	setHash : function(newHash){
		window.location.hash = this.currentLocation = newHash;
		
		this.fireEvent('hash-changed',newHash);
	},
	getHash : function(){
		var m;
		if (Browser.Engine.geko){
			m = /#(.*)$/.exec(window.location.href);
			return m && m[1] ? m[1] : '';
		}else{
			return window.location.hash.substr(1);
		}
	},
	setIframeHash: function(newHash) {
		var doc	= (this.iframe.contentDocument) ? this.iframe.contentDocumnet  : this.iframe.contentWindow.document;
		doc.open();
		doc.write('<html><body id="state">' + newHash + '</body></html>');
		doc.close();
	},
	updateHash : function (newHash){
		if ($type(document.id(newHash))) {
			this.debug_msg("Exception: History locations can not have the same value as _any_ IDs that might be in the document," +
				" due to a bug in IE; please ask the developer to choose a history location that does not match any HTML" +
				" IDs in this document. The following ID is already taken and cannot be a location: " + newLocation);
		}
		
		this.ignoreLocationChange = true;
		
		if (this.useIframe) this.setIframeHash(newHash);
		else this.setHash(newHash);
	},
	start : function() {
		this.startupCheck = this.doStart.periodical(this.options.pollInterval, this);
		return this;
	},
	doStart: function () {
		if (false === this.isStartBlocked()) {
			$clear(this.startupCheck);
			this.handle = this.checkHash.periodical(this.options.pollInterval, this);
		}
	},
	stop : function(){
		$clear(this.handle);
		return this;
	},
	blockStart: function (forKey) {
		// prefix, to get not into trubble with reserved words
		this.startBlockers.set('block:'+forKey, true);
	},
	unblockStart: function (forKey) {
		// prefix, to get not into trubble with reserved words
		this.startBlockers.erase('block:'+forKey);
	},
	isStartBlocked: function () {
		return (0 < this.startBlockers.getLength());
	}
});
