/* 	Class:
		HistoryManager

	Author:
		Neil Jenkins - http://www.nmjenkins.com
		
	Version:
		2.0 (2008-08-23)
		
	Version history:
		2.0  Update to mootools 1.2 and rewrite IE handling to be more robust.
			 Also includes small API name change and a change to the hashes used
			 for each state, so is not 100% compatible with the older versions,
			 hence the new version number.
		1.21 Fix IE quoting bug.
		1.2  Clean up code to make better use of Mootools framework
		1.1  Update to allow IE to keep its history even when navigating to a different site and back.
		1.0  Initial release
		
	License:
		GNU GPL 2.0: http://creativecommons.org/licenses/GPL/2.0/
		
	Description:
		Javascript class for restoring use of the back/forward buttons to web pages that are completely
		dynamic and therefore don't actually navigate to different pages.
		
	Usage:
		Calling new HistoryManager() returns an instance of the History Manager
		e.g. var h = new HistoryManager();
		This should be done AFTER the DOM has loaded for compatibility with IE;
		the mootools 'domready' event is useful for this. 

		Public interfaces:

		addState(String: hash)
			This method creates a new history state in the browser (as though a link has been clicked)
			and also sets the location hash to the supplied argument to allow for bookmarking.

			The hash is expected to be a vaild URI hash component; the global function encodeURI() is useful for
			this. Encoding the state of a javascript program into a string is very much specific to each program therefore
			no processing is done by this module; it is left to the subscribing functions to encode and parse the state.
			
			e.g. h.addState('tab3');
		
		addEvent(String: event, Function: callbackFunction)
			This method subscribes functions to be called when the history state changes.
			NB The only event currently available is 'onHistoryChange'.
			   Functions subscribed to this event will be called with the hash of the new state as their argument.
			e.g. h.addEvent('onHistoryChange', functionToCall);
		
		removeEvent(String: event, Function: callbackFunction)
			This method removes functions subscribed to the HistoryManager by the addEvent method		
			e.g. h.removeEvent('onHistoryChange', functionToRemove);

		getCurrentHash()
			Returns the current hash.
			e.g. var state = h.getCurrentHash();

	Dependencies:
		mootools 1.2: http://mootools.net

	Notes:
		This is a singleton; there can only ever be one instance of the class. Calling new HistoryManger() for a second time
		will simply return a reference to the current instance.
		Supports Gecko, Safari 2+, Opera 9+ and IE6+
*/

var HistoryManager = (function() {

	var HistoryManagerSingleton = new Class({
		
		Implements: Events,
		
		initialize: function() {
			// Store initial location
			this._currentLocation = this._getHash();
			
			// Internet Explorer 6 & 7
			if (Browser.Engine.trident && Browser.Engine.version <= 5) {
				this._iframe = new IFrame({
					src: "javascript:'<html><body>" + this._currentLocation.replace(/(['"])/g, '\\$1') + "</body></html>'",
					styles: { display: 'none' }
				}).inject(document.body).contentWindow;

				this.addState = this._addStateIE;				
				this._monitorIE.periodical(200, this);
			}
			// Safari 2
			else if (Browser.Engine.webkit419) {
				this._form = new Element("form", {method: 'get'}).inject(document.body);
				this._historyCounter = history.length;
				this._stateHistory = [];
				this._stateHistory[history.length] = this._getHash();
				
				this.addState = this._addStateSafari;
				this._monitorSafari.periodical(200, this);
			}
			// Opera 9.25
			else if (Browser.Engine.presto925) {
				this.addState = this._addStateDefault;
	
				window.$justForOpera = this._monitorDefault.bind(this);
				new Element('img', {
					src: "javascript:location.href='javascript:$justForOpera();';",
					style: "position: absolute; top: -1000px;"
				}).inject(document.body);
			}
			// Everything else
			else {
				this.addState = this._addStateDefault;
				this._monitorDefault.periodical(200, this);
			}
		},
		
		getCurrentHash: function() {
			return this._currentLocation;
		},
		
		// All history manager hashes will have a / as the first char to work around
		// an IE bug whereby it will add changes to location.hash to the history
		// stack if there is an element of that id in the page. ID's cannot contain
		// the forward slash character therefore if we start every hash with that
		// there will never be a collisiton. Hence slice off the first char.
		
		// Use location.href because location.hash is already URI decoded, which may cause
		// problems with encoding/decoding functions.
		
		_getHash: function() {
			return top.location.href.indexOf("#") > -1 ? top.location.href.split('#')[1].slice(1) : '';
		},
		
		_addStateIE: function(hash) {
			if (this._currentLocation != hash) {
				this._currentLocation = hash;
				top.location.hash = "#/" + hash;

				this._iframe.document.open('text/html');
				this._iframe.document.write('<html><body>', hash, '</body></html>');
				this._iframe.document.close();
			}
			return this;			
		},
		
		_monitorIE: function() {
			var hash = this._iframe.document.body.innerText;
			
			if (hash != this._currentLocation) {
				top.location.hash = "#/" + hash;
				this._currentLocation = hash;
				// this.fireEvent('onHistoryChange', [hash]);
			}
		},
		
		_addStateSafari: function(hash) {
			if (this._currentLocation != hash) {
				this._form.set('action', '#/' + hash).submit()
				this._currentLocation = hash;
				this._stateHistory[history.length] = this._getHash();
				this._historyCounter = history.length;
			}
			return this;		
		},
	
		_monitorSafari: function() {
			if (history.length != this._historyCounter) {
				this._historyCounter = history.length;
				this._currentLocation = this._stateHistory[history.length];
				// this.fireEvent('onHistoryChange', [this._currentLocation]);
			}
		},
	
		_addStateDefault: function(hash) {
			if (this._currentLocation != hash) {
				top.location.hash = '#/' + hash;
				this._currentLocation = hash;
			}
			return this;			
		},
	
		_monitorDefault: function() {
			var hash = this._getHash();

			if (hash != this._currentLocation) {
				this._currentLocation = hash;
			// 	this.fireEvent('onHistoryChange', [hash]);
			}
		}
	});
		
	var singleton;

	return function() {
		return singleton || (singleton = new HistoryManagerSingleton());
	}
	
})();

/*
Script: Browser.js
    The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash.

License:
    MIT-style license.
*/

var Browser = $merge({

    Engine: {name: 'unknown', version: 0},

    Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()},

    Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},

    Plugins: {},

    Engines: {

        presto: function(){
            return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
        },

        trident: function(){
            return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? 5 : 4);
        },

        webkit: function(){
            return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
        },

        gecko: function(){
            return (document.getBoxObjectFor == undefined) ? false : ((document.getElementsByClassName) ? 19 : 18);
        }

    }

}, Browser || {});


