/* create the namespace */

if (typeof rsh === 'undefined') {
  rsh = {};
}

/* stub constructor for graceful degradation */

rsh.stub = function() {
	return {
		create: function(){}
		, init: function(){}
		, addCallback: function(){}
		, add: function(){}
		, getCurrentHash: function(){}
		, getSerializedStack: function(){}
	};
};

/* object factory for creating a UA-specific instance */

rsh.getInstance = function(forceCompatibility) {
	var i, j, version;
	for (i = this.versions.length - 1, j = 0; i >= j; i--) {
		version = this.versions[i];
		//use the base version in forced compatibility mode
		forceCompatibility = forceCompatibility && j === 0;
		if (version.condition || forceCompatibility) {
			//if the user agent matches a condition from
			//the versions array, construct that version
			return version.konstructor();
		}
	}
	//otherwise return a stub object with stub methods
	return this.stub();
}

/* empty array for additional constructors */
	
rsh.versions = [];

/* base constructor for compliant browsers */

rsh.versions.push({
	handle: 'base'
	, condition: rsh.utils.browser.brand.gecko
	, konstructor: function(options, secrets) {

		//propagate the secrets object up the prototype chain
		secrets = secrets || {};

		//add members to the secrets object using dot notation
		secrets.PAGELOADEDSTRING = "rshPageLoaded";

		//declare new private members
		var that, currentHash, firstLoad, fireListenerWhenReady, listener, history,
		persister, waitTimer, extractHash, getCurrentHash, monitorHashForChanges,
		fireHistoryEvent;

		//define new private members

		/* an object to insert, read from and write to a hidden form field */
		persister = function() {

			var that, formID, fieldID, styles, markup, storageField;

			//create and insert the form field
			formID = "rshStorageForm";
			fieldID = "rshStorageField";
			styles = 'left:-1000px;top:-1000px;width:1px;height:1px;border:0;position:absolute;';
			markup = '<form id="' + formID + '" style="' + styles + '">' + 
				'<input type="text" id="' + fieldID + '" style="' + styles + '" />' +
			'</form>';
			document.write(markup);
			storageField = document.getElementById(fieldID);
			if (typeof window.opera !== "undefined") {
				//Opera needs to focus this element before persisting values in it
				storageField.focus();
			}

			that = {

				write: function(val) {
					storageField.value = val;
				}
				, read: function() {
					return storageField.value;
				}
				, deserialize: function() {
					return rsh.utils.json.from(this.read());
					
				}
			};
			
			return that;

		};

		/*a rudimentary history stack*/
		history = function() {
			var stack = [];

			var that = {
				add: function(val) {
					stack.push(val);
				}
				, remove: function(val) {
					for (var i = 0, j = stack.length; i < j; i++) {
						if (stack[i] === val) {
							stack.splice(i,1);
							return true;
						}
					}
					return false;
				}
				, has: function(val) {
					for (var i = 0, j = stack.length; i < j; i++) {
						if (stack[i] === val) {
							return true;
						}
					}
					return false;
				}
				, serialize: function() {//TODO: name?
					return rsh.utils.json.to(stack);
				}
				, reload: function(arr) {
					stack = arr;
				}
				, lengthOf: function() {
					return stack.length;
				}
			};
			return that;
		}();
		
		/*an object to keep track of the wait time between add requests*/
		waitTimer = function() {
			var unit = 200;
			var current = 0;

			var that = {
				add: function() {
					this.current += this.unit;
				}
				, subtract: function() {
					if (this.current > 0) {
						this.current = this.current - this.unit;
					}
				}
				, getCurrent: function() {
					return current;
				}
			};
			
			return that;
		}();

		/* Returns either the hash portion of a url (minus the # sign) or the entire string */
		extractHash = function(s, returnBlank) {
			if (s && s.match) {
				var m = s.match(/[#]([^#]+)$/);
				return m ? m[1] : returnBlank ? "" : s;
			}
			return "";
		};

		/*Return the current hash or an empty string*/
		getCurrentHash = function() {
			return extractHash(window.location.href, true);
		};

		monitorHashForChanges = function() {
			
			/*need to decode?*/
			var hash = getCurrentHash();

			/*if the hash has changed, save it and fire a history event*/
			if (hash !== currentHash) {
				currentHash = hash;
				fireHistoryEvent(hash);	
			}

		}

		fireHistoryEvent = function(newHash) {
			var decodedHash = decodeURIComponent(newHash)
			listener.call(null, decodedHash);
		}


		that = {
			/**
				Set up the object; this needs to run inline within the body tags
				so that HTML elements can be written to the document body
			*/
			create: function() {
				
				var initialHash, myPersister;
				
				myPersister = persister();
		
				/*Set a handler to clear out firstLoad when we leave the page - needed for FF1.5+*/
				rsh.utils.addListener(window,'unload',function() {
					firstLoad = null;
				});

				/*Get our initial hash and save it*/
				initialHash = getCurrentHash();
				currentHash = initialHash;
				
				/*Either throw a history event or prepare for future history events*/
				firstLoad = !history.has(secrets.PAGELOADEDSTRING);
				console.log("firstLoad = " + firstLoad);
				if (firstLoad) {
					history.add(secrets.PAGELOADEDSTRING);
				} else {
					fireListenerWhenReady = true;
				}
		
				/*Start monitoring hash changes on a cycle*/
				setInterval(monitorHashForChanges, 100);

			},
			/**
				Get the object ready for action; call this on pageload
			*/
			init: function(listener) {




				if (listener) {
					this.addCallback(listener);
				}
			},
			/**
				Set the listener for history events
			*/
			addCallback: function(func) {
				listener = func;
				/*If the page was just loaded and we should not ignore it, fire an event to our new listener now*/
				if (fireListenerWhenReady) {
					fireHistoryEvent(currentHash);
					fireListenerWhenReady = false;
				}
			},
			/**
				Add a history event to the stack
			*/
			add: function(newHash) {
		
				var encodedHash = encodeURIComponent(extractHash(newHash));
		
				var addWhenReady = function() {
			
					/*Decrement the current wait time*/
					waitTimer.subtract();
			
					/*Save this as our current location*/
					currentHash = encodedHash;
			
					/*Change the browser location*/
					window.location.hash = encodedHash;
			
					history.add(encodedHash);
				}
		
				/*Now queue up this add request*/
				window.setTimeout(addWhenReady, waitTimer.getCurrent());

				/*Increment the current wait time*/
				waitTimer.add();
		
			},
			getCurrentHash: function() {
				return getCurrentHash();
			},
			getSerializedStack: function() {
				return history.serialize();
			}

		};


		//Add privileged members to that using dot notation
		//that.whatever = '';

		return that;

	}
	
});

/*
	TODO
		--callback hooks for addons
		--custom events mechanism in adapter for easier callback hooks
		--wrap all method definitions in parens?

*/


