function noop() {}

// usage: isset(typeof(variable)) returns true if variable is defined.
function isset(ref) { return ref.toString ? ref.toString().toLowerCase() !== "undefined" : false; }

// Object.extend alias
function $$(el, forceID) {
	if (el && el !== document && el.nodeType !== 3 && typeof(el)!=="string") {
		Object.extend(el, Element);
		if (!!forceID && (!isset(typeof(el.id)) || el.id === '')) {
			el.forceID();
		}
		if (el.isNode('OPTION') || (el.isNode('INPUT') && ['RADIO','CHECKBOX'].in_array(el.type.toUpperCase()))) {
			Object.extend(el, Input);
		} else if (el.isNode('TABLE') || el.isNode('TBODY')) {
			Object.extend(el, Table);
		} else if (el.isNode('FORM')) {
			Object.extend(el, Form);
		}
		if (el.hasClassName('hide')) {
			el.hide().removeClassName('hide');
		}
	}
	return el;
}

// getElementById alias
function $(id) {
	var el = null;
	if (document.getElementById) { el = document.getElementById(id); }
	else if (document.all) { el = document.all[id]; }
	else if (document.layers) { el = document.layers[id]; }
	return el !== null ? $$(el) : null;
}

/// Call Object.extend(obj,obj2) to add obj2's functionality to obj
Object.extend = function (d, s) {
	if (typeof(s)==='function' && s.prototype) {
		return Object.extend(d, s.prototype);
	} else if (d) {
		for (var p in s) {
			if (p.toString() !== 'prototype') {
				if (d.prototype && typeof(d.prototype)==='object') { d.prototype[p] = s[p]; }
				else if (!d.nodeType || d.nodeType !== 3) { d[p] = s[p]; } // textNode checking added for IE 6
			}
			else { for (var p2 in p) { d.prototype[p2] = s[p][p2]; } }
		}
	}
	return d;
};
// TODO: add argument handling for functions
Function.prototype.bind = function (who) {
	var func = this, args = [], i;
	for (i=1; i<arguments.length; ++i) { args.push(arguments[i]); }
	return function () { return func.apply(who,args); };
};
// Function.curry allows you to pre-prepare some of a function's arguments
// crockford, pg 44, js: the good parts
Function.prototype.curry = function () {
	var slice = Array.prototype.slice,
		args = slice.apply(arguments),
		that = this;
	return function () { return that.apply(null, args.concat(slice.apply(arguments))); };
};

//================= array functions ==================\\
Object.extend(Array, {
	/// find the index of an element in an array. -1 is its not in the array
	indexOf: function (toFind) {
		var retval = false, i;
		for (i = 0; retval === false && i < this.length; ++i) { if (this[i] === toFind) { retval = i; } }
		return retval === false ? -1 : retval;
	},
	in_array: function (obj) {
		obj = String(obj).replace('(', '\\(', 'g').replace(')', '\\)', 'g');
		return new RegExp('(^|,)'+obj+'(,|$)','gi').test(this);
	},
	// Array.reduce calls function f on each element of the array, starting at value
	reduce: function (f, value) {
		var i;
		for (i = 0; i < this.length; ++i) { value = f(this[i], value); }
	}
});

//================= Element functions ==================\\
var Element = new function() {
	var me = this;
	me.attrib = function(attr, value) {
		if (!isset(typeof(value))) { value = attr; }
		this.attr = value;
		this.setAttribute(attr, value);
		return this;
	};
	me.unattrib = function(attr) {
		delete(this.attr);
		this.removeAttribute(attr);
		return this;
	};
	me.getChild = function(nodeName) {
		var children = this.childNodes, child = false;
		if (children && children.length) {
			for (var i=0; child === false && i < children.length; ++i)
				{ if ($$(children[i]).isNode(nodeName)) { child = children[i]; } }
		}
		return child;
	};
	me.appendChildren = function(nodes) {
		for (var i = 0; i < nodes.length; ++i) { this.appendChild(nodes[i]); }
		return this;
	};
	me.insertAfter = function(node) {
		this.parentNode.insertBefore(node, this.nextSibling);
	};
	me.create = function(nodeName, id, classname, innerHTML) {
		if (this !== document) {
			return this.appendChild(document.create.apply(document, arguments));
		}
	};
	me.createFirst = function() {
		return this.prependChild(document.create.apply(document, arguments));
	}
	me.prependChild = function(node) {
		return this.childNodes && this.childNodes.length > 0 ?
			this.insertBefore(node, this.firstChild) :
			this.appendChild(node);
	};
	me.createText = function(text) {
		var retval;
		if (!isset(typeof(this.canHaveChildren)) || this.canHaveChildren) {
			// check for IE compatibility
			retval = document.createTextNode(text);
			this.appendChild(retval);
		} else { this.text = retval = text; }
		return retval;
	};
	me.getChildNodes = function(nonText) {
		var nodes = [];
		for (var i = 0; this.childNodes && i < this.childNodes.length; ++i) {
			if (!nonText || this.childNodes[i].nodeType !== 3) { nodes.push($$(this.childNodes[i])); }
		}
		return nodes;
	};
	me.childNodesNonText = function (index) {
		var nodes = this.getChildNodes(true);
		return index && index > 0 && index < nodes.length ? nodes[index] : nodes;
	};
	me.isNode = function(nodeName) {
		return this.nodeName && typeof(nodeName)==='string' && this.nodeName.toUpperCase() === nodeName.toUpperCase();
	};
	me.remove = function() {
		if (this.parentNode) { this.parentNode.removeChild(this); }
	};
	me.empty = function() {
		while (this.hasChildNodes()) { this.removeChild(this.firstChild); } // Container
		if (isset(typeof(this.value))) {
			this.value = ''; // Input
			this.setAttribute("value", ''); // Input
		}
		return this;
	};
	me.curStyle = function() {
		var retval = null;
		if (window.getComputedStyle) { retval = window.getComputedStyle(this,null); }
		else if (this.currentStyle) { retval = this.currentStyle; }
		return retval;
	};
	me.hasClassName = function(className) {
		return this.className &&
				this.className.length !== 0 &&
				(this.className === className || this.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")));
	};
	/// cross-browser event handling for IE5+,  NS6 and Mozilla - By Scott Andrew
	// http://www.scottandrew.com/weblog/articles/cbs-events
	me.addEvent = function(evType, fn, useCapture ) {
		var retval;
		try {
			if (this.addEventListener) {
				this.addEventListener( evType, fn, useCapture );
				this.addedEventHandlers = this.addedEventHandlers || [];
				this.addedEventHandlers.push(fn);
				retval = true;
			} else if (this.attachEvent) { retval = this.attachEvent( "on" + evType, fn ); }
			else { retval = false; }
		} catch (ex2) {}
		return retval;
	};
	me.removeEvent = function(type, fn) {
		if (this.removeEventListener) { this.removeEventListener(type, fn, false); }
		else if (this.detachEvent) { this.detachEvent("on" + type, fn); }
	};
	me.setEvent = function(type, fn, useCapture ) {
		if (this.addedEventHandlers) {
			for (var i=0; i<this.addedEventHandlers; i++) {
				this.removeEvent(type, this.addedEventHandlers[i]);
			}
			this.addedEventHandlers = [];
		}
		if (type === 'click') { this.setClick(fn); }
		else if (type === 'change') { this.onchange = fn; }
		else {
			this['on' + type] = fn;
		}
	};
	me.addClick = function (fn, useCapture) {
		return this.addEvent('click', fn, useCapture);
	};
	me.setClick = function(fn) {
		this.onclick = fn;
		return this;
	};
	me.addDblClick = function(fn, useCapture) {
		return this.addEvent('dblclick', fn, useCapture);
	};
	me.setDblClick = function(fn) {
		this.ondblclick = fn; return this;
	};
	/// Set the class for an element, wiping out all others
	me.setClassName = function(className) {
		if (!isset(typeof(className))) { className = ''; }
		this.className = className;
		return this;
	};
	/// Add a class to an element (if it hasn't already been added)
	me.addClassName = function(className) {
		if (Array.is_a(className)) { className = className.join(' '); }
		if (this.className && this.className !== '') {
			if (!this.hasClassName(className)) { this.className = className + ' ' + this.className; }
		} else { this.setClassName(className); }
		return this.hasClassName(className);
	};
	/// Remove a class to an element (if it exists)
	me.removeClassName = function(className) {
		if (this.hasClassName(className)) {
			this.className = this.className.replace(new RegExp("(^|\\s)" + className + "(\\s|$)"), " ");
			if (this.className && typeof(this.className)==="string") { this.className = this.className.trim(); }
		}
		return !this.hasClassName(className);
	};
	me.toggleClassName = function(className, add) {
		return !!add ? this.addClassName(className) : this.removeClassName(className);
	};
	me.cloak = function() {
		if (this.style) { this.style.visibility = 'hidden'; }
		return this;
	};
	me.reveal = function(display) {
		display = display || "visible";
		if (this.style) { this.style.visibility = display; }
		return this;
	};
	me.visible = function() {
		var visible = (this.style && this.getStyle("visibility") !== 'hidden');
		return !this.isHidden() && visible;
	};
	me.hide = function() {
		if (this.style) { this.style.display = 'none'; }
		return this;
	};
	me.show = function(display, table) {
		if (this.style) {
			if (!isset(typeof(display)) || display === null) { display = 'block'; }
			if (!!table) {
				// from http://blog.delaguardia.com.mx/index.php?op=ViewArticle&articleId=28&blogId=1
				try {
					if (this.nodeName.toUpperCase() === 'TR') { this.style.display = 'table-row'; }
					else if (this.nodeName.toUpperCase() === 'TABLE') { this.style.display = 'table'; }
					else if (this.nodeName.toUpperCase() === 'TD') { this.style.display = 'table-column'; }
				} catch(e) { this.style.display = display; }
			} else { this.style.display = display; }
			this.removeClassName('hide');
		}
		return this;
	};
	me.isHidden = function() {
		return this.style && this.getStyle("display") === 'none';
	};
	me.setHidden = function(hide, display) {
		if (!hide) { this.show(display); } else { this.hide(); }
		return this;
	};
	me.toggle = function(display, table) {
		if (this.isHidden()) { this.show(display, table); }
		else { this.hide(); }
		return this;
	};
	me.update = function(val) {
		if (val !== 0 && !val) { val = ''; }
		if (this.nodeName) {
			if (this.isNode('input') || this.isNode('textarea')) {
				this.setAttribute("value", val);
				this.value = val;
			} else if (this.isNode("table")) {
				if (!val || val === '') { this.empty(); }
				else {
					throw("The innerHTML property of a table can not be set in IE.");
				}
			} else if (isset(typeof(this.innerHTML))) {
				try {
					this.innerHTML = val;
				} catch (e) {
					if (this.isNode('p')) { throw("IE sometimes can't set the innerHTML of a p tag."); }
					else { throw("Exception setting InnerHTML: "+e.name+" / "+e.message); }
				}
			}
		}
		return this;
	};
	me.append = function(val) {
		return this.update(this.innerHTML + val);
	};
	me.scan = function(text) {
		var retval = null;
		if (this.nodeName) {
			if (this.isNode('input') || this.isNode('textarea')) { retval = this.value; }
			else if (this.isNode('select')) {
				if (this.selectedIndex >= 0) {
					var sel = this.options[this.selectedIndex];
					retval = text ? sel.text : sel.value;
				} else { retval = null; }
			} else if (isset(typeof(this.innerHTML))) {
				retval = text ? this.innerHTML.replace(/<br ?\/?>/ig,"\n").replace(/(<([^>]+)>)/ig,"") : this.innerHTML;
			}
		}
		return retval;
	};
	me.xerox = function(obj, text) {
		if ($$(obj)) { this.update(obj.scan(text)); }
		return this;
	};
	me.append = function(val) {
		if (val !==0 && !val) { val = ''; }
		this.update(this.scan() + val);
		return this;
	};
	me.getStyle = function(prop) {
		if (this.style && this.style[prop] && this.style[prop] !== "") {
			return this.style[prop];
		} else {
			// try to get it from a stylesheet
			// taken from http://www.javascriptkit.com/dhtmltutors/dhtmlcascade4.shtml
			var retval;
			if (this.currentStyle) { //if IE5+
				retval = this.currentStyle[prop];
			} else if (window.getComputedStyle) { //if NS6+
				var elstyle = window.getComputedStyle(this, "");
				retval = elstyle.getPropertyValue(prop.toHyphenated());
			}
			return retval;
		}
	};
	me.classify = function () {
		if (arguments.length > 0) {
			for (var i = arguments.length; i > 0;  --i) { this.addClassName(arguments[i - 1]); }
		}
		return this;
	};
	me.declassify = function () {
		if (arguments.length > 0) {
			for (var i = arguments.length; i > 0;  --i) { this.removeClassName(arguments[i - 1]); }
		}
		return this;
	};
	me.textify = function () { this.createText.apply(this, arguments); return this; },
	me.stylify = function (prop, value) {
		if (this.style) { this.style[prop] = isset(typeof(value)) ? value : null; }
		return this;
	};
	me.setFaded = function (opacity) {
		opacity = parseFloat(opacity);
		if (isNaN(opacity) || opacity > 1) { opacity = 1; }
		else if (opacity < 0) { opacity = 0; }
		if (this.style) {
			this.style.opacity = opacity;
			this.style.MozOpacity = opacity;
			this.style.KhtmlOpacity = opacity;
			this.style.filter = "alpha(opacity="+(100*opacity)+")";
		}
		return this;
	};
	me.doFade = function (fadingIn, onComplete) {
		var opac = fadingIn ? 0 : 1, mult = 2, el = this,
			interval = window.setInterval(function () {
				if (!el.isHidden() && (fadingIn ? opac < 1 : opac > 0)) {
					if (fadingIn) { opac += mult * 1e-2; }
					else { opac -= mult*1e-2; }
					mult *= 1.5;
					el.setFaded(opac);
				} else {
					opac = fadingIn ? 1 : 0;
					el.setFaded(opac);
					window.clearInterval(interval);
					if (typeof(onComplete)==="function") { onComplete(); }
				}
			}, 50);
		return this;
	};
	return me;
}();

//================= DOMContentLoaded Setup ==================\\
// more on this below
var startStack = function () {},
	lastStack = function () {},
	registerOnLoad = function (func, last) {
		var orgOnLoad;
		if (ranOnload) {
			if (typeof(func) === "function") { func(); }
		} else if (!last) {
			orgOnLoad = startStack;
			startStack = function () {
				orgOnLoad();
				if (typeof(func) === "function") { func(); }
				return;
			};
		} else {
			orgOnLoad = lastStack;
			lastStack = function () {
				orgOnLoad();
				if (typeof(func) === "function") { func(); }
				return;
			};
		}
	};

window.addEvent = function () {return Element.prototype.addEvent.apply(window,arguments);};
window.addLoad = function () { registerOnLoad(arguments[0],arguments[1]); };
window.addUnload = window.addEvent.curry("unload");
window.addBeforeUnload = window.addEvent.curry("beforeunload");
document.create = function (nodeName, id, classname, innerHTML) {
	var type, forID;
	if (nodeName.substring(0,5) === "input" && nodeName !== "input") {
		type = nodeName.substring(6);
		nodeName = "input";
		// enables "input:checkbox" eg
	}
	if (nodeName.substring(0,5) === "label" && nodeName !== "label") {
		forID = nodeName.substring(6);
		nodeName = "label";
		// enables label:inputid" eg
	}
	var el = document.createElement(nodeName);
	if (id && id.length > 0) { el.id = id; }
	$$(el);
	if (isset(typeof(classname)) && classname) { el.addClassName(classname); }
	if (type && nodeName === "input") { el.setAttribute("type", type); }
	if (forID && nodeName === "label") { el.setAttribute("for", forID); }
	if (isset(typeof(innerHTML)) && innerHTML !== false) { el.update(innerHTML); }
	return $$(el);
};

/// parts from MooTools copyright (c) 2007 Valerio Proietti, <http://mad4milk.net> and prototype.js
var Ajax = function(url, params) {
	var me = this;
	
	function setup() {
		me.options = {
			method: 'post',
			asynchronous: true,
			onRequest: null,
			onSuccess: noop,
			onFailure: noop,
			onComplete: noop,
			urlEncoded: true,
			encoding: 'utf-8',
			parameters: "",
			headers: {}
		};
		me.url = url;
		me.transport = window.XMLHttpRequest ?
			new XMLHttpRequest() :
			(ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : false);
		me.updateOptions(params);
		me.options.method = me.options.method.toLowerCase();
		if (me.options.urlEncoded && me.options.method === 'post') {
			var encoding = !!me.options.encoding ? '; charset=' + me.options.encoding : '';
			me.options.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
		}
	}
	
	me.updateOptions = function(params) {
		me.options = Object.extend(me.options, params);
	};
	me.send = function() {
		var url = me.url;
		if (me.options.parameters && me.options.method === 'get')
			{ url += (me.url.indexOf('?') > -1 ? '&' : '?') + me.options.parameters; }
		me.transport.open(me.options.method.toUpperCase(), url, me.options.asynchronous);
		me.transport.onreadystatechange = me.onStateChange.bind(me);
		if ((me.options.method === 'post') && me.transport.overrideMimeType)
			{ me.options.headers.Connection = 'close'; }
		for (var type in me.options.headers) {
			if (me.options.headers.hasOwnProperty(type)) {
				try { me.transport.setRequestHeader(type, me.options.headers[type]); }
				catch(e) {}
			}
		}
		var post = me.options.method === 'post' ? me.options.parameters : null;
		me.transport.send(post);
		// Force Firefox to handle ready state 4 for synchronous requests
		if (!me.options.asynchronous && me.transport.overrideMimeType) {
			me.onStateChange();
		}
	};
	me.onStateChange = function () {
		if (me.transport.readyState !== 4) { return; }
		var status = 0;
		try {status = me.transport.status;} catch(e) {}
		if (me.options.onComplete) {
			me.options.onComplete(me.transport);
		}
		if ((status >= 200) && (status < 300)) {
			me.options.onSuccess(me.transport);
		} else {
			if (me.transport.status > 0) {
				me.options.onFailure(me.transport);
			}
		}
		me.transport.onreadystatechange = noop;
	};
	
	setup();
	return me;
};

//================= string functions =================\\
Object.extend(String,{
	/// replace HTML characters with their equivalent (e.g. for use in a textbox)
	editFormat: function () {
		var txt = this;
		return txt.replace(/&#39;/g, "'").replace(/&quot;/g, '"').replace(/<br \/>/g, "\n");
	},
	/// trim off whitespace as well as special HTML characters (&XXX;)
	trim: function () {
		var str = this, retval;
		if (str === "") { retval = ""; }
		else { retval = str.replace(/^((&[a-zA-Z]*;)|\s)*/g,"").replace(/(((&[a-zA-Z]*;)|\s)*)$/g,""); }
		return retval;
	},
	// fast pure trim function
	trim2: function () {
		var str = this.replace(/^\s+/, ''), i;
		for (i = str.length - 1; i >= 0; --i) {
			if (/\S/.test(str.charAt(i))) {
				str = str.substring(0, i + 1);
				break;
			}
		}
		return str;
	},
	/// change a camelcase string to hyphenated (backgroundColor -> background-color)
	toHyphenated: function () {
		var str = this, tmp;
		tmp = str.substr(1).substr(0,str.length-2);
		tmp = tmp.replace(/([A-Z])/g, '-$1');
		return (str.substr(0,1) + tmp + str.substr(-1)).toLowerCase();
	},

	//+ Jonas Raoni Soares Silva
	//@ http://jsfromhell.com/string/wordwrap [v1.1]
	wordWrap: function (m, b, c){
    	var i, j, l, s, r;
		if (m < 1) { return this; }
		else {
			for (i = -1, l = (r = this.split("\n")).length; ++i < l; r[i] += s) {
				for (s = r[i], r[i] = ""; s.length > m; r[i] += s.slice(0, j) + ((s = s.slice(j)).length ? b : "")) {
					j = c == 2 || (j = s.slice(0, m + 1).match(/\S*(\s)?$/))[1] ? m : j.input.length - j[0].length
					|| c == 1 && m || j.input.length + (j = s.slice(m).match(/^\S*/)).input.length;
				}
			}
		}
    	return r.join("\n");
	},
	find: function (what) { return this.indexOf(what)>=0 ? true : false; }, // from niftyCube
	reverse: function () { return this.split("").reverse().join(""); },
	repeat: function (num) {
		num = parseInt(num, 10);
		return !num ? '' : Array.dim(num, this).join('');
	}
});
String.prototype.substring = String.prototype.slice; // replaces String.substring with the better String.slice. Implementation is identical.

function cancelBubble(e) {
	// from http://www.quirksmode.org/js/events_order.html
	if (!e) { e = window.event; }
	if (e) {
		e.cancelBubble = true;
		if (e.stopPropagation) { e.stopPropagation(); }
	}
}

//	Can be called on any value to determine if it is an array (note: Function.arguments is not an array)
// doug from crockford, pg 61, js: the good parts.
// kangax from http://thinkweb2.com/projects/prototype/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
Array.is_a = function (val) {
	var doug = isset(typeof(val)) && !!val &&
			typeof(val) === "object" &&
			typeof(val.length) === "number" &&
			typeof(val.splice) === "function" &&
			!val.propertyIsEnumerable('length'),
		kangax = Object.prototype.toString.call(val) === "[object Array]"; // Object.prototype.toString.call([]);
	if (doug !== kangax) { throw new Exception("Array.is_a: detection method results differ"); }
	return doug && kangax;
};

//================= DOMContentLoaded ==================\\
// from http://ajaxian.com/archives/ajaxian-featured-tutorial-when-is-your-page-ready

var ranOnload = false; // Flag to determine if we've ran the starting stack already.
	
if (document.addEventListener) {
	// Mozilla actually has a DOM READY event.
	document.addEventListener("DOMContentLoaded", function () { if (!ranOnload) { ranOnload = true; startStack(); }}, false);
} else if (document.all && !window.opera) {
	// This is the IE style which exploits a property of the (standards defined) defer attribute
	document.write("<scr" + "ipt id='DOMReady' defer=true " + "src=//:><\/scr" + "ipt>");
	document.getElementById("DOMReady").onreadystatechange = function () {
		if (this.readyState==="complete" && !ranOnload) { ranOnload = true; startStack(); }
	};
}
var orgOnLoad = window.onload;
window.onload = function () {
	if (typeof(orgOnLoad)==='function') { orgOnLoad(); }
	if (!ranOnload) {
		ranOnload = true;
		startStack();
	}
};

window.enterKey = function (e) {
	if (!e) { e = window.event; }
	return (e.which || e.keyCode || -1) === 13;
};

