// +----------------------------------------------------------------------------+
// | Jinx Framework version 1.1.4												|
// | Copyright (c) 2005 Matt Zabriskie (matt@visioncube.com)					|
// |      _ _              _   _  _  _											|
// |     | (_)_ __ __  __ / | / || || |											|
// |  _  | | | '_ \\ \/ / | | | || || |_										|
// | | |_| | | | | |>  <  | |_| ||__   _|										|
// |  \___/|_|_| |_/_/\_\ |_(_)_(_) |_|											|
// |																			|
// | Javascript Inheritance & eXtension Framework								|
// | Licensed under The MIT License.											|
// |																			|
// | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,			|
// | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF			|
// | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.		|
// | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY		|
// | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,		|
// | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE			|
// | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.						|
// |																			|
// | Portions of this framework are modified or based upon various other		|
// | libraries, scripts and frameworks.  Such content is subject to the			|
// | original license and maintain original copyright.							|
// |																			|
// | Contributor(s):															|
// |	Sam Stephenson (sam@conio.net)											|
// |	Peter-Paul Koch (ppk@xs4all.nl)											|
// |	Aaron Boodman (boogs@youngpup.net)										|
// |	Yukio Mituiwa (mituiwa@linet.gr.jp)										|
// |	Douglas Crockford (douglas@crockford.com)								|
// +----------------------------------------------------------------------------+

// +----------------------------------------------------------------------------+
// | Building Blocks															|
// +----------------------------------------------------------------------------+

// Jinx object
var Jinx =
{
	version:	'1.1.4',
	doNothing:	function () { }
}

// ------------------------------------------------------------------------------

// Enable class creation
function Class ()
{
	return function ()
	{
		this.construct.apply (this, arguments);
	}
}

// ------------------------------------------------------------------------------

/* Enable extending an object
 *
 * param	object applying extension to
 * param	object being extended
 * return	object
 */
Object.extend = function (destination, source)
{
	for (property in source)
	{
		destination[property] = source[property];
	}
	return destination;
}

// ------------------------------------------------------------------------------

/* Perform object extension
 *
 * param	object applying extension to
 * return	object
 */
Object.prototype.extend = function (object)
{
	return Object.extend.apply (this, [this, object]);
}

// ------------------------------------------------------------------------------

// Bind a method to an instance of a class
Function.prototype.bind = function (instance)
{
	var method = this;
	return function () { method.apply (instance, arguments); }
}

// ------------------------------------------------------------------------------

if (!Function.prototype.apply)
{
	/* Simulate object inheritance
	 *
	 * param	object
	 * param	array parameters to apply to object
	 */
	Function.prototype.apply = function (object, parameters)
	{
		var retval, method = '____apply', aparameters = [];

		if (!object) object = window;
		if (!parameters) parameters = [];
    
		for (var i=0; i<parameters.length; i++)
		{
			aparameters[i] = 'parameters[' + i + ']';
		}
    
		object[method] = this;
		retval = eval ('object[method] (' + aparameters.join (', ') + ')');
		delete object[method]; 
		return retval;
	}
}

// ------------------------------------------------------------------------------

/* Returns the return	value of the first argument that executes successfully.
 *
 * param	function
 * return	mixed
 */
function coalesce ()
{
	var retval;
	for (var i=0; i<arguments.length; i++)
	{
		var func = arguments[i];
		try
		{
			retval = func ();
			break;
		}
		catch (e) { Jinx.doNothing (); }
	}
	return retval;
}

// +----------------------------------------------------------------------------+
// | Ajax																		|
// +----------------------------------------------------------------------------+

/* NOTE:
 *	Browser may cache the first response that it receives from an Ajax request.
 *	This is most noticable when more than one request is made to a single
 *	server side script (i.e. using Ajax.PeriodicUpdater).  Your server side
 *	scripting should set headers to prevent caching and force revalidation.
 *
 *	The following headers will need to be set:
 *		Pragma = no-cache
 *		Expires = Thu, 01 Jan 1970 00:00:00 GMT
 *		Cache-Control = no-cache, must-revalidate, post-check=0, pre-check=0
 */

// Base class for performing Asynchronous Javascript And Xml requests
var Ajax = new Class ();

Ajax.Status = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.prototype = 
{
	// Construct object
	construct: function ()
	{
		this.readyState = {UNINITIALIZED:0, LOADING:1, LOADED:2, INTERACTIVE:3, COMPLETE:4};
		this.ScriptFragment = '(?:<script.*?>)((\n|.)*?)(?:<\/script>)';
		this.url = undefined;
	},

	/* Get the transport for the Ajax request
	 *
	 * return XMLHttpRequest
	 */
	getTransport: function ()
	{		
		return coalesce
		(
			function () { return new XMLHttpRequest (); },
			function () { return new ActiveXObject ('Msxml2.XMLHTTP'); },
			function () { return new ActiveXObject ('Microsoft.XMLHTTP'); },
			function () { return false; }
		);
	},

	/* Set the options for the Ajax object
	 *
	 * param	object options
	 */
	setOptions: function (options)
	{
		this.options = 
		{
			method:			'post',
			asynchronous:	true,
			paramHolder:	[],
			parameters:		'',
			urlFormat:		'get',
			autoStart:		true
		}.extend (options || {});
	},

	/* Set the parameters for the request
	 *
	 * param	array URL parameters
	 * return	void
	 */
	setParameters: function (params)
	{
		this.options.paramHolder = params;
		return;
	},

	/* Add a parameter to the Ajax request
	 *
	 * param	string parameter key
	 * param	string parameter value
	 * return	void
	 */
	addParameter: function (key, value)
	{
		this.removeParameter (key);
		this.options.paramHolder[this.options.paramHolder.length] = [key, value];
		return;
	},

	/* Remove a parameter from the request
	 *
	 * param	string parameter key
	 * return	void
	 */
	removeParameter: function (key)
	{
		for (var i=0; i<this.options.paramHolder.length; i++)
		{
			var temp = this.options.paramHolder[i][0];
			if (temp == key)
			{
				this.options.paramHolder.splice (i, 1);
				break;
			}
		}
		return;
	},

	/* Get whether or not the request suceeded
	 *
	 * return	boolean whether or not request was successful
	 */
	responseIsSuccess: function ()
	{
		return this.transport.status == undefined ||
			this.transport.status == 0 ||
			this.transport.status >= 200 && this.transport.status < 300 ||
			false;
	}
}

// ------------------------------------------------------------------------------

// Class for performing Asynchronous Javascript And Xml Requests
Ajax.Request = new Class ();
Ajax.Request.prototype = new Ajax ().extend
({
	/* Construct object
	 *
	 * param	string url
	 * param	object options
	 */
	construct: function (url, options)
	{
		this.transport = this.getTransport ();
		this.url = url;

		if (arguments.length == 0) return;

		this.setOptions (options);
		
		if (this.options.autoStart) this.request ();
	},

	/* Perform request
	 *
	 * param	string URL
	 */
	request: function ()
	{
		try
		{
			this.formatURL ();

			this.transport.open (this.options.method, this.url, this.options.asynchronous);

			if (this.options.asynchronous)
			{
				this.transport.onreadystatechange = this.onStateChange.bind (this);
				setTimeout
				((
					function ()
					{
						this.respondToReadyState (this.readyState.LOADING);
					}
				).bind (this), 10);
			}

			this.setRequestHeaders ();

			var body = this.options.postBody ? this.options.postBody : this.options.parameters;
			this.transport.send (this.options.method.toLowerCase () == 'post' ? body : null);
		}
		catch (e) { Jinx.doNothing (); }
	},

	/* Format URL for request
	 *
	 * return	void
	 */
	formatURL: function ()
	{
		if (this.options.urlFormat.toLowerCase () == 'path')
		{
			this.url = this.url.rtrim ('/');
			this.url	+= '/';
		}
		else
		{
			this.url = this.url.rtrim ('\\?');
			this.url	+= '?';
		}

		this.formatParameters ();
		if (this.options.method.toLowerCase () == 'get')
		{
			this.url += this.options.parameters;
		}
		return;
	},

	/* Format parameters for request
	 *
	 * return	void
	 */
	formatParameters: function ()
	{
		if (this.options.urlFormat.toLowerCase () == 'path' && this.options.method.toLowerCase () != 'post')
		{
			var divider  = '/';
			var equals   = '/';
		}
		else
		{
			var divider  = '&';
			var equals   = '=';
		}

		for (var i=0; i<this.options.paramHolder.length; i++)
		{
			var key		= this.options.paramHolder[i][0];
			var value	= this.options.paramHolder[i][1];

			this.options.parameters += encodeURIComponent (key) + equals + encodeURIComponent (value) + divider;
		}

		this.options.parameters = this.options.parameters.rtrim (divider);

		return;
	},

	// Set headers for the request
	setRequestHeaders: function ()
	{
		var requestHeaders = ['X-Requested-With', 'XMLHttpRequest', 'X-Jinx-Version', Jinx.version];

		if (this.options.method == 'post')
		{
			requestHeaders.push ('Content-type', 'application/x-www-form-urlencoded');

			/* Force "Connection: close" for Mozilla browsers to work around
			 * a bug where XMLHttpReqeuest sends an incorrect Content-length
			 * header. See Mozilla Bugzilla #246651. 
			 */
			if (this.transport.overrideMimeType)
			{
				requestHeaders.push ('Connection', 'close');
			}

		}

		if (this.options.requestHeaders)
		{
			requestHeaders.push.apply (requestHeaders, this.options.requestHeaders);
		}

		for (var i=0; i<requestHeaders.length; i += 2)
		{
			this.transport.setRequestHeader (requestHeaders[i], requestHeaders[i+1]);
		}
	},

	// Handle state change
	onStateChange: function ()
	{
		var readyState = this.transport.readyState;
		if (readyState != this.readyState.LOADING)
		{
			this.respondToReadyState (readyState);
		}

		if (this.options.onStateChange)
		{
			this.options.onStateChange (readyState);
		}
	},

	/* Respond to request ready state
	 *
	 * param	integer ready state
	 */
	respondToReadyState: function (readyState)
	{
		if (readyState == this.readyState.COMPLETE)
		{
			(this.options['on' + this.transport.status] ||
				this.options['on' + (this.responseIsSuccess () ? "Success" : "Failure")] ||
				Jinx.doNothing) (this.transport);
		}

		(this.options['on' + Ajax.Status[readyState]] || Jinx.doNothing) (this.transport);

		// Avoid memory leak in MSIE: clean up the oncomplete event handler
		if (readyState == this.readyState.COMPLETE)
		{
			this.transport.onreadystatechange = Jinx.doNothing;
		}
	}
});

// ------------------------------------------------------------------------------

// Class for performing Asynchronous Javascript And Xml Updates
Ajax.Updater = new Class ();
Ajax.Updater.prototype = new Ajax.Request ().extend
({
	/* Construct object
	 *
	 * param	string url
	 * param	object options
	 * param	object container
	 */
	construct: function (url, options, container)
	{
		this.url = url;
		this.containers =
		{
			success: container.success ? $ (container.success) : $ (container),
			failure: container.failure ? $ (container.failure) :
				(container.success ? null : $ (container))
		}

		this.setOptions (options);

		var onComplete = this.options.onComplete || Jinx.doNothing;
		this.options.onComplete =
		(
			function ()
			{
				this.updateContent ();
				onComplete (this.transport);
			}
		).bind (this);

		this.request ();
	},

	// Update container content
	updateContent: function ()
	{
		var receiver = this.responseIsSuccess () ? this.containers.success : this.containers.failure;

		var match    = new RegExp (this.ScriptFragment, 'img');
		var response = this.transport.responseText.replace (match, '');
		var scripts  = this.transport.responseText.match (match);

		if (receiver)
		{
			if (this.options.insertion)
				new this.options.insertion (receiver, response);
			else
				receiver.innerHTML = response;
		}

		if (this.options.evalScripts && scripts)
		{
			match = new RegExp (this.ScriptFragment, 'im');
			setTimeout
			((
				function ()
				{
					for (var i=0; i<scripts.length; i++)
						eval (scripts[i].match (match)[1]);
				}
			).bind (this), 10);
		}
	}
});

// ------------------------------------------------------------------------------

// Class for performing periodic Asynchronous Javascript And Xml Updates
Ajax.PeriodicUpdater = new Class ();
Ajax.PeriodicUpdater.prototype = new Ajax ().extend
({
	/* Construct object
	 *
	 * param	string url
	 * param	object options
	 * param	object container
	 */
	construct: function (url, options, container)
	{
		this.setOptions (options);
		this.onComplete = this.options.onComplete;
		this.frequency = (this.options.frequency || 2);
		this.updater = {};
		this.url = url;
		this.container = container;

		if (this.options.autoStart == true || this.options.autoStart == undefined)
		{
			this.start ();
		}
	},

	// Start updating
	start: function ()
	{
		this.options.onComplete = this.updateComplete.bind (this);
		this.execute ();
	},

	// Stop updating
	stop: function ()
	{
		this.updater.onComplete = undefined;
		clearTimeout (this.timer);
		(this.onComplete || Jinx.doNothing).apply (this, arguments);
	},

	/* Handle completion of update
	 *
	 * param	object XMLHttpRequest
	 */
	updateComplete: function (transport)
	{
		if (this.onComplete) this.onComplete (transport);
		this.timer = setTimeout (this.execute.bind (this), this.frequency * 1000);
	},

	// Perform update
	execute: function ()
	{
		this.updater = new Ajax.Updater (this.url, this.options, this.container);
	}
});

// +----------------------------------------------------------------------------+
// | Array																		|
// +----------------------------------------------------------------------------+

/* Checks if a value exists in the array
 *
 * param	string value to search for in array
 * return	boolean
 */
Array.prototype.contains = function (value)
{
	var retval = false;
	for (var i=0; i<this.length; i++)
	{
		if (this[i] == value)
		{
			retval = true;
			break;
		}
	}
	return retval;
}

// ------------------------------------------------------------------------------

/* Checks if the given index exists in the array
 *
 * param	mixed key to search for
 * return	boolean
 */
Array.prototype.keyExists = function (key)
{
	return (isUndefined (this[key])) ? false : true;
}

// ------------------------------------------------------------------------------

/* Push one or more elements onto the end of array
 * Overrides built-in Array.push to allow array as arguments
 *
 * param	mixed array or scalar data type
 * return	integer number of elements in the array
 */
Array.prototype.push = function ()
{
	for (var i=0; i<arguments.length; i++)
	{
		if (isArray (arguments[i]))
		{
			for (var c=0; c<arguments[i].length; c++)
			{
				this[this.length] = arguments[i][c];
			}
		}
		else
		{
			this[this.length] = arguments[i];
		}
	}
	return this.length;
}

// ------------------------------------------------------------------------------

/* Convert the values of an array to lowercase
 *
 * return	array
 */
Array.prototype.toLowerCase = function ()
{
	var retval = this;

	for (var i=0; i<retval.length; i++)
	{
		retval[i] = String (retval[i]).toLowerCase ();
	}

	return retval;
}

// ------------------------------------------------------------------------------

/* Convert the values of an array to uppercase
 *
 * return	array
 */
Array.prototype.toUpperCase = function ()
{
	var retval = this;

	for (var i=0; i<retval.length; i++)
	{
		retval[i] = String (retval[i]).toUpperCase ();
	}

	return retval;
}

// ------------------------------------------------------------------------------

/* Craete an array containing a range of elements
 *
 * param	int starting element of array
 * param	int ending element of array
 * param	int number to increment between elements in the sequence
 * return	array
 */
function range (low, high, step)
{
	var retval		= new Array ();
	var charcode	= false;

	if (! isInteger (low) && ! isInteger (high))
	{
		low = low.charCodeAt (0);
		high = high.charCodeAt (0);
		charcode = true;
	}

	if (isUndefined (step) || step < 1)
	{
		step = 1;
	}

	for (var i=low;
			(low > high) ? i>=high : i<=high;
			(low > high) ? i-=step : i+=step)
	{
		retval[retval.length] = (charcode) ? String.fromCharCode (i) : i;
	}

	return retval;
}

// +----------------------------------------------------------------------------+
// | Browser																	|
// +----------------------------------------------------------------------------+

// Object for obtaining information about the browser
var Browser =
{
	platform: navigator.platform,
	version: navigator.appVersion.substring (0, 3),
	agent: null,
		isFirefox: false,
		isICab: false,
		isIE: false,
		isKonqueror: false,
		isOmniWeb: false,
		isOpera: false,
		isSafari: false,
		isWebTV: false,
		isNetscape: false,
	os: null,
		isLinux: false,
		isMac: false,
		isUnix: false,
		isWindows: false,
	flashInstalled: false,
	flashVersion: null,

	// Detect the browser
	detectAgent: function ()
	{
		if (/firefox/i.test (navigator.userAgent))
		{
			Browser.agent = 'Firefox';
			Browser.isFirefox = true;
		}
		else if (/icab/i.test (navigator.userAgent))
		{
			Browser.agent = 'iCab';
			Browser.isICab = true;
		}
		else if (/msie/i.test (navigator.userAgent))
		{
			Browser.agent = 'Internet Explorer';
			Browser.isIE = true;
		}
		else if (/konqueror/i.test (navigator.userAgent))
		{
			Browser.agent = 'Konqueror';
			Browser.isKonqueror = true;
		}
		else if (/omniweb/i.test (navigator.userAgent))
		{
			Browser.agent = 'OmniWeb';
			Browser.isOmniWeb = true;
		}
		else if (/opera/i.test (navigator.userAgent))
		{
			Browser.agent = 'Opera';
			Browser.isOpera = true;
		}
		else if (/safari/i.test (navigator.userAgent))
		{
			Browser.agent = 'Safari';
			Browser.isSafari = true;
		}
		else if (/webtv/i.test (navigator.userAgent))
		{
			Browser.agent = 'WebTV';
			Browser.isWebTV = true;
		}
		else if (!/compatible/i.test (navigator.userAgent))
		{
			Browser.agent = 'Netscape Navigator';
			Browser.isNetscape = true;
		}
		else Browser.agent = 'Unknown';
	},

	// Detect the operating system
	detectOS: function ()
	{
		if (/linux/i.test (navigator.userAgent))
		{
			Browser.os = 'Linux';
			Browser.isLinux = true;
		}
		else if (/mac/i.test (navigator.userAgent))
		{
			Browser.os = 'Mac';
			Browser.isMac = true;
		}
		else if (/x11/i.test (navigator.userAgent))
		{
			Browser.os = 'Unix';
			Browser.isUnix = true;
		}
		else if (/win/i.test (navigator.userAgent))
		{
			Browser.os = 'Windows';
			Browser.isWindows = true;
		}
		else Browser.os = 'Unknown';
	},

	// Detect whether or not the browser supports flash
	detectFlash: function ()
	{
		if (navigator.plugins && navigator.plugins.length > 0)
		{
			var objFlash = navigator.plugins['Shockwave Flash'];
			if (! isUndefined (objFlash))
			{
				Browser.flashInstalled = true;
				if (objFlash.description)
				{
					var version = objFlash.description.replace (/[a-z]/gi, '');
					Browser.flashVersion = version.replace (/\s*[0-9]+$/g, '');
				}
			}

			if (navigator.plugins['Shockwave Flash 2.0'])
			{
				Browser.flashInstalled = true;
				Browser.flashVersion = 2;
			}
		}
		else if (navigator.mimeTypes && navigator.mimeTypes.length > 0)
		{
			objFlash = navigator.mimeTpes['application/x-shockwave-flash'];
			if (objFlash && objFlash.enabledPlugin)
			{
				Browser.flashInstalled = true;
			}
			else
			{
				Browser.flashInstalled = false;
			}
		}
		else
		{
			// VB Script to detect flash on windows
			document.write 
			(
				'<script language="VBScript">\n' +
				'	on error resume next\n' +
				'	For i = 2 to 6\n' + 
				'		If Not (IsObject (CreateObject ("ShockwaveFlash.ShockwaveFlash." & i))) Then\n' + 
				'		Else\n' + 
				'			Browser.flashInstalled = true\n' + 
				'			Browser.flashVersion = i\n' + 
				'		End If\n' + 
				'	Next\n' + 
				'</script>'
			);
		}
	}
}

// Detect the properties of the Browser
Browser.detectAgent ();
Browser.detectOS ();
Browser.detectFlash ();

// +----------------------------------------------------------------------------+
// | Chord																		|
// +----------------------------------------------------------------------------+

// Class for key chord binding
var Chord = new Class ();
Chord.prototype =
{
	/* Construct object
	 *
	 * param	mixed string to be typed or array of key codes
	 * param	object options
	 */
	construct: function (code, options)
	{
		this.setOptions (options);

		this.code = [];
		this.timer = undefined;
		this.index = 0;
		this.locked = !this.options.requiresLock;

		if (isString (code))
		{
			code = code.toUpperCase ()
			for (var i=0; i<code.length; i++)
			{
				this.code[i] = code.charCodeAt (i);
			}
		}
		else if (code.constructor == Array)
		{
			this.code = code;
		}

		if (this.options.enable == true || this.options.enable == undefined)
		{
			this.enable ();
		}
	},

	// Enable chord
	enable: function ()
	{
		Event.createListener (window, 'keydown', this.handleKeyDown.bind (this));
		Event.createListener (window, 'keyup', this.handleKeyUp.bind (this));
	},
	
	// Disable chord
	disable: function ()
	{
		Event.removeListener (window, 'keydown', this.handleKeyDown.bind (this));
		Event.removeListener (window, 'keyup', this.handleKeyUp.bind (this));
		this.reset ();
	},
	
	// Reset chord
	reset: function ()
	{
		clearTimeout (this.timer);
		this.timer = undefined;
		this.index = 0;
		this.locked = !this.options.requiresLock;
	},

	/* Set options for the chord
	 *
	 * param	object options
	 */
	setOptions: function (options)
	{
		this.options =
		{
			onComplete:		Jinx.doNothing,
			onReset:		Jinx.doNothing,
			lockKey:		keyCode.CONTROL,
			requiresLock:	true,
			enable:			true,
			timeOut:		500
		}.extend (options || {})
	},

	/* Handle key down
	 *
	 * param	object event
	 */
	handleKeyDown: function (e)
	{
		e = Event.standardize (e);
		Event.stop (e);

		if (this.options.requiresLock && e.keyCode == this.options.lockKey)
		{
			this.locked = true;
		}
		else if (this.locked)
		{
			if (e.keyCode == this.code[this.index])
			{
				if (++this.index == this.code.length)
				{
					this.options.onComplete ();
					this.reset ();
				}

				clearTimeout (this.timer);
				this.timer = setTimeout (this.reset.bind (this), this.options.timeOut);
			}
			else
			{
				this.reset ();
				this.options.onReset ();
			}
		}
	},

	/* Handle key up
	 *
	 * param	object event
	 */
	handleKeyUp: function (e)
	{
		e = Event.standardize (e);
		Event.stop (e);

		if (this.options.requiresLock && e.keyCode == this.options.lockKey)
		{
			this.reset ();
			this.options.onReset ();
		}
	}
}

// +----------------------------------------------------------------------------+
// | Control Structures															|
// +----------------------------------------------------------------------------+

/* Include a JavaScript source file
 *
 * param	string path of file to include
 * return	void
 */
function include (path)
{
	var objHead		= document.getElementsByTagName('head').item (0);
	var objScript	= document.createElement ('script');

	objScript.setAttribute ('type', 'text/javascript');
	objScript.setAttribute ('src', path);
	objHead.appendChild (objScript);
}

// +----------------------------------------------------------------------------+
// | Cookie																		|
// +----------------------------------------------------------------------------+

// Object for managing cookies
var Cookie =
{
	/* Get the value of a cookie
	 *
	 * param	string name of the cookie
	 * return	mixed value of the cookie or false if cookie doesn't exist
	 */
	read: function (name)
	{
		var arrCookies = document.cookie.split (';');
		for (var i=0; i<arrCookies.length; i++)
		{
			var arrCookie = arrCookies[i].split ('=');
			arrCookie[0] = arrCookie[0].trim ();
			if (arrCookie[0] == name)
			{
				return decodeURIComponent (arrCookie[1]).trim ();
			}
		}
		return false;
	},

	/* Set a new cookie
	 *
	 * param	string name of the cookie
	 * param	string value of the cookie
	 * param	integer number of days to keep the cookie
	 * param	string path the cookie exists under
	 * return	void
	 */
	write: function (name, value, expires, path)
	{
		if (expires)
		{
			var date = new Date ();
			date.setTime (date.getTime () + (((((expires * 24) * 60) * 60) * 1000)));
			expires = '; expires=' + date.toGMTString ();
		}
		else expires = '';

		if (!path) path = '/';

		document.cookie = sprintf ('%s=%s%s; path=%s', name, encodeURIComponent (value), expires, path);
	},

	/* Delete a cookie
	 *
	 * param	string name of the cookie
	 * return	void
	 */
	remove: function (name)
	{
		this.write (name, '', -1);
	},

	/* Create an empty array
	 *
	 * param Array value is inflated	 
	 */	 
	_inflateArray : function (ary) 
	{
		var beg = this.nextEntry (ary) - 1; 
		
		for (var i = beg ; i > -1; i--) 
		{
			ary[i] = null;
		}
	},

	/* Get the position on the next value in the array
	 *
	 * param	string name of the cookie
	 * return	void
	 */
	nextEntry : function (ary) 
	{
		var j = 0; 
		for (var i = 1; ary[i]; i++)
		{
			j = i
		}
		
		return j + 1;
	},
	
	/* Array read from cookie
	 *
	 * param	string name of the cookie
	 * return	void
	 */
	readArray : function (name, ary, expires, path) 
	{
		this._inflateArray (ary); 
		var ent = this.read (name); 
		
		if (ent)
		{
			i = 1; 
			while (ent.indexOf('^') != '-1') 
			{
				ary[i] = ent.substring(0,ent.indexOf('^')); 
				i++;
				ent = ent.substring(ent.indexOf('^')+1, ent.length);
			}
		}
	},

	/* Array write to cookie
	 *
	 * param	string name of the cookie
	 * return	void
	 */
	writeArray : function (name, ary, expires, path) 
	{
		var value = ''; 
		
		for (var i = 1; ary[i]; i++)
		{
			value += ary[i] + '^';
		}
	
		this.write (name, value, expires, path);
	},

	/* Get the count of array entries
	 *
	 * param	string name of the cookie
	 * return	void
	 */	
	arrayEntryCount : function (name, ary)
	{
		this.readArray (name, ary);
		return this.nextEntry (ary) - 1;
	},
	
	/* Remove an array value, by position
	 *
	 * param	string name of the cookie
	 * return	void
	 */
	removeArrayEntry : function (name, ary, value, expires)
	{
		var strNewArray = ''; 
		this.readArray (name, ary); 

		var position = 0;
		for (var i = 0; i <= ary.length; ++i)
		{
			if (ary[i] == value)
			{
				position = i;
				break;
			}
		}
		
		for (var i = 1; i < position; ++i) 
		{
			strNewArray += ary[i] + '^';
		}
		
		for (var j = position + 1; ary[j]; ++j)
		{
			strNewArray += ary[j] + '^';
		}

		this.write (name, strNewArray, expires);
	},
	
	addArrayEntry : function (name, ary, value, expires)
	{
		this.removeArrayEntry (name, ary, value, expires);
		this.readArray (name, ary);
		var intNextPosition = this.nextEntry (ary);
		ary[intNextPosition] = value;
		this.writeArray (name, ary);			
	}
}

// +----------------------------------------------------------------------------+
// | Date															|
// +----------------------------------------------------------------------------+

/* Format the date according to the parameters provided
 *
 * param	string how to format the date
 * return	string
 */
Date.prototype.format = function (format)
{
	var retval		= '';
	var arrDays		= ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
	var arrMonths	= ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
	if (this.isLeapYear ())
	{
		var arrDaysMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
	}
	else
	{
		var arrDaysMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
	}

	for (var i=0; i<format.length; i++)
	{

		switch (format.charAt (i))
		{

			// Day --------------------------------------------------------------

			case 'd':
				// Description: Day of the month, 2 digits with leading zeros
				// Example value: 01 to 31
				retval += (this.getDate () < 10) ? '0' + this.getDate () : this.getDate ();
				break;
		
			case 'D':
				// Description: A textual representation of a day, three letters
				// Example value: Mon through Sun
				retval += arrDays[this.getDay ()].substring (0, 3);
				break;

			case 'j':
				// Day of the month without leading zeros
				// Example value: 1 to 31
				retval += this.getDate ();
				break;

			case 'l':
				// Description: A full textual representation of the day of the week
				// Example value: Sunday through Saturday
				retval += arrDays[this.getDay ()];
				break;

			case 'S':
				// Description: English ordinal suffix for the day of the month, 2 characters
				// Example value: st, nd, rd or th. For use with j
				switch (this.getDate ())
				{
					case 1: case 21: case 31:
						retval += 'st';
						break;

					case 2: case 22:
						retval += 'nd';
						break;

					case 3: case 23:
						retval += 'rd';
						break;

					default:
						retval += 'th';
						break;
				}
				break;

			case 'w':
				// Description: Numeric representation of the day of the week
				// Example value: 0 (for Sunday) through 6 (for Saturday)
				retval += this.getDay ();
				break;

			case 'z':
				// Description: The day of the year (starting from 0)
				// Example value: 0 through 365
				var day = 0;
				for (i=0; i<arrDaysMonth.length; i++)
				{
					if (i == this.getMonth ())
					{
						day += this.getDate ();
						retval += day;
						break;
					}
					else
					{
						day += arrDaysMonth[i];
					}
				}
				break;

			// Week -------------------------------------------------------------

			case 'W':
				// Description: ISO-8601 week number of year, weeks starting on Monday
				// Example value: Example: 42 (the 42nd week in the year)
				var date = new Date (this.getFullYear (), this.getMonth (), this.getDate (), 0, 0, 0);
				date.setDate (date.getDate () - (date.getDay () + 6) % 7 + 3);
				var ms = date.valueOf ();
				date.setMonth (0);
				date.setDate (4);
				retval += Math.round ((ms - date.valueOf ()) / (7 * 864e5)) + 1;
				break;

			// Month ------------------------------------------------------------

			case 'F':
				// Description: A full textual representation of a month, such as January or March
				// Example value: January through December
				retval += arrMonths[this.getMonth ()];
				break;

			case 'm':
				// Description: Numeric representation of a month, with leading zeros
				// Example value: 01 through 12
				retval += (this.getMonth () < 9) ? '0' + (this.getMonth () + 1) : (this.getMonth () + 1);
				break;

			case 'M':
				// Description: A short textual representation of a month, three letters
				// Example value: Jan through Dec
				retval += arrMonths[this.getMonth ()].substring (0, 3);
				break;

			case 'n':
				// Description: Numeric representation of a month, without leading zeros
				// Example value: 1 through 12
				retval += (this.getMonth () + 1);
				break;

			case 't':
				// Description: Number of days in the given month
				// Example value: 28 through 31
				retval += arrDaysMonth[this.getMonth ()];
				break;

			// Year -------------------------------------------------------------

			case 'L':
				// Description: Whether it's a leap year
				// Example value: 1 if it is a leap year, 0 otherwise.
				retval += (this.isLeapYear ()) ? 1 : 0;
				break;

			case 'Y':
				// Description: 	A full numeric representation of a year, 4 digits
				// Example value: Examples: 1999 or 2003
				retval += this.getFullYear ();
				break;

			case 'y':
				// Description: A two digit representation of a year
				// Example value: Examples: 99 or 03
				var year = String (this.getFullYear ());
				retval += year.substring (2, 2);
				break;

			// Time -------------------------------------------------------------

			case 'a':
				// Description: Lowercase Ante meridiem and Post meridiem
				// Example value: am or pm
				retval += (this.getHours () < 12) ? 'am' : 'pm';
				break;

			case 'A':
				// Description: Uppercase Ante meridiem and Post meridiem
				// Example value: AM or PM
				retval += (this.getHours () < 12) ? 'AM' : 'PM';
				break;

			case 'g':
				// Description: 12-hour format of an hour without leading zeros
				// Example value: 1 through 12
				if (this.getHours () == 0 || this.getHours () == 12)
				{
					retval += 12;
				}
				else if (this.getHours () < 12)
				{
					retval += this.getHours ();
				}
				else
				{
					retval += this.getHours () - 12;
				}
				break;

			case 'G':
				// Description: 24-hour format of an hour without leading zeros
				// Example value: 0 through 23
				retval += this.getHours ();
				break;

			case 'h':
				// Description: 12-hour format of an hour with leading zeros
				// Example value: 01 through 12
				var hours;
				if (this.getHours () == 0 || this.getHours () == 12)
				{
					hours = 12;
				}
				else if (this.getHours () < 12)
				{
					hours = this.getHours ();
				}
				else
				{
					hours = (this.getHours () - 12);
				}

				retval += (hours < 10) ? hours = '0' + hours : hours;
				break;

			case 'H':
				// Description: 24-hour format of an hour with leading zeros
				// Example value: 00 through 23
				retval += (this.getHours < 10) ? '0' + this.getHours () : this.getHours ();
				break;

			case 'i':
				// Description: Minutes with leading zeros
				// Example value: 00 to 59
				retval += (this.getMinutes () < 10) ? '0' + this.getMinutes () : this.getMinutes ();
				break;

			case 's':
				// Description: Seconds, with leading zeros
				// Example value: 00 through 59
				retval += (this.getSeconds () < 10) ? '0' + this.getSeconds () : this.getSeconds ();
				break;

			// Timezone ---------------------------------------------------------

			case 'I':
				// Description: Whether or not the date is in daylight savings time
				// Example value: 1 if Daylight Savings Time, 0 otherwise
				timezone = this.toTimeString ();
				retval += (timezone.match (/Daylight/i)) ? 1 : 0;
				break;

			case 'O':
				// Description: Difference to Greenwhich time (GMT) in hours
				// Example value: -0700
				timezone = this.toTimeString ();
				timezone = timezone.replace (/\d{2}\:\d{2}\:\d{2} GMT/i, '');
				timezone = timezone.replace (/\((\w|\s)+\)/i, '');
				retval += timezone;
				break;
			
			case 'T':
				// Description: Timezone setting of the machine
				// Example value: EST, MDT ...
				timezone = this.toTimeString ();
				timezone = timezone.substring (timezone.indexOf ('(') + 1, timezone.indexOf (')'));
				timezone = timezone.replace (/([a-z]|\s)+/g, '');
				retval += timezone;
				break;

			case 'Z':
				// Description: Timezone offset in seconds.
				// Example value: -43200 through 43200
				retval += this.getTimezoneOffset ();
				break;

			// Full Date/Time ---------------------------------------------------

			case 'U':
				// Description: Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
				// Example value: 1131668500
				retval += Math.round ((this.valueOf () / 1000));
				break;

			default:
				retval += format.charAt (i);
				break;

		}

	}
	
	return retval;
}

// ------------------------------------------------------------------------------

/* Determine if the date is leap year
 * Allows test to be performed on an object. 
 *
 * return	boolean
 */
Date.prototype.isLeapYear = function ()
{
	return Date.isLeapYear (this.getFullYear ());
}

// ------------------------------------------------------------------------------

/* Determine if the date is leap year
 * Allows test to be performed statically.
 *
 * param	integer year to test
 * return	boolean
 */
Date.isLeapYear = function (year)
{
	var retval = false;
	if (((year % 4 == 0) && (year % 100 != 0)) || 
		((year % 400 == 0) && (year % 4000 != 0)))
	{
		retval = true;
	}
	return retval;
}

// +----------------------------------------------------------------------------+
// | Document																	|
// +----------------------------------------------------------------------------+

/* Alias for document.getElementById (id)
 *
 * param	mixed element or element id
 * return	mixed element if only one argument otherwise array of elements
 */
function $ ()
{
	var elements = [];

	for (var i=0; i<arguments.length; i++)
	{
		var element = arguments[i];
		if (isString (element))
		{
			element = document.getElementById (element);
		}
		elements.push (element);
	}

	if (elements.length == 1)
	{
		return elements[0];
	}

	return elements;
}

// ------------------------------------------------------------------------------

/* Convert an element into an array
 *
 * param	mixed element or element id
 * return	array
 */
function $A (element)
{
	element = $ (element);
	if (!element) return [];
	else if (element.toArray)
	{
		return element.toArray ();
	}
	else
	{
		var retval = [];
		for (var i=0; i<element.length; i++)
		{
			retval.push (element[i]);
		}
		return retval;
	}
}

// ------------------------------------------------------------------------------

/* Alias for document.getElementById (id).className = className
 *
 * param	mixed element or element id
 * param	string CSS class name
 * return	string element.className
 */
function $C (element, className)
{
	element = $ (element);

	if (! isUndefined (className))
	{
		element.className = className;
	}

	return element.className;
}

// ------------------------------------------------------------------------------

/* Alias for document.getElementById (id).innerHTML = value
 *
 * param	mixed element or element id
 * param	string value
 * param	boolean whether or not to append the value
 * return	string
 */
function $H (element, value, append)
{
	element = $ (element);

	if (! $ ('__Jinx_innerHTML_placeholder__'))
	{
		var doc = document.getElementsByTagName ('body').item (0);
		var div = document.createElement ('div');
		doc.appendChild (div);
		div.setAttribue ('id', '__Jinx_innerHTML_placeholder__');
		Element.hide (div);
	}

	if (! isUndefined (value))
	{
		if (append)
		{
			value = element.innerHTML + value;
		}

		div.innerHTML = value;
		value = div.innerHTML;

		if (div.innerHTML != element.innerHTML)
		{
			element.innerHTML = value;
		}

		div.innerHTML = '';
	}

	return element.innerHTML;
}

// ------------------------------------------------------------------------------

/* Alias for document.getElementById (id).value = value
 *
 * param	mixed element or element id
 * param	string value
 * param	boolean whether or not to append the value
 * return	string
 */
function $F (element, value, append)
{
	if (isUndefined(append)) append = false;
	
	element = $ (element);

	if (! isUndefined (value))
	{
		if (!append) element.value = value;
		else element.value += value;
	}

	return element.value;
}

// ------------------------------------------------------------------------------

/* Alias for document.getElementById (id).src = source
 *
 * param	mixed element or element id
 * param	string source
 * return	string
 */
function $I (element, source)
{
	element = $ (element);

	if (! isUndefined (source))
	{
		element.src = source;
	}

	return element.src;
}

// ------------------------------------------------------------------------------

/* Alias for document.getElementById (id).style[attribute] = value
 *
 * param	mixed element or element id
 * param	string style attribute
 * param	string value of attribute (optional)
 * return	string value of attribute
 */
function $S (element, attribute, value)
{
	element = $ (element);

	if (! isUndefined (value))
	{
		element.style[attribute] = value;
	}

	return element.style[attribute];
}

// ------------------------------------------------------------------------------

/* Obtain a nodelist of elements by their CSS className
 *
 * param	string className to search for
 * return	mixed element if only one otherwise array of elements
 */
document.getElementsByClassName = function (className)
{
	var nodeList = document.getElementsByTagName ('*');
	var elements = [];

	for (var i=0; i<nodeList.length; i++)
	{
		var node = nodeList[i];
		var classNames = node.className.split (' ');
		for (var j=0; j<classNames.length; j++)
		{
			if (classNames[j] == className)
			{
				elements.push (node);
				break;
			}
		}
	}

	if (elements.length == 1)
	{
		return elements[0];
	}

	return elements;
}

// +----------------------------------------------------------------------------+
// | DOM																		|
// +----------------------------------------------------------------------------+

// Object for working with Document Object Model
var DOMDocument = new Class ();
DOMDocument.prototype =
{
	// Construct object
	construct: function ()
	{
		this.document = undefined;
	},

	/* Load DOM Document
	 *
	 * param	string URL of Document
	 * param	handle function to call when document is loaded
	 */
	loadDocument: function (url, callback)
	{
		callback = isFunction (callback) ? callback : Jinx.doNothing;
		if (document.implementation && document.implementation.createDocument)
		{
			this.document = document.implementation.createDocument ('', '', null);
			this.document.onload = function () { callback (this.document, url); }
		}
		else if (window.ActiveXObject)
		{
			this.document = new ActiveXObject ('Microsoft.XMLDOM');
			this.document.onreadystatechange = function ()
			{
				if (this.document.readyState == this.readyState.COMPLETE) callback (this.document, url);
			}
		}
		
		this.document.load (url);
	},

	/* Retrieve the DOM Document
	 *
	 * return	DOM Document
	 */
	getDocument: function ()
	{
		return this.document;
	},

	/* Get DOM document iterator
	 *
	 * param	bitmask what nodes to show
	 * param	handle function used for filtering out elements
	 * param	boolean whether or not to expand entity references in XML documents
	 */
	getIterator: function (whatToShow, filter, entityReferenceExpansion)
	{
		whatToShow = isUndefined (whatToShow) ? NodeFilter.SHOW_ALL : whatToShow;
		filter = isFunction (filter) ? filter : function () { return	NodeFilter.FILTER_ACCEPT; };
		entityReferenceExpansion = isBoolean (entityReferenceExpansion) ? entityReferenceExpansion : false;
		return document.createNodeIterator (this.document, whatToShow, filter, entityReferenceExpansion);
	}
}

// ------------------------------------------------------------------------------

// Object for working with Nodes
var Node =
{
	/* Generates a string representaion of a node
	 *
	 * param	parentNode
	 * return	string
	 */
	serialize: function (parentNode)
	{
		var retval = '';
		if (! isUndefined (parentNode.xml))
		{
			for (var i=0; i<parentNode.childNodes.length; i++)
			{
				var node = parentNode.childNodes[i];
				if (node.nodeType == nodeType.NODE_CDATA_SECTION)
				{
					retval += node.nodeValue;
				}
				else
				{
					retval += node.xml;
				}
			}
		}
		else
		{
			for (var i=0; i<parentNode.childNodes.length; i++)
			{
				var node = parentNode.childNodes[i];
				if (node.nodeType == nodeType.NODE_CDATA_SECTION)
				{
					retval += node.nodeValue;
				}
				else
				{
					var xmlSerializer = new XMLSerializer ();
					retval += xmlSerializer.serializeToString (node);
				}
			}
		}
		return retval;
	}
}

// ------------------------------------------------------------------------------

// Make node types available as constants
var nodeType =
{	
	NODE_ELEMENT:				1,
	NODE_ATTRIBUTE:				2,
	NODE_TEXT:					3,
	NODE_CDATA_SECTION:			4,
	NODE_ENTITY_REFERENCE:		5,
	NODE_ENTITY:				6,
	NODE_PROCESSING_INSTRUCTION:7,
	NODE_COMMENT:				8,
	NODE_DOCUMENT:				9,
	NODE_DOCUMENT_TYPE:			10,
	NODE_DOCUMENT_FRAGMENT:		11,
	NODE_NOTATION:				12
}

// +----------------------------------------------------------------------------+
// | Element																	|
// +----------------------------------------------------------------------------+

// Object for dynamically managing document elements
var Element =
{
	/* Make element visible
	 *
	 * param	mixed element or element id
	 */
	show: function (element)
	{
		$S (element, 'display', 'block');
	},

	/* Make element invisible
	 *
	 * param	mixed element or element id
	 */
	hide: function (element)
	{
		$S (element, 'display', 'none');
	},

	/* Toggle between element visibility
	 *
	 * param	mixed element or element id
	 */
	toggleDisplay: function (element)
	{
		if ($S (element, 'display') == 'none')
		{
			Element.show (element);
		}
		else
		{
			Element.hide (element);
		}
	},

	/* Remove element from DOM
	 *
	 * param	mixed element or element id
	 */
	remove: function (element)
	{
		element = $ (element);
		element.parentNode.removeChild (element);
	},

	/* Get the offsetHeight of the element
	 *
	 * param	mixed element or element id
	 */
	getHeight: function (element)
	{
		element = $ (element);
		return element.offsetHeight;
	},

	/* Get the offsetWidth of the element
	 *
	 * param	mixed element or element id
	 */
	getWidth: function (element)
	{
		element = $ (element);
		return element.offsetWidth;
	},
	
	/* Determine if element posseses className
	 *
	 * param	mixed element or element id
	 * param	string className to search for
	 */
	hasClassName: function (element, className)
	{
		element = $ (element);
		var classes = element.className.split (' ');
		for (var i=0; i<classes.length; i++)
		{
			if (classes[i] == className)
			{
				return true;
			}
		}
		return false;
	},

	/* Add a className to element
	 *
	 * param	mixed element or element id
	 * param	string className
	 * param	string className to add className before
	 */
	addClassName: function (element, className, forceBefore)
	{
		element = $ (element);
		this.removeClassName (element, className);
		if (! isUndefined (forceBefore) && element.className.match (RegExp ('(^| )' + forceBefore)))
		{
			element.className = element.className.replace (RegExp ('( |^)' + forceBefore), '$1' + className + ' ' + forceBefore);
		}
		else
		{
			element.className += ' ' + className;
		}
	},

	/* Remove a className from element
	 *
	 * param	mixed element or element id
	 * param	string className
	 */
	removeClassName: function (element, className)
	{
		element = $ (element);
		var newClassName = '';
		var classes = element.className.split (' ');
		for (var i=0; i<classes.length; i++)
		{
			if (classes[i] == className)
			{
				continue;
			}
			newClassName += classes[i] + ' ';
		}
		element.className = newClassName.trim ();
	},

	slideVert: function (element, duration)
	{
		element = $ (element);
		if (parseInt ($S (element, 'height')) == 0)
		{
			Element.slideDown (element, duration);
		}
		else
		{
			Element.slideUp (element, duration);
		}
	},

	slideDown: function (element, duration)
	{
		element = $ (element);
		if (isUndefined (duration)) duration = 500;
		var height = element.offsetHeight;

		if (parseInt ($S (element, 'height')) >= height)
		{
			$S (element, 'height', height + 'px');
		}
		else
		{
			$S (element, 'height', Math.sum (parseInt ($S (element, 'height')), Math.ceil (height / duration)) + 'px');
			setTimeout (function () {Element.slideDown (element, duration)}, duration);
		}
	},

	slideUp: function (element, duration)
	{
		element = $ (element);
		if (isUndefined (duration)) duration = 500;
		var height = element.offsetHeight;

		if (parseInt ($S (element, 'height')) <= 0)
		{
			$S (element, 'height', '0px');
		}
		else
		{
			$S (element, 'height', parseInt ($S (element, 'height')) - Math.ceil (height / duration) + 'px');
			setTimeout (function () {Element.slideUp (element, duration)}, duration);
		}
	}
}

// +----------------------------------------------------------------------------+
// | Event																		|
// +----------------------------------------------------------------------------+

// Object for handling events
var Event =
{
	/* Create event listener
	 *
	 * param	object to add event to
	 * param	string event to listen for
	 * param	function to handle event with
	 */
	createListener: function (el, event, func)
	{
		if (isString (el))
		{
			el = $ (el);
		}

		if (el.attachEvent)
		{
			el.attachEvent ('on' + event, func);
		}
		else if (el.addEventListener)
		{
			el.addEventListener (event, func, true);
		}
		else
		{
			el['on' + event] = func;
		}
	},

	/* Remove event listener
	 *
	 * param	object to remove event frp,
	 * param	string event to remove
	 * param	function event was being handled with
	 */
	removeListener: function (el, event, func)
	{
		if (isString (el))
		{
			el = $ (el);
		}

		if (el.detachEvent)
		{
			el.detachEvent ('on' + event, func);
		}
		else if (el.removeEventListener)
		{
			el.removeEventListener (event, func, true);
		}
		else
		{
			el['on' + event] = null;
		}
	},

	/* Element the event ocurred on
	 *
	 * param	object Event
	 */
	element: function (event)
	{
		return coalesce
		(
			function () { return	event.target; },
			function () { return	event.srcElement; }
		);
	},

	/* Whether or not left click triggered event
	 *
	 * param	object Event
	 */
	isLeftClick: function (event)
	{
		return coalesce
		(
			function () { return	(event.which && event.which == 1); },
			function () { return	(event.button && event.button == 1); }
		);
	},

	/* X position of mouse
	 *
	 * param	object Event
	 */
	pointerX: function (event)
	{
		return coalesce
		(
			function () { return	(event.pageX); },
			function ()
			{
				return (event.clientX + 
				coalesce
				(
					function () { return	document.documentElement.scrollLeft; },
					function () { return	document.body.scrollLeft; }
				)); 
			}
		);
	},

	/* Y position of mouse
	 *
	 * param	object Event
	 */
	pointerY: function (event)
	{
		return coalesce
		(
			function () { return	(event.pageY); },
			function ()
			{
				return (event.clientY + 
				coalesce
				(
					function () { return	document.documentElement.scrollTop; },
					function () { return	document.body.scrollTop; }
				)); 
			}
		);
	},

	/* Stop event propogation
	 *
	 * param	object Event
	 */
	stop: function (event)
	{
		if (event.preventDefault)
		{
			event.preventDefault ();
			//event.stopPropogation ();
		}
		else if (event.returnValue)
		{
			event.returnValue = false;
			event.cancelBubble ();
		}
	},

	/* Standardize an event for all clients
	 *
	 * param	object Event
	 * return	Event compatible for all clients
	 */
	standardize: function (event)
	{
		if (!event && window.event)
		{
			event = window.event;
		}

		if (!event.preventDefault)
		{
			event.preventDefault = function ()
			{
				this.returnValue = false;
				this.keyCode = 0;
			}
		}

		if (!event.srcElement)
		{
			event.srcElement = event.target;
		}

		if (!event.offsetX)
		{
			event.offsetX = event.layerX;
			event.offsetY = event.layerY;
		}

		if (event.srcElement.nodeType == nodeType.NODE_TEXT)
		{
			event.srcElement = enet.srcElement.parentNode;
		}

		return event;
	},

	/* Standardize an event element for all clients
	 * Deprecated.  Event.standardize will handle standardization of Elements.
	 *
	 * param	object Event
	 * return	Element compatible for all clients
	 */
	standardizeElement: function (event)
	{
		event = Event.standardize (event);
		return event.srcElement;
	}
}

// ------------------------------------------------------------------------------

// Make key codes available as constants
var keyCode =
{
	BACKSPACE:	8,
	TAB:		9,
	ENTER:		13,
	SHIFT:		16,
	CONTROL:	17,
	ALT:		18,
	CAPSLOCK:	20,
	ESC:		27,
	SPACEBAR:	32,

	PAGEUP:33, PAGEDOWN:34,

	END:35, HOME:36,

	LEFT:37, UP:38, RIGHT:39, DOWN:40,

	INSERT:45, DELETE:46,

	ZERO:48, ONE:49, TWO:50, THREE:51, FOUR:52,
	FIVE:53, SIX:54, SEVEN:55, EIGHT:56, NINE:57,		

	A:65, B:66, C:67, D:68, E:69, F:70, G:71, H:72, I:73,
	J:74, K:75, L:76, M:77, N:78, O:79, P:80, Q:81, R:82,
	S:83, T:84, U:85, V:86, W:87, X:88, Y:89, Z:90,

	START:		91,

	F1:112, F2:113, F3:114, F4:115, F5:116, F6:117,
	F7:118, F8:119, F9:120, F10:121, F11:122, F12:123,

	NUMLOCK:	144,
	SCROLLLOCK:	145
}

// +----------------------------------------------------------------------------+
// | Form																		|
// +----------------------------------------------------------------------------+

// Object for dynamically managing forms
var Form =
{
	/* Disable the form
	 *
	 * param	mixed form or form id
	 */
	disable: function (form)
	{
		form = $ (form);
		for (var i=0; i<form.elements.length; i++)
		{
			try
			{
				Form.Element.disable (form.elements[i]);
			}
			catch (e) { continue; }
		}
	},

	/* Enable the form
	 *
	 * param	mixed form or form id
	 */
	enable: function (form)
	{
		form = $ (form);
		for (var i=0; i<form.elements.length; i++)
		{
			try
			{
				Form.Element.enable (form.elements[i]);
			}
			catch (e) { continue; }
		}
	},

	/* Set focus to the first element in the form
	 *
	 * param	mixed form or form id
	 */
	focusFirstElement: function (form)
	{
		form = $ (form);
		for (var i=0; i<form.elements.length; i++)
		{
			var element = form.elements[i];
			if (element.type != 'hidden' && element.disabled == false)
			{
				Form.Element.activate (element);
				break;
			}
		}
	},

	/* Submit the form
	 *
	 * param	mixed form or form id
	 */
	submit: function (form)
	{
		form = $ (form);
		form.submit ();
	},

	/* Reset the form
	 *
	 * param	mixed form or form id
	 */
	reset: function (form)
	{
		form = $ (form);
		form.reset ();
	},	

	/* Get the method of the form
	 *
	 * return	string
	 */
	getMethod: function (form)
	{
		form = $ (form);
		return form.method;
	},

	/* Set the method of the form
	 *
	 * param	mixed form or form id
	 * param	string method to apply to form
	 */
	setMethod: function (form, method)
	{
		form = $ (form);
		form.method = method;
	},
	
	/* Get the action of the form
	 *
	 * param	mixed form or form id
	 * return	string
	 */
	getAction: function (form)
	{
		form = $ (form);
		return form.action;
	},

	/* Set the method of the form
	 *
	 * param	mixed form or form id
	 * param	string action to apply to form
	 */
	setAction: function (form, action)
	{
		form = $ (form);
		form.action = action;
	},

	/* Serialize the elements in the form
	 *
	 * param	mixed form or form id
	 * return	string
	 */
	serialize: function (form)
	{
		form = $ (form);
		var elements = Form.getElements (form);
		var queryComponents = [];
    
		for (var i=0; i<elements.length; i++)
		{
			var queryComponent = Form.Element.serialize (elements[i]);
			if (queryComponent)
			{
				queryComponents.push (queryComponent);
			}
		}
    
		return queryComponents.join ('&');
	},

	clear: function (form)
	{
		form = $ (form);
		var elements = this.getElements (form);
		
		for (var i = 0; i < elements.length; ++i)
		{
			Form.Element.clear (elements[i]);
		}
	},

	/* Get the elements within a form
	 *
	 * param	mixed form or form id
	 * return	array
	 */
	getElements: function (form)
	{
		form = $ (form);
		var elements = [];

		for (tagName in Form.Element.Serializer)
		{
			var tagElements = form.getElementsByTagName (tagName);
			for (var i=0; i<tagElements.length; i++)
			{
				elements.push (tagElements[i]);
			}
		}
		
		return elements;
	},

	/* Get the input elements within a form
	 *
	 * param	mixed form or form id
	 * param	string typeName of input element
	 * param	string name of input element
	 * return	array
	 */
	getInputs: function (form, typeName, name)
	{
		form = $ (form);
		var inputs = form.getElementsByTagName ('input');

		if (!typeName && !name)
		{
			return inputs;
		}

		var matchingInputs = [];
		for (var i=0; i<inputs.length; i++)
		{
			var input = inputs[i];
			if ((typeName && input.type != typeName) || (name && input.name != name))
			{
				continue;
			}
			matchingInputs.push (input);
		}

		return matchingInputs;
	},

	/* Get the elements index within the form.
	 *
	 * param	mixed Element or Element id
	 * return	mixed Integer if element found, otherwise false
	 */
	getIndexByElement: function (element)
	{
		element = $ (element);
		for (var i=0; i<element.form.elements.length; i++)
		{
			if (element == element.form.elements[i])
			{
				return i;
			}
		}

		return false;
	}
}

// ------------------------------------------------------------------------------

// Object for dynamically managing form elements
Form.Element =
{	
	/* Focus and select field
	 *
	 * param	mixed element or element id
	 */
	activate: function (element)
	{
		element = $ (element);
		element.focus ();
		element.select ();
	},

	/* Clear the value of the field
	 *
	 * param	mixed element or element id
	 */
	clear: function (element)
	{
		element = $ (element);
		element.value = '';
	},

	/* Disable the field
	 *
	 * param	mixed element or element id
	 */
	disable: function (element)
	{
		element = $ (element);
		element.disabled = true;
	},

	/* Enable the field
	 *
	 * param	mixed element or element id
	 */
	enable: function (element)
	{
		element = $ (element);
		element.disabled = false;
	},

	/* Set focus on the field
	 *
	 * param	mixed element or element id
	 */
	focus: function (element)
	{
		element = $ (element);
		element.focus ();
	},

	/* Select the field
	 *
	 * param	mixed element or element id
	 */
	select: function (element)
	{
		element = $ (element);
		element.select ();
	},

	/* Get the value of the field
	 *
	 * param	mixed element or element id
	 */
	getValue: function (element)
	{
		return $F (element);
	},

	/* Set the value of the field
	 *
	 * param	mixed element or element id
	 */
	setValue: function (element, value)
	{
		element = $ (element);
		element.value = value;
	},

	/* Serialize form element
	 *
	 * param	mixed element or element id
	 * return	mixed string, or void
	 */
	serialize: function (element)
	{
		element = $ (element);
		var method	= element.tagName.toLowerCase ();
		var param	= Form.Element.Serializer[method] (element);

		if (param)
		{
			return encodeURIComponent (param[0]) + '=' + encodeURIComponent (param[1]);
		}
		return null;
	}
}

// ------------------------------------------------------------------------------

// Object for serializing form elements
Form.Element.Serializer =
{
	/* Serialize form input element
	 *
	 * param	Input
	 * return	mixed array, or string
	 */
	input: function (element)
	{
		var retval = '';
		switch (element.type.toLowerCase ())
		{
			case 'submit': case 'hidden': case 'password': case 'text':
				retval = Form.Element.Serializer.textarea (element);
				break;
			case 'checkbox': case 'radio':
				retval = Form.Element.Serializer.inputSelector (element);
				break;
		}
		return retval;
	},

	/* Serialize form inputSelector element
	 *
	 * param	Input
	 * return	mixed array, or void
	 */
	inputSelector: function (element)
	{
		if (element.checked)
		{
			return [element.name, element.value];
		}
		return null;
	},

	/* Serialize form textarea element
	 *
	 * param	Input
	 * return	array
	 */
	textarea: function (element)
	{
		return [element.name, element.value];
	},

	/* Serialize form select element
	 *
	 * param	Input
	 * return	mixed array, or string
	 */
	select: function (element)
	{
		var retval = '';
		if (element.type == 'select-one')
		{
			var index = element.selectedIndex;
			if (index >= 0)
			{
				retval = element.options[index].value || element.options[index].text;
			}
		}
		else
		{
			retval = [];
			for (var i=0; i<element.length; i++)
			{
				var opt = element.options[i];
				if (opt.selected)
				{
					retval.push(opt.value || opt.text);
				}
			}
		}
		return [element.name, retval];
	}
}

// ------------------------------------------------------------------------------

// Object for selecting a range of checkboxes
Form.CheckboxRange =
{
	startrange: undefined,

	/* Enable checkbox range selection for a form
	 *
	 * param	mixed Form or Form ID
	 * return	void
	 */
	enable: function (form)
	{
		var checkboxes = Form.getInputs (form, 'checkbox');
		for (var i=0; i<checkboxes.length; i++)
		{
			Event.createListener (checkboxes[i], 'click', Form.CheckboxRange._selectRange);
		}
	},

	/* Perform range selection on checkboxes
	 *
	 * param	object Event
	 * return	void
	 */
	_selectRange: function (e)
	{
		e = Event.standardize (e);
		var checkbox = e.srcElement;
		var endrange = Form.getIndexByElement (checkbox);

		if (! isUndefined (Form.CheckboxRange.startrange) && e.shiftKey)
		{
			var form		= checkbox.form;
			var startrange	= Form.CheckboxRange.startrange;
			if (startrange > endrange)
			{
				var temp	= endrange;
				endrange	= startrange;
				startrange	= temp;
			}
			
			for (var i=startrange + 1; i<endrange; i++)
			{
				if (form.elements[i].type == 'checkbox')
				{
					form.elements[i].checked = form.elements[Form.CheckboxRange.startrange].checked;
				}
			}

			Form.CheckboxRange.startrange = undefined;
		}
		else
		{
			Form.CheckboxRange.startrange = Form.getIndexByElement (checkbox);
		}
	}
}

// +----------------------------------------------------------------------------+
// | Insert																		|
// +----------------------------------------------------------------------------+

// Base object used for inserting HTML
var Insert = new Object ();

/* Create a contextual fragment for TBODY and TR elements
 *
 * param	string Text to create a fragment for
 * return	string HTML Markup
 */
Insert._createContextualFragment = function (text)
{
	var objDiv = document.createElement ('div');
	objDiv.innerHTML = '<table><tbody>' + text + '</tbody></table>';
	return $A (objDiv.childNodes[0].childNodes[0].childNodes);
}

/* Insert HTML before an element
 *
 * param	mixed Element or element id
 * param	string HTML to be appended
 * return	void
 */
Insert.Before = function (element, text)
{
	element = $ (element);
	
	try
	{
		element.insertAdjacentHTML ('BeforeBegin', text);
	}
	catch (e)
	{
		if (element.insertAdjacentHTML &&
			(element.tagName.toLowerCase () == 'tbody' || element.tagName.toLowerCase () == 'tr')
			)
		{
			var fragment = this._createContextualFragment (text);
			for (var i=0; i<fragment.length; i++)
			{
				element.parentNode.insertBefore (fragment[i], element);
			}
		}
		else
		{
			var range = element.ownerDocument.createRange ();
			range.setStartBefore (element);
			
			var fragment = range.createContextualFragment (text);
			element.parentNode.insertBefore (fragment, element);
		}
	}
}

/* Insert HTML before the first child of an element
 *
 * param	mixed Element or element id
 * param	string HTML to be appended
 * return	void
 */
Insert.Top = function (element, text)
{
	element = $ (element);

	try
	{
		element.insertAdjacentHTML ('AfterBegin', text);
	}
	catch (e)
	{
		if (element.insertAdjacentHTML &&
			(element.tagName.toLowerCase () == 'tbody' || element.tagName.toLowerCase () == 'tr')
			)
		{
			var fragment = this._createContextualFragment (text);
			for (var i=0; i<fragment.length; i++)
			{
				element.insertBefore (fragment[i], element.firstChild);
			}
		}
		else
		{
			var range = element.ownerDocument.createRange ();
			range.selectNodeContents (element);
			range.collapse (true);

			var fragment = range.createContextualFragment (text);
			element.insertBefore (fragment, element.firstChild);
		}
	}
}

/* Insert HTML after the last child of an element
 *
 * param	mixed Element or element id
 * param	string HTML to be appended
 * return	void
 */
Insert.Bottom = function (element, text)
{
	element = $ (element);

	try
	{
		element.insertAdjacentHTML ('BeforeEnd', text);
	}
	catch (e)
	{
		if (element.insertAdjacentHTML &&
			(element.tagName.toLowerCase () == 'tbody' || element.tagName.toLowerCase () == 'tr')
			)
		{
			var fragment = this._createContextualFragment (text);
			for (var i=0; i<fragment.length; i++)
			{
				element.appendChild (fragment[i]);
			}
		}
		else
		{
			var range = element.ownerDocument.createRange ();
			range.selectNodeContents (element);
			range.collapse (element);
				
			var fragment = range.createContextualFragment (text);
			element.appendChild (fragment);
		}
	}
}

/* Insert HTML after an element
 *
 * param	mixed Element or element id
 * param	string HTML to be appended
 * return	void
 */
Insert.After = function (element, text)
{
	element = $ (element);
	
	try
	{
		element.insertAdjacentHTML ('AfterEnd', text);
	}
	catch (e)
	{
		if (element.insertAdjacentHTML &&
			(element.tagName.toLowerCase () == 'tbody' || element.tagName.toLowerCase () == 'tr')
			)
		{
			var fragment = this._createContextualFragment (text);
			for (var i=0; i<fragment.length; i++)
			{
				element.parentNode.insertBefore (fragment[i], element.nextSibling);
			}
		}
		else
		{
			var range = element.ownerDocument.createRange ();
			range.setStartAfter (element);
			
			var fragment = range.createContextualFragment (text);
			element.parentNode.insertBefore (fragment, element.nextSibling);
		}
	}
}

// +----------------------------------------------------------------------------+
// | Math																		|
// +----------------------------------------------------------------------------+

/* Calculate the sum of two or more numbers
 *
 * param	integer
 * return	integer
 */
Math.sum = function ()
{
	var retval = Number (0);
	for (var i=0; i<arguments.length; i++)
	{
		retval = Number (retval) + Number (arguments[i]);
	}
	return Number (retval);
}

// +----------------------------------------------------------------------------+
// | Number																		|
// +----------------------------------------------------------------------------+

/* Convert decimal to another base
 * Supports conversion to Binary, Octal, Decimal, or Hexadecimal
 *
 * param	integer decimal value
 * param	integer base to convert decimal to
 * return	string
 */
Number.prototype.toBase = function (base)
{
	var value = parseInt (this, 10);
	var retval = '';
	var hextbl = '0123456789ABCDEF'.split ('');
	var quotient = value;
	var remainder = 0;
	while (quotient > 0)
	{
		remainder = Math.floor (quotient % base);
		retval = hextbl[remainder] + retval;
		quotient = Math.floor (quotient / base);
	}
	return String (retval);
}

// ------------------------------------------------------------------------------

/* See if value is greater than min value and less than max value
 *
 * param	integer minimum value to match
 * param	integer maximum value to match
 * param	boolean whether or not to include the min and max
 * return	boolean
 */
Number.prototype.between = function (min, max, equal)
{
	if (isUndefined (equal) || equal)
		return (this >= Number (min) && this <= Number (max));
	else
		return (this > Number (min) && this < Number (max));
}

// +----------------------------------------------------------------------------+
// | PeriodicExecutor															|
// +----------------------------------------------------------------------------+

// Class for performing functionallity at an interval
var PeriodicExecutor = new Class ();
PeriodicExecutor.prototype =
{
	/* Contruct object
	 *
	 * param	function to perform at interval
	 * param	integer interval for execution
	 * param	boolean whether or not to start interval at instantiation
	 */
	construct: function (func, interval, start)
	{
		this.func = func;
		this.interval = interval;
		this.isExecuting = false;
		this.intervalId = null;

		if (start == true || start == undefined)
		{
			this.start ();
		}
	},
	
	// Start interval execution
	start: function ()
	{
		this.intervalId = setInterval (this.execute.bind (this), this.interval * 1000);
	},

	// Stop interval execution
	stop: function ()
	{
		clearInterval (this.intervalId);
	},

	// Perform execution
	execute: function ()
	{
		if (!this.isExecuting)
		{
			try
			{
				this.isExecuting = true;
				this.func ();
			}
			finally
			{
				this.isExecuting = false;
			}
		}
	}
}

// +----------------------------------------------------------------------------+
// | Position																	|
// +----------------------------------------------------------------------------+

// Object for performing DHTML positioning effects
var Position = 
{
	/* Get the coordinates of an element
	 *
	 * param	mixed element or element id
	 */
	getCoordinates: function (element)
	{
		element = $ (element);
		var coords	= {x:element.offsetLeft, y:element.offsetTop};
		if (element.offsetParent)
		{
			var temp = Position.getCoordinates (element.offsetParent);
			coords.x += temp.x;
			coords.y += temp.y;
		}
		return coords;
	},

	/* Center an element on top of another element
	 *
	 * param	mixed element or element id
	 * param	mixed parent or parent id
	 */
	centerElement: function (element, parent)
	{
		element			= $ (element);
		var elHeight	= element.offsetHeight;
		var elWidth		= element.offsetWidth;

		if (! isUndefined (parent))
		{
			parent = $ (parent);
			var coords		= Position.getCoordinates (parent);
			var parHeight	= parent.offsetHeight;
			var parWidth	= parent.offsetWidth;
			var top			= ((parHeight / 2) - (elHeight / 2)) + coords.y;
			var left		= ((parWidth / 2) - (elWidth / 2)) + coords.x;
		}
		else
		{
			var parHeight	= Window.clientHeight;
			var parWidth	= Window.clientWidth;
			var parOffset	= 0;
			if (window.innerHeight)
			{
				  parOffset = window.pageYOffset
			}
			else if (document.documentElement && document.documentElement.scrollTop)
			{
				parOffset = document.documentElement.scrollTop
			}
			else if (document.body)
			{
				  parOffset = document.body.scrollTop
			}
			
			var top			= (parHeight / 2) - (elHeight/2) + parOffset;
			if (top < 0)
			{
				top = 0;
			}
			var left		= (parWidth / 2) - (elWidth / 2);
		}

		$S (element, 'position', 'absolute');
		$S (element, 'top', top + 'px');
		$S (element, 'left', left + 'px');
	}
}

// +----------------------------------------------------------------------------+
// | Request																	|
// +----------------------------------------------------------------------------+

// Object for retrieving client values passed during an HTTP request
var Request =
{
	/* Get a parameter from the query string
	 *
	 * return	mixed, string if parameter found, otherwise null
	 */
	getParameter: function (name)
	{
		var query = location.search.substring (1, location.search.length);
		query = query.replace (/%26/g, '&');
		query = query.replace (/%3D/g, '=');

		var arrPair = query.split ('&');
		for (var i=0; i<arrPair.length; i++)
		{
			var arrparam	= arrPair[i].split ('=');
			if (encodeURIComponent (arrParam[0]).trim () == name)
			{
				return encodeURIComponent (arrParam[1]).trim ();
			}
		}
		return null;
	},

	/* Get an array of parameter names
	 *
	 * return	mixed, array if names found, otherwise null
	 */
	getParameterNames: function ()
	{
		var retval = [];
		var query = location.search.substring (1, location.search.length);
		query = query.replace (/%26/g, '&');
		query = query.replace (/%3D/g, '=');

		var arrPair = query.split ('&');
		for (var i=0; i<arrPair.length; i++)
		{
			var arrparam	= arrPair[i].split ('=');
			arrParam[0] = arrParam[0].trim ();
			retval[retval.length] = arrParam[0];
		}
		return (retval.length > 0) ? retval : null;
	},

	/* Get an array of parameter values
	 *
	 * return	mixed, array if values found, otherwise null
	 */
	getParameterValues: function (name)
	{
		var retval = [];
		var query = location.search.substring (1, location.search.length);
		query = query.replace (/%26/g, '&');
		query = query.replace (/%3D/g, '=');

		var arrPair = query.split ('&');
		for (var i=0; i<arrPair.length; i++)
		{
			var arrparam	= arrPair[i].split ('=');
			arrParam[0] = arrParam[0].trim ();
			arrParam[1] = arrParam[1].trim ();
			if (encodeURIComponent (arrParam[0]) == name)
			{
				retval[retval.length] = arrParam[1];
			}
		}
		return (retval.length > 0) ? retval : null;
	},

	/* Get the array of cokies found in the request
	 *
	 * return	mixed, array if cookies found, otherwise null
	 */
	getCookies: function ()
	{
		var retval = [];
		var arrCookies = document.cookie.split (';');
		for (i=0; i<arrCookies.length; i++)
		{
			arrCookie = arrCookies[i].split ('=');
			arrCookie[1] = arrCookie[1].trim ();
			retval[retval.length] = arrCookie[1];
		}
		return (retval.length > 0) ? retval : null;
	},

	/* Get any query string taht is part of the HTTP request URI
	 *
	 * return	string
	 */
	getQueryString: function ()
	{
		return location.search.substring (1, location.search.length);
	},

	/* Get the URI to the current location
	 *
	 * return	string
	 */
	getRequestURI: function ()
	{
		var retval = location.toString ();
		return retval.replace (location.search, '');
	}
}

// +----------------------------------------------------------------------------+
// | Selection																	|
// +----------------------------------------------------------------------------+

// Get the value of selected text
var Selection =
{
	/* Get the value of the selection
	 *
	 * return	string value of selection
	 */
	getValue: function ()
	{
		if (window.getSelection)
		{
			return window.getSelection ();
		}
		else if (document.getSelection)
		{
			return document.getSelection ();
		}
		else if (document.selection)
		{
			return document.selection.createRange ().text;
		}
		else return '';
	}
}

// +----------------------------------------------------------------------------+
// | String																		|
// +----------------------------------------------------------------------------+

/* Output a string
 *
 * param	string 
 */
function echo ()
{
	for (var i=0; i<arguments.length; i++)
	{
		document.write (arguments[i]);
	}
}

// ------------------------------------------------------------------------------

/* Output a formatted string
 *
 * param	string format
 * return	integer number of characters printed
 */
function printf ()
{
	string = sprintf (arguments);
	document.write (string);
	return (string.length > 0) ? string.length : -1;
}

// ------------------------------------------------------------------------------

/* Return a string
 *
 * param	string
 * return	string
 */
function sprint ()
{
	var output = '';
	for (var i=0; i<arguments.length; i++)
	{
		output += arguments[i];
	}

	return output;
}

// ------------------------------------------------------------------------------

/* Return a formatted string
 *
 * Usage:
 * %[flags][width][.precision][modifiers]type
 * c - the argument is treated as an integer, and presented as the character with that ASCII value.
 * d - the argument is treated as an integer, and presented as a (signed) decimal number.
 * e - the argument is treated as scientific notation (e.g. 1.2.e+2).
 * E - the argument is treated as scientific notation (e.g. 1.2.E+2).
 * f - the argument is treated as a float, and presented as a floating-point number.
 * o - the argument is treated as an integer, and presented as an octal number.
 * s - the argument is treated as and presented as a string.
 * u - the argument is treated as an integer, and presented as an unsigned decimal number.
 * x - the argument is treated as an integer, and presented as a hexadecimal number (with lowercase letters).
 * X - the argument is treated as an integer, and presented as a hexadecimal number (with uppercase letters).
 *
 * param	string format
 * return	string
 */
function sprintf ()
{
	// Global constants
	var SIZEDEF	=	0x00001;
	var ZERODEF	= 	0x00002;
	var DOTDEF	=	0x00004;
	var BARDEF	=	0x00008;
	var SIGNDEF	=	0x00010;
	var STARDEF	=	0x00020;
	var NUMDEF	=	0x00040;
	var ESCAPE	=	1;
	var BASE8	=	8;
	var BASE10	=	10;
	var BASE16	=	16;

	// Convert an array to decimal
	var atodec = function (array)
	{
		var retval = 0;
		for (var i=0; i<array.length; i++)
		{
			if (isUndefined (array[i])) continue;
			retval = parseInt (retval + array[i], BASE10);
		}
		return retval;
	}

	// Output a padded value
	var outwidth = function (value, _num, _opt)
	{
		var retval = value;
		if (_opt & SIZEDEF)
		{
			var space = '';
			for (var i=retval.length; i<_num; i++)
			{
				space += outspace (_opt);
			}

			if (_opt & BARDEF)
				retval += space;
			else
				retval = space + retval;
		}
		return retval;
	}

	// Output a space or a zero
	var outspace = function (_opt)
	{
		var retval;
		if (_opt & ZERODEF)
			retval = '0';
		else
			retval = ' ';
		return retval;
	}

	// Format a character
	var outchar = function (value, _num, _opt)
	{
		var retval;
		if (isNumeric (value))
			retval = String.fromCharCode (value);
		else
			retval = value[0];

		return retval;
	}

	// Format a string
	var outstring = function (value, _pcn, _num, _opt)
	{
		var retval = String (value);
		if (_opt & SIZEDEF)
		{
			var space = '';
			for (var i=_num; i>value.length; i--)
			{
				space += outspace (_opt);
			}

			if (_opt & BARDEF)
				retval += space;
			else
				retval = space + retval;
		}

		if (_pcn)
			retval = retval.substring (0, _pcn);

		return retval;
	}

	// Format a decimal
	var decimal = function (value, _num, _opt)
	{
		var retval = String (number (value, _num, _opt));
		if (retval.indexOf ('.') > 0)
		{
			retval = retval.substring (0, retval.indexOf ('.'));
		}

		retval = outwidth (retval, _num, _opt);

		if (value < 0)
			retval *= -1;
		else if (_opt & SIGNDEF)
			retval = '+' + retval;

		return retval;
	}

	// Format an exponent
	var exponent = function (value, _case, _pcn, _num, _opt)
	{
		var retval = parseFloat (value);
		var exp = Math.floor (Math.log (retval) / Math.LN10);
		var man = retval / Math.pow (BASE10, exp);
		
		retval = floating (man, _pcn, _num, _opt) + _case + String (exp);
		return retval;
	}

	// Format a floating point
	var floating = function (value, _pcn, _num, _opt)
	{
		var retval = String (number (value, _num, _opt));
		if (_pcn)
		{
			var precision = retval.substring (retval.indexOf ('.'), _pcn + 2);
			var aprecision = precision.split (''), aretval = retval.split ('');
			var round = aretval[(retval.indexOf ('.') + 1) + _pcn];
			if (round >= 5)
				precision = precision.substring (0, precision.length - 1) + String (Number (aprecision[precision.length - 1]) + 1);

			while (precision.length <= _pcn)
			{
				precision = precision + '0';
			}
			retval = parseInt (retval, BASE10) + precision;
		}
		else if (_opt & DOTDEF)
		{
			retval = retval.substr (0, retval.indexOf ('.'));
		}

		retval = outwidth (retval, _num, _opt);

		if (value < 0)
			retval *= -1
		else if (_opt & SIGNDEF)
			retval = '+' + retval;

		return retval;
	}

	// Format a number
	var number = function (value, _num, _opt)
	{
		var retval = parseFloat (value, BASE10);		
		if (isNaN (retval))
			retval = 0;
		else if (retval < 0)
			retval *= -1;
		return Number (retval);
	}

	// Format octal
	var octal = function (value, _num, _opt)
	{
		var retval = Number (value).toBase (BASE8);
		retval = outwidth (retval, _num, _opt);

		if (_opt & NUMDEF)
			retval = '0' + retval;

		return retval;
	}

	// Format hex
	var hex = function (value, _case, _num, _opt)
	{
		var retval = Number (value).toBase (BASE16);
		retval = outwidth (retval, _num, _opt);

		if (_case.charCodeAt (0) == 'X'.charCodeAt (0))
			retval = retval.toUpperCase ();
		else
			retval = retval.toLowerCase ();

		if (_opt & NUMDEF)
			retval = '0' + _case + retval;

		return retval;
	}

	var flag = 0, argc = 0;
	var argv = (isObject (arguments[0])) ? arguments[0] : arguments;
	var format = argv[argc].split ('');
	var output = '';
	var numbuf = [0,0,0,0,0,0], pcnbuf = [];
	var numindex = 0, pcnindex = 0, num = 0, opt = 0, pcn = 0;
	
	for (var i=0; i<format.length; i++)
	{
		if (!(flag & ESCAPE) && format[i] != '%')
		{
			output += format[i];
		}
		else if (format[i] == '%')
		{
			if ((flag & ESCAPE))
			{
				flag &= ~ESCAPE;
				output += format[i];
			}
			else
			{
				flag |= ESCAPE;
				numindex = -1;
				pcnindex = -1;
				num = 0;
				opt = 0;
				pcn = 0;
			}
		}
		else if ((flag & ESCAPE) && format[i] == 'c')
		{
			argc++;
			output += outchar (argv[argc], num, opt);
			flag &= ~ESCAPE;
		}
		else if ((flag & ESCAPE) && (format[i] == 'd' || format[i] == 'i'))
		{
			argc++;
			output += decimal (argv[argc], num, opt);
			flag &= ~ESCAPE;
		}
		else if ((flag & ESCAPE) && (format[i] == 'e' || format[i] == 'E'))
		{
			argc++;
			output += exponent (argv[argc], format[i], pcn, num, opt);
			flag &= ~ESCAPE;
		}
		else if ((flag & ESCAPE) && format[i] == 'f')
		{
			argc++;
			output += floating (argv[argc], pcn, num, opt);
			flag &= ~ESCAPE;
		}
		else if ((flag & ESCAPE) && format[i] == 'o')
		{
			argc++;
			output += octal (argv[argc], num, opt);
			flag &= ~ESCAPE;
		}
		else if ((flag & ESCAPE) && format[i] == 's')
		{
			argc++;
			output += outstring (argv[argc], pcn, num, opt);
			flag &= ~ESCAPE;
		}
		else if ((flag & ESCAPE) && format[i] == 'u')
		{
			argc++;
			output += number (argv[argc], num, opt);
			flag &= ~ESCAPE;
		}
		else if ((flag & ESCAPE) && (format[i] == 'x' || format[i] == 'X'))
		{
			argc++;
			output += hex (argv[argc], format[i], num, opt);
			flag &= ~ESCAPE;
		}
		else if ((flag & ESCAPE) && isNumeric (format[i]))
		{
			if(numindex > 3) flag &= ~ESCAPE;
			else
			{
				if (opt & DOTDEF)
				{
					pcnbuf[++pcnindex] = format[i];
					pcnbuf[pcnindex + 1] = 0;
					pcn = atodec (pcnbuf);
				}
				else if (!(opt & STARDEF))
				{
					numbuf[++numindex] = format[i];
					numbuf[numindex + 1] = 0;
					num = atodec (numbuf);
				}
				opt |= SIZEDEF;
				if (format[i] == '0')
					opt |= ZERODEF;
			}
		}
		else if ((flag & ESCAPE) && 
			  (format[i] == '.' || format[i] == ' ' ||
				format[i] == '-' || format[i] == '+' ||
				format[i] == '#' || format[i] == '*'))
		{
			if (format[i] == '.') opt |= DOTDEF;
			if (format[i] == '-') opt |= BARDEF;
			if (format[i] == '+') opt |= SIGNDEF;
			if (format[i] == '#') opt |= NUMDEF;
			if (format[i] == '*')
			{
				argc++;
				numbuf[++numindex] = argv[argc];
				numbuf[numindex + 1] = 0;
				num = atodec (numbuf);
				opt |= SIZEDEF;
				opt |= STARDEF;
			}
		}
	}

	return output;
}

// ------------------------------------------------------------------------------

/* Repeat a string
 *
 * param	integer number of times to repeat string
 * return	string
 */
String.prototype.repeat = function (count)
{
	var retval = '';
	for (var i=0; i<count; i++)
	{
		retval += this;
	}
	return retval;
}

// ------------------------------------------------------------------------------

/* Strip HTML or XML tags from a string
 *
 * return	string
 */
String.prototype.stripTags = function ()
{
	return this.replace (/<\/?[^>]+>/gi, '');
}

// ------------------------------------------------------------------------------

/* Strip quotes from a string
 * If optional argument is provided will replace quotes with escaped quotes
 *
 * param	boolean whether or not to replace quotes with escaped quotes
 * return	string
 */
String.prototype.stripQuotes = function (replace)
{
	var quotes = new RegExp ('\"', 'gm');
	var replacement = (replace) ? '\\\"' : '';
	return this.replace (quotes, replacement);
}

// ------------------------------------------------------------------------------

/* Strip whitespace from the beginning of a string
 *
 * param	character list of characters to be replaced
 * return	string
 */
String.prototype.ltrim = function ()
{
	var retval = this;
	var regexp = '';
	if (arguments.length > 0)
	{
		for (var i=0; i<arguments.length; i++)
		{
			regexp += sprintf ('|%s+', arguments[i]);
		}
	}

	//regexp = new RegExp (sprintf ('^(?:\u0020+|\t+|\n+|\r+|\v+|\0+%s)+', regexp), 'g');
	regexp = new RegExp (sprintf ('^(?:\u0020+|\t+|\n+|\r+|\u000b+|\0+%s)+', regexp), 'g');

	retval = retval.replace (regexp, '');
	return retval;
}

// ------------------------------------------------------------------------------

/* Strip whitespace from the end of a string
 *
 * param	character list of characters to be replaced
 * return	string
 */
String.prototype.rtrim = function ()
{
	var retval = this;
	var regexp = '';
	if (arguments.length > 0)
	{
		for (var i=0; i<arguments.length; i++)
		{
			regexp += sprintf ('|%s+', arguments[i]);
		}
	}

	//regexp = new RegExp (sprintf ('(?:\u0020+|\t+|\n+|\r+|\v+|\0+%s)+$', regexp), 'g');
	regexp = new RegExp (sprintf ('(?:\u0020+|\t+|\n+|\r+|\u000b+|\0+%s)+$', regexp), 'g');

	retval = retval.replace (regexp, '');
	return retval;
}

// ------------------------------------------------------------------------------

/* Strip whitespace from the beginning and end of a string
 *
 * param	character list of characters to be replaced
 * return	string
 */
String.prototype.trim = function ()
{
	var retval = this;
	var regexp = '';
	if (arguments.length > 0)
	{
		for (var i=0; i<arguments.length; i++)
		{
			regexp += sprintf ('|%s+', arguments[i]);
		}
	}

	//regexp = new RegExp (sprintf ('^(?:\u0020+|\t+|\n+|\r+|\v+|\0+%s)+|(?:\u0020+|\t+|\n+|\r+|\v+|\0+%s)+$', regexp, regexp), 'g');
	regexp = new RegExp (sprintf ('^(?:\u0020+|\t+|\n+|\r+|\u000b+|\0+%s)+|(?:\u0020+|\t+|\n+|\r+|\u000b+|\0+%s)+$', regexp, regexp), 'g');

	retval = retval.replace (regexp, '');
	return retval;
}

// +----------------------------------------------------------------------------+
// | Variable Handling															|
// +----------------------------------------------------------------------------+

/* Defines a named constant
 *
 * param	string name of constant
 * param	mixed value of constant
 * return	boolean
 */
function define (name, value)
{
	try
	{
		eval ('window.' + name + ' = value');
		return true;
	}
	catch (e) { return	false; }
}

// ------------------------------------------------------------------------------

/* Checks whether a given named constant exists
 *
 * param	string name of constant
 * return	boolean
 */
function defined (name)
{
	return eval ('!window.' + name + ' ? false : true');
}

// ------------------------------------------------------------------------------

/* Finds whether a variable is an array
 *
 * param	mixed variable to be checked
 * return	boolean
 */
function isArray (value)
{
    return isObject (value) && value.constructor == Array;
}

// ------------------------------------------------------------------------------

/* Finds whether a variable is a boolean
 *
 * param	mixed variable to be checked
 * return	boolean
 */
function isBoolean (value)
{
    return typeof value == 'boolean';
}

// ------------------------------------------------------------------------------

/* Determine whether a variable is empty
 *
 * param	mixed variable to be checked
 * return	boolean true if empty otherwise false
 */
function isEmpty (value)
{
	var retval = false;
	if (isUndefined (value) || value == false || value == null)
	{
		retval = true;
	}
	else if (isString (value) && (value.trim () == '' || value.trim () == '0'))
	{
		retval = true;
	}
	else if (isInteger (value) && value == 0)
	{
		retval = true;
	}
	else if (isArray (value) && value.length == 0)
	{
		retval = true;
	}
	return retval;
}

// ------------------------------------------------------------------------------

/* Finds whether a variable is a float
 *
 * param	mixed variable to be checked
 * return	boolean
 */
function isFloat (value)
{
	return typeof value == 'number' && String (value).indexOf ('.') > 0;
}

// ------------------------------------------------------------------------------

/* Finds whether a variable is a function
 *
 * param	mixed variable to be checked
 * return	boolean
 */
function isFunction (value)
{
    return typeof value == 'function';
}

// ------------------------------------------------------------------------------

/* Finds whether a variable is an integer
 *
 * param	mixed variable to be checked
 * return	boolean
 */
function isInteger (value)
{
    return typeof value == 'number' && isFinite (value);
}

// ------------------------------------------------------------------------------

/* Finds whether a variable is NULL
 *
 * param	mixed variable to be checked
 * return	boolean
 */
function isNull (value)
{
    return typeof value == 'object' && !value;
}

// ------------------------------------------------------------------------------

/* Finds whether a variable is a number or a numeric string
 *
 * param	mixed variable to be checked
 * return	boolean
 */
function isNumeric (value)
{
	return !isNaN (parseInt (value, 10));
}

// ------------------------------------------------------------------------------

/* Finds whether a variable is an object
 *
 * param	mixed variable to be checked
 * return	boolean
 */
function isObject (value)
{
    return	(value && typeof value == 'object') || isFunction (value);
}

// ------------------------------------------------------------------------------

/* Finds whether a variable is a scalar
 *
 * param	mixed variable to be checked
 * return	boolean
 */
function isScalar (value)
{
	return isInteger (value) || isFloat (value) || isString (value) || isBoolean (value);
}

// ------------------------------------------------------------------------------

/* Finds whether a variable is a string
 *
 * param	mixed variable to be checked
 * return	boolean
 */
function isString (value)
{
    return typeof value == 'string';
}

// ------------------------------------------------------------------------------

/* Finds whether a variable is UNDEFINED
 *
 * param	mixed variable to be checked
 * return	boolean
 */
function isUndefined (value)
{
    return typeof value == 'undefined';
} 

// ------------------------------------------------------------------------------

/* Dumps information about a variable
 *
 * param	mixed expression
 * return	void
 */
var VARDUMPDEF = -1;
function varDump ()
{
	VARDUMPDEF++;
	var tab = String ('\t');
	for (var i=0; i<arguments.length; i++)
	{
		echo (tab.repeat (VARDUMPDEF));
		switch (typeof arguments[i])
		{
			case 'boolean':
				printf ('bool(%s)\n', arguments[i]);
				break;

			case 'number':
				if (isFloat (arguments[i]))
				{
					printf ('float(%f)\n', arguments[i]);
				}
				else
				{
					printf ('int(%d)\n', arguments[i]);
				}
				break;

			case 'string':
				printf ('string(%d) "%s"\n', arguments[i].length, arguments[i]);
				break;

			case 'object':
				if (isNull (arguments[i]))
				{
					echo ('null\n');
				}
				else if (isArray (arguments[i]))
				{
					printf ('array(%d) {\n', arguments[i].length);
					for (var c=0; c<arguments[i].length; c++)
					{
						echo (tab.repeat (VARDUMPDEF + 1));
						printf ('[%d]=>\n', c);
						varDump (arguments[i][c]);
					}
					echo (tab.repeat (VARDUMPDEF));
					echo ('}\n');
				}
				else
				{
					var object = [];
					for (property in arguments[i])
					{
						if (typeof arguments[i][property] != 'function')
						{
							object[object.length] = property;
						}
					}
					var constructor = String (arguments[i].constructor).replace (/\s+/gm, '');
					constructor = constructor.replace (/function/, '');
					constructor = constructor.replace (/\(\S+/, '');
					printf ('object(%s) (%d) {\n', constructor, object.length);
					for (var c=0; c<object.length; c++)
					{
						echo (tab.repeat (VARDUMPDEF + 1));
						printf ('[%s]=>\n', object[c]);
						varDump (arguments[i][object[c]]);
					}
					echo (tab.repeat (VARDUMPDEF));
					echo ('}\n');
				}
				break;

			case 'undefined': default:
				echo ('undefined\n');
				break;
		}
	}
	VARDUMPDEF--;
}

// +----------------------------------------------------------------------------+
// | Window																		|
// +----------------------------------------------------------------------------+

// Object for obtaining information about the window
var Window =
{
	clientWidth:	0,
	clientHeight:	0,
	offsetWidth:	0,
	offsetHeight:	0,
	scrollTop:		0,
	scrollLeft:		0,
	scrollWidth:	0,
	scrollHeight:	0,
	isLoaded:		false,
	
	// Determine the clientWidth and clientHeight of the window
	detectDimensions: function ()
	{
		if (document.documentElement && document.documentElement.clientWidth)
		{
			this.clientWidth	= document.documentElement.clientWidth;
			this.clientHeight	= document.documentElement.clientHeight;
			this.offsetWidth	= document.documentElement.offsetWidth;
			this.offsetHeight	= document.documentElement.offsetHeight;
		}
		else if (document.body && document.body.clientWidth)
		{
			this.clientWidth	= document.body.clientWidth;
			this.clientHeight	= document.body.clientHeight;
			this.offsetWidth	= document.body.offsetWidth;
			this.offsetHeight	= document.body.offsetHeight;
		}
	},

	// Determine the scrollTop and scrollLeft of the window
	detectScroll: function ()
	{
		if (document.documentElement && document.documentElement.scrollTop)
		{
			this.scrollTop		= document.documentElement.scrollTop;
			this.scrollLeft		= document.documentElement.scrollLeft;
			this.scrollWidth	= document.documentElement.scrollWidth;
			this.scrollHeight	= document.documentElement.scrollHeight;
		}
		else if (document.body && document.body.scrollTop)
		{
			this.scrollTop		= document.body.scrollTop;
			this.scrollLeft		= document.body.scrollLeft;
			this.scrollWidth	= document.body.scrollWidth;
			this.scrollHeight	= document.body.scrollHeight;
		}
	},

	// Specify that the window has finished loading the content
	setIsLoaded: function ()
	{
		Window.isLoaded = true;
	}
}

/* This function overloads the native document.getElementbyId function of IE and Opera
   to compensate for the fact that they both incorrectly allow element names to be treated as ids
   This function makes it work correctly according to spec and like Firefox
*/
if(Browser.isOpera || Browser.isIE){
  document.nativeGetElementById = document.getElementById;
  //redefine it!
  document.getElementById = function(id){
    var elem = document.nativeGetElementById(id);
    if(elem){
      //verify it is a valid match!
      if(elem.id == id){
        //valid match!
        return elem;
      } else {
        //not a valid match!
        //the non-standard, document.all array has keys for all name'd, and id'd elements
        //start at one, because we know the first match, is wrong!
        for(var i=1;i<document.all[id].length;i++){
          if(document.all[id][i].id == id){
            return document.all[id][i];
          }
        }
      }
      if(typeof elem.id == 'object')
		{
			return elem;
		} 
    }
    return null;
  };
}

// Detect the properties of the Window
Window.detectDimensions ();
Window.detectScroll ();

// Listen for events to update Window properties
Event.createListener (window, 'resize', Window.detectDimensions);
Event.createListener (window, 'scroll', Window.detectScroll);
Event.createListener (window, 'load', Window.setIsLoaded);
