/* Handy Objects
--------------------------------------------------------------------------*/
var Cookie = {
	get: function(name) {
		var cookies = document.cookie.split('; ');
		for (var x=0; x<cookies.length; x++) {
			var cookie = cookies[x].split('='); 
			if (cookie[0] == name) return cookie[1];
		}
		return false;
	},

	set: function(name, value, expDate) {
		document.cookie = name+"="+escape(value) + ";expires="+expDate.toGMTString();
	},

	remove: function(name) {
		Cookie.set(name, '', new Date(Date.parse('Thu, 01-Jan-1970 00:00:01 GMT')));
	}
};

var Request = {
	getVar: function(name) {
		var getvars = document.location.search.substring(1).split('&');
		for (var x=0; x<getvars.length; x++) {
			var getvar = getvars[x].split('=');
			if (getvar[0] == name) return getvar[1];
		}
		return false;
	}
};

// Legacy function names
var getCookie = Cookie.get;
var getGet = Request.getVar;

/* Extensions to native JS objects
--------------------------------------------------------------------------*/
Object.extend(String.prototype, {
	/**
	 * Extends the .substring() method to allow negative numbers to
	 * reference indices from the end of the string rather than the beginning.
	 */
	substring: function(start, end) {
		if (start < 0) start = this.length + start;
		if (end < 0) end = this.length + end;
		if (end !== 0 && !end) end = this.length;
		var newString = '';
		for (var ssi=start; ssi<end; ssi++) {
			newString += this.charAt(ssi);
		}
		return newString;
	},

	// Deprecated
	// See Prototype's String.escapeHTML()
	// http://www.prototypejs.org/api/string/escapeHTML
	/**
	 * Converts special characters to their HTML entities
	 */
	 htmlEntities: function() {
		var chars = {
			'&': 'amp',	'<': 'lt', '>': 'gt', '\"': 'quot'
		};

		var newString = this;
		for (var chacacter in chars) {
			var regExp = new RegExp();
			regExp.compile(chacacter,'g');
			newString = newString.replace(regExp, '&'+chars[chacacter]+';');
		}
		return newString;
	},

	// Deprecated
	// Don't hide JS data inside HREF ever again.
	// Use DOM.getCommentData() instead.
	/**
	 * Get Url Argument
	 * Turns a URL into an argument for links which function
	 * purely as event launchers.
	 */
	getUrlArgument: function() {
		// Parses a URL like "javascript:void(42);" and returns "42"
		if (val = this.match(/[Jj]avascript:void\(\'?(.*?)\'?\);?/i)) {
			if (val[1]) return val[1];
		}
		return this;
	},

	// Deprecated by Prototype's String.strip()
	// See: http://www.prototypejs.org/api/string/strip
	/**
	 * Trims a string
	 */
	trim: function() {
		return this.replace(/^\s+/g, '').replace(/\s+$/g, '');
	},

	/**
	 * Is Numeric
	 * checks if a string is a number
	 */
	isNumeric: function () {
		return !this.match(/\D/);
	},

	toDOMNodes: function() {
		var wrapper = document.createElement('div');
		wrapper.innerHTML = this;
		return $(wrapper).immediateDescendants();
	}
});

Object.extend(Form.Element, {
	setValue: function(inputElement, newValue) {
		inputElement = $(inputElement);
		switch(inputElement.type) {
			case 'text':
			case 'password':
				inputElement.value = newValue;
			break;
			case 'select':
			case 'select-one':
				for (var x=0; x<inputElement.options.length; x++) {
					if (inputElement.options[x].value == newValue) {
						inputElement.selectedIndex = x;
						return true;
					}
				}
				return false;
			break;
			case 'checkbox':
				inputElement.checked = bool(newValue);
			break;
			default:
				alert('setValue() can\'t yet handle input type: '+inputElement.type);
				return false;
			break;
		}
		return true;
	}
});

Object.extend(Form.Element, {
	getForm: function(element) {
    element = $(element);
    while (element.tagName != 'FORM') {
    	element = element.parentNode;
    	if (element == document) return false;
    }
    return element;
	}
});

Object.extend(Number.prototype, {
	// Deprecated
	// See Prototype's Number.toPaddedString(length)
	// http://www.prototypejs.org/api/number/toPaddedString
	/**
	 * Pads a number with zeroes until it is the desired length (digits)
	 * (Caution: Returns a string, not a float, out of necessity)
	 * Example: (7).zeroPad(3) == '007'
	 */
	zeroPad: function(digits) {
		var str = this.toString();
		while (str.length < digits) {
			str = '0'+str;
		}
		return str;
	}
});

Object.extend(Array.prototype, {
	// Deprecated
	// See Prototype's Array.without(Array)
	// http://www.prototypejs.org/api/array/without
	/**
	 * Deletes any occurrences of needle from Array
	 * Not recommended for use on multidimensional arrays.
	 */
	deleteVals: function(needle) {
		var newValue = [];
		if (this.length > 0) {
			for (var n=0; n<this.length; n++) {
				if (this[n] != needle) newValue.push(this[n]);
			}
		}
		return newValue;
	},

	// Deprecated
	// See Prototype's Array.uniq()
	// http://www.prototypejs.org/api/array/uniq
	/**
	 * Weeds out duplicate values in an array.
	 * Not recommended for use on multidimensional arrays.
	 */
	unique: function() {
		newArray = new Array();
		for (key in this) {
			if (typeof(this[key]) != 'function') {
				if (newArray.inArray(this[key]) == -1) newArray.push(this[key]);
			}
		}
		return newArray;
	},

	// Deprecated
	// See JavaScript's native Array.indexOf()
	/**
	 * In Array
	 * Finds a value in an array
	 */
	inArray: function(needle) {
		if (this.length > 0) {
			for (var n=0; n<this.length; n++) {
				if (this[n] == needle) return n;
			}
		}
		return -1;
	}
});



/* Patches to Prototype
--------------------------------------------------------------------------*/
Object.extend(Position, {
	distanceBetween: function(coords0, coords1) {
		// Determines the straight distance between two points.
		// coords1 = [x: 1, y: 1]
		// or
		// coords1 = [1,1]
		if (coords0 instanceof Array) {
			var x0=coords0[0];
			var y0=coords0[1];
		} else {
			var x0=coords0.x;
			var y0=coords0.y;
		}
		if (coords1 instanceof Array) {
			var x1=coords1[0];
			var y1=coords1[1];
		} else {
			var x1=coords1.x;
			var y1=coords1.y;
		}
		return Math.sqrt((x0-x1)*(x0-x1) + (y0-y1)*(y0-y1));
	}
});

Object.extend(Object, {
	extendProperties: function(originalObject, extendingObject) {
		// Tries to intelligently extend an object's properties based
		// on direction given in the property names of the extending
		// object.
		for (var originalKeyName in extendingObject) {
			var modifier = originalKeyName.substring(0,1);
			var keyName = originalKeyName.substring(1);
			if (extendingObject[originalKeyName] instanceof Array) {
				// - Prune: Remove from it
				// + Merge: Add to it
				// ! Overwrite: Replace it entirely
				switch(modifier) {
					case '-':
						for (var x=0; x<extendingObject[originalKeyName].length; x++) {
							originalObject[keyName] = originalObject[keyName].deleteVals(extendingObject[originalKeyName][x]);
						}
					break;
					case '+':
						originalObject[keyName] = originalObject[keyName].concat(extendingObject[originalKeyName]);
					break;
					default:
						keyName = originalKeyName; // No valid modifier, so we need to restore the wrongly clipped keyName
					case '!':
						originalObject[keyName] = extendingObject[originalKeyName];
					break;
				}
			} else if (extendingObject[originalKeyName] instanceof Object) {
				// - Prune: Remove from it
				// + Merge: Add to it or overwrite preexisting conflicting values.
				// ! Overwrite: Replace it entirely
				switch(modifier) {
					case '-':
						for (var key in extendingObject[originalKeyName]) {
							if (typeof(Object.prototype[key]) == 'undefined') {
								delete(originalObject[keyName][key]);
							}
						}
					break;
					case '+':
						Object.extend(originalObject[keyName], extendingObject[originalKeyName]);
					break;
					default:
						keyName = originalKeyName; // No valid modifier, so we need to restore the wrongly clipped keyName
					case '!':
						originalObject[keyName] = extendingObject[originalKeyName];
					break;
				}
			} else {
				originalObject[originalKeyName] = extendingObject[originalKeyName];
			}
		}
	}
});

Object.extend(Element, {
	/**
	 * Tons faster and simpler than Prototype's
	 */
	hasClassName: function(element, className) {
		return Element.manipulateClass(element, className, 'find');
	},

	addClassName: function(element, className) {
		return Element.manipulateClass(element, className, 'add');
	},

	removeClassName: function(element, className) {
		return Element.manipulateClass(element, className, 'remove');
	},

	clearClassNames: function(element) {
		element.className = '';
		return element;
	},

	manipulateClass: function(element, className, action) {
		if (typeof(element.tagName) != 'undefined') {
			var classes = element.className;
			if (action == 'add') {
				if (!Element.hasClassName(element, className)) {
					element.className += ' '+className;
					return element;
				}
			}
			var cNames = classes.split(' ');
			if (cNames.length > 0) {
				if (action == 'remove') {
					cNames = cNames.deleteVals(className);
				} else if (action == 'find') {
					return (cNames.indexOf(className) != -1);
				}
			}
			if (action == 'add') {
				if (typeof(className) == 'array') {
					cNames = cNames.concat(className);
				} else {
					cNames.push(className);
				}
			}
			if (action == 'add' || action == 'remove') element.className = cNames.join(' ').trim();
			if (action == 'find') return false;
		} else {
			return false;
		}
	},

	/**
	 * Element.visible
	 * By Kramer
	 *
	 * I was disappointed that Prototype's Element.visible really only
	 * reports on the status of element.style.display. What I really
	 * wanted to know was "can the user see this element, or is it
	 * contained inside a hidden element?" This extension fixes that with
	 * the new "recursive" option.
	 *
	 * Recursive option added to seek up the tree to make sure
	 * all parent nodes are visible, too. This should effectively
	 * return a true/false indicating whether the given element is
	 * indeed VISIBLE TO THE USER.
	 */
	visible: function(element, recursive) {
		if (!recursive) return $(element).style.display != 'none';
		var search = element;
		while (search = search.parentNode) {
			if ($(search).style.display == 'none') return false;
		}
		return true;
	},
	insertedNodes: [],
	insertNode: function(node, tagName, key){
		for(var x=0; x<Element.insertedNodes.length; x++){
			if(Element.insertedNodes[x].node == node){
				return false;
			}
		}
		var insertedNode = {
			node: node,
			key: (key)? key : null
		};

		/* Create and place container */
		insertedNode.container = document.createElement('span');
		Element.addClassName(insertedNode.container, "iFrameHackContainer");
		insertedNode.node.parentNode.insertBefore(insertedNode.container, insertedNode.node);
		insertedNode.container.appendChild(insertedNode.node);

		/* Create and place throbber */
		insertedNode.newNode = document.createElement(tagName);
		Element.addClassName(insertedNode.newNode, "newNode");
		insertedNode.container.appendChild(insertedNode.newNode);

		var dims = Element.getDimensions(insertedNode.container);

		/* Add essential styles to container and throbber */
		Object.extend(insertedNode.container.style, {
			position: "relative",
			display: "block",
			zoom: "1"
		});
		Object.extend(insertedNode.newNode.style, {
			position: "absolute",
			top: "0",
			left: "0",
			width: (document.all)?dims['width']+"px" : "100%",
			height: (document.all)?dims['height']+"px" : "100%"
		});

		Element.insertedNodes.push(insertedNode);
		return insertedNode;
	},
	removeNode: function(node, key){
		var insertedNode = null;
		for(var x=0; x<Element.insertedNodes.length; x++){
			if(Element.insertedNodes[x].node == node && ((!key && !Element.insertedNodes[x].key) || Element.insertedNodes[x].key == key)){
				insertedNode = Element.insertedNodes.splice(x, 1);
				break;
			}
		}
		if(!insertedNode){
			return false;
		}
		insertedNode = insertedNode[0];
		insertedNode.container.parentNode.insertBefore(node, insertedNode.container);
		insertedNode.container.parentNode.removeChild(insertedNode.container);
		return true;
	},
	iFrameHacks: [],
	iFrameHack: function(node){
		if(!document.all){
			return false;
		}
		var iFrameHack = Element.insertNode(node, 'iframe', 'iFrameHack');
		if(!iFrameHack){
			return false;
		}
		Element.addClassName(iFrameHack.container, "iFrameContainer");
		Element.addClassName(iFrameHack.newNode, "iFrame");
		Object.extend(iFrameHack.newNode.style, {
			filter: "alpha(opacity=0)",
			opacity: "0",
			zIndex: "2"
		});
		if(parseInt(iFrameHack.node.style.zIndex) > 1){
			Object.extend(iFrameHack.newNode.style, {
				zIndex: parseInt(iFrameHack.node.style.zIndex)+1
			});
		}
		return iFrameHack;
	},
	cancelIFrameHack: function(node){
		if(!document.all){
			return false;
		}
		Element.removeNode(node, 'iFrameHack');
	},
	parseClasses: function(el, parseChildren){
		var classes = String(el.className).split(" ");
		var keepClasses = [];
		for(var x=0; x<classes.length; x++){
			if(classes[x].indexOf(':') != -1){
				var matches = classes[x].match(/^(\w*):{1,2}\{(.*)\}$/);
				var ob = {};
				var vars = matches[2].split(",");
				for(var y=0; y<vars.length; y++){
					vars[y] = vars[y].split(/::?/);
					if(!vars[y][1]) vars[y][1] = '';
					ob[vars[y][0]] = vars[y][1];
				}
				el[matches[1]] = ob;
			}else{
				keepClasses.push(classes[x]);
			}
		}
		el.className = keepClasses.join(" ");
		if(parseChildren){
			var eles = el.getElementsByTagName('*');
			for(var x=0; x<eles.length; x++){
				this.parseClasses(eles[x]);
			}
		}
	}
});


Event._observe = Event.observe;
Object.extend(Event, {
	/**
	 * Returns the keycode related to an event, browser-independent
	 */
	keyCode: function(event) {
		if (typeof(event.which) == 'undefined') return event.keyCode;
		return Math.max(event.which, event.keyCode);
	},

	/**
	 * Event.observe now returns an ID which can be sent to Event.stopObserving to terminate that event handler.
	 */
	registry: [],

	id: 0,

	observe: function(element, name, observer, useCapture) {
		Event.id++;
		var regEntry = {
			id: Event.id,
			element: element,
			name: name,
			observer: observer,
			useCapture: useCapture
		};
		Event.registry.push(regEntry);
		Event._observe(element, name, observer, useCapture);
		return regEntry;
	},

	unObserve: function(regEntry) {
		for (var x=0; x<Event.registry.length; x++) {
			var a = Event.registry[x];
			if (a.id == regEntry.id) {
				Event.registry.splice(x, 1);
				Event.stopObserving(a.element, a.name, a.observer, a.useCapture);
				return true;
			}
		}
		return false;
	},

	fire: function(el, name) {
		for(var x=0; x<Event.registry.length; x++){
			if(Event.registry[x].element == el && Event.registry[x].name == name){
				Event.registry[x].observer();
			}
		}
	}
});


/* Plain old functions
--------------------------------------------------------------------------*/
document.getElementsBySelector = function(selectors, withinNodes) {
	if (!(selectors instanceof Array)) selectors = [selectors];
	if (typeof(withinNodes) == 'undefined') {
		var withinNodes = [document.getElementsByTagName('body')[0]];
	} else if (!(withinNodes instanceof Array)) {
		var withinNodes = [withinNodes];
	}
	var resultNodes = [];
	for (var cSel=0; cSel<selectors.length; cSel++) {
		for (var cNodes=0; cNodes<withinNodes.length; cNodes++) {
			var resultNodes = resultNodes.concat(Element.getElementsBySelector(withinNodes[cNodes], selectors[cSel]));
		}
	}
	return resultNodes.unique();
};

function getCoordsCenter(coords) {
	return {
		x: coords.x/2,
		y: coords.y/2
	};
};
function getViewportSize() {
	var retVal = {
		x: self.innerWidth || (document.documentElement.clientWidth || document.body.clientWidth),
		y: self.innerHeight || (document.documentElement.clientHeight || document.body.clientHeight)
	};
	// Legacy property names
	retVal.height = retVal.y;
	retVal.width = retVal.x;
	return retVal;
};
function getViewportCenter() {
	return getCoordsCenter(getViewportSize());
};
function getScreenSize() {
	var screenW = 640, screenH = 480;
	if (parseInt(navigator.appVersion)>3) {
	 screenW = screen.width;
	 screenH = screen.height;
	}
	else if (navigator.appName == "Netscape"
	    && parseInt(navigator.appVersion)==3
	    && navigator.javaEnabled()
	   )
	{
	 var jToolkit = java.awt.Toolkit.getDefaultToolkit();
	 var jScreenSize = jToolkit.getScreenSize();
	 screenW = jScreenSize.width;
	 screenH = jScreenSize.height;
	}

	return {
		x: screenW,
		y: screenH
	};
};
function getScreenCenter() {
	return getCoordsCenter(getScreenSize());
}

/**
 * Changes pretty much any variable into its boolean equivalent
 * Strings like yes, no, true, false, y, n
 * Numbers like 0, 1, 2...
 * @return bool
 */
function bool(arg) {
	// Returns true or false
	// Does everything concievable to make arg into a boolean value
	if (typeof(arg) == 'undefined') return false;
	if (typeof(arg) == 'boolean') return arg;
	switch(arg.toLowerCase()) {
		case 'yes':
		case 'true':
		case 'y':
			return true;
		break;
		case 'false':
		case 'no':
		case 'n':
			return false;
		break;
		default:
			if (arg === true) return true;
			if (arg === false) return false;
			if (parseInt(arg) > 0) return true;
			if (parseInt(arg) == 0) return false;
		break;
	}
	return null; // Inconclusive
}

/**
 * Converts a UNIX timestamp into a JS Date object
 */
function unixToDate(unixtime) {
	var time = new Date();
	time.setTime(unixtime*1000);
	return time;
}

// Deprecated (partially)
// See Prototype's each()
// http://www.prototypejs.org/api/hash/each
// Although I don't think each() is recursive, not sure.
/**
 * For-each within an object. Recursive if you want it to be.
 **/
function foreach(obj, func, recursive) {
	for (var key in obj) {
		if (!Object.prototype[key]) {
			if (obj[key] instanceof Object && recursive) {
				foreach(obj[key], func);
			}
			func(obj[key], obj);
		}
	}
}

/**
 * Executes the given command within a different thread
 **/
Function.prototype.newThread = function(delay) {
	if (typeof(delay) == 'undefined') delay = 1;
	setTimeout(this, delay);
};
newThread = function(toExec, delay) {
	toExec.newThread(delay);
};

/* Adam added 5-22-07 to fix getElementsBySelector in IE */
Object.extend(Selector.prototype, {
	buildMatchExpression: function() {
		var params = this.params, conditions = [], clause;
		
		if (params.wildcard)
			conditions.push('true');
		if (clause = params.id)
			conditions.push('Element.readAttribute(element, "id") == ' + clause.inspect());
		if (clause = params.tagName)
			conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
		if ((clause = params.classNames).length > 0)
			for (var i = 0, length = clause.length; i < length; i++)
				conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
		if (clause = params.attributes) {
			clause.each(function(attribute) {
				var value = 'Element.readAttribute(element, ' + attribute.name.inspect() + ')';
				var splitValueBy = function(delimiter) {
					return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
				}
				switch (attribute.operator) {
					case '=':       conditions.push(value + ' == ' + attribute.value.inspect()); break;
					case '~=':      conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
					case '|=':      conditions.push(splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()); break;
					case '!=':      conditions.push(value + ' != ' + attribute.value.inspect()); break;
					case '':
					case undefined: conditions.push('Element.hasAttribute(element, ' + attribute.name.inspect() + ')'); break;
					default:        throw 'Unknown operator ' + attribute.operator + ' in selector';
				}
			});
		}
		return conditions.join(' && ');
	}
});
/* End added */