Array.prototype.inArray = function (value) {
	var i;
	for (i=0; i < this.length; i++) {
		if (this[i] === value) {
			return i;
		}
	}
	return -1;
};

function StateMachine() {
	/*
	this.historyflag
		Denotes whether or not the user is navigating with the step function.

	this.buildstring
		This is the present state of the StateMachine at any given moment. It may or may not match what is presently in the user's location bar. It is used to keep track of the proposed state until that state is committed.

	this.hash
		This is an array that contains a list of all states that have existed to this point in chronological order. States that occurred in the future are always overwritten if not navigated with backward and forward.

	this.pointer
		This is the index within this.hash which is presently focused.
	*/

	this.ignore = new Array("section");

	this.historyflag = false;
	this.buildstring = this.getHash();
	this.hash = new Array();
	this.pointer = this.hash.length;
	this.hash[this.pointer] = this.buildstring;
}

	StateMachine.prototype.getHash = function () {
		/*
		This function should not be called except in the constructor for this class, and in the class that manages polling for changes.
		This grabs the hash value as it actually appears in the location bar.
		To access the hash values as you need them,
		*/
		var buildstring = window.location.hash.replace(/^#/,"");
		if (buildstring.length > 0 && buildstring.charAt(buildstring.length-1) != "&") {
			buildstring = buildstring + '&';
		}

		return buildstring;
	}

	StateMachine.prototype.process = function(suppliedhash) {
		/*
		If the hash in the location bar does not match what we presently believe to be the history state, we need to store it.
		1. Increment the pointer, we're getting ready to add a new history state to the hash array.
		2. Add that history state. (Set from an immutable value passed to the function).
		3. Reset the buildstring so you don't end up in a scenario where the values are too far divergent.
		4. In case they had stepped back in the history, lose all future history states, you've bifucarated.
		*/
		if (!this.historyflag) { // They're navigating without the step function
			this.pointer++;
			this.hash[this.pointer] = suppliedhash;
			this.buildstring = suppliedhash;
			this.hash.length = this.pointer + 1;
		} else { // They're navigating with the step function
			this.historyflag = false;
		}
	}

	StateMachine.prototype.commit = function (passthrough) {
		/*
		WARNING! STATE CHANGES ARE MODELED AFTER RDBMS TRANSACTIONS!
		NO ACTUAL CHANGES ARE MADE TO THE STATE UNTIL THIS FUNCTION IS CALLED!

		Takes a passthrough value to help avoid race conditions in scenarios when using the step function.
		Set the hash to the string being built, avoiding a situation where it could be an empty string (causes problems in Safari).
		*/

		if (passthrough) { this.buildstring = passthrough; }

		if (this.buildstring.length > 0) {
			window.location.hash = this.buildstring;
		} else {
			window.location.hash = "#";
		}

	}

	StateMachine.prototype.rollback = function () {
		/*
		WARNING! STATE CHANGES ARE MODELED AFTER RDBMS TRANSACTIONS!
		UNTIL A COMMIT IS MADE YOU CAN CALL THIS FUNCTION TO REVERT TO THE LAST SET STATE.
		*/

		this.buildstring = this.hash[this.pointer];
		return this.buildstring;
	}

	StateMachine.prototype.step = function (distance) {
		/* Allows you to step backward and forward in time--within the known history. */
		this.historyflag = true;

		var upperbound = this.hash.length - 1;

		if (this.pointer + distance > 0) {
			if (this.pointer + distance > upperbound) {
				this.pointer = upperbound;
			} else {
				this.pointer += distance;
			}
		} else {
			this.pointer = 0;
		}

		this.commit(this.hash[this.pointer]);
	}

	StateMachine.prototype.hashBuilder = function (variable, value) {
		if (this.buildstring.indexOf(variable) != -1) {
			var regex = "/"+variable+"=.*?&/";
			return this.buildstring.replace(eval(regex), (variable+"=" + value + "&"));
		} else {
			return this.buildstring + variable + "=" + value +"&";
		}
	}

	StateMachine.prototype.setHashValue = function (variable,value) {
		/* Sets a single value in the buildstring. Also called by setHashMultiple. */
		if (variable == '') return;

		if (this.buildstring.indexOf(variable) != -1) {
			var regex = "/"+variable+"=.*?&/";
			this.buildstring = this.buildstring.replace(eval(regex), (variable+"=" + encodeURIComponent(value) + "&"));
		} else {
			this.buildstring = this.buildstring + variable + "=" + encodeURIComponent(value) +"&";
		}
	}

	StateMachine.prototype.setHashMultiple = function (data) {
		/*
		You may pass either an object or a string to be stored in the hash.
			Objects must be  of the associative array type: { label: value, label: value }
			Strings must be of this format: [#,?]label=value[&amp;,%26,&]label=value[&amp;,%26,&]

		With the string method it allows for easily passing an anchor tag's href into the StateMachine so that the script doesn't need to do any processing of values before handing it off to StateMachine.

		The object handling method is appropriate when used in conjunction with another function or a state already saved in an object to be loaded at a later point.

		*/

		switch (typeof(data)) {
			case "object":
				for (var x in data) { this.setHashValue(x,data[x]); }
				return this.buildstring;
			break;
			case "string":
				data = data.replace(/^[\?\#]/,"");
				
				var stringstotry = new Array("&", "%26", "&amp;");
				for (var i = 0; i<stringstotry.length; i++) {
					if (data.indexOf(stringstotry[i]) != -1) {
						var resultindex = i;
					}
				}				
				var pairs = data.split(stringstotry[resultindex]);

				for (var i=0; i < pairs.length; i++) {
					var temp = pairs[i].split('=');
					this.setHashValue(temp[0],temp[1]);
				}
				return this.buildstring;
			break;
		}

		return false;
	}

	StateMachine.prototype.unsetHashValue = function (variable) {
		/* Unsets a single value in the buildstring. Also called by unsetHashMultiple. */
		if (this.buildstring.indexOf(variable) != -1) {
			var regex = "/"+variable+"=.*?&/";
			this.buildstring = this.buildstring.replace(eval(regex), "");

			return this.buildstring;
		}

		return false;
	}

	StateMachine.prototype.unsetHashMultiple = function (data) {
		/*
		I cannot find a reason that the values you will need to unset in the hash will be stored in a querystring format. As such, this function only takes arguments of type array.
		*/
		switch (data.constructor.toLowerCase) {
			case "array":
				for (var i = 0; i < data.length; i++) { this.unsetHashValue(data[i]); }
				return this.buildstring;
			break;
		}
		return false;
	}

	StateMachine.prototype.resetHash = function () {
		this.buildstring = "";
	}

	/* In the event that you need to access a possible specific state value. */
	StateMachine.prototype.getHashValue = function (variable) { return this.getAgnosticValue(this.hash[this.pointer], variable); }
	StateMachine.prototype.getLocationHashValue = function (variable) { return this.getAgnosticValue(window.location.hash, variable); }
	StateMachine.prototype.getBuildstringValue = function (variable) { return this.getAgnosticValue(this.buildstring, variable); }
	StateMachine.prototype.getAgnosticValue = function (actingstring, variable) {
		if (actingstring.indexOf(variable) != -1) {
			var regex = "/(.*)("+variable+"=)(.*?)(&.*)/";
			return actingstring.replace(eval(regex), "$3");
		} else {
			return '';
		}
	}

	StateMachine.prototype.getHashObject = function (variable) { return this.getAgnosticObject(this.hash[this.pointer], variable); }
	StateMachine.prototype.getLocationObject = function (variable) { return this.getAgnosticObject(window.location.hash, variable); }
	StateMachine.prototype.getBuildstringObject = function (variable) { return this.getAgnosticObject(this.buildstring, variable); }
	StateMachine.prototype.getAgnosticObject = function (actingstring, variable) {
		if (actingstring.length > 0) {
			var pairs = actingstring.split("&");
			var objectstring = "";
			for (var i = 0; i < pairs.length - 1; i++) {
				var tempstring = pairs[i].split("=");
				objectstring += tempstring[0] + ": \"" + tempstring[1] + "\", ";
			}
			objectstring = objectstring.substring(0, objectstring.length - 2);
			return eval("({" + objectstring + "})");
		}
	}

	StateMachine.prototype.getBuildstring = function() { return this.buildstring; }
	StateMachine.prototype.toString = function() { return this.hash[this.pointer]; }



/*
Timer by Algorithm
http://www.codingforums.com/archive/index.php/t-10531.html
*/
function Timer(){
	this.obj = (arguments.length)?arguments[0]:window;
	return this;
}
	// The set functions should be called with:
	// - The name of the object method (as a string) (required)
	// - The millisecond delay (required)
	// - Any number of extra arguments, which will all be
	// passed to the method when it is evaluated.

	Timer.prototype.setInterval = function(func, msec){
		var i = Timer.getNew();
		var t = Timer.buildCall(this.obj, i, arguments);
		Timer.set[i].timer = window.setInterval(t,msec);
		return i;
	}
	Timer.prototype.setTimeout = function(func, msec){
		var i = Timer.getNew();
		Timer.buildCall(this.obj, i, arguments);
		Timer.set[i].timer = window.setTimeout("Timer.callOnce("+i+");",msec);
		return i;
	}

	// The clear functions should be called with
	// the return value from the equivalent set function.
	Timer.prototype.clearInterval = function(i){
		if(!Timer.set[i]) return;
		window.clearInterval(Timer.set[i].timer);
		Timer.set[i] = null;
	}
	Timer.prototype.clearTimeout = function(i){
		if(!Timer.set[i]) return;
		window.clearTimeout(Timer.set[i].timer);
		Timer.set[i] = null;
	}

	// Private data
	Timer.set = new Array();
	Timer.buildCall = function(obj, i, args){
		var t = "";
		Timer.set[i] = new Array();
		if(obj != window){
			Timer.set[i].obj = obj;
			t = "Timer.set["+i+"].obj.";
		}
		t += args[0]+"(";
		if(args.length > 2){
			Timer.set[i][0] = args[2];
			t += "Timer.set["+i+"][0]";
			for(var j=1; (j+2)<args.length; j++){
				Timer.set[i][j] = args[j+2];
				t += ", Timer.set["+i+"]["+j+"]";
			}
		}
		t += ");";
		Timer.set[i].call = t;
		return t;
	}
	Timer.callOnce = function(i){
		if(!Timer.set[i]) return;
		eval(Timer.set[i].call);
		Timer.set[i] = null;
	}
	Timer.getNew = function(){
		var i = 0;
		while(Timer.set[i]) i++;
		return i;
	}



function ViewManager(StateMachine) {
	this.StateMachine = (StateMachine ? StateMachine : new StateMachine());
	this.views = new Array();

	// The polling timer isn't actually initialized until ViewManager.refresh() is called the first time.
	// This happens in the onload event for the window object.
	this.timer = new Timer(this);
}

	ViewManager.prototype.poll = function () {
		// Polls the location bar for changes
		// If the current location bar hash does not match the last stored hash value
		// 1. Store the current hash.
		// 2. Update the "future hash" (buildstring) to reflect that change.
		// 3. Refresh the page, don't keep polling.
		// Else, keep polling.

		var tempval = this.StateMachine.getHash();
		var tempval2 = this.StateMachine.toString();

		for (var i=0; i < this.StateMachine.ignore.length; i++) {
			if (tempval.indexOf(this.StateMachine.ignore[i]) != -1 || tempval2.indexOf(this.StateMachine.ignore[i]) != -1) {
				var regex = "/"+this.StateMachine.ignore[i]+"=.*?&/";
				tempval = tempval.replace(eval(regex), "");
				tempval2 = tempval2.replace(eval(regex), "");
			}
		}

		if (!this.StateMachine.historyflag && tempval != tempval2) {
			this.StateMachine.process(tempval);
			this.refresh();
		} else {
			this.timer.setTimeout("poll", 100, "");
		}
	}

	ViewManager.prototype.refresh = function () {
		getAJAXresponse(this.StateMachine);
		//getAJAXresponse("all=1&" + this.StateMachine);

		// Restart polling now that the refresh is completed.
		this.timer.setTimeout("poll", 100, "");
	}

var sm = new StateMachine();
var vm = new ViewManager(sm);