window.dhtmlHistory = {

	isIE: false,
	isOpera: false,
	isSafari: false,
	isKonquerer: false,
	isGecko: false,
	isSupported: false,
	isChrome: false,

	create: function(options) {
		var that = this;
		var UA = navigator.userAgent.toLowerCase();
		var platform = navigator.platform.toLowerCase();
		var vendor = navigator.vendor || "";
		if (vendor === "KDE") {
			this.isKonqueror = true;
			this.isSupported = false;
		} else if (typeof window.opera !== "undefined") {
			this.isOpera = true;
			this.isSupported = true;
		} else if (typeof document.all !== "undefined") {
			this.isIE = true;
			this.isSupported = true;
		} else if (vendor.indexOf("Apple Computer, Inc.") > -1 && UA.indexOf("Version//") < 0) {
			this.isSafari = true;
			this.isSupported = platform.indexOf("mac") > -1;
		} else if (vendor.indexOf("Apple Computer, Inc.") > -1 && UA.indexOf("Version//") > -1) {
			this.isGecko = true;
			this.isSupported = true;
		} else if (UA.indexOf("chrome") > -1) {
			this.isGecko = true;
			this.isSupported = true;
			this.isChrome = true;
		} else if (UA.indexOf("gecko") != -1) {
			this.isGecko = true;
			this.isSupported = true;
		}

		window.historyStorage.setup(options);

		if (this.isSafari) {
			this.createSafari();
		} else if (this.isOpera) {
			this.createOpera();
		}


		var initialHash = this.getCurrentLocation();

		this.currentLocation = initialHash;

		if (this.isIE) {
			this.createIE(initialHash);
		}
		var unloadHandler = function() {
			that.firstLoad = null;
		};
		this.addEventListener(window, 'unload', unloadHandler);

		if (this.isIE) {

			this.ignoreLocationChange = true;
		} else {
			if (!historyStorage.hasKey(this.PAGELOADEDSTRING)) {

				this.ignoreLocationChange = true;
				this.firstLoad = true;
				historyStorage.put(this.PAGELOADEDSTRING, true);
			} else {

				this.ignoreLocationChange = false;
				this.fireOnNewListener = true;
			}
		}
		var locationHandler = function() {
			that.checkLocation();
		};
		setInterval(locationHandler, 100);
	},


	initialize: function() {

		if (this.isIE) {

			if (!historyStorage.hasKey(this.PAGELOADEDSTRING)) {

				this.fireOnNewListener = false;
				this.firstLoad = true;
				historyStorage.put(this.PAGELOADEDSTRING, true);
			}

			else {
				this.fireOnNewListener = true;
				this.firstLoad = false;
			}
		}
	},

	addListener: function(listener) {
		this.listener = listener;

		if (this.fireOnNewListener) {
			this.fireHistoryEvent(this.currentLocation);
			this.fireOnNewListener = false;
		}
	},


	addEventListener: function(o, e, l) {
		if (o.addEventListener) {
			o.addEventListener(e, l, false);
		} else if (o.attachEvent) {
			o.attachEvent('on' + e, function() {
				l(window.event);
			});
		}
	},


	add: function(newLocation, historyData) {

		if (this.isSafari) {


			newLocation = this.removeHash(newLocation);

			historyStorage.put(newLocation, historyData);

			this.currentLocation = newLocation;


			window.location.hash = newLocation;


			this.putSafariState(newLocation);
		} else {

			var that = this;
			var addImpl = function() {

				if (that.currentWaitTime > 0) {
					that.currentWaitTime = that.currentWaitTime - that.waitTime;
				}

				newLocation = that.removeHash(newLocation);

				if (document.getElementById(newLocation) && that.debugMode) {
					var e = "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;
					throw new Error(e);
				}

				historyStorage.put(newLocation, historyData);
				that.ignoreLocationChange = true;
				that.ieAtomicLocationChange = true;
				that.currentLocation = newLocation;
				window.location.hash = newLocation;

				if (that.isIE) {
					that.iframe.src = "blank.html?" + newLocation;
				}
				that.ieAtomicLocationChange = false;
			};

			window.setTimeout(addImpl, this.currentWaitTime);

			this.currentWaitTime = this.currentWaitTime + this.waitTime;
		}
	},

	isFirstLoad: function() {
		return this.firstLoad;
	},

	getVersion: function() {
		return "0.6";
	},
	getCurrentLocation: function() {
		var r = (this.isSafari
			? this.getSafariState()
			: this.getCurrentHash()
		);
		return r;
	},

	getCurrentHash: function() {
		var r = window.location.href;
		var i = r.indexOf("#");
		return (i >= 0
			? r.substr(i + 1)
			: ""
		);
	},
	PAGELOADEDSTRING: "DhtmlHistory_pageLoaded",
	listener: null,
	waitTime: 200,
	currentWaitTime: 0,
	currentLocation: null,
	iframe: null,
	safariHistoryStartPoint: null,
	safariStack: null,
	safariLength: null,
	ignoreLocationChange: null,
	fireOnNewListener: null,
	firstLoad: null,
	ieAtomicLocationChange: null,

	createIE: function(initialHash) {

		this.waitTime = 400;
		var styles = (historyStorage.debugMode
			? 'width: 800px;height:80px;border:1px solid black;'
			: historyStorage.hideStyles
		);
		var iframeID = "rshHistoryFrame";
		var iframeHTML = '<iframe frameborder="0" id="' + iframeID + '" style="' + styles + '" src="blank.html?' + initialHash + '"></iframe>';
		document.write(iframeHTML);
		this.iframe = document.getElementById(iframeID);
	},


	createOpera: function() {
		this.waitTime = 400;
		var imgHTML = '<img src="javascript:location.href=\'javascript:dhtmlHistory.checkLocation();\';" style="' + historyStorage.hideStyles + '" />';
		document.write(imgHTML);
	},


	createSafari: function() {
		var formID = "rshSafariForm";
		var stackID = "rshSafariStack";
		var lengthID = "rshSafariLength";
		var formStyles = historyStorage.debugMode ? historyStorage.showStyles : historyStorage.hideStyles;
		var inputStyles = (historyStorage.debugMode
			? 'width:800px;height:20px;border:1px solid black;margin:0;padding:0;'
			: historyStorage.hideStyles
		);
		var safariHTML = '<form id="' + formID + '" style="' + formStyles + '">'
			+ '<input type="text" style="' + inputStyles + '" id="' + stackID + '" value="[]"/>'
			+ '<input type="text" style="' + inputStyles + '" id="' + lengthID + '" value=""/>'
		+ '</form>';
		document.write(safariHTML);
		this.safariStack = document.getElementById(stackID);
		this.safariLength = document.getElementById(lengthID);
		if (!historyStorage.hasKey(this.PAGELOADEDSTRING)) {
			this.safariHistoryStartPoint = history.length;
			this.safariLength.value = this.safariHistoryStartPoint;
		} else {
			this.safariHistoryStartPoint = this.safariLength.value;
		}
	},


	getSafariStack: function() {
		var r = this.safariStack.value;
		return historyStorage.fromJSON(r);
	},

	getSafariState: function() {
		var stack = this.getSafariStack();
		var state = stack[history.length - this.safariHistoryStartPoint - 1];
		return state;
	},

	putSafariState: function(newLocation) {
		var stack = this.getSafariStack();
		stack[history.length - this.safariHistoryStartPoint] = newLocation;
		this.safariStack.value = historyStorage.toJSON(stack);
	},

	fireHistoryEvent: function(newHash) {

		var historyData = historyStorage.get(newHash);

		this.listener.call(null, newHash, historyData);
	},

	/*Private: See if the browser has changed location. This is the primary history mechanism for Firefox. For IE, we use this to
	handle an important edge case: if a user manually types in a new hash value into their IE location bar and press enter, we want to
	to intercept this and notify any history listener.*/
	checkLocation: function() {


		if (!this.isIE && this.ignoreLocationChange) {
			this.ignoreLocationChange = false;
			return;
		}

		if (!this.isIE && this.ieAtomicLocationChange) {
			return;
		}


		var hash = this.getCurrentLocation();

		if (hash == this.currentLocation) {
			return;
		}
		/*In IE, users manually entering locations into the browser; we do this by comparing the browser's location against the
		iframe's location; if they differ, we are dealing with a manual event and need to place it inside our history, otherwise
		we can return*/
		this.ieAtomicLocationChange = true;
		if (this.isIE && this.getIframeHash() != hash) {
			this.iframe.src = "blank.html?" + hash;
		}
		else if (this.isIE) {

			return;
		}

		this.currentLocation = hash;
		this.ieAtomicLocationChange = false;

		this.fireHistoryEvent(hash);
	},

	getIframeHash: function() {
		var doc = this.iframe.contentWindow.document;
		var hash = String(doc.location.search);
		if (hash.length == 1 && hash.charAt(0) == "?") {
			hash = "";
		}
		else if (hash.length >= 2 && hash.charAt(0) == "?") {
			hash = hash.substring(1);
		}
		return hash;
	},

	removeHash: function(hashValue) {
		var r;
		if (hashValue === null || hashValue === undefined) {
			r = null;
		}
		else if (hashValue === "") {
			r = "";
		}
		else if (hashValue.length == 1 && hashValue.charAt(0) == "#") {
			r = "";
		}
		else if (hashValue.length > 1 && hashValue.charAt(0) == "#") {
			r = hashValue.substring(1);
		}
		else {
			r = hashValue;
		}
		return r;
	},

	iframeLoaded: function(newLocation) {

		if (this.ignoreLocationChange) {
			this.ignoreLocationChange = false;
			return;
		}

		var hash = String(newLocation.search);
		if (hash.length == 1 && hash.charAt(0) == "?") {
			hash = "";
		}
		else if (hash.length >= 2 && hash.charAt(0) == "?") {
			hash = hash.substring(1);
		}

		window.location.hash = hash;

		this.fireHistoryEvent(hash);
	}
};
/*
	historyStorage: An object that uses a hidden form to store history state across page loads. The mechanism for doing so relies on
	the fact that browsers save the text in form data for the life of the browser session, which means the text is still there when
	the user navigates back to the page. This object can be used independently of the dhtmlHistory object for caching of Ajax
	session information.
	
	dependencies: 
		* json2007.js (included in a separate file) or alternate JSON methods passed in through an options bundle.
*/
window.historyStorage = {
	
	
	setup: function(options) {
		
		/*
			options - object to store initialization parameters - passed in from dhtmlHistory or directly into historyStorage
			options.debugMode - boolean that causes hidden form fields to be shown for development purposes.
			options.toJSON - function to override default JSON stringifier
			options.fromJSON - function to override default JSON parser
		*/
		
		
		if (typeof options !== "undefined") {
			if (options.debugMode) {
				this.debugMode = options.debugMode;
			}
			if (options.toJSON) {
				this.toJSON = options.toJSON;
			}
			if (options.fromJSON) {
				this.fromJSON = options.fromJSON;
			}
		}		
		
		
		var formID = "rshStorageForm";
		var textareaID = "rshStorageField";
		var formStyles = this.debugMode ? historyStorage.showStyles : historyStorage.hideStyles;
		var textareaStyles = (historyStorage.debugMode
			? 'width: 800px;height:80px;border:1px solid black;'
			: historyStorage.hideStyles
		);
		var textareaHTML = '<form id="' + formID + '" style="' + formStyles + '">'
			+ '<textarea id="' + textareaID + '" style="' + textareaStyles + '"></textarea>'
		+ '</form>';
		document.write(textareaHTML);
		this.storageField = document.getElementById(textareaID);
		if (typeof window.opera !== "undefined") {
			this.storageField.focus();
		}
	},
	
	
	put: function(key, value) {
		this.assertValidKey(key);
		
		if (this.hasKey(key)) {
			this.remove(key);
		}
		
		this.storageHash[key] = value;
		
		this.saveHashTable();
	},
	
	get: function(key) {
		this.assertValidKey(key);
		
		this.loadHashTable();
		var value = this.storageHash[key];
		if (value === undefined) {
			value = null;
		}
		return value;
	},
	
	remove: function(key) {
		this.assertValidKey(key);
		
		this.loadHashTable();
		
		delete this.storageHash[key];
		
		this.saveHashTable();
	},
	
	reset: function() {
		this.storageField.value = "";
		this.storageHash = {};
	},
	
	hasKey: function(key) {
		this.assertValidKey(key);
		
		this.loadHashTable();
		return (typeof this.storageHash[key] !== "undefined");
	},
	
	isValidKey: function(key) {
		return (typeof key === "string");
	},
	
	
	showStyles: 'border:0;margin:0;padding:0;',
	hideStyles: 'left:-1000px;top:-1000px;width:1px;height:1px;border:0;position:absolute;',
	
	
	debugMode: false,
	
	
	
	storageHash: {},
	
	hashLoaded: false, 
	
	storageField: null,
	
	assertValidKey: function(key) {
		var isValid = this.isValidKey(key);
		if (!isValid && this.debugMode) {
			throw new Error("Please provide a valid key for window.historyStorage. Invalid key = " + key + ".");
		}
	},
	
	loadHashTable: function() {
		if (!this.hashLoaded) {	
			var serializedHashTable = this.storageField.value;
			if (serializedHashTable !== "" && serializedHashTable !== null) {
				this.storageHash = this.fromJSON(serializedHashTable);
				this.hashLoaded = true;
			}
		}
	},
	
	saveHashTable: function() {
		this.loadHashTable();
		var serializedHashTable = this.toJSON(this.storageHash);
		this.storageField.value = serializedHashTable;
	},
	
	toJSON: function(o) {
		return o.toJSONString();
	},
	fromJSON: function(s) {
		return s.parseJSON();
	}
};
