if (!window.jQuery) {
    
/*!
 * jQuery JavaScript Library v1.3.2
 * http://jquery.com/
 *
 * Copyright (c) 2009 John Resig
 * Dual licensed under the MIT and GPL licenses.
 * http://docs.jquery.com/License
 *
 * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
 * Revision: 6246
 */
(function(){

var 
	// Will speed up references to window, and allows munging its name.
	window = this,
	// Will speed up references to undefined, and allows munging its name.
	undefined,
	// Map over jQuery in case of overwrite
	_jQuery = window.jQuery,
	// Map over the $ in case of overwrite
	_$ = window.$,

	jQuery = window.jQuery = window.$ = function( selector, context ) {
		// The jQuery object is actually just the init constructor 'enhanced'
		return new jQuery.fn.init( selector, context );
	},

	// A simple way to check for HTML strings or ID strings
	// (both of which we optimize for)
	quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
	// Is it a simple selector
	isSimple = /^.[^:#\[\.,]*$/;

jQuery.fn = jQuery.prototype = {
	init: function( selector, context ) {
		// Make sure that a selection was provided
		selector = selector || document;

		// Handle $(DOMElement)
		if ( selector.nodeType ) {
			this[0] = selector;
			this.length = 1;
			this.context = selector;
			return this;
		}
		// Handle HTML strings
		if ( typeof selector === "string" ) {
			// Are we dealing with HTML string or an ID?
			var match = quickExpr.exec( selector );

			// Verify a match, and that no context was specified for #id
			if ( match && (match[1] || !context) ) {

				// HANDLE: $(html) -> $(array)
				if ( match[1] )
					selector = jQuery.clean( [ match[1] ], context );

				// HANDLE: $("#id")
				else {
					var elem = document.getElementById( match[3] );

					// Handle the case where IE and Opera return items
					// by name instead of ID
					if ( elem && elem.id != match[3] )
						return jQuery().find( selector );

					// Otherwise, we inject the element directly into the jQuery object
					var ret = jQuery( elem || [] );
					ret.context = document;
					ret.selector = selector;
					return ret;
				}

			// HANDLE: $(expr, [context])
			// (which is just equivalent to: $(content).find(expr)
			} else
				return jQuery( context ).find( selector );

		// HANDLE: $(function)
		// Shortcut for document ready
		} else if ( jQuery.isFunction( selector ) )
			return jQuery( document ).ready( selector );

		// Make sure that old selector state is passed along
		if ( selector.selector && selector.context ) {
			this.selector = selector.selector;
			this.context = selector.context;
		}

		return this.setArray(jQuery.isArray( selector ) ?
			selector :
			jQuery.makeArray(selector));
	},

	// Start with an empty selector
	selector: "",

	// The current version of jQuery being used
	jquery: "1.3.2",

	// The number of elements contained in the matched element set
	size: function() {
		return this.length;
	},

	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	get: function( num ) {
		return num === undefined ?

			// Return a 'clean' array
			Array.prototype.slice.call( this ) :

			// Return just the object
			this[ num ];
	},

	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	pushStack: function( elems, name, selector ) {
		// Build a new jQuery matched element set
		var ret = jQuery( elems );

		// Add the old object onto the stack (as a reference)
		ret.prevObject = this;

		ret.context = this.context;

		if ( name === "find" )
			ret.selector = this.selector + (this.selector ? " " : "") + selector;
		else if ( name )
			ret.selector = this.selector + "." + name + "(" + selector + ")";

		// Return the newly-formed element set
		return ret;
	},

	// Force the current matched set of elements to become
	// the specified array of elements (destroying the stack in the process)
	// You should use pushStack() in order to do this, but maintain the stack
	setArray: function( elems ) {
		// Resetting the length to 0, then using the native Array push
		// is a super-fast way to populate an object with array-like properties
		this.length = 0;
		Array.prototype.push.apply( this, elems );

		return this;
	},

	// Execute a callback for every element in the matched set.
	// (You can seed the arguments with an array of args, but this is
	// only used internally.)
	each: function( callback, args ) {
		return jQuery.each( this, callback, args );
	},

	// Determine the position of an element within
	// the matched set of elements
	index: function( elem ) {
		// Locate the position of the desired element
		return jQuery.inArray(
			// If it receives a jQuery object, the first element is used
			elem && elem.jquery ? elem[0] : elem
		, this );
	},

	attr: function( name, value, type ) {
		var options = name;

		// Look for the case where we're accessing a style value
		if ( typeof name === "string" )
			if ( value === undefined )
				return this[0] && jQuery[ type || "attr" ]( this[0], name );

			else {
				options = {};
				options[ name ] = value;
			}

		// Check to see if we're setting style values
		return this.each(function(i){
			// Set all the styles
			for ( name in options )
				jQuery.attr(
					type ?
						this.style :
						this,
					name, jQuery.prop( this, options[ name ], type, i, name )
				);
		});
	},

	css: function( key, value ) {
		// ignore negative width and height values
		if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
			value = undefined;
		return this.attr( key, value, "curCSS" );
	},

	text: function( text ) {
		if ( typeof text !== "object" && text != null )
			return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );

		var ret = "";

		jQuery.each( text || this, function(){
			jQuery.each( this.childNodes, function(){
				if ( this.nodeType != 8 )
					ret += this.nodeType != 1 ?
						this.nodeValue :
						jQuery.fn.text( [ this ] );
			});
		});

		return ret;
	},

	wrapAll: function( html ) {
		if ( this[0] ) {
			// The elements to wrap the target around
			var wrap = jQuery( html, this[0].ownerDocument ).clone();

			if ( this[0].parentNode )
				wrap.insertBefore( this[0] );

			wrap.map(function(){
				var elem = this;

				while ( elem.firstChild )
					elem = elem.firstChild;

				return elem;
			}).append(this);
		}

		return this;
	},

	wrapInner: function( html ) {
		return this.each(function(){
			jQuery( this ).contents().wrapAll( html );
		});
	},

	wrap: function( html ) {
		return this.each(function(){
			jQuery( this ).wrapAll( html );
		});
	},

	append: function() {
		return this.domManip(arguments, true, function(elem){
			if (this.nodeType == 1)
				this.appendChild( elem );
		});
	},

	prepend: function() {
		return this.domManip(arguments, true, function(elem){
			if (this.nodeType == 1)
				this.insertBefore( elem, this.firstChild );
		});
	},

	before: function() {
		return this.domManip(arguments, false, function(elem){
			this.parentNode.insertBefore( elem, this );
		});
	},

	after: function() {
		return this.domManip(arguments, false, function(elem){
			this.parentNode.insertBefore( elem, this.nextSibling );
		});
	},

	end: function() {
		return this.prevObject || jQuery( [] );
	},

	// For internal use only.
	// Behaves like an Array's method, not like a jQuery method.
	push: [].push,
	sort: [].sort,
	splice: [].splice,

	find: function( selector ) {
		if ( this.length === 1 ) {
			var ret = this.pushStack( [], "find", selector );
			ret.length = 0;
			jQuery.find( selector, this[0], ret );
			return ret;
		} else {
			return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){
				return jQuery.find( selector, elem );
			})), "find", selector );
		}
	},

	clone: function( events ) {
		// Do the clone
		var ret = this.map(function(){
			if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
				// IE copies events bound via attachEvent when
				// using cloneNode. Calling detachEvent on the
				// clone will also remove the events from the orignal
				// In order to get around this, we use innerHTML.
				// Unfortunately, this means some modifications to
				// attributes in IE that are actually only stored
				// as properties will not be copied (such as the
				// the name attribute on an input).
				var html = this.outerHTML;
				if ( !html ) {
					var div = this.ownerDocument.createElement("div");
					div.appendChild( this.cloneNode(true) );
					html = div.innerHTML;
				}

				return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
			} else
				return this.cloneNode(true);
		});

		// Copy the events from the original to the clone
		if ( events === true ) {
			var orig = this.find("*").andSelf(), i = 0;

			ret.find("*").andSelf().each(function(){
				if ( this.nodeName !== orig[i].nodeName )
					return;

				var events = jQuery.data( orig[i], "events" );

				for ( var type in events ) {
					for ( var handler in events[ type ] ) {
						jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
					}
				}

				i++;
			});
		}

		// Return the cloned set
		return ret;
	},

	filter: function( selector ) {
		return this.pushStack(
			jQuery.isFunction( selector ) &&
			jQuery.grep(this, function(elem, i){
				return selector.call( elem, i );
			}) ||

			jQuery.multiFilter( selector, jQuery.grep(this, function(elem){
				return elem.nodeType === 1;
			}) ), "filter", selector );
	},

	closest: function( selector ) {
		var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null,
			closer = 0;

		return this.map(function(){
			var cur = this;
			while ( cur && cur.ownerDocument ) {
				if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) {
					jQuery.data(cur, "closest", closer);
					return cur;
				}
				cur = cur.parentNode;
				closer++;
			}
		});
	},

	not: function( selector ) {
		if ( typeof selector === "string" )
			// test special case where just one selector is passed in
			if ( isSimple.test( selector ) )
				return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector );
			else
				selector = jQuery.multiFilter( selector, this );

		var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
		return this.filter(function() {
			return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
		});
	},

	add: function( selector ) {
		return this.pushStack( jQuery.unique( jQuery.merge(
			this.get(),
			typeof selector === "string" ?
				jQuery( selector ) :
				jQuery.makeArray( selector )
		)));
	},

	is: function( selector ) {
		return !!selector && jQuery.multiFilter( selector, this ).length > 0;
	},

	hasClass: function( selector ) {
		return !!selector && this.is( "." + selector );
	},

	val: function( value ) {
		if ( value === undefined ) {			
			var elem = this[0];

			if ( elem ) {
				if( jQuery.nodeName( elem, 'option' ) )
					return (elem.attributes.value || {}).specified ? elem.value : elem.text;
				
				// We need to handle select boxes special
				if ( jQuery.nodeName( elem, "select" ) ) {
					var index = elem.selectedIndex,
						values = [],
						options = elem.options,
						one = elem.type == "select-one";

					// Nothing was selected
					if ( index < 0 )
						return null;

					// Loop through all the selected options
					for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
						var option = options[ i ];

						if ( option.selected ) {
							// Get the specifc value for the option
							value = jQuery(option).val();

							// We don't need an array for one selects
							if ( one )
								return value;

							// Multi-Selects return an array
							values.push( value );
						}
					}

					return values;				
				}

				// Everything else, we just grab the value
				return (elem.value || "").replace(/\r/g, "");

			}

			return undefined;
		}

		if ( typeof value === "number" )
			value += '';

		return this.each(function(){
			if ( this.nodeType != 1 )
				return;

			if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) )
				this.checked = (jQuery.inArray(this.value, value) >= 0 ||
					jQuery.inArray(this.name, value) >= 0);

			else if ( jQuery.nodeName( this, "select" ) ) {
				var values = jQuery.makeArray(value);

				jQuery( "option", this ).each(function(){
					this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
						jQuery.inArray( this.text, values ) >= 0);
				});

				if ( !values.length )
					this.selectedIndex = -1;

			} else
				this.value = value;
		});
	},

	html: function( value ) {
		return value === undefined ?
			(this[0] ?
				this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
				null) :
			this.empty().append( value );
	},

	replaceWith: function( value ) {
		return this.after( value ).remove();
	},

	eq: function( i ) {
		return this.slice( i, +i + 1 );
	},

	slice: function() {
		return this.pushStack( Array.prototype.slice.apply( this, arguments ),
			"slice", Array.prototype.slice.call(arguments).join(",") );
	},

	map: function( callback ) {
		return this.pushStack( jQuery.map(this, function(elem, i){
			return callback.call( elem, i, elem );
		}));
	},

	andSelf: function() {
		return this.add( this.prevObject );
	},

	domManip: function( args, table, callback ) {
		if ( this[0] ) {
			var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
				scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ),
				first = fragment.firstChild;

			if ( first )
				for ( var i = 0, l = this.length; i < l; i++ )
					callback.call( root(this[i], first), this.length > 1 || i > 0 ?
							fragment.cloneNode(true) : fragment );
		
			if ( scripts )
				jQuery.each( scripts, evalScript );
		}

		return this;
		
		function root( elem, cur ) {
			return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ?
				(elem.getElementsByTagName("tbody")[0] ||
				elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
				elem;
		}
	}
};

// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;

function evalScript( i, elem ) {
	if ( elem.src )
		jQuery.ajax({
			url: elem.src,
			async: false,
			dataType: "script"
		});

	else
		jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );

	if ( elem.parentNode )
		elem.parentNode.removeChild( elem );
}

function now(){
	return +new Date;
}

jQuery.extend = jQuery.fn.extend = function() {
	// copy reference to target object
	var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;
		target = arguments[1] || {};
		// skip the boolean and the target
		i = 2;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !jQuery.isFunction(target) )
		target = {};

	// extend jQuery itself if only one argument is passed
	if ( length == i ) {
		target = this;
		--i;
	}

	for ( ; i < length; i++ )
		// Only deal with non-null/undefined values
		if ( (options = arguments[ i ]) != null )
			// Extend the base object
			for ( var name in options ) {
				var src = target[ name ], copy = options[ name ];

				// Prevent never-ending loop
				if ( target === copy )
					continue;

				// Recurse if we're merging object values
				if ( deep && copy && typeof copy === "object" && !copy.nodeType )
					target[ name ] = jQuery.extend( deep, 
						// Never move original objects, clone them
						src || ( copy.length != null ? [ ] : { } )
					, copy );

				// Don't bring in undefined values
				else if ( copy !== undefined )
					target[ name ] = copy;

			}

	// Return the modified object
	return target;
};

// exclude the following css properties to add px
var	exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
	// cache defaultView
	defaultView = document.defaultView || {},
	toString = Object.prototype.toString;

jQuery.extend({
	noConflict: function( deep ) {
		window.$ = _$;

		if ( deep )
			window.jQuery = _jQuery;

		return jQuery;
	},

	// See test/unit/core.js for details concerning isFunction.
	// Since version 1.3, DOM methods and functions like alert
	// aren't supported. They return false on IE (#2968).
	isFunction: function( obj ) {
		return toString.call(obj) === "[object Function]";
	},

	isArray: function( obj ) {
		return toString.call(obj) === "[object Array]";
	},

	// check if an element is in a (or is an) XML document
	isXMLDoc: function( elem ) {
		return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
			!!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument );
	},

	// Evalulates a script in a global context
	globalEval: function( data ) {
		if ( data && /\S/.test(data) ) {
			// Inspired by code by Andrea Giammarchi
			// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
			var head = document.getElementsByTagName("head")[0] || document.documentElement,
				script = document.createElement("script");

			script.type = "text/javascript";
			if ( jQuery.support.scriptEval )
				script.appendChild( document.createTextNode( data ) );
			else
				script.text = data;

			// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
			// This arises when a base node is used (#2709).
			head.insertBefore( script, head.firstChild );
			head.removeChild( script );
		}
	},

	nodeName: function( elem, name ) {
		return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
	},

	// args is for internal usage only
	each: function( object, callback, args ) {
		var name, i = 0, length = object.length;

		if ( args ) {
			if ( length === undefined ) {
				for ( name in object )
					if ( callback.apply( object[ name ], args ) === false )
						break;
			} else
				for ( ; i < length; )
					if ( callback.apply( object[ i++ ], args ) === false )
						break;

		// A special, fast, case for the most common use of each
		} else {
			if ( length === undefined ) {
				for ( name in object )
					if ( callback.call( object[ name ], name, object[ name ] ) === false )
						break;
			} else
				for ( var value = object[0];
					i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
		}

		return object;
	},

	prop: function( elem, value, type, i, name ) {
		// Handle executable functions
		if ( jQuery.isFunction( value ) )
			value = value.call( elem, i );

		// Handle passing in a number to a CSS property
		return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ?
			value + "px" :
			value;
	},

	className: {
		// internal only, use addClass("class")
		add: function( elem, classNames ) {
			jQuery.each((classNames || "").split(/\s+/), function(i, className){
				if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
					elem.className += (elem.className ? " " : "") + className;
			});
		},

		// internal only, use removeClass("class")
		remove: function( elem, classNames ) {
			if (elem.nodeType == 1)
				elem.className = classNames !== undefined ?
					jQuery.grep(elem.className.split(/\s+/), function(className){
						return !jQuery.className.has( classNames, className );
					}).join(" ") :
					"";
		},

		// internal only, use hasClass("class")
		has: function( elem, className ) {
			return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
		}
	},

	// A method for quickly swapping in/out CSS properties to get correct calculations
	swap: function( elem, options, callback ) {
		var old = {};
		// Remember the old values, and insert the new ones
		for ( var name in options ) {
			old[ name ] = elem.style[ name ];
			elem.style[ name ] = options[ name ];
		}

		callback.call( elem );

		// Revert the old values
		for ( var name in options )
			elem.style[ name ] = old[ name ];
	},

	css: function( elem, name, force, extra ) {
		if ( name == "width" || name == "height" ) {
			var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];

			function getWH() {
				val = name == "width" ? elem.offsetWidth : elem.offsetHeight;

				if ( extra === "border" )
					return;

				jQuery.each( which, function() {
					if ( !extra )
						val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
					if ( extra === "margin" )
						val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
					else
						val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
				});
			}

			if ( elem.offsetWidth !== 0 )
				getWH();
			else
				jQuery.swap( elem, props, getWH );

			return Math.max(0, Math.round(val));
		}

		return jQuery.curCSS( elem, name, force );
	},

	curCSS: function( elem, name, force ) {
		var ret, style = elem.style;

		// We need to handle opacity special in IE
		if ( name == "opacity" && !jQuery.support.opacity ) {
			ret = jQuery.attr( style, "opacity" );

			return ret == "" ?
				"1" :
				ret;
		}

		// Make sure we're using the right name for getting the float value
		if ( name.match( /float/i ) )
			name = styleFloat;

		if ( !force && style && style[ name ] )
			ret = style[ name ];

		else if ( defaultView.getComputedStyle ) {

			// Only "float" is needed here
			if ( name.match( /float/i ) )
				name = "float";

			name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();

			var computedStyle = defaultView.getComputedStyle( elem, null );

			if ( computedStyle )
				ret = computedStyle.getPropertyValue( name );

			// We should always get a number back from opacity
			if ( name == "opacity" && ret == "" )
				ret = "1";

		} else if ( elem.currentStyle ) {
			var camelCase = name.replace(/\-(\w)/g, function(all, letter){
				return letter.toUpperCase();
			});

			ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];

			// From the awesome hack by Dean Edwards
			// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291

			// If we're not dealing with a regular pixel number
			// but a number that has a weird ending, we need to convert it to pixels
			if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
				// Remember the original values
				var left = style.left, rsLeft = elem.runtimeStyle.left;

				// Put in the new values to get a computed value out
				elem.runtimeStyle.left = elem.currentStyle.left;
				style.left = ret || 0;
				ret = style.pixelLeft + "px";

				// Revert the changed values
				style.left = left;
				elem.runtimeStyle.left = rsLeft;
			}
		}

		return ret;
	},

	clean: function( elems, context, fragment ) {
		context = context || document;

		// !context.createElement fails in IE with an error but returns typeof 'object'
		if ( typeof context.createElement === "undefined" )
			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;

		// If a single string is passed in and it's a single tag
		// just do a createElement and skip the rest
		if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) {
			var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
			if ( match )
				return [ context.createElement( match[1] ) ];
		}

		var ret = [], scripts = [], div = context.createElement("div");

		jQuery.each(elems, function(i, elem){
			if ( typeof elem === "number" )
				elem += '';

			if ( !elem )
				return;

			// Convert html string into DOM nodes
			if ( typeof elem === "string" ) {
				// Fix "XHTML"-style tags in all browsers
				elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
					return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
						all :
						front + "></" + tag + ">";
				});

				// Trim whitespace, otherwise indexOf won't work as expected
				var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();

				var wrap =
					// option or optgroup
					!tags.indexOf("<opt") &&
					[ 1, "<select multiple='multiple'>", "</select>" ] ||

					!tags.indexOf("<leg") &&
					[ 1, "<fieldset>", "</fieldset>" ] ||

					tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
					[ 1, "<table>", "</table>" ] ||

					!tags.indexOf("<tr") &&
					[ 2, "<table><tbody>", "</tbody></table>" ] ||

				 	// <thead> matched above
					(!tags.indexOf("<td") || !tags.indexOf("<th")) &&
					[ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||

					!tags.indexOf("<col") &&
					[ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||

					// IE can't serialize <link> and <script> tags normally
					!jQuery.support.htmlSerialize &&
					[ 1, "div<div>", "</div>" ] ||

					[ 0, "", "" ];

				// Go to html and back, then peel off extra wrappers
				div.innerHTML = wrap[1] + elem + wrap[2];

				// Move to the right depth
				while ( wrap[0]-- )
					div = div.lastChild;

				// Remove IE's autoinserted <tbody> from table fragments
				if ( !jQuery.support.tbody ) {

					// String was a <table>, *may* have spurious <tbody>
					var hasBody = /<tbody/i.test(elem),
						tbody = !tags.indexOf("<table") && !hasBody ?
							div.firstChild && div.firstChild.childNodes :

						// String was a bare <thead> or <tfoot>
						wrap[1] == "<table>" && !hasBody ?
							div.childNodes :
							[];

					for ( var j = tbody.length - 1; j >= 0 ; --j )
						if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
							tbody[ j ].parentNode.removeChild( tbody[ j ] );

					}

				// IE completely kills leading whitespace when innerHTML is used
				if ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) )
					div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
				
				elem = jQuery.makeArray( div.childNodes );
			}

			if ( elem.nodeType )
				ret.push( elem );
			else
				ret = jQuery.merge( ret, elem );

		});

		if ( fragment ) {
			for ( var i = 0; ret[i]; i++ ) {
				if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
					scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
				} else {
					if ( ret[i].nodeType === 1 )
						ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
					fragment.appendChild( ret[i] );
				}
			}
			
			return scripts;
		}

		return ret;
	},

	attr: function( elem, name, value ) {
		// don't set attributes on text and comment nodes
		if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
			return undefined;

		var notxml = !jQuery.isXMLDoc( elem ),
			// Whether we are setting (or getting)
			set = value !== undefined;

		// Try to normalize/fix the name
		name = notxml && jQuery.props[ name ] || name;

		// Only do all the following if this is a node (faster for style)
		// IE elem.getAttribute passes even for style
		if ( elem.tagName ) {

			// These attributes require special treatment
			var special = /href|src|style/.test( name );

			// Safari mis-reports the default selected property of a hidden option
			// Accessing the parent's selectedIndex property fixes it
			if ( name == "selected" && elem.parentNode )
				elem.parentNode.selectedIndex;

			// If applicable, access the attribute via the DOM 0 way
			if ( name in elem && notxml && !special ) {
				if ( set ){
					// We can't allow the type property to be changed (since it causes problems in IE)
					if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
						throw "type property can't be changed";

					elem[ name ] = value;
				}

				// browsers index elements by id/name on forms, give priority to attributes.
				if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
					return elem.getAttributeNode( name ).nodeValue;

				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
				if ( name == "tabIndex" ) {
					var attributeNode = elem.getAttributeNode( "tabIndex" );
					return attributeNode && attributeNode.specified
						? attributeNode.value
						: elem.nodeName.match(/(button|input|object|select|textarea)/i)
							? 0
							: elem.nodeName.match(/^(a|area)$/i) && elem.href
								? 0
								: undefined;
				}

				return elem[ name ];
			}

			if ( !jQuery.support.style && notxml &&  name == "style" )
				return jQuery.attr( elem.style, "cssText", value );

			if ( set )
				// convert the value to a string (all browsers do this but IE) see #1070
				elem.setAttribute( name, "" + value );

			var attr = !jQuery.support.hrefNormalized && notxml && special
					// Some attributes require a special call on IE
					? elem.getAttribute( name, 2 )
					: elem.getAttribute( name );

			// Non-existent attributes return null, we normalize to undefined
			return attr === null ? undefined : attr;
		}

		// elem is actually elem.style ... set the style

		// IE uses filters for opacity
		if ( !jQuery.support.opacity && name == "opacity" ) {
			if ( set ) {
				// IE has trouble with opacity if it does not have layout
				// Force it by setting the zoom level
				elem.zoom = 1;

				// Set the alpha filter to set the opacity
				elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
					(parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
			}

			return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
				(parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
				"";
		}

		name = name.replace(/-([a-z])/ig, function(all, letter){
			return letter.toUpperCase();
		});

		if ( set )
			elem[ name ] = value;

		return elem[ name ];
	},

	trim: function( text ) {
		return (text || "").replace( /^\s+|\s+$/g, "" );
	},

	makeArray: function( array ) {
		var ret = [];

		if( array != null ){
			var i = array.length;
			// The window, strings (and functions) also have 'length'
			if( i == null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval )
				ret[0] = array;
			else
				while( i )
					ret[--i] = array[i];
		}

		return ret;
	},

	inArray: function( elem, array ) {
		for ( var i = 0, length = array.length; i < length; i++ )
		// Use === because on IE, window == document
			if ( array[ i ] === elem )
				return i;

		return -1;
	},

	merge: function( first, second ) {
		// We have to loop this way because IE & Opera overwrite the length
		// expando of getElementsByTagName
		var i = 0, elem, pos = first.length;
		// Also, we need to make sure that the correct elements are being returned
		// (IE returns comment nodes in a '*' query)
		if ( !jQuery.support.getAll ) {
			while ( (elem = second[ i++ ]) != null )
				if ( elem.nodeType != 8 )
					first[ pos++ ] = elem;

		} else
			while ( (elem = second[ i++ ]) != null )
				first[ pos++ ] = elem;

		return first;
	},

	unique: function( array ) {
		var ret = [], done = {};

		try {

			for ( var i = 0, length = array.length; i < length; i++ ) {
				var id = jQuery.data( array[ i ] );

				if ( !done[ id ] ) {
					done[ id ] = true;
					ret.push( array[ i ] );
				}
			}

		} catch( e ) {
			ret = array;
		}

		return ret;
	},

	grep: function( elems, callback, inv ) {
		var ret = [];

		// Go through the array, only saving the items
		// that pass the validator function
		for ( var i = 0, length = elems.length; i < length; i++ )
			if ( !inv != !callback( elems[ i ], i ) )
				ret.push( elems[ i ] );

		return ret;
	},

	map: function( elems, callback ) {
		var ret = [];

		// Go through the array, translating each of the items to their
		// new value (or values).
		for ( var i = 0, length = elems.length; i < length; i++ ) {
			var value = callback( elems[ i ], i );

			if ( value != null )
				ret[ ret.length ] = value;
		}

		return ret.concat.apply( [], ret );
	}
});

// Use of jQuery.browser is deprecated.
// It's included for backwards compatibility and plugins,
// although they should work to migrate away.

var userAgent = navigator.userAgent.toLowerCase();

// Figure out what browser is being used
jQuery.browser = {
	version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
	safari: /webkit/.test( userAgent ),
	opera: /opera/.test( userAgent ),
	msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
	mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
};

jQuery.each({
	parent: function(elem){return elem.parentNode;},
	parents: function(elem){return jQuery.dir(elem,"parentNode");},
	next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
	prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
	nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
	prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
	siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
	children: function(elem){return jQuery.sibling(elem.firstChild);},
	contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
}, function(name, fn){
	jQuery.fn[ name ] = function( selector ) {
		var ret = jQuery.map( this, fn );

		if ( selector && typeof selector == "string" )
			ret = jQuery.multiFilter( selector, ret );

		return this.pushStack( jQuery.unique( ret ), name, selector );
	};
});

jQuery.each({
	appendTo: "append",
	prependTo: "prepend",
	insertBefore: "before",
	insertAfter: "after",
	replaceAll: "replaceWith"
}, function(name, original){
	jQuery.fn[ name ] = function( selector ) {
		var ret = [], insert = jQuery( selector );

		for ( var i = 0, l = insert.length; i < l; i++ ) {
			var elems = (i > 0 ? this.clone(true) : this).get();
			jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
			ret = ret.concat( elems );
		}

		return this.pushStack( ret, name, selector );
	};
});

jQuery.each({
	removeAttr: function( name ) {
		jQuery.attr( this, name, "" );
		if (this.nodeType == 1)
			this.removeAttribute( name );
	},

	addClass: function( classNames ) {
		jQuery.className.add( this, classNames );
	},

	removeClass: function( classNames ) {
		jQuery.className.remove( this, classNames );
	},

	toggleClass: function( classNames, state ) {
		if( typeof state !== "boolean" )
			state = !jQuery.className.has( this, classNames );
		jQuery.className[ state ? "add" : "remove" ]( this, classNames );
	},

	remove: function( selector ) {
		if ( !selector || jQuery.filter( selector, [ this ] ).length ) {
			// Prevent memory leaks
			jQuery( "*", this ).add([this]).each(function(){
				jQuery.event.remove(this);
				jQuery.removeData(this);
			});
			if (this.parentNode)
				this.parentNode.removeChild( this );
		}
	},

	empty: function() {
		// Remove element nodes and prevent memory leaks
		jQuery(this).children().remove();

		// Remove any remaining nodes
		while ( this.firstChild )
			this.removeChild( this.firstChild );
	}
}, function(name, fn){
	jQuery.fn[ name ] = function(){
		return this.each( fn, arguments );
	};
});

// Helper function used by the dimensions and offset modules
function num(elem, prop) {
	return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
}
var expando = "jQuery" + now(), uuid = 0, windowData = {};

jQuery.extend({
	cache: {},

	data: function( elem, name, data ) {
		elem = elem == window ?
			windowData :
			elem;

		var id = elem[ expando ];

		// Compute a unique ID for the element
		if ( !id )
			id = elem[ expando ] = ++uuid;

		// Only generate the data cache if we're
		// trying to access or manipulate it
		if ( name && !jQuery.cache[ id ] )
			jQuery.cache[ id ] = {};

		// Prevent overriding the named cache with undefined values
		if ( data !== undefined )
			jQuery.cache[ id ][ name ] = data;

		// Return the named cache data, or the ID for the element
		return name ?
			jQuery.cache[ id ][ name ] :
			id;
	},

	removeData: function( elem, name ) {
		elem = elem == window ?
			windowData :
			elem;

		var id = elem[ expando ];

		// If we want to remove a specific section of the element's data
		if ( name ) {
			if ( jQuery.cache[ id ] ) {
				// Remove the section of cache data
				delete jQuery.cache[ id ][ name ];

				// If we've removed all the data, remove the element's cache
				name = "";

				for ( name in jQuery.cache[ id ] )
					break;

				if ( !name )
					jQuery.removeData( elem );
			}

		// Otherwise, we want to remove all of the element's data
		} else {
			// Clean up the element expando
			try {
				delete elem[ expando ];
			} catch(e){
				// IE has trouble directly removing the expando
				// but it's ok with using removeAttribute
				if ( elem.removeAttribute )
					elem.removeAttribute( expando );
			}

			// Completely remove the data cache
			delete jQuery.cache[ id ];
		}
	},
	queue: function( elem, type, data ) {
		if ( elem ){
	
			type = (type || "fx") + "queue";
	
			var q = jQuery.data( elem, type );
	
			if ( !q || jQuery.isArray(data) )
				q = jQuery.data( elem, type, jQuery.makeArray(data) );
			else if( data )
				q.push( data );
	
		}
		return q;
	},

	dequeue: function( elem, type ){
		var queue = jQuery.queue( elem, type ),
			fn = queue.shift();
		
		if( !type || type === "fx" )
			fn = queue[0];
			
		if( fn !== undefined )
			fn.call(elem);
	}
});

jQuery.fn.extend({
	data: function( key, value ){
		var parts = key.split(".");
		parts[1] = parts[1] ? "." + parts[1] : "";

		if ( value === undefined ) {
			var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);

			if ( data === undefined && this.length )
				data = jQuery.data( this[0], key );

			return data === undefined && parts[1] ?
				this.data( parts[0] ) :
				data;
		} else
			return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
				jQuery.data( this, key, value );
			});
	},

	removeData: function( key ){
		return this.each(function(){
			jQuery.removeData( this, key );
		});
	},
	queue: function(type, data){
		if ( typeof type !== "string" ) {
			data = type;
			type = "fx";
		}

		if ( data === undefined )
			return jQuery.queue( this[0], type );

		return this.each(function(){
			var queue = jQuery.queue( this, type, data );
			
			 if( type == "fx" && queue.length == 1 )
				queue[0].call(this);
		});
	},
	dequeue: function(type){
		return this.each(function(){
			jQuery.dequeue( this, type );
		});
	}
});/*!
 * Sizzle CSS Selector Engine - v0.9.3
 *  Copyright 2009, The Dojo Foundation
 *  Released under the MIT, BSD, and GPL Licenses.
 *  More information: http://sizzlejs.com/
 */
(function(){

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
	done = 0,
	toString = Object.prototype.toString;

var Sizzle = function(selector, context, results, seed) {
	results = results || [];
	context = context || document;

	if ( context.nodeType !== 1 && context.nodeType !== 9 )
		return [];
	
	if ( !selector || typeof selector !== "string" ) {
		return results;
	}

	var parts = [], m, set, checkSet, check, mode, extra, prune = true;
	
	// Reset the position of the chunker regexp (start from head)
	chunker.lastIndex = 0;
	
	while ( (m = chunker.exec(selector)) !== null ) {
		parts.push( m[1] );
		
		if ( m[2] ) {
			extra = RegExp.rightContext;
			break;
		}
	}

	if ( parts.length > 1 && origPOS.exec( selector ) ) {
		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
			set = posProcess( parts[0] + parts[1], context );
		} else {
			set = Expr.relative[ parts[0] ] ?
				[ context ] :
				Sizzle( parts.shift(), context );

			while ( parts.length ) {
				selector = parts.shift();

				if ( Expr.relative[ selector ] )
					selector += parts.shift();

				set = posProcess( selector, set );
			}
		}
	} else {
		var ret = seed ?
			{ expr: parts.pop(), set: makeArray(seed) } :
			Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
		set = Sizzle.filter( ret.expr, ret.set );

		if ( parts.length > 0 ) {
			checkSet = makeArray(set);
		} else {
			prune = false;
		}

		while ( parts.length ) {
			var cur = parts.pop(), pop = cur;

			if ( !Expr.relative[ cur ] ) {
				cur = "";
			} else {
				pop = parts.pop();
			}

			if ( pop == null ) {
				pop = context;
			}

			Expr.relative[ cur ]( checkSet, pop, isXML(context) );
		}
	}

	if ( !checkSet ) {
		checkSet = set;
	}

	if ( !checkSet ) {
		throw "Syntax error, unrecognized expression: " + (cur || selector);
	}

	if ( toString.call(checkSet) === "[object Array]" ) {
		if ( !prune ) {
			results.push.apply( results, checkSet );
		} else if ( context.nodeType === 1 ) {
			for ( var i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
					results.push( set[i] );
				}
			}
		} else {
			for ( var i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
					results.push( set[i] );
				}
			}
		}
	} else {
		makeArray( checkSet, results );
	}

	if ( extra ) {
		Sizzle( extra, context, results, seed );

		if ( sortOrder ) {
			hasDuplicate = false;
			results.sort(sortOrder);

			if ( hasDuplicate ) {
				for ( var i = 1; i < results.length; i++ ) {
					if ( results[i] === results[i-1] ) {
						results.splice(i--, 1);
					}
				}
			}
		}
	}

	return results;
};

Sizzle.matches = function(expr, set){
	return Sizzle(expr, null, null, set);
};

Sizzle.find = function(expr, context, isXML){
	var set, match;

	if ( !expr ) {
		return [];
	}

	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
		var type = Expr.order[i], match;
		
		if ( (match = Expr.match[ type ].exec( expr )) ) {
			var left = RegExp.leftContext;

			if ( left.substr( left.length - 1 ) !== "\\" ) {
				match[1] = (match[1] || "").replace(/\\/g, "");
				set = Expr.find[ type ]( match, context, isXML );
				if ( set != null ) {
					expr = expr.replace( Expr.match[ type ], "" );
					break;
				}
			}
		}
	}

	if ( !set ) {
		set = context.getElementsByTagName("*");
	}

	return {set: set, expr: expr};
};

Sizzle.filter = function(expr, set, inplace, not){
	var old = expr, result = [], curLoop = set, match, anyFound,
		isXMLFilter = set && set[0] && isXML(set[0]);

	while ( expr && set.length ) {
		for ( var type in Expr.filter ) {
			if ( (match = Expr.match[ type ].exec( expr )) != null ) {
				var filter = Expr.filter[ type ], found, item;
				anyFound = false;

				if ( curLoop == result ) {
					result = [];
				}

				if ( Expr.preFilter[ type ] ) {
					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );

					if ( !match ) {
						anyFound = found = true;
					} else if ( match === true ) {
						continue;
					}
				}

				if ( match ) {
					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
						if ( item ) {
							found = filter( item, match, i, curLoop );
							var pass = not ^ !!found;

							if ( inplace && found != null ) {
								if ( pass ) {
									anyFound = true;
								} else {
									curLoop[i] = false;
								}
							} else if ( pass ) {
								result.push( item );
								anyFound = true;
							}
						}
					}
				}

				if ( found !== undefined ) {
					if ( !inplace ) {
						curLoop = result;
					}

					expr = expr.replace( Expr.match[ type ], "" );

					if ( !anyFound ) {
						return [];
					}

					break;
				}
			}
		}

		// Improper expression
		if ( expr == old ) {
			if ( anyFound == null ) {
				throw "Syntax error, unrecognized expression: " + expr;
			} else {
				break;
			}
		}

		old = expr;
	}

	return curLoop;
};

var Expr = Sizzle.selectors = {
	order: [ "ID", "NAME", "TAG" ],
	match: {
		ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
		CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
		TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
		CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
		PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
	},
	attrMap: {
		"class": "className",
		"for": "htmlFor"
	},
	attrHandle: {
		href: function(elem){
			return elem.getAttribute("href");
		}
	},
	relative: {
		"+": function(checkSet, part, isXML){
			var isPartStr = typeof part === "string",
				isTag = isPartStr && !/\W/.test(part),
				isPartStrNotTag = isPartStr && !isTag;

			if ( isTag && !isXML ) {
				part = part.toUpperCase();
			}

			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
				if ( (elem = checkSet[i]) ) {
					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}

					checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
						elem || false :
						elem === part;
				}
			}

			if ( isPartStrNotTag ) {
				Sizzle.filter( part, checkSet, true );
			}
		},
		">": function(checkSet, part, isXML){
			var isPartStr = typeof part === "string";

			if ( isPartStr && !/\W/.test(part) ) {
				part = isXML ? part : part.toUpperCase();

				for ( var i = 0, l = checkSet.length; i < l; i++ ) {
					var elem = checkSet[i];
					if ( elem ) {
						var parent = elem.parentNode;
						checkSet[i] = parent.nodeName === part ? parent : false;
					}
				}
			} else {
				for ( var i = 0, l = checkSet.length; i < l; i++ ) {
					var elem = checkSet[i];
					if ( elem ) {
						checkSet[i] = isPartStr ?
							elem.parentNode :
							elem.parentNode === part;
					}
				}

				if ( isPartStr ) {
					Sizzle.filter( part, checkSet, true );
				}
			}
		},
		"": function(checkSet, part, isXML){
			var doneName = done++, checkFn = dirCheck;

			if ( !part.match(/\W/) ) {
				var nodeCheck = part = isXML ? part : part.toUpperCase();
				checkFn = dirNodeCheck;
			}

			checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
		},
		"~": function(checkSet, part, isXML){
			var doneName = done++, checkFn = dirCheck;

			if ( typeof part === "string" && !part.match(/\W/) ) {
				var nodeCheck = part = isXML ? part : part.toUpperCase();
				checkFn = dirNodeCheck;
			}

			checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
		}
	},
	find: {
		ID: function(match, context, isXML){
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				return m ? [m] : [];
			}
		},
		NAME: function(match, context, isXML){
			if ( typeof context.getElementsByName !== "undefined" ) {
				var ret = [], results = context.getElementsByName(match[1]);

				for ( var i = 0, l = results.length; i < l; i++ ) {
					if ( results[i].getAttribute("name") === match[1] ) {
						ret.push( results[i] );
					}
				}

				return ret.length === 0 ? null : ret;
			}
		},
		TAG: function(match, context){
			return context.getElementsByTagName(match[1]);
		}
	},
	preFilter: {
		CLASS: function(match, curLoop, inplace, result, not, isXML){
			match = " " + match[1].replace(/\\/g, "") + " ";

			if ( isXML ) {
				return match;
			}

			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
				if ( elem ) {
					if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
						if ( !inplace )
							result.push( elem );
					} else if ( inplace ) {
						curLoop[i] = false;
					}
				}
			}

			return false;
		},
		ID: function(match){
			return match[1].replace(/\\/g, "");
		},
		TAG: function(match, curLoop){
			for ( var i = 0; curLoop[i] === false; i++ ){}
			return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
		},
		CHILD: function(match){
			if ( match[1] == "nth" ) {
				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
				var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
					match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);

				// calculate the numbers (first)n+(last) including if they are negative
				match[2] = (test[1] + (test[2] || 1)) - 0;
				match[3] = test[3] - 0;
			}

			// TODO: Move to normal caching system
			match[0] = done++;

			return match;
		},
		ATTR: function(match, curLoop, inplace, result, not, isXML){
			var name = match[1].replace(/\\/g, "");
			
			if ( !isXML && Expr.attrMap[name] ) {
				match[1] = Expr.attrMap[name];
			}

			if ( match[2] === "~=" ) {
				match[4] = " " + match[4] + " ";
			}

			return match;
		},
		PSEUDO: function(match, curLoop, inplace, result, not){
			if ( match[1] === "not" ) {
				// If we're dealing with a complex expression, or a simple one
				if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) {
					match[3] = Sizzle(match[3], null, null, curLoop);
				} else {
					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
					if ( !inplace ) {
						result.push.apply( result, ret );
					}
					return false;
				}
			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
				return true;
			}
			
			return match;
		},
		POS: function(match){
			match.unshift( true );
			return match;
		}
	},
	filters: {
		enabled: function(elem){
			return elem.disabled === false && elem.type !== "hidden";
		},
		disabled: function(elem){
			return elem.disabled === true;
		},
		checked: function(elem){
			return elem.checked === true;
		},
		selected: function(elem){
			// Accessing this property makes selected-by-default
			// options in Safari work properly
			elem.parentNode.selectedIndex;
			return elem.selected === true;
		},
		parent: function(elem){
			return !!elem.firstChild;
		},
		empty: function(elem){
			return !elem.firstChild;
		},
		has: function(elem, i, match){
			return !!Sizzle( match[3], elem ).length;
		},
		header: function(elem){
			return /h\d/i.test( elem.nodeName );
		},
		text: function(elem){
			return "text" === elem.type;
		},
		radio: function(elem){
			return "radio" === elem.type;
		},
		checkbox: function(elem){
			return "checkbox" === elem.type;
		},
		file: function(elem){
			return "file" === elem.type;
		},
		password: function(elem){
			return "password" === elem.type;
		},
		submit: function(elem){
			return "submit" === elem.type;
		},
		image: function(elem){
			return "image" === elem.type;
		},
		reset: function(elem){
			return "reset" === elem.type;
		},
		button: function(elem){
			return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
		},
		input: function(elem){
			return /input|select|textarea|button/i.test(elem.nodeName);
		}
	},
	setFilters: {
		first: function(elem, i){
			return i === 0;
		},
		last: function(elem, i, match, array){
			return i === array.length - 1;
		},
		even: function(elem, i){
			return i % 2 === 0;
		},
		odd: function(elem, i){
			return i % 2 === 1;
		},
		lt: function(elem, i, match){
			return i < match[3] - 0;
		},
		gt: function(elem, i, match){
			return i > match[3] - 0;
		},
		nth: function(elem, i, match){
			return match[3] - 0 == i;
		},
		eq: function(elem, i, match){
			return match[3] - 0 == i;
		}
	},
	filter: {
		PSEUDO: function(elem, match, i, array){
			var name = match[1], filter = Expr.filters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			} else if ( name === "contains" ) {
				return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
			} else if ( name === "not" ) {
				var not = match[3];

				for ( var i = 0, l = not.length; i < l; i++ ) {
					if ( not[i] === elem ) {
						return false;
					}
				}

				return true;
			}
		},
		CHILD: function(elem, match){
			var type = match[1], node = elem;
			switch (type) {
				case 'only':
				case 'first':
					while (node = node.previousSibling)  {
						if ( node.nodeType === 1 ) return false;
					}
					if ( type == 'first') return true;
					node = elem;
				case 'last':
					while (node = node.nextSibling)  {
						if ( node.nodeType === 1 ) return false;
					}
					return true;
				case 'nth':
					var first = match[2], last = match[3];

					if ( first == 1 && last == 0 ) {
						return true;
					}
					
					var doneName = match[0],
						parent = elem.parentNode;
	
					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
						var count = 0;
						for ( node = parent.firstChild; node; node = node.nextSibling ) {
							if ( node.nodeType === 1 ) {
								node.nodeIndex = ++count;
							}
						} 
						parent.sizcache = doneName;
					}
					
					var diff = elem.nodeIndex - last;
					if ( first == 0 ) {
						return diff == 0;
					} else {
						return ( diff % first == 0 && diff / first >= 0 );
					}
			}
		},
		ID: function(elem, match){
			return elem.nodeType === 1 && elem.getAttribute("id") === match;
		},
		TAG: function(elem, match){
			return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
		},
		CLASS: function(elem, match){
			return (" " + (elem.className || elem.getAttribute("class")) + " ")
				.indexOf( match ) > -1;
		},
		ATTR: function(elem, match){
			var name = match[1],
				result = Expr.attrHandle[ name ] ?
					Expr.attrHandle[ name ]( elem ) :
					elem[ name ] != null ?
						elem[ name ] :
						elem.getAttribute( name ),
				value = result + "",
				type = match[2],
				check = match[4];

			return result == null ?
				type === "!=" :
				type === "=" ?
				value === check :
				type === "*=" ?
				value.indexOf(check) >= 0 :
				type === "~=" ?
				(" " + value + " ").indexOf(check) >= 0 :
				!check ?
				value && result !== false :
				type === "!=" ?
				value != check :
				type === "^=" ?
				value.indexOf(check) === 0 :
				type === "$=" ?
				value.substr(value.length - check.length) === check :
				type === "|=" ?
				value === check || value.substr(0, check.length + 1) === check + "-" :
				false;
		},
		POS: function(elem, match, i, array){
			var name = match[2], filter = Expr.setFilters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			}
		}
	}
};

var origPOS = Expr.match.POS;

for ( var type in Expr.match ) {
	Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
}

var makeArray = function(array, results) {
	array = Array.prototype.slice.call( array );

	if ( results ) {
		results.push.apply( results, array );
		return results;
	}
	
	return array;
};

// Perform a simple check to determine if the browser is capable of
// converting a NodeList to an array using builtin methods.
try {
	Array.prototype.slice.call( document.documentElement.childNodes );

// Provide a fallback method if it does not work
} catch(e){
	makeArray = function(array, results) {
		var ret = results || [];

		if ( toString.call(array) === "[object Array]" ) {
			Array.prototype.push.apply( ret, array );
		} else {
			if ( typeof array.length === "number" ) {
				for ( var i = 0, l = array.length; i < l; i++ ) {
					ret.push( array[i] );
				}
			} else {
				for ( var i = 0; array[i]; i++ ) {
					ret.push( array[i] );
				}
			}
		}

		return ret;
	};
}

var sortOrder;

if ( document.documentElement.compareDocumentPosition ) {
	sortOrder = function( a, b ) {
		var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
} else if ( "sourceIndex" in document.documentElement ) {
	sortOrder = function( a, b ) {
		var ret = a.sourceIndex - b.sourceIndex;
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
} else if ( document.createRange ) {
	sortOrder = function( a, b ) {
		var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
		aRange.selectNode(a);
		aRange.collapse(true);
		bRange.selectNode(b);
		bRange.collapse(true);
		var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
}

// Check to see if the browser returns elements by name when
// querying by getElementById (and provide a workaround)
(function(){
	// We're going to inject a fake input element with a specified name
	var form = document.createElement("form"),
		id = "script" + (new Date).getTime();
	form.innerHTML = "<input name='" + id + "'/>";

	// Inject it into the root element, check its status, and remove it quickly
	var root = document.documentElement;
	root.insertBefore( form, root.firstChild );

	// The workaround has to do additional checks after a getElementById
	// Which slows things down for other browsers (hence the branching)
	if ( !!document.getElementById( id ) ) {
		Expr.find.ID = function(match, context, isXML){
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
			}
		};

		Expr.filter.ID = function(elem, match){
			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
			return elem.nodeType === 1 && node && node.nodeValue === match;
		};
	}

	root.removeChild( form );
})();

(function(){
	// Check to see if the browser returns only elements
	// when doing getElementsByTagName("*")

	// Create a fake element
	var div = document.createElement("div");
	div.appendChild( document.createComment("") );

	// Make sure no comments are found
	if ( div.getElementsByTagName("*").length > 0 ) {
		Expr.find.TAG = function(match, context){
			var results = context.getElementsByTagName(match[1]);

			// Filter out possible comments
			if ( match[1] === "*" ) {
				var tmp = [];

				for ( var i = 0; results[i]; i++ ) {
					if ( results[i].nodeType === 1 ) {
						tmp.push( results[i] );
					}
				}

				results = tmp;
			}

			return results;
		};
	}

	// Check to see if an attribute returns normalized href attributes
	div.innerHTML = "<a href='#'></a>";
	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
			div.firstChild.getAttribute("href") !== "#" ) {
		Expr.attrHandle.href = function(elem){
			return elem.getAttribute("href", 2);
		};
	}
})();

if ( document.querySelectorAll ) (function(){
	var oldSizzle = Sizzle, div = document.createElement("div");
	div.innerHTML = "<p class='TEST'></p>";

	// Safari can't handle uppercase or unicode characters when
	// in quirks mode.
	if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
		return;
	}
	
	Sizzle = function(query, context, extra, seed){
		context = context || document;

		// Only use querySelectorAll on non-XML documents
		// (ID selectors don't work in non-HTML documents)
		if ( !seed && context.nodeType === 9 && !isXML(context) ) {
			try {
				return makeArray( context.querySelectorAll(query), extra );
			} catch(e){}
		}
		
		return oldSizzle(query, context, extra, seed);
	};

	Sizzle.find = oldSizzle.find;
	Sizzle.filter = oldSizzle.filter;
	Sizzle.selectors = oldSizzle.selectors;
	Sizzle.matches = oldSizzle.matches;
})();

if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
	var div = document.createElement("div");
	div.innerHTML = "<div class='test e'></div><div class='test'></div>";

	// Opera can't find a second classname (in 9.6)
	if ( div.getElementsByClassName("e").length === 0 )
		return;

	// Safari caches class attributes, doesn't catch changes (in 3.2)
	div.lastChild.className = "e";

	if ( div.getElementsByClassName("e").length === 1 )
		return;

	Expr.order.splice(1, 0, "CLASS");
	Expr.find.CLASS = function(match, context, isXML) {
		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
			return context.getElementsByClassName(match[1]);
		}
	};
})();

function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	var sibDir = dir == "previousSibling" && !isXML;
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];
		if ( elem ) {
			if ( sibDir && elem.nodeType === 1 ){
				elem.sizcache = doneName;
				elem.sizset = i;
			}
			elem = elem[dir];
			var match = false;

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 && !isXML ){
					elem.sizcache = doneName;
					elem.sizset = i;
				}

				if ( elem.nodeName === cur ) {
					match = elem;
					break;
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	var sibDir = dir == "previousSibling" && !isXML;
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];
		if ( elem ) {
			if ( sibDir && elem.nodeType === 1 ) {
				elem.sizcache = doneName;
				elem.sizset = i;
			}
			elem = elem[dir];
			var match = false;

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 ) {
					if ( !isXML ) {
						elem.sizcache = doneName;
						elem.sizset = i;
					}
					if ( typeof cur !== "string" ) {
						if ( elem === cur ) {
							match = true;
							break;
						}

					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
						match = elem;
						break;
					}
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

var contains = document.compareDocumentPosition ?  function(a, b){
	return a.compareDocumentPosition(b) & 16;
} : function(a, b){
	return a !== b && (a.contains ? a.contains(b) : true);
};

var isXML = function(elem){
	return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
		!!elem.ownerDocument && isXML( elem.ownerDocument );
};

var posProcess = function(selector, context){
	var tmpSet = [], later = "", match,
		root = context.nodeType ? [context] : context;

	// Position selectors must be done after the filter
	// And so must :not(positional) so we move all PSEUDOs to the end
	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
		later += match[0];
		selector = selector.replace( Expr.match.PSEUDO, "" );
	}

	selector = Expr.relative[selector] ? selector + "*" : selector;

	for ( var i = 0, l = root.length; i < l; i++ ) {
		Sizzle( selector, root[i], tmpSet );
	}

	return Sizzle.filter( later, tmpSet );
};

// EXPOSE
jQuery.find = Sizzle;
jQuery.filter = Sizzle.filter;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.filters;

Sizzle.selectors.filters.hidden = function(elem){
	return elem.offsetWidth === 0 || elem.offsetHeight === 0;
};

Sizzle.selectors.filters.visible = function(elem){
	return elem.offsetWidth > 0 || elem.offsetHeight > 0;
};

Sizzle.selectors.filters.animated = function(elem){
	return jQuery.grep(jQuery.timers, function(fn){
		return elem === fn.elem;
	}).length;
};

jQuery.multiFilter = function( expr, elems, not ) {
	if ( not ) {
		expr = ":not(" + expr + ")";
	}

	return Sizzle.matches(expr, elems);
};

jQuery.dir = function( elem, dir ){
	var matched = [], cur = elem[dir];
	while ( cur && cur != document ) {
		if ( cur.nodeType == 1 )
			matched.push( cur );
		cur = cur[dir];
	}
	return matched;
};

jQuery.nth = function(cur, result, dir, elem){
	result = result || 1;
	var num = 0;

	for ( ; cur; cur = cur[dir] )
		if ( cur.nodeType == 1 && ++num == result )
			break;

	return cur;
};

jQuery.sibling = function(n, elem){
	var r = [];

	for ( ; n; n = n.nextSibling ) {
		if ( n.nodeType == 1 && n != elem )
			r.push( n );
	}

	return r;
};

return;

window.Sizzle = Sizzle;

})();
/*
 * A number of helper functions used for managing events.
 * Many of the ideas behind this code originated from
 * Dean Edwards' addEvent library.
 */
jQuery.event = {

	// Bind an event to an element
	// Original by Dean Edwards
	add: function(elem, types, handler, data) {
		if ( elem.nodeType == 3 || elem.nodeType == 8 )
			return;

		// For whatever reason, IE has trouble passing the window object
		// around, causing it to be cloned in the process
		if ( elem.setInterval && elem != window )
			elem = window;

		// Make sure that the function being executed has a unique ID
		if ( !handler.guid )
			handler.guid = this.guid++;

		// if data is passed, bind to handler
		if ( data !== undefined ) {
			// Create temporary function pointer to original handler
			var fn = handler;

			// Create unique handler function, wrapped around original handler
			handler = this.proxy( fn );

			// Store data in unique handler
			handler.data = data;
		}

		// Init the element's event structure
		var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
			handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
				// Handle the second event of a trigger and when
				// an event is called after a page has unloaded
				return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
					jQuery.event.handle.apply(arguments.callee.elem, arguments) :
					undefined;
			});
		// Add elem as a property of the handle function
		// This is to prevent a memory leak with non-native
		// event in IE.
		handle.elem = elem;

		// Handle multiple events separated by a space
		// jQuery(...).bind("mouseover mouseout", fn);
		jQuery.each(types.split(/\s+/), function(index, type) {
			// Namespaced event handlers
			var namespaces = type.split(".");
			type = namespaces.shift();
			handler.type = namespaces.slice().sort().join(".");

			// Get the current list of functions bound to this event
			var handlers = events[type];
			
			if ( jQuery.event.specialAll[type] )
				jQuery.event.specialAll[type].setup.call(elem, data, namespaces);

			// Init the event handler queue
			if (!handlers) {
				handlers = events[type] = {};

				// Check for a special event handler
				// Only use addEventListener/attachEvent if the special
				// events handler returns false
				if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
					// Bind the global event handler to the element
					if (elem.addEventListener)
						elem.addEventListener(type, handle, false);
					else if (elem.attachEvent)
						elem.attachEvent("on" + type, handle);
				}
			}

			// Add the function to the element's handler list
			handlers[handler.guid] = handler;

			// Keep track of which events have been used, for global triggering
			jQuery.event.global[type] = true;
		});

		// Nullify elem to prevent memory leaks in IE
		elem = null;
	},

	guid: 1,
	global: {},

	// Detach an event or set of events from an element
	remove: function(elem, types, handler) {
		// don't do events on text and comment nodes
		if ( elem.nodeType == 3 || elem.nodeType == 8 )
			return;

		var events = jQuery.data(elem, "events"), ret, index;

		if ( events ) {
			// Unbind all events for the element
			if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
				for ( var type in events )
					this.remove( elem, type + (types || "") );
			else {
				// types is actually an event object here
				if ( types.type ) {
					handler = types.handler;
					types = types.type;
				}

				// Handle multiple events seperated by a space
				// jQuery(...).unbind("mouseover mouseout", fn);
				jQuery.each(types.split(/\s+/), function(index, type){
					// Namespaced event handlers
					var namespaces = type.split(".");
					type = namespaces.shift();
					var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");

					if ( events[type] ) {
						// remove the given handler for the given type
						if ( handler )
							delete events[type][handler.guid];

						// remove all handlers for the given type
						else
							for ( var handle in events[type] )
								// Handle the removal of namespaced events
								if ( namespace.test(events[type][handle].type) )
									delete events[type][handle];
									
						if ( jQuery.event.specialAll[type] )
							jQuery.event.specialAll[type].teardown.call(elem, namespaces);

						// remove generic event handler if no more handlers exist
						for ( ret in events[type] ) break;
						if ( !ret ) {
							if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
								if (elem.removeEventListener)
									elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
								else if (elem.detachEvent)
									elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
							}
							ret = null;
							delete events[type];
						}
					}
				});
			}

			// Remove the expando if it's no longer used
			for ( ret in events ) break;
			if ( !ret ) {
				var handle = jQuery.data( elem, "handle" );
				if ( handle ) handle.elem = null;
				jQuery.removeData( elem, "events" );
				jQuery.removeData( elem, "handle" );
			}
		}
	},

	// bubbling is internal
	trigger: function( event, data, elem, bubbling ) {
		// Event object or event type
		var type = event.type || event;

		if( !bubbling ){
			event = typeof event === "object" ?
				// jQuery.Event object
				event[expando] ? event :
				// Object literal
				jQuery.extend( jQuery.Event(type), event ) :
				// Just the event type (string)
				jQuery.Event(type);

			if ( type.indexOf("!") >= 0 ) {
				event.type = type = type.slice(0, -1);
				event.exclusive = true;
			}

			// Handle a global trigger
			if ( !elem ) {
				// Don't bubble custom events when global (to avoid too much overhead)
				event.stopPropagation();
				// Only trigger if we've ever bound an event for it
				if ( this.global[type] )
					jQuery.each( jQuery.cache, function(){
						if ( this.events && this.events[type] )
							jQuery.event.trigger( event, data, this.handle.elem );
					});
			}

			// Handle triggering a single element

			// don't do events on text and comment nodes
			if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
				return undefined;
			
			// Clean up in case it is reused
			event.result = undefined;
			event.target = elem;
			
			// Clone the incoming data, if any
			data = jQuery.makeArray(data);
			data.unshift( event );
		}

		event.currentTarget = elem;

		// Trigger the event, it is assumed that "handle" is a function
		var handle = jQuery.data(elem, "handle");
		if ( handle )
			handle.apply( elem, data );

		// Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
		if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
			event.result = false;

		// Trigger the native events (except for clicks on links)
		if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
			this.triggered = true;
			try {
				elem[ type ]();
			// prevent IE from throwing an error for some hidden elements
			} catch (e) {}
		}

		this.triggered = false;

		if ( !event.isPropagationStopped() ) {
			var parent = elem.parentNode || elem.ownerDocument;
			if ( parent )
				jQuery.event.trigger(event, data, parent, true);
		}
	},

	handle: function(event) {
		// returned undefined or false
		var all, handlers;

		event = arguments[0] = jQuery.event.fix( event || window.event );
		event.currentTarget = this;
		
		// Namespaced event handlers
		var namespaces = event.type.split(".");
		event.type = namespaces.shift();

		// Cache this now, all = true means, any handler
		all = !namespaces.length && !event.exclusive;
		
		var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");

		handlers = ( jQuery.data(this, "events") || {} )[event.type];

		for ( var j in handlers ) {
			var handler = handlers[j];

			// Filter the functions by class
			if ( all || namespace.test(handler.type) ) {
				// Pass in a reference to the handler function itself
				// So that we can later remove it
				event.handler = handler;
				event.data = handler.data;

				var ret = handler.apply(this, arguments);

				if( ret !== undefined ){
					event.result = ret;
					if ( ret === false ) {
						event.preventDefault();
						event.stopPropagation();
					}
				}

				if( event.isImmediatePropagationStopped() )
					break;

			}
		}
	},

	props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),

	fix: function(event) {
		if ( event[expando] )
			return event;

		// store a copy of the original event object
		// and "clone" to set read-only properties
		var originalEvent = event;
		event = jQuery.Event( originalEvent );

		for ( var i = this.props.length, prop; i; ){
			prop = this.props[ --i ];
			event[ prop ] = originalEvent[ prop ];
		}

		// Fix target property, if necessary
		if ( !event.target )
			event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either

		// check if target is a textnode (safari)
		if ( event.target.nodeType == 3 )
			event.target = event.target.parentNode;

		// Add relatedTarget, if necessary
		if ( !event.relatedTarget && event.fromElement )
			event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;

		// Calculate pageX/Y if missing and clientX/Y available
		if ( event.pageX == null && event.clientX != null ) {
			var doc = document.documentElement, body = document.body;
			event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
			event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
		}

		// Add which for key events
		if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
			event.which = event.charCode || event.keyCode;

		// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
		if ( !event.metaKey && event.ctrlKey )
			event.metaKey = event.ctrlKey;

		// Add which for click: 1 == left; 2 == middle; 3 == right
		// Note: button is not normalized, so don't use it
		if ( !event.which && event.button )
			event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));

		return event;
	},

	proxy: function( fn, proxy ){
		proxy = proxy || function(){ return fn.apply(this, arguments); };
		// Set the guid of unique handler to the same of original handler, so it can be removed
		proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
		// So proxy can be declared as an argument
		return proxy;
	},

	special: {
		ready: {
			// Make sure the ready event is setup
			setup: bindReady,
			teardown: function() {}
		}
	},
	
	specialAll: {
		live: {
			setup: function( selector, namespaces ){
				jQuery.event.add( this, namespaces[0], liveHandler );
			},
			teardown:  function( namespaces ){
				if ( namespaces.length ) {
					var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
					
					jQuery.each( (jQuery.data(this, "events").live || {}), function(){
						if ( name.test(this.type) )
							remove++;
					});
					
					if ( remove < 1 )
						jQuery.event.remove( this, namespaces[0], liveHandler );
				}
			}
		}
	}
};

jQuery.Event = function( src ){
	// Allow instantiation without the 'new' keyword
	if( !this.preventDefault )
		return new jQuery.Event(src);
	
	// Event object
	if( src && src.type ){
		this.originalEvent = src;
		this.type = src.type;
	// Event type
	}else
		this.type = src;

	// timeStamp is buggy for some events on Firefox(#3843)
	// So we won't rely on the native value
	this.timeStamp = now();
	
	// Mark it as fixed
	this[expando] = true;
};

function returnFalse(){
	return false;
}
function returnTrue(){
	return true;
}

// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
	preventDefault: function() {
		this.isDefaultPrevented = returnTrue;

		var e = this.originalEvent;
		if( !e )
			return;
		// if preventDefault exists run it on the original event
		if (e.preventDefault)
			e.preventDefault();
		// otherwise set the returnValue property of the original event to false (IE)
		e.returnValue = false;
	},
	stopPropagation: function() {
		this.isPropagationStopped = returnTrue;

		var e = this.originalEvent;
		if( !e )
			return;
		// if stopPropagation exists run it on the original event
		if (e.stopPropagation)
			e.stopPropagation();
		// otherwise set the cancelBubble property of the original event to true (IE)
		e.cancelBubble = true;
	},
	stopImmediatePropagation:function(){
		this.isImmediatePropagationStopped = returnTrue;
		this.stopPropagation();
	},
	isDefaultPrevented: returnFalse,
	isPropagationStopped: returnFalse,
	isImmediatePropagationStopped: returnFalse
};
// Checks if an event happened on an element within another element
// Used in jQuery.event.special.mouseenter and mouseleave handlers
var withinElement = function(event) {
	// Check if mouse(over|out) are still within the same parent element
	var parent = event.relatedTarget;
	// Traverse up the tree
	while ( parent && parent != this )
		try { parent = parent.parentNode; }
		catch(e) { parent = this; }
	
	if( parent != this ){
		// set the correct event type
		event.type = event.data;
		// handle event if we actually just moused on to a non sub-element
		jQuery.event.handle.apply( this, arguments );
	}
};
	
jQuery.each({ 
	mouseover: 'mouseenter', 
	mouseout: 'mouseleave'
}, function( orig, fix ){
	jQuery.event.special[ fix ] = {
		setup: function(){
			jQuery.event.add( this, orig, withinElement, fix );
		},
		teardown: function(){
			jQuery.event.remove( this, orig, withinElement );
		}
	};			   
});

jQuery.fn.extend({
	bind: function( type, data, fn ) {
		return type == "unload" ? this.one(type, data, fn) : this.each(function(){
			jQuery.event.add( this, type, fn || data, fn && data );
		});
	},

	one: function( type, data, fn ) {
		var one = jQuery.event.proxy( fn || data, function(event) {
			jQuery(this).unbind(event, one);
			return (fn || data).apply( this, arguments );
		});
		return this.each(function(){
			jQuery.event.add( this, type, one, fn && data);
		});
	},

	unbind: function( type, fn ) {
		return this.each(function(){
			jQuery.event.remove( this, type, fn );
		});
	},

	trigger: function( type, data ) {
		return this.each(function(){
			jQuery.event.trigger( type, data, this );
		});
	},

	triggerHandler: function( type, data ) {
		if( this[0] ){
			var event = jQuery.Event(type);
			event.preventDefault();
			event.stopPropagation();
			jQuery.event.trigger( event, data, this[0] );
			return event.result;
		}		
	},

	toggle: function( fn ) {
		// Save reference to arguments for access in closure
		var args = arguments, i = 1;

		// link all the functions, so any of them can unbind this click handler
		while( i < args.length )
			jQuery.event.proxy( fn, args[i++] );

		return this.click( jQuery.event.proxy( fn, function(event) {
			// Figure out which function to execute
			this.lastToggle = ( this.lastToggle || 0 ) % i;

			// Make sure that clicks stop
			event.preventDefault();

			// and execute the function
			return args[ this.lastToggle++ ].apply( this, arguments ) || false;
		}));
	},

	hover: function(fnOver, fnOut) {
		return this.mouseenter(fnOver).mouseleave(fnOut);
	},

	ready: function(fn) {
		// Attach the listeners
		bindReady();

		// If the DOM is already ready
		if ( jQuery.isReady )
			// Execute the function immediately
			fn.call( document, jQuery );

		// Otherwise, remember the function for later
		else
			// Add the function to the wait list
			jQuery.readyList.push( fn );

		return this;
	},
	
	live: function( type, fn ){
		var proxy = jQuery.event.proxy( fn );
		proxy.guid += this.selector + type;

		jQuery(document).bind( liveConvert(type, this.selector), this.selector, proxy );

		return this;
	},
	
	die: function( type, fn ){
		jQuery(document).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
		return this;
	}
});

function liveHandler( event ){
	var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
		stop = true,
		elems = [];

	jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
		if ( check.test(fn.type) ) {
			var elem = jQuery(event.target).closest(fn.data)[0];
			if ( elem )
				elems.push({ elem: elem, fn: fn });
		}
	});

	elems.sort(function(a,b) {
		return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest");
	});
	
	jQuery.each(elems, function(){
		if ( this.fn.call(this.elem, event, this.fn.data) === false )
			return (stop = false);
	});

	return stop;
}

function liveConvert(type, selector){
	return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
}

jQuery.extend({
	isReady: false,
	readyList: [],
	// Handle when the DOM is ready
	ready: function() {
		// Make sure that the DOM is not already loaded
		if ( !jQuery.isReady ) {
			// Remember that the DOM is ready
			jQuery.isReady = true;

			// If there are functions bound, to execute
			if ( jQuery.readyList ) {
				// Execute all of them
				jQuery.each( jQuery.readyList, function(){
					this.call( document, jQuery );
				});

				// Reset the list of functions
				jQuery.readyList = null;
			}

			// Trigger any bound ready events
			jQuery(document).triggerHandler("ready");
		}
	}
});

var readyBound = false;

function bindReady(){
	if ( readyBound ) return;
	readyBound = true;

	// Mozilla, Opera and webkit nightlies currently support this event
	if ( document.addEventListener ) {
		// Use the handy event callback
		document.addEventListener( "DOMContentLoaded", function(){
			document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
			jQuery.ready();
		}, false );

	// If IE event model is used
	} else if ( document.attachEvent ) {
		// ensure firing before onload,
		// maybe late but safe also for iframes
		document.attachEvent("onreadystatechange", function(){
			if ( document.readyState === "complete" ) {
				document.detachEvent( "onreadystatechange", arguments.callee );
				jQuery.ready();
			}
		});

		// If IE and not an iframe
		// continually check to see if the document is ready
		if ( document.documentElement.doScroll && window == window.top ) (function(){
			if ( jQuery.isReady ) return;

			try {
				// If IE is used, use the trick by Diego Perini
				// http://javascript.nwbox.com/IEContentLoaded/
				document.documentElement.doScroll("left");
			} catch( error ) {
				setTimeout( arguments.callee, 0 );
				return;
			}

			// and execute any waiting functions
			jQuery.ready();
		})();
	}

	// A fallback to window.onload, that will always work
	jQuery.event.add( window, "load", jQuery.ready );
}

jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
	"mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
	"change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){

	// Handle event binding
	jQuery.fn[name] = function(fn){
		return fn ? this.bind(name, fn) : this.trigger(name);
	};
});

// Prevent memory leaks in IE
// And prevent errors on refresh with events like mouseover in other browsers
// Window isn't included so as not to unbind existing unload events
jQuery( window ).bind( 'unload', function(){ 
	for ( var id in jQuery.cache )
		// Skip the window
		if ( id != 1 && jQuery.cache[ id ].handle )
			jQuery.event.remove( jQuery.cache[ id ].handle.elem );
}); 
(function(){

	jQuery.support = {};

	var root = document.documentElement,
		script = document.createElement("script"),
		div = document.createElement("div"),
		id = "script" + (new Date).getTime();

	div.style.display = "none";
	div.innerHTML = '   <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';

	var all = div.getElementsByTagName("*"),
		a = div.getElementsByTagName("a")[0];

	// Can't get basic test support
	if ( !all || !all.length || !a ) {
		return;
	}

	jQuery.support = {
		// IE strips leading whitespace when .innerHTML is used
		leadingWhitespace: div.firstChild.nodeType == 3,
		
		// Make sure that tbody elements aren't automatically inserted
		// IE will insert them into empty tables
		tbody: !div.getElementsByTagName("tbody").length,
		
		// Make sure that you can get all elements in an <object> element
		// IE 7 always returns no results
		objectAll: !!div.getElementsByTagName("object")[0]
			.getElementsByTagName("*").length,
		
		// Make sure that link elements get serialized correctly by innerHTML
		// This requires a wrapper element in IE
		htmlSerialize: !!div.getElementsByTagName("link").length,
		
		// Get the style information from getAttribute
		// (IE uses .cssText insted)
		style: /red/.test( a.getAttribute("style") ),
		
		// Make sure that URLs aren't manipulated
		// (IE normalizes it by default)
		hrefNormalized: a.getAttribute("href") === "/a",
		
		// Make sure that element opacity exists
		// (IE uses filter instead)
		opacity: a.style.opacity === "0.5",
		
		// Verify style float existence
		// (IE uses styleFloat instead of cssFloat)
		cssFloat: !!a.style.cssFloat,

		// Will be defined later
		scriptEval: false,
		noCloneEvent: true,
		boxModel: null
	};
	
	script.type = "text/javascript";
	try {
		script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
	} catch(e){}

	root.insertBefore( script, root.firstChild );
	
	// Make sure that the execution of code works by injecting a script
	// tag with appendChild/createTextNode
	// (IE doesn't support this, fails, and uses .text instead)
	if ( window[ id ] ) {
		jQuery.support.scriptEval = true;
		delete window[ id ];
	}

	root.removeChild( script );

	if ( div.attachEvent && div.fireEvent ) {
		div.attachEvent("onclick", function(){
			// Cloning a node shouldn't copy over any
			// bound event handlers (IE does this)
			jQuery.support.noCloneEvent = false;
			div.detachEvent("onclick", arguments.callee);
		});
		div.cloneNode(true).fireEvent("onclick");
	}

	// Figure out if the W3C box model works as expected
	// document.body must exist before we can do this
	jQuery(function(){
		var div = document.createElement("div");
		div.style.width = div.style.paddingLeft = "1px";

		document.body.appendChild( div );
		jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
		document.body.removeChild( div ).style.display = 'none';
	});
})();

var styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat";

jQuery.props = {
	"for": "htmlFor",
	"class": "className",
	"float": styleFloat,
	cssFloat: styleFloat,
	styleFloat: styleFloat,
	readonly: "readOnly",
	maxlength: "maxLength",
	cellspacing: "cellSpacing",
	rowspan: "rowSpan",
	tabindex: "tabIndex"
};
jQuery.fn.extend({
	// Keep a copy of the old load
	_load: jQuery.fn.load,

	load: function( url, params, callback ) {
		if ( typeof url !== "string" )
			return this._load( url );

		var off = url.indexOf(" ");
		if ( off >= 0 ) {
			var selector = url.slice(off, url.length);
			url = url.slice(0, off);
		}

		// Default to a GET request
		var type = "GET";

		// If the second parameter was provided
		if ( params )
			// If it's a function
			if ( jQuery.isFunction( params ) ) {
				// We assume that it's the callback
				callback = params;
				params = null;

			// Otherwise, build a param string
			} else if( typeof params === "object" ) {
				params = jQuery.param( params );
				type = "POST";
			}

		var self = this;

		// Request the remote document
		jQuery.ajax({
			url: url,
			type: type,
			dataType: "html",
			data: params,
			complete: function(res, status){
				// If successful, inject the HTML into all the matched elements
				if ( status == "success" || status == "notmodified" )
					// See if a selector was specified
					self.html( selector ?
						// Create a dummy div to hold the results
						jQuery("<div/>")
							// inject the contents of the document in, removing the scripts
							// to avoid any 'Permission Denied' errors in IE
							.append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))

							// Locate the specified elements
							.find(selector) :

						// If not, just inject the full result
						res.responseText );

				if( callback )
					self.each( callback, [res.responseText, status, res] );
			}
		});
		return this;
	},

	serialize: function() {
		return jQuery.param(this.serializeArray());
	},
	serializeArray: function() {
		return this.map(function(){
			return this.elements ? jQuery.makeArray(this.elements) : this;
		})
		.filter(function(){
			return this.name && !this.disabled &&
				(this.checked || /select|textarea/i.test(this.nodeName) ||
					/text|hidden|password|search/i.test(this.type));
		})
		.map(function(i, elem){
			var val = jQuery(this).val();
			return val == null ? null :
				jQuery.isArray(val) ?
					jQuery.map( val, function(val, i){
						return {name: elem.name, value: val};
					}) :
					{name: elem.name, value: val};
		}).get();
	}
});

// Attach a bunch of functions for handling common AJAX events
jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
	jQuery.fn[o] = function(f){
		return this.bind(o, f);
	};
});

var jsc = now();

jQuery.extend({
  
	get: function( url, data, callback, type ) {
		// shift arguments if data argument was ommited
		if ( jQuery.isFunction( data ) ) {
			callback = data;
			data = null;
		}

		return jQuery.ajax({
			type: "GET",
			url: url,
			data: data,
			success: callback,
			dataType: type
		});
	},

	getScript: function( url, callback ) {
		return jQuery.get(url, null, callback, "script");
	},

	getJSON: function( url, data, callback ) {
		return jQuery.get(url, data, callback, "json");
	},

	post: function( url, data, callback, type ) {
		if ( jQuery.isFunction( data ) ) {
			callback = data;
			data = {};
		}

		return jQuery.ajax({
			type: "POST",
			url: url,
			data: data,
			success: callback,
			dataType: type
		});
	},

	ajaxSetup: function( settings ) {
		jQuery.extend( jQuery.ajaxSettings, settings );
	},

	ajaxSettings: {
		url: location.href,
		global: true,
		type: "GET",
		contentType: "application/x-www-form-urlencoded",
		processData: true,
		async: true,
		/*
		timeout: 0,
		data: null,
		username: null,
		password: null,
		*/
		// Create the request object; Microsoft failed to properly
		// implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
		// This function can be overriden by calling jQuery.ajaxSetup
		xhr:function(){
			return window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
		},
		accepts: {
			xml: "application/xml, text/xml",
			html: "text/html",
			script: "text/javascript, application/javascript",
			json: "application/json, text/javascript",
			text: "text/plain",
			_default: "*/*"
		}
	},

	// Last-Modified header cache for next request
	lastModified: {},

	ajax: function( s ) {
		// Extend the settings, but re-extend 's' so that it can be
		// checked again later (in the test suite, specifically)
		s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));

		var jsonp, jsre = /=\?(&|$)/g, status, data,
			type = s.type.toUpperCase();

		// convert data if not already a string
		if ( s.data && s.processData && typeof s.data !== "string" )
			s.data = jQuery.param(s.data);

		// Handle JSONP Parameter Callbacks
		if ( s.dataType == "jsonp" ) {
			if ( type == "GET" ) {
				if ( !s.url.match(jsre) )
					s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
			} else if ( !s.data || !s.data.match(jsre) )
				s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
			s.dataType = "json";
		}

		// Build temporary JSONP function
		if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
			jsonp = "jsonp" + jsc++;

			// Replace the =? sequence both in the query string and the data
			if ( s.data )
				s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
			s.url = s.url.replace(jsre, "=" + jsonp + "$1");

			// We need to make sure
			// that a JSONP style response is executed properly
			s.dataType = "script";

			// Handle JSONP-style loading
			window[ jsonp ] = function(tmp){
				data = tmp;
				success();
				complete();
				// Garbage collect
				window[ jsonp ] = undefined;
				try{ delete window[ jsonp ]; } catch(e){}
				if ( head )
					head.removeChild( script );
			};
		}

		if ( s.dataType == "script" && s.cache == null )
			s.cache = false;

		if ( s.cache === false && type == "GET" ) {
			var ts = now();
			// try replacing _= if it is there
			var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
			// if nothing was replaced, add timestamp to the end
			s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
		}

		// If data is available, append data to url for get requests
		if ( s.data && type == "GET" ) {
			s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;

			// IE likes to send both get and post data, prevent this
			s.data = null;
		}

		// Watch for a new set of requests
		if ( s.global && ! jQuery.active++ )
			jQuery.event.trigger( "ajaxStart" );

		// Matches an absolute URL, and saves the domain
		var parts = /^(\w+:)?\/\/([^\/?#]+)/.exec( s.url );

		// If we're requesting a remote document
		// and trying to load JSON or Script with a GET
		if ( s.dataType == "script" && type == "GET" && parts
			&& ( parts[1] && parts[1] != location.protocol || parts[2] != location.host )){

			var head = document.getElementsByTagName("head")[0];
			var script = document.createElement("script");
			script.src = s.url;
			if (s.scriptCharset)
				script.charset = s.scriptCharset;

			// Handle Script loading
			if ( !jsonp ) {
				var done = false;

				// Attach handlers for all browsers
				script.onload = script.onreadystatechange = function(){
					if ( !done && (!this.readyState ||
							this.readyState == "loaded" || this.readyState == "complete") ) {
						done = true;
						success();
						complete();

						// Handle memory leak in IE
						script.onload = script.onreadystatechange = null;
						head.removeChild( script );
					}
				};
			}

			head.appendChild(script);

			// We handle everything using the script element injection
			return undefined;
		}

		var requestDone = false;

		// Create the request object
		var xhr = s.xhr();

		// Open the socket
		// Passing null username, generates a login popup on Opera (#2865)
		if( s.username )
			xhr.open(type, s.url, s.async, s.username, s.password);
		else
			xhr.open(type, s.url, s.async);

		// Need an extra try/catch for cross domain requests in Firefox 3
		try {
			// Set the correct header, if data is being sent
			if ( s.data )
				xhr.setRequestHeader("Content-Type", s.contentType);

			// Set the If-Modified-Since header, if ifModified mode.
			if ( s.ifModified )
				xhr.setRequestHeader("If-Modified-Since",
					jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );

			// Set header so the called script knows that it's an XMLHttpRequest
			xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

			// Set the Accepts header for the server, depending on the dataType
			xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
				s.accepts[ s.dataType ] + ", */*" :
				s.accepts._default );
		} catch(e){}

		// Allow custom headers/mimetypes and early abort
		if ( s.beforeSend && s.beforeSend(xhr, s) === false ) {
			// Handle the global AJAX counter
			if ( s.global && ! --jQuery.active )
				jQuery.event.trigger( "ajaxStop" );
			// close opended socket
			xhr.abort();
			return false;
		}

		if ( s.global )
			jQuery.event.trigger("ajaxSend", [xhr, s]);

		// Wait for a response to come back
		var onreadystatechange = function(isTimeout){
			// The request was aborted, clear the interval and decrement jQuery.active
			if (xhr.readyState == 0) {
				if (ival) {
					// clear poll interval
					clearInterval(ival);
					ival = null;
					// Handle the global AJAX counter
					if ( s.global && ! --jQuery.active )
						jQuery.event.trigger( "ajaxStop" );
				}
			// The transfer is complete and the data is available, or the request timed out
			} else if ( !requestDone && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) {
				requestDone = true;

				// clear poll interval
				if (ival) {
					clearInterval(ival);
					ival = null;
				}

				status = isTimeout == "timeout" ? "timeout" :
					!jQuery.httpSuccess( xhr ) ? "error" :
					s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? "notmodified" :
					"success";

				if ( status == "success" ) {
					// Watch for, and catch, XML document parse errors
					try {
						// process the data (runs the xml through httpData regardless of callback)
						data = jQuery.httpData( xhr, s.dataType, s );
					} catch(e) {
						status = "parsererror";
					}
				}

				// Make sure that the request was successful or notmodified
				if ( status == "success" ) {
					// Cache Last-Modified header, if ifModified mode.
					var modRes;
					try {
						modRes = xhr.getResponseHeader("Last-Modified");
					} catch(e) {} // swallow exception thrown by FF if header is not available

					if ( s.ifModified && modRes )
						jQuery.lastModified[s.url] = modRes;

					// JSONP handles its own success callback
					if ( !jsonp )
						success();
				} else
					jQuery.handleError(s, xhr, status);

				// Fire the complete handlers
				complete();

				if ( isTimeout )
					xhr.abort();

				// Stop memory leaks
				if ( s.async )
					xhr = null;
			}
		};

		if ( s.async ) {
			// don't attach the handler to the request, just poll it instead
			var ival = setInterval(onreadystatechange, 13);

			// Timeout checker
			if ( s.timeout > 0 )
				setTimeout(function(){
					// Check to see if the request is still happening
					if ( xhr && !requestDone )
						onreadystatechange( "timeout" );
				}, s.timeout);
		}

		// Send the data
		try {
			xhr.send(s.data);
		} catch(e) {
			jQuery.handleError(s, xhr, null, e);
		}

		// firefox 1.5 doesn't fire statechange for sync requests
		if ( !s.async )
			onreadystatechange();

		function success(){
			// If a local callback was specified, fire it and pass it the data
			if ( s.success )
				s.success( data, status );

			// Fire the global callback
			if ( s.global )
				jQuery.event.trigger( "ajaxSuccess", [xhr, s] );
		}

		function complete(){
			// Process result
			if ( s.complete )
				s.complete(xhr, status);

			// The request was completed
			if ( s.global )
				jQuery.event.trigger( "ajaxComplete", [xhr, s] );

			// Handle the global AJAX counter
			if ( s.global && ! --jQuery.active )
				jQuery.event.trigger( "ajaxStop" );
		}

		// return XMLHttpRequest to allow aborting the request etc.
		return xhr;
	},

	handleError: function( s, xhr, status, e ) {
		// If a local callback was specified, fire it
		if ( s.error ) s.error( xhr, status, e );

		// Fire the global callback
		if ( s.global )
			jQuery.event.trigger( "ajaxError", [xhr, s, e] );
	},

	// Counter for holding the number of active queries
	active: 0,

	// Determines if an XMLHttpRequest was successful or not
	httpSuccess: function( xhr ) {
		try {
			// IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
			return !xhr.status && location.protocol == "file:" ||
				( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223;
		} catch(e){}
		return false;
	},

	// Determines if an XMLHttpRequest returns NotModified
	httpNotModified: function( xhr, url ) {
		try {
			var xhrRes = xhr.getResponseHeader("Last-Modified");

			// Firefox always returns 200. check Last-Modified date
			return xhr.status == 304 || xhrRes == jQuery.lastModified[url];
		} catch(e){}
		return false;
	},

	httpData: function( xhr, type, s ) {
		var ct = xhr.getResponseHeader("content-type"),
			xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0,
			data = xml ? xhr.responseXML : xhr.responseText;

		if ( xml && data.documentElement.tagName == "parsererror" )
			throw "parsererror";
			
		// Allow a pre-filtering function to sanitize the response
		// s != null is checked to keep backwards compatibility
		if( s && s.dataFilter )
			data = s.dataFilter( data, type );

		// The filter can actually parse the response
		if( typeof data === "string" ){

			// If the type is "script", eval it in global context
			if ( type == "script" )
				jQuery.globalEval( data );

			// Get the JavaScript object, if JSON is used.
			if ( type == "json" )
				data = window["eval"]("(" + data + ")");
		}
		
		return data;
	},

	// Serialize an array of form elements or a set of
	// key/values into a query string
	param: function( a ) {
		var s = [ ];

		function add( key, value ){
			s[ s.length ] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
		};

		// If an array was passed in, assume that it is an array
		// of form elements
		if ( jQuery.isArray(a) || a.jquery )
			// Serialize the form elements
			jQuery.each( a, function(){
				add( this.name, this.value );
			});

		// Otherwise, assume that it's an object of key/value pairs
		else
			// Serialize the key/values
			for ( var j in a )
				// If the value is an array then the key names need to be repeated
				if ( jQuery.isArray(a[j]) )
					jQuery.each( a[j], function(){
						add( j, this );
					});
				else
					add( j, jQuery.isFunction(a[j]) ? a[j]() : a[j] );

		// Return the resulting serialization
		return s.join("&").replace(/%20/g, "+");
	}

});
var elemdisplay = {},
	timerId,
	fxAttrs = [
		// height animations
		[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
		// width animations
		[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
		// opacity animations
		[ "opacity" ]
	];

function genFx( type, num ){
	var obj = {};
	jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function(){
		obj[ this ] = type;
	});
	return obj;
}

jQuery.fn.extend({
	show: function(speed,callback){
		if ( speed ) {
			return this.animate( genFx("show", 3), speed, callback);
		} else {
			for ( var i = 0, l = this.length; i < l; i++ ){
				var old = jQuery.data(this[i], "olddisplay");
				
				this[i].style.display = old || "";
				
				if ( jQuery.css(this[i], "display") === "none" ) {
					var tagName = this[i].tagName, display;
					
					if ( elemdisplay[ tagName ] ) {
						display = elemdisplay[ tagName ];
					} else {
						var elem = jQuery("<" + tagName + " />").appendTo("body");
						
						display = elem.css("display");
						if ( display === "none" )
							display = "block";
						
						elem.remove();
						
						elemdisplay[ tagName ] = display;
					}
					
					jQuery.data(this[i], "olddisplay", display);
				}
			}

			// Set the display of the elements in a second loop
			// to avoid the constant reflow
			for ( var i = 0, l = this.length; i < l; i++ ){
				this[i].style.display = jQuery.data(this[i], "olddisplay") || "";
			}
			
			return this;
		}
	},

	hide: function(speed,callback){
		if ( speed ) {
			return this.animate( genFx("hide", 3), speed, callback);
		} else {
			for ( var i = 0, l = this.length; i < l; i++ ){
				var old = jQuery.data(this[i], "olddisplay");
				if ( !old && old !== "none" )
					jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
			}

			// Set the display of the elements in a second loop
			// to avoid the constant reflow
			for ( var i = 0, l = this.length; i < l; i++ ){
				this[i].style.display = "none";
			}

			return this;
		}
	},

	// Save the old toggle function
	_toggle: jQuery.fn.toggle,

	toggle: function( fn, fn2 ){
		var bool = typeof fn === "boolean";

		return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
			this._toggle.apply( this, arguments ) :
			fn == null || bool ?
				this.each(function(){
					var state = bool ? fn : jQuery(this).is(":hidden");
					jQuery(this)[ state ? "show" : "hide" ]();
				}) :
				this.animate(genFx("toggle", 3), fn, fn2);
	},

	fadeTo: function(speed,to,callback){
		return this.animate({opacity: to}, speed, callback);
	},

	animate: function( prop, speed, easing, callback ) {
		var optall = jQuery.speed(speed, easing, callback);

		return this[ optall.queue === false ? "each" : "queue" ](function(){
		
			var opt = jQuery.extend({}, optall), p,
				hidden = this.nodeType == 1 && jQuery(this).is(":hidden"),
				self = this;
	
			for ( p in prop ) {
				if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
					return opt.complete.call(this);

				if ( ( p == "height" || p == "width" ) && this.style ) {
					// Store display property
					opt.display = jQuery.css(this, "display");

					// Make sure that nothing sneaks out
					opt.overflow = this.style.overflow;
				}
			}

			if ( opt.overflow != null )
				this.style.overflow = "hidden";

			opt.curAnim = jQuery.extend({}, prop);

			jQuery.each( prop, function(name, val){
				var e = new jQuery.fx( self, opt, name );

				if ( /toggle|show|hide/.test(val) )
					e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
				else {
					var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
						start = e.cur(true) || 0;

					if ( parts ) {
						var end = parseFloat(parts[2]),
							unit = parts[3] || "px";

						// We need to compute starting value
						if ( unit != "px" ) {
							self.style[ name ] = (end || 1) + unit;
							start = ((end || 1) / e.cur(true)) * start;
							self.style[ name ] = start + unit;
						}

						// If a +=/-= token was provided, we're doing a relative animation
						if ( parts[1] )
							end = ((parts[1] == "-=" ? -1 : 1) * end) + start;

						e.custom( start, end, unit );
					} else
						e.custom( start, val, "" );
				}
			});

			// For JS strict compliance
			return true;
		});
	},

	stop: function(clearQueue, gotoEnd){
		var timers = jQuery.timers;

		if (clearQueue)
			this.queue([]);

		this.each(function(){
			// go in reverse order so anything added to the queue during the loop is ignored
			for ( var i = timers.length - 1; i >= 0; i-- )
				if ( timers[i].elem == this ) {
					if (gotoEnd)
						// force the next step to be the last
						timers[i](true);
					timers.splice(i, 1);
				}
		});

		// start the next in the queue if the last step wasn't forced
		if (!gotoEnd)
			this.dequeue();

		return this;
	}

});

// Generate shortcuts for custom animations
jQuery.each({
	slideDown: genFx("show", 1),
	slideUp: genFx("hide", 1),
	slideToggle: genFx("toggle", 1),
	fadeIn: { opacity: "show" },
	fadeOut: { opacity: "hide" }
}, function( name, props ){
	jQuery.fn[ name ] = function( speed, callback ){
		return this.animate( props, speed, callback );
	};
});

jQuery.extend({

	speed: function(speed, easing, fn) {
		var opt = typeof speed === "object" ? speed : {
			complete: fn || !fn && easing ||
				jQuery.isFunction( speed ) && speed,
			duration: speed,
			easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
		};

		opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
			jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;

		// Queueing
		opt.old = opt.complete;
		opt.complete = function(){
			if ( opt.queue !== false )
				jQuery(this).dequeue();
			if ( jQuery.isFunction( opt.old ) )
				opt.old.call( this );
		};

		return opt;
	},

	easing: {
		linear: function( p, n, firstNum, diff ) {
			return firstNum + diff * p;
		},
		swing: function( p, n, firstNum, diff ) {
			return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
		}
	},

	timers: [],

	fx: function( elem, options, prop ){
		this.options = options;
		this.elem = elem;
		this.prop = prop;

		if ( !options.orig )
			options.orig = {};
	}

});

jQuery.fx.prototype = {

	// Simple function for setting a style value
	update: function(){
		if ( this.options.step )
			this.options.step.call( this.elem, this.now, this );

		(jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );

		// Set display property to block for height/width animations
		if ( ( this.prop == "height" || this.prop == "width" ) && this.elem.style )
			this.elem.style.display = "block";
	},

	// Get the current size
	cur: function(force){
		if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) )
			return this.elem[ this.prop ];

		var r = parseFloat(jQuery.css(this.elem, this.prop, force));
		return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
	},

	// Start an animation from one number to another
	custom: function(from, to, unit){
		this.startTime = now();
		this.start = from;
		this.end = to;
		this.unit = unit || this.unit || "px";
		this.now = this.start;
		this.pos = this.state = 0;

		var self = this;
		function t(gotoEnd){
			return self.step(gotoEnd);
		}

		t.elem = this.elem;

		if ( t() && jQuery.timers.push(t) && !timerId ) {
			timerId = setInterval(function(){
				var timers = jQuery.timers;

				for ( var i = 0; i < timers.length; i++ )
					if ( !timers[i]() )
						timers.splice(i--, 1);

				if ( !timers.length ) {
					clearInterval( timerId );
					timerId = undefined;
				}
			}, 13);
		}
	},

	// Simple 'show' function
	show: function(){
		// Remember where we started, so that we can go back to it later
		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
		this.options.show = true;

		// Begin the animation
		// Make sure that we start at a small width/height to avoid any
		// flash of content
		this.custom(this.prop == "width" || this.prop == "height" ? 1 : 0, this.cur());

		// Start by showing the element
		jQuery(this.elem).show();
	},

	// Simple 'hide' function
	hide: function(){
		// Remember where we started, so that we can go back to it later
		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
		this.options.hide = true;

		// Begin the animation
		this.custom(this.cur(), 0);
	},

	// Each step of an animation
	step: function(gotoEnd){
		var t = now();

		if ( gotoEnd || t >= this.options.duration + this.startTime ) {
			this.now = this.end;
			this.pos = this.state = 1;
			this.update();

			this.options.curAnim[ this.prop ] = true;

			var done = true;
			for ( var i in this.options.curAnim )
				if ( this.options.curAnim[i] !== true )
					done = false;

			if ( done ) {
				if ( this.options.display != null ) {
					// Reset the overflow
					this.elem.style.overflow = this.options.overflow;

					// Reset the display
					this.elem.style.display = this.options.display;
					if ( jQuery.css(this.elem, "display") == "none" )
						this.elem.style.display = "block";
				}

				// Hide the element if the "hide" operation was done
				if ( this.options.hide )
					jQuery(this.elem).hide();

				// Reset the properties, if the item has been hidden or shown
				if ( this.options.hide || this.options.show )
					for ( var p in this.options.curAnim )
						jQuery.attr(this.elem.style, p, this.options.orig[p]);
					
				// Execute the complete function
				this.options.complete.call( this.elem );
			}

			return false;
		} else {
			var n = t - this.startTime;
			this.state = n / this.options.duration;

			// Perform the easing function, defaults to swing
			this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
			this.now = this.start + ((this.end - this.start) * this.pos);

			// Perform the next step of the animation
			this.update();
		}

		return true;
	}

};

jQuery.extend( jQuery.fx, {
	speeds:{
		slow: 600,
 		fast: 200,
 		// Default speed
 		_default: 400
	},
	step: {

		opacity: function(fx){
			jQuery.attr(fx.elem.style, "opacity", fx.now);
		},

		_default: function(fx){
			if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
				fx.elem.style[ fx.prop ] = fx.now + fx.unit;
			else
				fx.elem[ fx.prop ] = fx.now;
		}
	}
});
if ( document.documentElement["getBoundingClientRect"] )
	jQuery.fn.offset = function() {
		if ( !this[0] ) return { top: 0, left: 0 };
		if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
		var box  = this[0].getBoundingClientRect(), doc = this[0].ownerDocument, body = doc.body, docElem = doc.documentElement,
			clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
			top  = box.top  + (self.pageYOffset || jQuery.boxModel && docElem.scrollTop  || body.scrollTop ) - clientTop,
			left = box.left + (self.pageXOffset || jQuery.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft;
		return { top: top, left: left };
	};
else 
	jQuery.fn.offset = function() {
		if ( !this[0] ) return { top: 0, left: 0 };
		if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
		jQuery.offset.initialized || jQuery.offset.initialize();

		var elem = this[0], offsetParent = elem.offsetParent, prevOffsetParent = elem,
			doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement,
			body = doc.body, defaultView = doc.defaultView,
			prevComputedStyle = defaultView.getComputedStyle(elem, null),
			top = elem.offsetTop, left = elem.offsetLeft;

		while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
			computedStyle = defaultView.getComputedStyle(elem, null);
			top -= elem.scrollTop, left -= elem.scrollLeft;
			if ( elem === offsetParent ) {
				top += elem.offsetTop, left += elem.offsetLeft;
				if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName)) )
					top  += parseInt( computedStyle.borderTopWidth,  10) || 0,
					left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
				prevOffsetParent = offsetParent, offsetParent = elem.offsetParent;
			}
			if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" )
				top  += parseInt( computedStyle.borderTopWidth,  10) || 0,
				left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
			prevComputedStyle = computedStyle;
		}

		if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" )
			top  += body.offsetTop,
			left += body.offsetLeft;

		if ( prevComputedStyle.position === "fixed" )
			top  += Math.max(docElem.scrollTop, body.scrollTop),
			left += Math.max(docElem.scrollLeft, body.scrollLeft);

		return { top: top, left: left };
	};

jQuery.offset = {
	initialize: function() {
		if ( this.initialized ) return;
		var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop,
			html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';

		rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' };
		for ( prop in rules ) container.style[prop] = rules[prop];

		container.innerHTML = html;
		body.insertBefore(container, body.firstChild);
		innerDiv = container.firstChild, checkDiv = innerDiv.firstChild, td = innerDiv.nextSibling.firstChild.firstChild;

		this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
		this.doesAddBorderForTableAndCells = (td.offsetTop === 5);

		innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative';
		this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);

		body.style.marginTop = '1px';
		this.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0);
		body.style.marginTop = bodyMarginTop;

		body.removeChild(container);
		this.initialized = true;
	},

	bodyOffset: function(body) {
		jQuery.offset.initialized || jQuery.offset.initialize();
		var top = body.offsetTop, left = body.offsetLeft;
		if ( jQuery.offset.doesNotIncludeMarginInBodyOffset )
			top  += parseInt( jQuery.curCSS(body, 'marginTop',  true), 10 ) || 0,
			left += parseInt( jQuery.curCSS(body, 'marginLeft', true), 10 ) || 0;
		return { top: top, left: left };
	}
};


jQuery.fn.extend({
	position: function() {
		var left = 0, top = 0, results;

		if ( this[0] ) {
			// Get *real* offsetParent
			var offsetParent = this.offsetParent(),

			// Get correct offsets
			offset       = this.offset(),
			parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();

			// Subtract element margins
			// note: when an element has margin: auto the offsetLeft and marginLeft 
			// are the same in Safari causing offset.left to incorrectly be 0
			offset.top  -= num( this, 'marginTop'  );
			offset.left -= num( this, 'marginLeft' );

			// Add offsetParent borders
			parentOffset.top  += num( offsetParent, 'borderTopWidth'  );
			parentOffset.left += num( offsetParent, 'borderLeftWidth' );

			// Subtract the two offsets
			results = {
				top:  offset.top  - parentOffset.top,
				left: offset.left - parentOffset.left
			};
		}

		return results;
	},

	offsetParent: function() {
		var offsetParent = this[0].offsetParent || document.body;
		while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') )
			offsetParent = offsetParent.offsetParent;
		return jQuery(offsetParent);
	}
});


// Create scrollLeft and scrollTop methods
jQuery.each( ['Left', 'Top'], function(i, name) {
	var method = 'scroll' + name;
	
	jQuery.fn[ method ] = function(val) {
		if (!this[0]) return null;

		return val !== undefined ?

			// Set the scroll offset
			this.each(function() {
				this == window || this == document ?
					window.scrollTo(
						!i ? val : jQuery(window).scrollLeft(),
						 i ? val : jQuery(window).scrollTop()
					) :
					this[ method ] = val;
			}) :

			// Return the scroll offset
			this[0] == window || this[0] == document ?
				self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
					jQuery.boxModel && document.documentElement[ method ] ||
					document.body[ method ] :
				this[0][ method ];
	};
});
// Create innerHeight, innerWidth, outerHeight and outerWidth methods
jQuery.each([ "Height", "Width" ], function(i, name){

	var tl = i ? "Left"  : "Top",  // top or left
		br = i ? "Right" : "Bottom", // bottom or right
		lower = name.toLowerCase();

	// innerHeight and innerWidth
	jQuery.fn["inner" + name] = function(){
		return this[0] ?
			jQuery.css( this[0], lower, false, "padding" ) :
			null;
	};

	// outerHeight and outerWidth
	jQuery.fn["outer" + name] = function(margin) {
		return this[0] ?
			jQuery.css( this[0], lower, false, margin ? "margin" : "border" ) :
			null;
	};
	
	var type = name.toLowerCase();

	jQuery.fn[ type ] = function( size ) {
		// Get window width or height
		return this[0] == window ?
			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
			document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] ||
			document.body[ "client" + name ] :

			// Get document width or height
			this[0] == document ?
				// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
				Math.max(
					document.documentElement["client" + name],
					document.body["scroll" + name], document.documentElement["scroll" + name],
					document.body["offset" + name], document.documentElement["offset" + name]
				) :

				// Get or set width or height on the element
				size === undefined ?
					// Get width or height on the element
					(this.length ? jQuery.css( this[0], type ) : null) :

					// Set the width or height on the element (default to pixels if value is unitless)
					this.css( type, typeof size === "string" ? size : size + "px" );
	};

});
})();
} // end "if (!window.jQuery) {"
/*
 * jQuery JSON Plugin
 * version: 1.0 (2008-04-17)
 *
 * This document is licensed as free software under the terms of the
 * MIT License: http://www.opensource.org/licenses/mit-license.php
 *
 * Brantley Harris technically wrote this plugin, but it is based somewhat
 * on the JSON.org website's http://www.json.org/json2.js, which proclaims:
 * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that
 * I uphold.  I really just cleaned it up.
 *
 * It is also based heavily on MochiKit's serializeJSON, which is 
 * copywrited 2005 by Bob Ippolito.
 */
 
(function($) {   
    function toIntegersAtLease(n) 
    // Format integers to have at least two digits.
    {    
        return n < 10 ? '0' + n : n;
    }

    Date.prototype.toJSON = function(date)
    // Yes, it polutes the Date namespace, but we'll allow it here, as
    // it's damned usefull.
    {
        return this.getUTCFullYear()   + '-' +
             toIntegersAtLease(this.getUTCMonth()) + '-' +
             toIntegersAtLease(this.getUTCDate());
    };

    var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g;
    var meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        };
        
    $.quoteString = function(string)
    // Places quotes around a string, inteligently.
    // If the string contains no control characters, no quote characters, and no
    // backslash characters, then we can safely slap some quotes around it.
    // Otherwise we must also replace the offending characters with safe escape
    // sequences.
    {
        if (escapeable.test(string))
        {
            return '"' + string.replace(escapeable, function (a) 
            {
                var c = meta[a];
                if (typeof c === 'string') {
                    return c;
                }
                c = a.charCodeAt();
                return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
            }) + '"';
        }
        return '"' + string + '"';
    };
    
    $.toJSON = function(o, compact)
    {
        var type = typeof(o);
        
        if (type == "undefined")
            return "undefined";
        else if (type == "number" || type == "boolean")
            return o + "";
        else if (o === null)
            return "null";
        
        // Is it a string?
        if (type == "string") 
        {
            return $.quoteString(o);
        }
        
        // Does it have a .toJSON function?
        if (type == "object" && typeof o.toJSON == "function") 
            return o.toJSON(compact);
        
        // Is it an array?
        if (type != "function" && typeof(o.length) == "number") 
        {
            var ret = [];
            for (var i = 0; i < o.length; i++) {
                ret.push( $.toJSON(o[i], compact) );
            }
            if (compact)
                return "[" + ret.join(",") + "]";
            else
                return "[" + ret.join(", ") + "]";
        }
        
        // If it's a function, we have to warn somebody!
        if (type == "function") {
            throw new TypeError("Unable to convert object of type 'function' to json.");
        }
        
        // It's probably an object, then.
        var ret = [];
        for (var k in o) {
            var name;
            type = typeof(k);
            
            if (type == "number")
                name = '"' + k + '"';
            else if (type == "string")
                name = $.quoteString(k);
            else
                continue;  //skip non-string or number keys
            
            var val = $.toJSON(o[k], compact);
            if (typeof(val) != "string") {
                // skip non-serializable values
                continue;
            }
            
            if (compact)
                ret.push(name + ":" + val);
            else
                ret.push(name + ": " + val);
        }
        return "{" + ret.join(", ") + "}";
    };
    
    $.compactJSON = function(o)
    {
        return $.toJSON(o, true);
    };
    
    $.evalJSON = function(src)
    // Evals JSON that we know to be safe.
    {
        return eval("(" + src + ")");
    };
    
    $.secureEvalJSON = function(src)
    // Evals JSON in a way that is *more* secure.
    {
        var filtered = src;
        filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@');
        filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
        filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
        
        if (/^[\],:{}\s]*$/.test(filtered))
            return eval("(" + src + ")");
        else
            throw new SyntaxError("Error parsing JSON, source is not valid.");
    };
})(jQuery);
/**
 * Cookie plugin
 *
 * Copyright (c) 2006 Klaus Hartl (stilbuero.de)
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 */

/**
 * Create a cookie with the given name and value and other optional parameters.
 *
 * @example $.cookie('the_cookie', 'the_value');
 * @desc Set the value of a cookie.
 * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
 * @desc Create a cookie with all available options.
 * @example $.cookie('the_cookie', 'the_value');
 * @desc Create a session cookie.
 * @example $.cookie('the_cookie', null);
 * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
 *       used when the cookie was set.
 *
 * @param String name The name of the cookie.
 * @param String value The value of the cookie.
 * @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
 * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
 *                             If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
 *                             If set to null or omitted, the cookie will be a session cookie and will not be retained
 *                             when the the browser exits.
 * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
 * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
 * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
 *                        require a secure protocol (like HTTPS).
 * @type undefined
 *
 * @name $.cookie
 * @cat Plugins/Cookie
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */

/**
 * Get the value of a cookie with the given name.
 *
 * @example $.cookie('the_cookie');
 * @desc Get the value of a cookie.
 *
 * @param String name The name of the cookie.
 * @return The value of the cookie.
 * @type String
 *
 * @name $.cookie
 * @cat Plugins/Cookie
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */
jQuery.cookie = function(name, value, options) {
    if (typeof value != 'undefined') { // name and value given, set cookie
        options = options || {};
        if (value === null) {
            value = '';
            options = $.extend({}, options); // clone object since it's unexpected behavior if the expired property were changed
            options.expires = -1;
        }
        var expires = '';
        if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
            var date;
            if (typeof options.expires == 'number') {
                date = new Date();
                date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
            } else {
                date = options.expires;
            }
            expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
        }
        // NOTE Needed to parenthesize options.path and options.domain
        // in the following expressions, otherwise they evaluate to undefined
        // in the packed version for some reason...
        var path = options.path ? '; path=' + (options.path) : '';
        var domain = options.domain ? '; domain=' + (options.domain) : '';
        var secure = options.secure ? '; secure' : '';
        document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
    } else { // only name given, get cookie
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
};window.BRM = window.BRM || {};

BRM.getLogger = function(debug_div) {
  return function () {
  };
};

BRM.log = function () {};

// NOTE: to log/debug with Orbited, there are two methods:
//        Use firebug
//        1) include Orbited.js (and not Log4js)
//        2) Orbited.loggers[LOGGERNAME].enabled = true
//        And it should do logging for that logger
//        Use log4js
//        1) include log4js.js BEFORE including Orbited.js
//        2) Orbited.loggers[LOGGERNAME].setLevel(Log4js.Level.ALL)
//        3) Orbited.loggers[LOGGERNAME].addAppender(new Log4js.ConsoleAppender())
//        Note: Other levels and appenders can be set as well (see Log4js docs)
//
//     For either method to work, you must set Orbited.settings.log to true.
//
//     When you are making a call to the logger, prefix the line (first three
//     Characters) with ;;; which we will strip out at build time. So if you
//     have an if statement thats logging specific, start that line with ;;;
//     as well. If you do a try/catch thats specific to logging, prefix all
//     lines involved with ;;;. You'll want to put the try closing } and the
//     catch statement on the same line, or this won't work.
//
//     the logging functions (info, warn, debug, error, etc.) take any number
//     of arguments, like in firebug. If you're using firebug for the logging,
//     you'll actually be able to inspect the objects that you log. Therefore
//     don't do logger.debug(obj1 + " -> " + obj2); as this will convert both
//     objects to strings and not allow you to inspect them in firebug.
//     Instead call logger.debug(obj1, "->" obj2); Of course, for the Log4js
//     back-end, it will still toString the objects.

(function() {

    
    var HANDSHAKE_TIMEOUT = 30000;
    var RETRY_INTERVAL = 250;
    var RETRY_TIMEOUT = 30000;
    
    Orbited = {};
    
    Orbited.settings = {};
    Orbited.settings.hostname = document.domain;
    Orbited.settings.port = (location.port.length > 0) ? location.port : 80;
    Orbited.settings.protocol = location.protocol.slice(0, -1);
    Orbited.settings.log = false;
    Orbited.settings.streaming = true;
    Orbited.settings.HEARTBEAT_TIMEOUT = 6000;
    Orbited.settings.POLL_INTERVAL = 2000;
    Orbited.settings.pageLoggerHeight = '200px';
    Orbited.settings.pageLoggerWidth = null;
    Orbited.settings.enableFFPrivileges = false;
    Orbited.singleton = {};
    
    
    // Orbited CometSession Errors
    Orbited.Errors = {};
    Orbited.Errors.ConnectionTimeout = 101;
    Orbited.Errors.InvalidHandshake = 102;
    Orbited.Errors.UserConnectionReset = 103;
    Orbited.Errors.Unauthorized = 106;
    Orbited.Errors.RemoteConnectionFailed = 108;
    
    Orbited.Statuses = {};
    Orbited.Statuses.ServerClosedConnection = 201;
    Orbited.Statuses.SocketControlKilled = 301;
    
    Orbited.util = {};
    
    Orbited.util.browser = null;
    if (typeof(ActiveXObject) != "undefined") {
        Orbited.util.browser = 'ie';
    } else if (navigator.userAgent.indexOf('WebKit') != -1 || navigator.userAgent.indexOf('Konqueror') != -1) {
        Orbited.util.browser = 'webkit';
    } else if (navigator.product == 'Gecko' && window.find && !navigator.savePreferences) {
        Orbited.util.browser = 'firefox';
    } else if((typeof window.addEventStream) === 'function') {
        Orbited.util.browser = 'opera';
    }
    

    ////
    // NB: Base64 code was borrowed from Dojo; we had to fix decode for not
    //     striping NULs though.  Tom Trenka from Dojo wont fix this because
    //     he claims it helped to detect and avoid broken encoded data.
    //     See http://svn.dojotoolkit.org/src/dojox/trunk/encoding/base64.js
    //     See http://bugs.dojotoolkit.org/ticket/7400
    (function(){
        Orbited.base64 = {};
        
        var p = "=";
        var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        
        if (window.btoa && window.btoa('1') == 'MQ==') {
            Orbited.base64.encode = function(data) { return btoa(data); };
            Orbited.base64.decode = function(data) { return atob(data); };
            return;
        }
        
        Orbited.base64.encode=function(/* String */ba){
            //  summary
            //  Encode a string as a base64-encoded string
            var s=[];
            var l=ba.length;
            var rm=l%3;
            var x=l-rm;
            for (var i=0; i<x;){
            var t=ba.charCodeAt(i++)<<16|ba.charCodeAt(i++)<<8|ba.charCodeAt(i++);
            s.push(tab.charAt((t>>>18)&0x3f));
            s.push(tab.charAt((t>>>12)&0x3f));
            s.push(tab.charAt((t>>>6)&0x3f));
            s.push(tab.charAt(t&0x3f));
            }
            //  deal with trailers, based on patch from Peter Wood.
            switch(rm){
            case 2:
            t=ba.charCodeAt(i++)<<16|ba.charCodeAt(i++)<<8;
            s.push(tab.charAt((t>>>18)&0x3f));
            s.push(tab.charAt((t>>>12)&0x3f));
            s.push(tab.charAt((t>>>6)&0x3f));
            s.push(p);
            break;
            case 1:
            t=ba.charCodeAt(i++)<<16;
            s.push(tab.charAt((t>>>18)&0x3f));
            s.push(tab.charAt((t>>>12)&0x3f));
            s.push(p);
            s.push(p);
            break;
            }
            return s.join("");  //    string
        };
        
        
        Orbited.base64.decode=function(/* string */str){
            //  summary
            //  Convert a base64-encoded string to an array of bytes
            var s=str.split("");
            var out=[];
            var l=s.length;
            var tl=0;
            while(s[--l]==p){ ++tl; }   //    strip off trailing padding
            for (var i=0; i<l;){
            var t=tab.indexOf(s[i++])<<18;
            if(i<=l){ t|=tab.indexOf(s[i++])<<12; }
            if(i<=l){ t|=tab.indexOf(s[i++])<<6; }
            if(i<=l){ t|=tab.indexOf(s[i++]); }
            out.push(String.fromCharCode((t>>>16)&0xff));
            out.push(String.fromCharCode((t>>>8)&0xff));
            out.push(String.fromCharCode(t&0xff));
            }
            // strip off trailing padding
            while(tl--){ out.pop(); }
            return out.join(""); //     string
        };
    })();
    



    Orbited.loggers = {};
    Orbited.Loggers = {};
    Orbited.util.loggingSystem = null;
    
    if (window.Log4js) {
        Orbited.util.loggingSystem = 'log4js';
    }
    else if (window.console && console.firebug && console.firebug != "1.3.0") {
        Orbited.util.loggingSystem = 'firebug';
    }
    
    Orbited.getLogger = function(name) {
        if (!Orbited.loggers[name]) {
            var logger = null;
            switch (Orbited.util.loggingSystem) {
            case 'firebug':
            logger = new Orbited.Loggers.FirebugLogger(name);
            break;
            case 'log4js':
            logger = new Orbited.Loggers.Log4jsLogger(name);
            break;
            
            default:
            logger = new Orbited.Loggers.PageLogger(name);
            break;
            }
            Orbited.loggers[name] = logger;
        }
        return Orbited.loggers[name];
    };
    
    // TODO: is it confusing to have Orbited.Loggers be the various logging classes
    //     and Orbited.loggers be actual instances of logging classes?
    
    Orbited.Loggers.FirebugLogger = function(name) {
        var self = this;
        self.name = name;
        self.enabled = false;
        var padArgs = function(args) {
            var newArgs = [ name + ":" ];
            for (var i = 0; i < args.length; ++i) {
            newArgs.push(args[i]);
            }
            return newArgs;
        };
        self.log = function() {
            if (!self.enabled) { return; }
            console.log.apply(this, padArgs(arguments));
        };
        self.debug = function() {
            if (!self.enabled) { return; }
            console.debug.apply(this, padArgs(arguments));
        };
        self.info = function() {
            if (!self.enabled) { return; }
            console.info.apply(this, padArgs(arguments));
        };
        self.warn = function() {
            if (!self.enabled) { return; }
            console.warn.apply(this, padArgs(arguments));
        };
        self.error = function() {
            if (!self.enabled) { return; }
            console.error.apply(this, padArgs(arguments));
        };
        self.assert = function() {
            if (!self.enabled) { return; }
            var newArgs = [arguments[0], name + ":" ];
            for (var i = 1; i < arguments.length; ++i) {
            newArgs.push(arguments[i]);
            }
            console.assert.apply(this, newArgs);
        };
        self.trace = function() {
            if (!self.enabled) { return; }
            console.trace.apply(this, padArgs(arguments));
        };
    };
    Orbited.singleton.pageLoggerPane = null;

    Orbited.Loggers.PageLogger = function(name) {
        var self = this;
        self.enabled = false;
        self.name = name;

        var checkPane = function() {
            if (!Orbited.singleton.pageLoggerPane) {
            var p = document.createElement("div");
            p.border = "1px solid black";
            if(Orbited.settings.pageLoggerHeight) {
                p.style.height = Orbited.settings.pageLoggerHeight;
            }
            if(Orbited.settings.pageLoggerWidth) {
                p.style.height = Orbited.settings.pageLoggerWidth;
            }
            
            p.style.overflow = "scroll";
            document.body.appendChild(p);
            Orbited.singleton.pageLoggerPane = p;
            }
        };
        var show = function(data) {
            checkPane();
            var d = document.createElement('div');
            d.innerHTML = data;
            Orbited.singleton.pageLoggerPane.appendChild(d);
            Orbited.singleton.pageLoggerPane.scrollTop = Orbited.singleton.pageLoggerPane.scrollHeight;
        };
        self.log = function() {
            if (!self.enabled) { return; }
            var newArgs = [ "log", new Date(), "debug", "<b>" + name + "</b>" ];
            for (var i = 0; i < arguments.length; ++i) {
            newArgs.push(arguments[i]);
            }
            show(newArgs.join(", "));
        };
        self.debug = function() {
            if (!self.enabled) { return; }
            var newArgs = [ new Date(), "debug", "<b>" + name + "</b>" ];
            for (var i = 0; i < arguments.length; ++i) {
            newArgs.push(arguments[i]);
            }
            show(newArgs.join(", "));
        };
        self.info = function() {
            if (!self.enabled) { return; }
            var newArgs = [ new Date(), "info", "<b>" + name + "</b>" ];
            for (var i = 0; i < arguments.length; ++i) {
            newArgs.push(arguments[i]);
            }
            show(newArgs.join(", "));
        };
        self.warn = function() {
        };
        self.error = function() {
        };
        self.assert = function() {
        };
        self.trace = function() {
        };
    };


    Orbited.Loggers.Log4jsLogger = function(name) {
        var self = this;
        self.name = name;
        // NOTE: Why oh WHY doesn't Log4js accept dots in the logger names, and
        //         more importantly, why don't they have reasonble error messages?!
        var log4jsName = name;
        while (log4jsName.indexOf('.') != -1) {
            log4jsName = log4jsName.replace('.', '_');
        }
        var logger = Log4js.getLogger(log4jsName);
        self.logger = logger;
        logger.setLevel(Log4js.Level.OFF);
        
        var generateOutput = function(args) {
            var newArgs = [ name + ":" ];
            for (var i = 0; i < args.length; ++i) {
            newArgs.push(args[i]);
            }
            return newArgs.join(" ");
        };
        
        self.setLevel = function(level) {
            logger.setLevel(level);
        };
        self.addAppender = function(a) {
            logger.addAppender(a);
        };
        self.log= function() {
            // NOTE: log doesn't mean anything in Log4js. mapping it to info
            logger.info(generateOutput(arguments));
        };
        self.debug = function() {
            logger.debug(generateOutput(arguments));
        };
        self.info = function() {
            logger.info(generateOutput(arguments));
        };
        self.warn = function() {
            logger.warn(generateOutput(arguments));
        };
        self.error = function() {
            logger.error(generateOutput(arguments));
        };
        self.assert = function() {
        };
        self.trace = function() {
        };

    };
    Orbited.system = Orbited.getLogger('system');



    Orbited.CometTransports = {};

    Orbited.util.chooseTransport = function() {
        if (Orbited.settings.streaming == false || Orbited.util.browser == "webkit") {
            return Orbited.CometTransports.LongPoll;
        }
        var choices = [];
        for (var name in Orbited.CometTransports) {
            var transport = Orbited.CometTransports[name];
            if (typeof(transport[Orbited.util.browser]) == "number") {
                Orbited.system.log('viable transport: ', name);
                choices.push(transport);
            }
        }
        // TODO: sort the choices by the values of transport[Orbited.util.browser]
        //         and return the transport with the highest value.
        //    return XHRStream
        return choices[0];
    };



    var createXHR = function () {
        try { return new XMLHttpRequest(); } catch(e) {}
        try { return new ActiveXObject('MSXML3.XMLHTTP'); } catch(e) {}
        try { return new ActiveXObject('MSXML2.XMLHTTP.3.0'); } catch(e) {}
        try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {}
        try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) {}
        throw new Error('Could not find XMLHttpRequest or an alternative.');
    };


    Orbited.legacy = {};
    //Orbited.web.connect = function() {
    //
    //}

    Orbited.CometSession = function() {
        var self = this;
        self.readyState = self.READY_STATE_INITIALIZED;
        self.onopen = function() {};
        self.onread = function() {};
        self.onclose = function() {};
        var sessionUrl = null;
        var sessionKey = null;
        var sendQueue = [];
        var packetCount = 0;
        var xhr = null;
        var handshakeTimer = null;
        var cometTransport = null;
        var pingInterval = 30000;
        var pingTimeout = 30000;
        var timeoutTimer = null;
        var lastPacketId = 0;
        var sending = false;
        var xsdClose = null;

        /*
     * This will always fire same-domain and cross-subdomain.
     * It will fire most of the time cross-port, but it's not
     * strictly guaranteed.
     * -mario
     */
        var hardClose = function() {
            var tdata = encodePackets([[++packetCount, "close"]]);
            if (xsdClose) {
                xsdClose.contentWindow.sendCloseFrame(sessionUrl.render(),tdata);
            }
            else {
                xhr.open('POST', sessionUrl.render(), !sessionUrl.isSameDomain(location.href));
                xhr.send(tdata);
            }
        }

        /*
     * self.open can only be used when readyState is INITIALIZED. Immediately
     * following a call to self.open, the readyState will be OPENING until a
     * connection with the server has been negotiated. self.open takes a url
     * as a single argument which desiginates the remote url with which to
     * establish the connection.
     */
        self.open = function(_url) {
;;;         self.logger.debug('open');
            self.readyState = self.READY_STATE_OPENING;
            sessionUrl = new Orbited.URL(_url);
            if (sessionUrl.isSameDomain(location.href)) {
                xhr = createXHR();
            }
            else {
                xhr = new Orbited.XSDR();
                if (sessionUrl.isSamePort(location.href)) {
                    xsdClose = document.createElement('iframe');
                    xsdClose.style.display = 'block';
                    xsdClose.style.width = '0';
                    xsdClose.style.height = '0';
                    xsdClose.style.border = '0';
                    xsdClose.style.margin = '0';
                    xsdClose.style.padding = '0';
                    xsdClose.style.overflow = 'hidden';
                    xsdClose.style.visibility = 'hidden';
                    var ifUrl = new Orbited.URL("");
                    ifUrl.protocol = Orbited.settings.protocol;
                    ifUrl.domain = Orbited.settings.hostname;
                    ifUrl.port = Orbited.settings.port;
                    ifUrl.path = '/static/xsdClose.html';
                    ifUrl.hash = document.domain;
                    xsdClose.src = ifUrl.render();
                    document.body.appendChild(xsdClose);
                }
            }
            if (Orbited.settings.enableFFPrivileges) {
                try {
                    netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead');
                } catch (ex) { }
            }

            xhr.open('GET', _url, true);
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4) {
                    if (xhr.status == 200) {
                        sessionKey = xhr.responseText;
;;;                     self.logger.debug('session key is: ', sessionKey);
                        resetTimeout();
                        // START new URL way
                        //              sessionUrl.extendPath(sessionKey)
                        // END: new URL way
                        
                        // START: old URL way
                        if (sessionUrl.path[sessionUrl.path.length] != '/') {
                            sessionUrl.path += '/';
                        }
                        sessionUrl.path += sessionKey;
                        // END: old Url way
                        var transportClass = Orbited.util.chooseTransport();
                        cometTransport = new transportClass();
                        cometTransport.timeoutResetter = resetTimeout;
                        cometTransport.isSubDomain = sessionUrl.isSubDomain(location.href);
                        cometTransport.onReadFrame = transportOnReadFrame;
                        cometTransport.onclose = transportOnClose;
                        cometTransport.connect(sessionUrl.render());
                    } else {
                        xhr = null;
                        self.readyState = self.READY_STATE_CLOSED;
                        self.onclose(Orbited.Errors.InvalidHandshake);
                    }
                }
            };
            xhr.send(null);
        };
        
        /*
     * self.send is only callable when readyState is OPEN. It will queue the data
     * up for delivery as soon as the upstream xhr is ready.
     */
        self.send = function(data) {
;;;         self.logger.debug('send', data);
            if (self.readyState != self.READY_STATE_OPEN) {
                throw new Error("Invalid readyState");
            }

//            if (cometTransport.limbo) {
//;;;             self.logger.debug('limbo is true. setting sending to false');
  //              sending = false;
    //        }

            data = Orbited.base64.encode(data);
            sendQueue.push([++packetCount, "data", data]);
;;;         self.logger.debug('sending ==', sending);
            if (!sending) {
;;;             self.logger.debug('starting send');
                doSend();
            }
        };
        
        /*
     * self.close sends a close frame to the server, at the end of the queue.
     * It also sets the readyState to CLOSING so that no further data may be
     * sent. onclose is not called immediately -- it waits for the server to
     * send a close event.
     */
        self.close = function() {
            switch(self.readyState) {
                case self.READY_STATE_CLOSING:
                case self.READY_STATE_CLOSED:
                    return;
                case self.READY_STATE_INITIALIZED:
                    // TODO: call onclose here?
                    self.readyState = self.READY_STATE_CLOSED;
                    return;
                default:
                    break;
            }
            self.readyState = self.READY_STATE_CLOSING;
            sendQueue.push([++packetCount, "close"]);
            if (!sending) {
                doSend();
            }
        };
        
        /* self.reset is a way to close immediately. The send queue will be discarded
     * and a close frame will be sent to the server. onclose is called immediately
     * without waiting for a reply from the server.
     */
        self.reset = function() {
;;;         self.logger.debug('reset');
            var origState = self.readyState;
            self.readyState = self.READY_STATE_CLOSED;
            switch(origState) {
                case self.READY_STATE_INITIALIZED:
                    self.onclose(Orbited.Errors.UserConnectionReset);
                    break;
                case self.READY_STATE_OPENING:
                    xhr.onreadystatechange = function() {};
                    xhr.abort();
                    self.onclose(Orbited.Errors.UserConnectionReset);
                    break;
                case self.READY_STATE_OPEN:
                    self.sendQueue = [];
                    self.sending = false;
                    if (xhr.readyState < 4) {
                        xhr.onreadystatechange = function() {};
                        xhr.abort();
                    }
                    doClose(Orbited.Errors.UserConnectionReset);
                    hardClose();
                    break;
                case self.READY_STATE_CLOSING:
                    // TODO: Do nothing here?
                    //     we need to figure out if we've attempted to send the close
                    //     frame yet or not If not, we do something similar to case
                    //     OPEN. either way, we should kill the transport and
                    //     trigger onclose
                    //     -mcarter 7-29-08
                    break;
                
                case self.READY_STATE_CLOSED:
                    break;
            }
        };
        
        self.cleanup = function() {
            self.readyState = self.READY_STATE_CLOSED;
            cometTransport.close();
        }
        
        var transportOnReadFrame = function(frame) {
;;;         self.logger.debug('transportOnReadFrame');
;;;         self.logger.debug('READ FRAME: ', frame.id, frame.name, frame.data ? frame.data.length : '');
            if (!isNaN(frame.id)) {
                lastPacketId = Math.max(lastPacketId, frame.id);
            }
;;;         self.logger.debug(frame);
            switch(frame.name) {
                case 'close':
                    if (self.readyState < self.READY_STATE_CLOSED) {
                        doClose(Orbited.Statuses.ServerClosedConnection);
                    }
                    break;
                case 'data':
;;;                 self.logger.debug('base64 decoding ' + frame.data.length + ' bytes of data');
                    var data = Orbited.base64.decode(frame.data);
;;;                 self.logger.debug('decode complete');
                    self.onread(data);
                    break;
                case 'open':
                    if (self.readyState == self.READY_STATE_OPENING) {
                        self.readyState = self.READY_STATE_OPEN;
;;;                     self.logger.debug('Call self.onopen()');
                        self.onopen();
                    }
                    else {
                        //TODO Throw and error?
                    }
                    break;
                case 'ping':
                    // TODO: don't have a third element (remove the null).
                    // NOTE: don't waste a request when we get a longpoll ping.
                    switch(cometTransport.name) {
                        case 'longpoll':
                            break;
                        case 'poll':
                            break;
                        default:
                            sendQueue.push([++packetCount, "ping", null]);
                            if (!sending) {
                                doSend();
                            }
                            break;
                    }
                    break;
                case 'opt':
                    var args = frame.data.split(',');
                    switch(args[0]) {
                        case 'pingTimeout':
                            pingTimeout = parseInt(args[1])*1000;
                            break;
                        case 'pingInterval':
                            pingInterval = parseInt(args[1])*1000;
                            break;
                        default:
;;;                         self.logger.warn('unknown opt key', args[0]);
                            break;
                    }
                    break;
            }
;;;         self.logger.debug("resetting timeout from transportOnReadFrame");
            resetTimeout();
        };
        var transportOnClose = function() {
;;;         self.logger.debug('transportOnClose');
            if (self.readyState < self.READY_STATE_CLOSED) {
                try {
                    doClose(Orbited.Statuses.ServerClosedConnection);
                }
                catch(e) {
                    //        Fix for navigation-close
                    return;
                }
            }
        };
        var encodePackets = function(queue) {
            //TODO: optimize this.
            var output = [];
            for (var i =0; i < queue.length; ++i) {
                var frame = queue[i];
                for (var j =0; j < frame.length; ++j) {
                    var arg = frame[j];
                    if (arg == null) {
                        arg = "";
                    }
                    if (j == frame.length-1) {
                        output.push('0');
                    }
                    else {
                        output.push('1');
                    }
                    output.push(arg.toString().length);
                    output.push(',');
                    output.push(arg.toString());
                }
            }
            return output.join("");
        };

        var doSend = function(retries) {
;;;         self.logger.debug('in doSend');
            if (typeof(retries) == "undefined") {
                retries = 0;
            }
            // TODO: I don't think this timeout formula is quite right...
            //     -mcarter 8-3-08
            if (retries*RETRY_INTERVAL >= RETRY_TIMEOUT) {
                doClose(Orbited.Errors.ConnectionTimeout);
                sending = false;
                return;
            }
            if (sendQueue.length == 0) {
;;;             self.logger.debug('sendQueue exhausted');
                sending = false;
                return;
            }
            sending = true;
;;;         self.logger.debug('setting sending=true');
            var numSent = sendQueue.length;
            sessionUrl.setQsParameter('ack', lastPacketId);
            var tdata = encodePackets(sendQueue);
;;;         self.logger.debug('post', retries, tdata);
            if (Orbited.settings.enableFFPrivileges) {
                try {
                    netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead');
                } catch (ex) { }
            }
            xhr.open('POST', sessionUrl.render(), true);
            // NB: its awkard, but for reusing the XHR object in IE (7 at least),
            //     we can only reset the onreadystatechange *after* we call open;
            //     if we don't do this, the XHR will stop sending data.
            // See "Reusing XMLHttpRequest Object in IE"
            //     at http://keelypavan.blogspot.com/2006/03/reusing-xmlhttprequest-object-in-ie.html
            xhr.onreadystatechange = function() {
;;;             self.logger.debug('doSend onreadystatechange');
                switch(xhr.readyState) {
                    case 4:
                        if (xhr.status == 200) {
                            resetTimeout();
                            sendQueue.splice(0, numSent);
                            return doSend();
                        }
                        else {
                            //TODO: implement retry back-off;
                            window.setTimeout(function(){doSend(++retries);},RETRY_INTERVAL);
                        }
                        break;
                }
            };
            xhr.send(tdata);
        };

        var doClose = function(code) {
;;;         self.logger.debug('doClose', code);
            unsetTimeout();
            self.readyState = self.READY_STATE_CLOSED;
            if (cometTransport != null) {
                // TODO: is this line necessary?
                cometTransport.onReadFrame = function() {};
                cometTransport.onclose = function() { };
                cometTransport.close();
            }
            self.onclose(code);
            
        };

        var resetTimeout = function() {
;;;         self.logger.debug('reset Timeout', pingInterval+pingTimeout);
            unsetTimeout();
            timeoutTimer = window.setTimeout(timedOut, pingInterval + pingTimeout);
        };
        var unsetTimeout = function() {
            window.clearTimeout(timeoutTimer);
            
        };
        var timedOut = function() {
;;;         self.logger.debug('timed out!');
            doClose(Orbited.Errors.ConnectionTimeout);
        };
    };
    Orbited.CometSession.prototype.logger = Orbited.getLogger("Orbited.CometSession");
    Orbited.CometSession.prototype.READY_STATE_INITIALIZED    = 1;
    Orbited.CometSession.prototype.READY_STATE_OPENING    = 2;
    Orbited.CometSession.prototype.READY_STATE_OPEN        = 3;
    Orbited.CometSession.prototype.READY_STATE_CLOSING    = 4;
    Orbited.CometSession.prototype.READY_STATE_CLOSED    = 5;
    
    var currentTCPSocketId = 0;
    var openSockets = {};

    Orbited.test = {};
    Orbited.test.logger = Orbited.getLogger("Orbited.test");

    Orbited.test.socketcontrol = {};
    Orbited.test.socketcontrol.kill = function(t) {
;;;     Orbited.test.logger.debug("kill ordered for socket:", t);
        if (openSockets[t.id]) {
            openSockets[t.id](Orbited.Statuses.SocketControlKilled);
            t = null;
;;;         Orbited.test.logger.debug("socket killed");
        }
        else {
;;;         Orbited.test.logger.debug("socket not found");
        }
    };

    Orbited.test.stompdispatcher = {};
    Orbited.test.stompdispatcher.send = function(dest, msg) {
;;;     Orbited.test.logger.debug("stompdispatcher dispatching "+msg+" to "+dest);
        var s = document.createElement('script');
        s.src = "http://"+Orbited.settings.hostname+":"+Orbited.settings.port+"/system/test/stomp?";
        s.src += "msg="+msg;
        s.src += "&dest="+dest;
        document.body.appendChild(s);
    };

    Orbited.TCPSocket = function() {
        var self = this;
        self.id = ++currentTCPSocketId;

        // So we don't completely ambush people used to the 0.5 api...
        if (arguments.length > 0) {
            throw new Error("TCPSocket() accepts no arguments");
        }
        self.readyState = self.READY_STATE_INITIALIZED;
        self.onopen = function() { };
        self.onread = function() { };
        self.onclose = function() { };
        var onCloseTriggered = false;
        var buffer = "";
        var session = null;
        var binary = false;
        var handshakeState = null;
        var hostname = null;
        var port = null;

        /* self.open attempts to establish a tcp connection to the specified remote
     * hostname on the specified port. When specified as true, the optional
     * argument, isBinary, will cause onread to return byte arrays, and send
     * will only accept a byte array.
     */
        self.open = function(_hostname, _port, isBinary) {
            if (self.readyState != self.READY_STATE_INITIALIZED) {
                // TODO: allow reuse from readyState == self.READY_STATE_CLOSED?
                //         Re-use makes sense for xhr due to memory concerns, but
                //         probably not for tcp sockets. How often do you reconnect
                //         in the same page?
                //         -mcarter 7-30-08
                throw new Error("Invalid readyState");
            }
            if (_hostname == false) {
                throw new Error("No hostname specified");
            }
            if (isNaN(_port)) {
                throw new Error("Invalid port specified");
            }
            // handle isBinary undefined/null case
            binary = !!isBinary;
            self.readyState = self.READY_STATE_OPENING;
            hostname = _hostname;
            port = _port;
            session = new Orbited.CometSession();
            var sessionUrl = new Orbited.URL('/tcp');
            sessionUrl.domain = Orbited.settings.hostname;
            sessionUrl.port = Orbited.settings.port;
            sessionUrl.protocol = Orbited.settings.protocol;
            sessionUrl.setQsParameter('nocache', Math.random());
            session.open(sessionUrl.render());
            session.onopen = sessionOnOpen;
            session.onread = sessionOnRead;
            session.onclose = sessionOnClose;
            handshakeState = "initial";
        };

        self.close = function() {
            if (self.readyState == self.READY_STATE_CLOSED) {
                return;
            }
            self.readyState = self.READY_STATE_CLOSED;
            doClose(Orbited.Errors.UserConnectionReset);
        };

        /* self.reset closes the connection from this end immediately. The server
     * may be notified, but there is no guarantee. The main purpose of the reset
     * function is for a quick teardown in the case of a user navigation.
     * if reset is not called when IE navigates, for instance, there will be
     * potential issues with future TCPSocket communication.
     */
        self.reset = function() {
            if (session) {session.reset();}
        };

        self.send = function(data) {
            if (self.readyState != self.READY_STATE_OPEN) {
                throw new Error("Invalid readyState");
            }
            if (!binary) {
                data = Orbited.utf8.encode(data);
            }
;;;         self.logger.debug('SEND: ', data);
            //      try {
            session.send(data);
            //      }
            //      catch(e) {
            //      alert("Why sending: typeof(data) = " + typeof(data));
            //}
        };

        var process = function() {
            var result = Orbited.utf8.decode(buffer);
            var data = result[0];
            var i = result[1];
            buffer = buffer.slice(i);
            if (data.length > 0) {
                window.setTimeout(function() { self.onread(data); }, 0);
            }
        };

        var sessionOnRead = function(data) {
            switch(self.readyState) {
                case self.READY_STATE_OPEN:
;;;                 self.logger.debug('READ: ', data);
                    if (binary) {
                        window.setTimeout(function() { self.onread(data); }, 0);
                    }
                    else {
;;;                     self.logger.debug('start buffer size:', buffer.length);
                        buffer += data;
                        process();
;;;                     self.logger.debug('end buffer size:', buffer.length);
                    }
                    break;
                case self.READY_STATE_OPENING:
                    switch(handshakeState) {
                        case 'initial':
                            // NOTE: we should only get complete payloads during
                            //     the handshake. no need to buffer, then parse
                            data = Orbited.utf8.decode(data)[0];
;;;                         self.logger.debug('initial');
;;;                         self.logger.debug('data', data);
;;;                         self.logger.debug('len', data.length);
;;;                         self.logger.debug('typeof(data)', typeof(data));
;;;                         self.logger.debug('data[0] ', data.slice(0,1));
;;;                         self.logger.debug('type ', typeof(data.slice(0,1)));
                            var result = (data.slice(0,1) == '1');
;;;                         self.logger.debug('result', result);
                            if (!result) {
;;;                             self.logger.debug('!result');
                                var errorCode = data.slice(1,4);
                                doClose(parseInt(errorCode));
                            }
                            if (result) {
                                self.readyState = self.READY_STATE_OPEN;
;;;                             self.logger.debug('tcpsocket.onopen..');
                                self.onopen();
;;;                             self.logger.debug('did onopen');
                            }
                            break;
                    }
                    break;
            }
        };
        var doClose = function(code) {
;;;         self.logger.debug('doClose', code);
            if (session) {
                if (code == Orbited.Statuses.ServerClosedConnection || code == Orbited.Errors.Unauthorized || code == Orbited.Errors.RemoteConnectionFailed) {
                    session.cleanup();
                }
                else {
                    sessionOnClose = function() {};
                    session.close();
                }
                session = null;
            }
;;;         self.logger.debug('onCloseTriggered', onCloseTriggered);
            if (!onCloseTriggered) {
;;;             self.logger.debug('triggerClose timer', code);
                onCloseTriggered = true;
                window.setTimeout(function() {
;;;                 self.logger.debug('onclose!', code);
                    self.onclose(code);
                }, 0);
            }
        };

        openSockets[self.id] = doClose;

        var sessionOnOpen = function(data) {
            // TODO: TCPSocket handshake
            var payload = hostname + ':' + port + '\n';
;;;         self.logger.debug('sessionOpen; sending:', payload);
            payload = Orbited.utf8.encode(payload);
;;;         self.logger.debug('encoded payload:', payload);
            X = payload;
            session.send(payload);
            handshakeState = 'initial';
        };

        var sessionOnClose = function(code) {
;;;         self.logger.debug('sessionOnClose');
            // If we are in the OPENING state, then the handshake code should
            // handle the close
            doClose(code);
        };
    };
    Orbited.TCPSocket.prototype.toString = function() {
        return "<Orbited.TCPSocket " + this.id + ">";
    };
    Orbited.TCPSocket.prototype.logger = Orbited.getLogger("Orbited.TCPSocket");
    Orbited.TCPSocket.prototype.READY_STATE_INITIALIZED  = 1;
    Orbited.TCPSocket.prototype.READY_STATE_OPENING         = 2;
    Orbited.TCPSocket.prototype.READY_STATE_OPEN         = 3;
    Orbited.TCPSocket.prototype.READY_STATE_CLOSING         = 4;
    Orbited.TCPSocket.prototype.READY_STATE_CLOSED         = 5;





    // XXX: the Orbited.XSDR stuff (presumably) doesn't work yet.
    //    mcarter - 8-9-08 (~rev 476)

    Orbited.singleton.XSDR = {
        receiveCbs: {},
        queues: {},
        iframes: {},
        id: 0,
        register: function(receive, queue) {
            var id = ++Orbited.singleton.XSDR.id;
            Orbited.singleton.XSDR.receiveCbs[id] = receive;
            Orbited.singleton.XSDR.queues[id] = queue;
;;;         Orbited.system.debug('id is', id);
            return id;
        }
    };
    Orbited.XSDR = function() {
        var self = this;
        var ifr = null;
        var url;
        var method;
        var data;
        var requestHeaders;
        var queue = [];
        var id = Orbited.singleton.XSDR.register(function(data) { receive(data); },queue);
        var bridgeUrl = new Orbited.URL("");
        bridgeUrl.domain = Orbited.settings.hostname;
        bridgeUrl.port = Orbited.settings.port;
        bridgeUrl.path = '/static/xsdrBridge.html';
        bridgeUrl.hash = id.toString();
        bridgeUrl.protocol = Orbited.settings.protocol;
;;;     self.logger.debug('bridgeUrl.hash is', bridgeUrl.hash);
;;;     self.logger.debug('bridgeUrl.path is', bridgeUrl.path);
;;;     self.logger.debug('bridgeUrl is', bridgeUrl.render());
        var reset = function() {
            self.responseText = "";
            self.status = null;
            self.readyState = 0;
            url = null;
            method = null;
            data = null;
            requestHeaders = {};
        };
        reset();
        self.onreadystatechange = function() { };
        self.open = function(_method, _url, async) {
            if (self.readyState == 4) {
                reset();
            }
            if (self.readyState != 0) {
                throw new Error("Invalid readyState");
            }
            if (!async) {
                throw new Error("Only Async XSDR supported");
            }
;;;         self.logger.debug('open', _method, _url, async);
            self.readyState = 1;
            url = _url;
            method = _method;
        };

        self.send = function(data) {
            if (self.readyState != 1) {
                throw new Error("Invalid readyState");
            }
;;;         self.logger.debug('send', data);
            if (!ifr) {
;;;             self.logger.debug('creating iframe');
                ifr = document.createElement("iframe");
                hideIframe(ifr);
                ifr.src = bridgeUrl.render();
;;;             self.logger.debug('set ifr.src to', ifr.src);
                document.body.appendChild(ifr);
                Orbited.singleton.XSDR.iframes[id] = ifr;
            }
            else {
                queue.push([method, url, data, requestHeaders]);
            }
        };

        self.abort = function() {
            if (self.readyState > 0 && self.readyState < 4) {
                // TODO: push an ABORT command (so as not to reload the iframe)
                //          queue.push(['ABORT']);
;;;             self.logger.debug('ABORT called');
                ifr.src = "about:blank";
                document.body.removeChild(ifr);
                ifr = null;
                self.readyState = 4;
                self.onreadystatechange();
            }
        };


        //    self.abort = function() {
        //      if (self.readyState > 0 && self.readyState < 4) {
        //          queue.push(['ABORT']);
        //      }
        //    }

        self.setRequestHeader = function(key, val) {
            if (self.readyState != 0) {
                throw new Error("Invalid readyState");
            }
            requestHeaders[key] = val;
        };

        self.getResponseHeader = function() {
            if (self.readyState < 2) {
                throw new Error("Invalid readyState");
            }
            return responseHeaders[key];
        };

        var receive = function(payload) {
;;;         self.logger.debug('received', payload);
            switch(payload[0]) {
                case 'initialized':
                    queue.push([method, url, data, requestHeaders]);
;;;                 self.logger.debug('queue is', queue);
;;;                 self.logger.debug('Orbited.singleton.XSDR.queues[id] is', Orbited.singleton.XSDR.queues[id]);
                    break;
                case 'readystatechange':
                    data = payload[1];
                    self.readyState = data.readyState;
;;;                 self.logger.debug('readystatechange', self.readyState);
                    if (data.status) {
                        self.status = data.status;
;;;                     self.logger.debug('status', data.status);
                    }
                    if (data.responseText) {
                        self.responseText += data.responseText;
;;;                     self.logger.debug('responseText', data.responseText);
                    }
;;;                 self.logger.debug('doing trigger');
                    self.onreadystatechange();
;;;                 self.logger.debug('trigger complete');
                    break;
            }
        };

        var hideIframe =function (ifr) {
            ifr.style.display = 'block';
            ifr.style.width = '0';
            ifr.style.height = '0';
            ifr.style.border = '0';
            ifr.style.margin = '0';
            ifr.style.padding = '0';
            ifr.style.overflow = 'hidden';
            ifr.style.visibility = 'hidden';
        };

    };

    if (Orbited.util.browser == "opera") {
        var pmLocation = window.postMessage && "contentWindow" || "document";
        (window.postMessage && window || document).addEventListener('message', function(e) {
            var msg = e.data.split(" ");
            var cmd = msg.shift();
            if (cmd == "event") {
                var id = msg.shift();
                var dataString = msg.join(" ");
                var data = Orbited.JSON.parse(dataString);
                Orbited.singleton.XSDR.receiveCbs[id](data);
            }
            if (cmd == "queues") {
                id = msg.shift();
                var queue = Orbited.singleton.XSDR.queues[id];
                if (queue.length > 0) {
                    data = queue.shift();
                    Orbited.singleton.XSDR.iframes[id][pmLocation].postMessage(Orbited.JSON.stringify(data), e.origin);
                }
            }
        }, false);
    }

    Orbited.XSDR.prototype.logger = Orbited.getLogger("Orbited.XSDR");
    Orbited.singleton.XSDRBridgeLogger = Orbited.getLogger('XSDRBridge');

    /* Comet Transports!
 */
    var CT_READYSTATE_INITIAL = 0;
    var CT_READYSTATE_OPEN      = 1;
    var CT_READYSTATE_CLOSED  = 2;

    Orbited.CometTransports.XHRStream = function() {
        var self = this;
        self.name = 'xhrstream';
        var url = null;
        var xhr = null;
        var ackId = null;
        var offset = 0;
        var heartbeatTimer = null;
        var retryTimer = null;
        var buffer = "";
        var retryInterval = 50;
        self.readyState = CT_READYSTATE_INITIAL;
        self.onReadFrame = function(frame) {};
        self.onread = function(packet) { self.onReadFrame(packet); };
        self.onclose = function() { };

        self.close = function() {
            if (self.readyState == CT_READYSTATE_CLOSED) {
                return;
            }
            if (xhr != null && (xhr.readyState > 1 || xhr.readyState < 4)) {
                xhr.onreadystatechange = function() { };
                xhr.abort();
                xhr = null;
            }
            self.readyState = CT_READYSTATE_CLOSED;
            window.clearTimeout(heartbeatTimer);
            window.clearTimeout(retryTimer);
            self.onclose();
        };

        self.connect = function(_url) {
            if (self.readyState == CT_READYSTATE_OPEN) {
                throw new Error("Already Connected");
            }
            url = new Orbited.URL(_url);
            if (xhr == null) {
                if (url.isSameDomain(location.href)) {
                    xhr = createXHR();
                }
                else {
                    xhr = new Orbited.XSDR();
                }
            }
            url.path += '/xhrstream';
            //      url.setQsParameter('transport', 'xhrstream')
            self.readyState = CT_READYSTATE_OPEN;
            open();
        };
        var open = function() {
            try {
                if (typeof(ackId) == "number") {
                    url.setQsParameter('ack', ackId);
                }
                if (typeof(xhr)== "undefined" || xhr == null) {
                    throw new Error("how did this happen?");
                }
                if (Orbited.settings.enableFFPrivileges) {
                    try {
                        netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead');
                    }
                    catch (ex) { }
                }

                xhr.open('GET', url.render(), true);
                xhr.onreadystatechange = function() {
;;;                 self.logger.debug(xhr.readyState);
                    if (self.readyState == CT_READYSTATE_CLOSED) {
                        return;
                    }
                    switch(xhr.readyState) {
                        case 2:
                            // If we can't get the status, then we didn't actually
                            // get a valid xhr response -- we got a network error
                            try {
                                var status = xhr.status;
                            }
                            catch(e) {
                                return;
                            }
                            // If we got a 200, then we're in business
                            if (status == 200) {
                                try {
                                    heartbeatTimer = window.setTimeout(heartbeatTimeout, Orbited.settings.HEARTBEAT_TIMEOUT);
                                }
                                catch(e) {
                                //                 Happens after navigation
                                    self.close();
                                    return;
                                }
                                var testtimer = heartbeatTimer;
                            }
                            // Otherwise, case 4 should handle the reconnect,
                            // so do nothing here.
                            break;
                        case 3:
                            // If we can't get the status, then we didn't actually
                            // get a valid xhr response -- we got a network error
                            try {
                                var status = xhr.status;
                            }
                            catch(e) {
                                return;
                            }
                            // We successfully established a connection, so put the
                            // retryInterval back to a short value
                            if (status == 200) {
                                retryInterval = 50;
                                process();
                            }
                            break;
                        case 4:
                            var doReconnect = true;
                            try {
                                if (xhr.status === null) {
                                    doReconnect = true;
                                }
                                else {
                                    doReconnect = false;
                                }
                            }
                            catch(e) {
                            }
                            if (doReconnect) {
                                // Expoential backoff: Every time we fail to
                                // reconnect, double the interval.
                                // TODO cap the max value.
                                retryInterval *= 2;
                                //                  self.logger.debug('retryInterval', retryInterval)
                                window.clearTimeout(heartbeatTimer);
                                retryTimer = window.setTimeout(reconnect, retryInterval);
                                return;
                            }
                            switch(xhr.status) {
                                case 200:
                                    //                  alert('finished, call process');
                                    //                 if (typeof(Orbited) == "undefined") {
                                    //                      alert('must have reloaded')
                                    //                      break
                                    //                  }
                                    //                  alert('a');
                                    //                  alert('stream over ' +  typeof(console) + ' ' + typeof(Orbited) + ' ' + Orbited + ' ...');
                                    process();
                                    offset = 0;
                                    setTimeout(open, 0);
                                    window.clearTimeout(heartbeatTimer);
                                    break;
                                case 404:
                                    self.close();
                                    break;
                                default:
                                    self.close();
                                    break;
                            }
                            break;
                    }
                };
                xhr.send(null);
            }
            catch(e) {
                self.close();
            }
        };

        var reconnect = function() {
;;;         self.logger.debug('reconnect...')
            if (xhr.readyState < 4 && xhr.readyState > 0) {
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4) {
                reconnect();
                }
            };
;;;         self.logger.debug('do abort..')
            xhr.abort();
            window.clearTimeout(heartbeatTimer);
            }
            else {
;;;         self.logger.debug('reconnect do open');
            offset = 0;
            setTimeout(open, 0);
            }
        };
        // 12,ab011,hello world
        var commaPos = -1;
        var argEnd = null;
        var frame = [];
        var process = function() {
            var stream = xhr.responseText;
            receivedHeartbeat();

            // ignore leading whitespace, such as at the start of an xhr stream
            while (stream[offset] == ' ') {
            offset += 1;
            }
            // ignore leading whitespace, such as at the start of an xhr stream
            while (stream[offset] == 'x') {
            offset += 1;
            }

            var k = 0;
            while (true) {
            k += 1;
            if (k > 2000) {
                throw new Error("Borked XHRStream transport");
            }
            if (commaPos == -1) {
                commaPos = stream.indexOf(',', offset);
            }
            if (commaPos == -1) {
                return;
            }
            if (argEnd == null) {
                argSize = parseInt(stream.slice(offset+1, commaPos));
                argEnd = commaPos +1 + argSize;
            }
            
            if (stream.length < argEnd) {
                return;
            }
            var data = stream.slice(commaPos+1, argEnd);
            frame.push(data);
            var isLast = (stream.charAt(offset) == '0');
            offset = argEnd;
            argEnd = null;
            commaPos = -1;
            if (isLast) {
                var frameCopy = frame;
                frame = [];
                receivedPacket(frameCopy);
            }
            }

        };
        var receivedHeartbeat = function() {
            window.clearTimeout(heartbeatTimer);
;;;         self.logger.debug('clearing heartbeatTimer', heartbeatTimer);
            try {
            heartbeatTimer = window.setTimeout(function() {
;;;             self.logger.debug('timer', testtimer, 'did it');
                heartbeatTimeout();
            }, Orbited.settings.HEARTBEAT_TIMEOUT);
            }
            catch(e) {
            return;
            }
            var testtimer = heartbeatTimer;

;;;         self.logger.debug('heartbeatTimer is now', heartbeatTimer);
        };
        var heartbeatTimeout = function() {
;;;         self.logger.debug('heartbeat timeout... reconnect');
            reconnect();
        };
        var receivedPacket = function(args) {
            var testAckId = parseInt(args[0]);
            if (!isNaN(testAckId)) {
            ackId = testAckId;
            }
            var packet = {
            id: testAckId,
            name: args[1],
            data: args[2]
            };
            // TODO: shouldn't we put this in a window.setTimeout so that user
            //     code won't mess up our code?
            self.onread(packet);
        };
    };
    Orbited.CometTransports.XHRStream.prototype.logger = Orbited.getLogger("Orbited.CometTransports.XHRStream");
    // XHRStream supported browsers
    Orbited.CometTransports.XHRStream.firefox = 1.0;
    Orbited.CometTransports.XHRStream.firefox2 = 1.0;
    Orbited.CometTransports.XHRStream.firefox3 = 1.0;
    Orbited.CometTransports.XHRStream.safari2 = 1.0;
    Orbited.CometTransports.XHRStream.safari3 = 1.0;





    Orbited.CometTransports.LongPoll = function() {
        var self = this;
        self.name = 'longpoll';
        var url = null;
        var xhr = null;
        var ackId = null;
        var retryTimer = null;
        var buffer = "";
        var retryInterval = 50;
        self.readyState = CT_READYSTATE_INITIAL;
        self.onReadFrame = function(frame) {};
        self.onclose = function() { };

        self.close = function() {
;;;         self.logger.debug('close');
            if (self.readyState == CT_READYSTATE_CLOSED) {
                return;
            }
            if (xhr != null && (xhr.readyState > 1 || xhr.readyState < 4)) {
                xhr.onreadystatechange = function() { };
                xhr.abort();
                xhr = null;
            }
;;;         self.logger.debug('close! self.readyState now is 2');
            self.readyState = CT_READYSTATE_CLOSED;
            window.clearTimeout(retryTimer);
            self.onclose();
        };

        self.connect = function(_url) {
;;;         self.logger.debug('connect');
            if (self.readyState == CT_READYSTATE_OPEN) {
                throw new Error("Already Connected");
            }
            url = new Orbited.URL(_url);
            if (xhr == null) {
                if (url.isSameDomain(location.href)) {
                    xhr = createXHR();
                }
                else {
                    xhr = new Orbited.XSDR();
                }
            }
            url.path += '/longpoll';
            //      url.setQsParameter('transport', 'xhrstream')
            self.readyState = CT_READYSTATE_OPEN;
            open();
        };
        var open = function() {
;;;         self.logger.debug('open... self.readyState = ' + self.readyState);
            if (self.readyState == CT_READYSTATE_CLOSED) {
                return;
            }
            try {
                if (typeof(ackId) == "number") {
                    url.setQsParameter('ack', ackId);
                }
                if (typeof(xhr)== "undefined" || xhr == null) {
                    throw new Error("how did this happen?");
                }
                if (Orbited.settings.enableFFPrivileges) {
                    try {
                        netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead');
                    }
                    catch (ex) { }
                }
                xhr.open('GET', url.render(), true);
                xhr.onreadystatechange = function() {
;;;                 self.logger.debug('readystate', xhr.readyState);
                    switch(xhr.readyState) {
                        case 4:
                            try {
                                var test = xhr.status;
                            }
                            catch(e) {
                                // Exponential backoff: Every time we fail to
                                // reconnect, double the interval.
                                // TODO cap the max value.
;;;                             self.logger.debug("start reconnect Timer (couldn't access xhr.status)");
                                retryInterval *= 2;
                                window.setTimeout(reconnect, retryInterval);
                                return;
                            }
                            switch(xhr.status) {
                                case 200:
                                    self.timeoutResetter();
                                    process();
;;;                                 self.logger.debug("completed request, reconnect immediately");
                                    setTimeout(open, 0);
                                    break;
                                case 404:
                                    self.close();
                                    break;
                                case null:
                                    // NOTE: for the XSDR case:
                                    // (we can always get status, but maybe its null)
                                    retryInterval *= 2;
;;;                                 self.logger.debug("start reconnect Timer (null xhr.status)");
                                    window.setTimeout(reconnect, retryInterval);
                                    break;
                                default:
                                    // TODO: do we want to retry here?
;;;                                 self.logger.debug("something broke, xhr.status=", xhr.status);
                                    self.close();
                                    break;
                            }
                            break;
                    }
                };
                xhr.send(null);
            }
            catch(e) {
                self.close();
            }
        };

        var reconnect = function() {
;;;         self.logger.debug('reconnect...');
            if (xhr.readyState < 4 && xhr.readyState > 0) {
                xhr.onreadystatechange = function() {
                    if (xhr.readyState == 4) {
                        reconnect();
                    }
                };
;;;             self.logger.debug('do abort..');
                xhr.abort();
                window.clearTimeout(heartbeatTimer);
            }
            else {
;;;             self.logger.debug('reconnect do open');
                offset = 0;
                setTimeout(open, 0);
            }
        };
        // ( ab,hello world)
        // 12,ab011,hello world
        var process = function() {
;;;         self.logger.debug('process');
            var commaPos = -1;
            var argEnd = null;
            var argSize;
            var frame = [];
            var stream = xhr.responseText;
            var offset = 0;


            var k = 0;
            while (true) {
                k += 1;
                if (k > 2000) {
                    throw new Error("Borked XHRStream transport");
                }
                if (commaPos == -1) {
                    commaPos = stream.indexOf(',', offset);
                }
                if (commaPos == -1) {
;;;                 self.logger.debug('no more commas. offset:', offset, 'stream.length:', stream.length);
                    return;
                }
                if (argEnd == null) {
                    argSize = parseInt(stream.slice(offset+1, commaPos));
                    argEnd = commaPos +1 + argSize;
                }
;;;             self.logger.assert(true);
                /*          if (stream.length < argEnd) {
    //;;;        self.logger.debug('how did we get here? stream.length:', stream.length, 'argEnd:', argEnd, 'offset:', offset)
                return
                }*/
                var data = stream.slice(commaPos+1, argEnd);
;;;             self.logger.assert(data.length == argSize, 'argSize:', argSize, 'data.length', data.length);
                if (data.length != argSize) {
                    DEBUGDATA = stream;
                }
                frame.push(data);
                var isLast = (stream.charAt(offset) == '0');
                offset = argEnd;
                argEnd = null;
                commaPos = -1;
                if (isLast) {
                    var frameCopy = frame;
                    frame = [];
                    receivedPacket(frameCopy);
                }
            }

        };
        var receivedPacket = function(args) {
            var testAckId = parseInt(args[0]);
;;;         self.logger.debug('args', args);
            if (!isNaN(testAckId)) {
                ackId = testAckId;
            }
;;;         self.logger.debug('testAckId', testAckId, 'ackId', ackId);
            var packet = {
                id: testAckId,
                name: args[1],
                data: args[2]
            };
            // TODO: shouldn't we put this in a window.setTimeout so that user
            //     code won't mess up our code?
            self.onReadFrame(packet);
        };
    };
    Orbited.CometTransports.LongPoll.prototype.logger = Orbited.getLogger("Orbited.CometTransports.LongPoll");
    // LongPoll supported browsers
    /*
Orbited.CometTransports.LongPoll.firefox = 0.9
Orbited.CometTransports.LongPoll.firefox2 = 0.9
Orbited.CometTransports.LongPoll.firefox3 = 0.9
Orbited.CometTransports.LongPoll.safari2 = 0.9
Orbited.CometTransports.LongPoll.safari3 = 0.9
Orbited.CometTransports.LongPoll.opera = 0.9
Orbited.CometTransports.LongPoll.ie = 0.9
*/



    Orbited.CometTransports.Poll = function() {
        var self = this;
        self.name = 'poll';

//        self.limbo = false;

        var url = null;
        var xhr = null;
        var ackId = null;
        var retryTimer = null;
        var buffer = "";
        var baseRetryInterval = Orbited.settings.POLL_INTERVAL;
        var retryInterval = baseRetryInterval;
        self.readyState = CT_READYSTATE_INITIAL;
        self.onReadFrame = function(frame) {};
        self.onclose = function() { };

        self.close = function() {
;;;         self.logger.debug('close...');
            if (self.readyState == CT_READYSTATE_CLOSED) {
                return;
            }
            if (xhr != null && (xhr.readyState > 1 || xhr.readyState < 4)) {
                xhr.onreadystatechange = function() { };
                xhr.abort();
                xhr = null;
            }
            self.readyState = CT_READYSTATE_CLOSED;
            window.clearTimeout(retryTimer);
            self.onclose();
        };

        self.connect = function(_url) {
;;;         self.logger.debug('connect...');
            if (self.readyState == CT_READYSTATE_OPEN) {
                throw new Error("Already Connected");
            }
            url = new Orbited.URL(_url);
            if (xhr == null) {
                if (url.isSameDomain(location.href)) {
                    xhr = createXHR();
                }
                else {
                    xhr = new Orbited.XSDR();
                }
            }
            url.path += '/poll';
            //      url.setQsParameter('transport', 'xhrstream')
            self.readyState = CT_READYSTATE_OPEN;
            open();
        };
        var open = function() {
;;;         self.logger.debug('open...');
            try {
                if (typeof(ackId) == "number") {
                    url.setQsParameter('ack', ackId);
                }
                if (typeof(xhr)== "undefined" || xhr == null) {
                    throw new Error("how did this happen?");
                }
                
                if (Orbited.settings.enableFFPrivileges) {
                    try {
                        netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead');
                    } catch (ex) { }
                }
                xhr.open('GET', url.render(), true);
                xhr.onreadystatechange = function() {
                    switch(xhr.readyState) {
                        case 4:
                            try {
                                var test = xhr.status;
                            }
                            catch(e) {
                                // Exponential backoff: Every time we fail to
                                // reconnect, double the interval.
                                // TODO cap the max value.
                                retryInterval *= 2;
                                window.setTimeout(reconnect, retryInterval);
                                return;
                            }
                            switch(xhr.status) {
                                case 200:
                                    self.timeoutResetter();
                                    retryInterval = baseRetryInterval;
                                    process();
                                    setTimeout(open, retryInterval);
                                    break;
                                case 404:
                                    self.close();
                                    break;
                                case null:
                                    // NOTE: for the XSDR case: Long
                                    // (we can always get status, but maybe its null)
                                    retryInterval *= 2;
                                    window.setTimeout(reconnect, retryInterval);
                                    break;
                                default:
                                    // TODO: do we want to retry here?
                                    self.close();
                                    break;
                            }
                            break;
                    }
                };
                xhr.send(null);
            }
            catch(e) {
                self.close();
            }
        };
        
        var reconnect = function() {
;;;         self.logger.debug('reconnect...');
            if (xhr.readyState < 4 && xhr.readyState > 0) {
                xhr.onreadystatechange = function() {
                    if (xhr.readyState == 4) {
                        reconnect();
                    }
                };
;;;             self.logger.debug('do abort..');
                xhr.abort();
                window.clearTimeout(heartbeatTimer);
            } else {
;;;             self.logger.debug('reconnect do open');
                offset = 0;
                setTimeout(open, 0);
            }
        };
        // 12,ab011,hello world
        var process = function() {
;;;         self.logger.debug('process...');
            var commaPos = -1;
            var argEnd = null;
            var argSize;
            var frame = [];
            var stream = xhr.responseText;
            var offset = 0;

            var k = 0;

            while (true) {
                k += 1;
                if (k > 2000) {
                    throw new Error("Borked XHRStream transport");
                }
                if (commaPos == -1) {
                    commaPos = stream.indexOf(',', offset);
                }
                if (commaPos == -1) {
;;;                 self.logger.debug('no more commas. offset:', offset, 'stream.length:', stream.length);
//                    if (offset == 0 && stream.length == 0) {
//;;;                     self.logger.debug('setting limbo to true');
  //                      self.limbo = true;
    //                }
                    return;
                }
//;;;             self.logger.debug('setting limbo to false');
  //              self.limbo = false;

                if (argEnd == null) {
                    argSize = parseInt(stream.slice(offset+1, commaPos));
                    argEnd = commaPos +1 + argSize;
                }
                //;;;        self.logger.assert(true);
                /*          if (stream.length < argEnd) {
    //;;;        self.logger.debug('how did we get here? stream.length:', stream.length, 'argEnd:', argEnd, 'offset:', offset)
                return
                }*/
                var data = stream.slice(commaPos+1, argEnd);
;;;             self.logger.assert(data.length == argSize, 'argSize:', argSize, 'data.length', data.length);
                if (data.length != argSize) {
                    DEBUGDATA = stream;
                }
                frame.push(data);
                var isLast = (stream.charAt(offset) == '0');
                offset = argEnd;
                argEnd = null;
                commaPos = -1;
                if (isLast) {
                    var frameCopy = frame;
                    frame = [];
                    receivedPacket(frameCopy);
                }
            }
            
        };
        var receivedPacket = function(args) {
;;;         self.logger.debug('receivedPacket...');
            var testAckId = parseInt(args[0]);
;;;         self.logger.debug('args', args);
            if (!isNaN(testAckId)) {
            ackId = testAckId;
            }
;;;         self.logger.debug('testAckId', testAckId, 'ackId', ackId);
            var packet = {
            id: testAckId,
            name: args[1],
            data: args[2]
            };
            // TODO: shouldn't we put this in a window.setTimeout so that user
            //     code won't mess up our code?
            self.onReadFrame(packet);
        };
    };
    Orbited.CometTransports.Poll.prototype.logger = Orbited.getLogger("Orbited.CometTransports.Poll");

    // Poll supported browsers
    /*Orbited.CometTransports.Poll.firefox = 0.5
Orbited.CometTransports.Poll.opera = 0.5
Orbited.CometTransports.Poll.ie = 0.5
*/





    Orbited.CometTransports.HTMLFile = function() {
        var self = this;
        self.name = 'htmlfile';
        var id = ++Orbited.singleton.HTMLFile.i;
        Orbited.singleton.HTMLFile.instances[id] = self;
        var htmlfile = null;
        var ifr = null;
        var url = null;
        var restartUrl = null;
        var restartTimer = null;
        // TODO: move constant to Orbited.settings
        var baseRestartTimeout = 2000;
        var restartTimeout = baseRestartTimeout;
        self.onReadFrame = function(frame) {};
        self.onread = function(packet) { self.onReadFrame(packet); };
        self.onclose = function() { };
        self.connect = function(_url) {
            if (self.readyState == CT_READYSTATE_OPEN) {
            throw new Error("Already Connected");
            }
            self.logger.debug('self.connect', _url);
            url = new Orbited.URL(_url);
            url.path += '/htmlfile';
            url.setQsParameter('frameID', id.toString());
            self.readyState = CT_READYSTATE_OPEN;
            doOpen(url.render());
        };

        var doOpenIfr = function() {

            var ifr = document.createElement('iframe');
            ifr.src = url.render();
            document.body.appendChild(ifr);
        };

        var doOpen = function(_url) {
;;;         self.logger.debug('doOpen', _url);
            htmlfile = new ActiveXObject('htmlfile'); // magical microsoft object
            htmlfile.open();
            if (self.isSubDomain) {
                htmlfile.write('<html><script>' + 'document.domain="' + document.domain + '";' + '</script></html>');
            }
            else {
                htmlfile.write('<html></html>');
            }
            htmlfile.parentWindow.Orbited = Orbited;
            htmlfile.close();
            var iframe_div = htmlfile.createElement('div');
            htmlfile.body.appendChild(iframe_div);
            ifr = htmlfile.createElement('iframe');
            iframe_div.appendChild(ifr);
            ifr.src = _url;
            restartUrl = _url;
            restartTimer = window.setTimeout(reconnect, restartTimeout);
        };

        // TODO: expose this in another way besides the public api
        self.restartingStream = function(_url) {
            restartUrl = _url;
            restartTimer = window.setTimeout(reconnect, restartTimeout);
        };

        var reconnect = function() {
;;;         self.logger.debug('doing reconnect... ' + restartTimeout);
            restartTimeout*=2;
            ifr.src = restartUrl;
          restartTimer = window.setTimeout(reconnect, restartTimeout);
        };

        self.streamStarted = function() {
;;;         self.logger.debug('stream started..');
            window.clearTimeout(restartTimer);
            restartTimer = null;
            restartTimeout = baseRestartTimeout;
        };

        self.streamClosed = function() {
;;;         self.logger.debug('stream closed!');
            window.clearTimeout(restartTimer);
            self.close();
        };

        self.receive = function(id, name, data) {
            packet = {
            id: id,
            name: name,
            data: data
            };
            self.onread(packet);
        };

        self.close = function() {
            if (self.readyState == CT_READYSTATE_CLOSED) {
            return;
            }
;;;         self.logger.debug('close called, clearing timer');
            window.clearTimeout(restartTimer);
            self.readyState = CT_READYSTATE_CLOSED;
            ifr.src = 'about:blank';
            htmlfile = null;
            CollectGarbage();
            self.onclose();
        };

    };
    Orbited.CometTransports.HTMLFile.prototype.logger = Orbited.getLogger("Orbited.CometTransports.HTMLFile");
    // HTMLFile supported browsers
    Orbited.CometTransports.HTMLFile.ie = 1.0;
    Orbited.singleton.HTMLFile = {
        i: 0,
        instances: {}
    };




    Orbited.CometTransports.SSE = function() {
        var self = this;
        self.name = 'sse';
        self.onReadFrame = function(frame) {};
        self.onclose = function() { };
        self.readyState = CT_READYSTATE_INITIAL;
        var heartbeatTimer = null;
        var source = null;
        var url = null;
        var lastEventId = -1;

        self.close = function() {
            if (self.readyState == CT_READYSTATE_CLOSED) {
                return;
            }
            // TODO: can someone test this and get back to me? (No opera at the moment)
            //     : -mcarter 7-26-08
            self.readyState = CT_READYSTATE_CLOSED;
            doClose();
            self.onclose();
        };

        self.connect = function(_url) {
            if (self.readyState == CT_READYSTATE_OPEN) {
                throw new Error("Already Connected");
            }
            url = new Orbited.URL(_url);
            url.path += '/sse';
            self.readyState = CT_READYSTATE_OPEN;
            doOpen();
        };
        doClose = function() {
            source.removeEventSource(source.getAttribute('src'));
            source.setAttribute('src',"");
            if (opera.version() < 9.5) {
                document.body.removeChild(source);
            }
            source = null;
        };
        doOpen = function() {
            /*
        if (typeof(lastEventId) == "number") {
            url.setQsParameter('ack', lastEventId)
        }
*/
            source = document.createElement("event-source");
            source.setAttribute('src', url.render());
            // NOTE: without this check opera 9.5 would make two connections.
            if (opera.version() < 9.5) {
            document.body.appendChild(source);
            }
            source.addEventListener('payload', receivePayload, false);
            
            //      source.addEventListener('heartbeat', receiveHeartbeat, false);
            // start up the heartbeat timer...
            //      receiveHeartbeat();
        };
        
        var receivePayload = function(event) {
            var data = eval(event.data);
            if (typeof(data) != 'undefined') {
            for (var i = 0; i < data.length; ++i) {
                var packet = data[i];
                receive(packet[0], packet[1], packet[2]);
            }
            }
            
        };
        /*    var receiveHeartbeat = function() {
               window.clearTimeout(heartbeatTimer);
               heartbeatTimer = window.setTimeout(reconnect, Orbited.settings.HEARTBEAT_TIMEOUT)
              }      */
        var receive = function(id, name, data) {
            var tempId = parseInt(id);
            if (!isNaN(tempId)) {
            // NOTE: The old application/x-dom-event-stream transport doesn't
            //         allow us to put in the lastEventId on reconnect, so we are
            //         bound to get double copies of some of the events. Therefore
            //         we are going to throw out the duplicates. Its not clear to
            //         me that this is a perfect solution.
            //         -mcarter 8-9-08
            //          if (tempId <= lastEventId) {
            //          return
            //          }
            lastEventId = tempId;
            }
            // NOTE: we are dispatching null-id packets. Is this correct?
            //     -mcarter 9-8-08
            packet = {
            id: id,
            name: name,
            data: data
            };
            self.onReadFrame(packet);
        };
    };
    Orbited.CometTransports.SSE.prototype.logger = Orbited.getLogger("Orbited.CometTransports.SSE");
    
    Orbited.CometTransports.SSE.opera = 1.0;
    Orbited.CometTransports.SSE.opera8 = 1.0;
    Orbited.CometTransports.SSE.opera9 = 1.0;
    Orbited.CometTransports.SSE.opera9_5 = 0.8;



    /* This is an old implementation of the URL class. Jacob is cleaning it up
 * -mcarter, 7-30-08
 *
 * Jacob is actually throwing this away and rewriting from scratch
 * -mcarter 11-14-08
 */
    Orbited.URL = function(_url) {
        var self = this;
        var protocolIndex = _url.indexOf("://");
        if (protocolIndex != -1) self.protocol = _url.slice(0,protocolIndex);
        else protocolIndex = -3;

        var domainIndex = _url.indexOf('/', protocolIndex+3);
        if (domainIndex == -1) domainIndex=_url.length;
            
        var hashIndex = _url.indexOf("#", domainIndex);
        if (hashIndex != -1) self.hash = _url.slice(hashIndex+1);
        else hashIndex = _url.length;
            
        var uri = _url.slice(domainIndex, hashIndex);
        var qsIndex = uri.indexOf('?');
        if (qsIndex == -1) qsIndex=uri.length;

        self.path = uri.slice(0, qsIndex);
        self.qs = uri.slice(qsIndex+1);
        if (self.path == "") self.path = "/";
            
        var domain = _url.slice(protocolIndex+3, domainIndex);
        var portIndex = domain.indexOf(":");
        if (portIndex == -1) {
            self.port = 80;
            portIndex = domain.length;
        }
        else {
            self.port = parseInt(domain.slice(portIndex+1));
        }
        if (isNaN(this.port)) throw new Error("Invalid _url");
            
        self.domain = domain.slice(0, portIndex);

        self.render = function() {
            var output = "";
            if (typeof(self.protocol) != "undefined")
            output += self.protocol + "://";
            output += self.domain;
            if (self.port != 80 && typeof(self.port) != "undefined" && self.port != null)
            if (typeof(self.port) != "string" || self.port.length > 0)
                output += ":" + self.port;
            if (typeof(self.path) == "undefined" || self.path == null)
            output += '/';
            else
            output += self.path;
            if (self.qs.length > 0)
            output += '?' + self.qs;
            if (typeof(self.hash) != "undefined" && self.hash.length > 0)
            output += "#" + self.hash;
            return output;
        };
        self.isSamePort = function(_url) {
            _url = new Orbited.URL(_url);
            return _url.port == self.port;
        }
        self.isSameDomain = function(_url) {
            _url = new Orbited.URL(_url);
            
            if (!_url.domain || !self.domain)
            return true;
            return (_url.port == self.port && _url.domain == self.domain);
        };
        self.isSameParentDomain = function(_url) {
            _url = new Orbited.URL(_url);
            if (_url.domain == self.domain) {
            return true;
            }
            var orig_domain = _url.domain;
            var parts = document.domain.split('.');
            //      var orig_domain = document.domain
            for (var i = 0; i < parts.length-1; ++i) {
            var new_domain = parts.slice(i).join(".");
            if (orig_domain == new_domain)
                return true;
            }
            return false;
        };
        self.isSubDomain = function(_url) {
            _url = new Orbited.URL(_url);
            if (!_url.domain || !self.domain) {
                return false;
            }
            return (_url.port == self.port && self.domain.indexOf("."+_url.domain) > 0);
//            return (_url.port == self.port && _url.domain == self.domain.split('.').slice(1).join('.'));
        };
        var decodeQs = function(qs) {
            //      alert('a')
            if (qs.indexOf('=') == -1) return {};
            var result = {};
            var chunks = qs.split('&');
            for (var i = 0; i < chunks.length; ++i) {
            var cur = chunks[i];
            var pieces = cur.split('=');
            result[pieces[0]] = pieces[1];
            }
            return result;
        };
        var encodeQs = function(o) {
            var output = "";
            for (var key in o)
            output += "&" + key + "=" + o[key];
            return output.slice(1);
        };
        self.setQsParameter = function(key, val) {
            var curQsObj = decodeQs(self.qs);
            curQsObj[key] = val;
            self.qs = encodeQs(curQsObj);
        };

        self.mergeQs = function(qs) {
            var newQsObj = decodeQs(qs);
            for (key in newQsObj) {
            curQsObj[key] = newQsObj[key];
            }
        };
        self.removeQsParameter = function(key) {
            var curQsObj = decodeQs(self.qs);
            delete curQsObj[key];
            self.qs = encodeQs(curQsObj);
        };

        self.merge = function(targetUrl) {
            if (typeof(self.protocol) != "undefined" && self.protocol.length > 0) {
            self.protocol = targetUrl.protocol;
            }
            if (targetUrl.domain.length > 0) {
            self.domain = targetUrl.domain;
            self.port = targetUrl.port;
            }
            self.path = targetUrl.path;
            self.qs = targetUrl.qs;
            self.hash = targetUrl.hash;
        };

    };

    Orbited.utf8 = {};
    Orbited.utf8.decode = function(s) {
        var ret = [];
        var j = 0;
        function pad6(str) {
            while(str.length < 6) { str = "0" + str; } return str;
        }
        for (var i=0; i < s.length; i++) {
            if ((s.charCodeAt(i) & 0xf8) == 0xf0) {
                if (s.length -j < 4) { break; }
                j+=4;
                ret.push(String.fromCharCode(parseInt(
                    (s.charCodeAt(i) & 0x07).toString(2) +
                    pad6((s.charCodeAt(i+1) & 0x3f).toString(2)) +
                    pad6((s.charCodeAt(i+2) & 0x3f).toString(2)) +
                    pad6((s.charCodeAt(i+3) & 0x3f).toString(2))
                    , 2)));
                i += 3;
            } else if ((s.charCodeAt(i) & 0xf0) == 0xe0) {
                if (s.length -j < 3) { break; }
                j+=3;
                ret.push(String.fromCharCode(parseInt(
                    (s.charCodeAt(i) & 0x0f).toString(2) +
                    pad6((s.charCodeAt(i+1) & 0x3f).toString(2)) +
                    pad6((s.charCodeAt(i+2) & 0x3f).toString(2))
                    , 2)));
                i += 2;
            } else if ((s.charCodeAt(i) & 0xe0) == 0xc0) {
                if (s.length -j < 2) { break }
                j+=2;
                ret.push(String.fromCharCode(parseInt(
                    (s.charCodeAt(i) & 0x1f).toString(2) +
                    pad6((s.charCodeAt(i+1) & 0x3f).toString(2), 6)
                    , 2)));
                i += 1;
            } else {
                j+=1;
                ret.push(String.fromCharCode(s.charCodeAt(i)));
            }
        }
        return [ret.join(""), j];
    };
    
    // TODO rename to encode
    Orbited.utf8.encode = function(text) {
        var ret = [];

        function pad(str, len) {
            while(str.length < len) { str = "0" + str; } return str;
        }
        var e = String.fromCharCode;
        for (var i=0; i < text.length; i++) {
            var chr = text.charCodeAt(i);
            if (chr <= 0x7F) {
            ret.push(e(chr));
            } else if(chr <= 0x7FF) {
            var binary = pad(chr.toString(2), 11);
            ret.push(e(parseInt("110"    + binary.substr(0,5), 2)));
            ret.push(e(parseInt("10"    + binary.substr(5,6), 2)));
            } else if(chr <= 0xFFFF) {
            var binary = pad(chr.toString(2), 16);
            ret.push(e(parseInt("1110"    + binary.substr(0,4), 2)));
            ret.push(e(parseInt("10"    + binary.substr(4,6), 2)));
            ret.push(e(parseInt("10"    + binary.substr(10,6), 2)));
            } else if(chr <= 0x10FFFF) {
            var binary = pad(chr.toString(2), 21);
            ret.push(e(parseInt("11110" + binary.substr(0,3), 2)));
            ret.push(e(parseInt("10"    + binary.substr(3,6), 2)));
            ret.push(e(parseInt("10"    + binary.substr(9,6), 2)));
            ret.push(e(parseInt("10"    + binary.substr(15,6), 2)));
            }
        }
        return ret.join("");
    };

        /*
         * We create Orbited.JSON whether or not some other JSON
         * exists. This is because Orbited.JSON is compatible with
         * JSON.js (imported by xsdrBridge), whereas various other
         * JSONs, including the one that ships with Prototype, are
         * not, leading to dumb errors.
         *     -mario
         */

        Orbited.JSON = function () {

            function f(n) {
            // Format integers to have at least two digits.
            return n < 10 ? '0' + n : n;
            }

            Date.prototype.toJSON = function (key) {

            return this.getUTCFullYear()   + '-' +
                f(this.getUTCMonth() + 1) + '-' +
                f(this.getUTCDate())       + 'T' +
                f(this.getUTCHours())       + ':' +
                f(this.getUTCMinutes())   + ':' +
                f(this.getUTCSeconds())   + 'Z';
            };

            String.prototype.toJSON =
            Number.prototype.toJSON =
            Boolean.prototype.toJSON = function (key) {
                return this.valueOf();
            };

            var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
            escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
            gap,
            indent,
            meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
            },
            rep;


            function quote(string) {
            
            // If the string contains no control characters, no quote characters, and no
            // backslash characters, then we can safely slap some quotes around it.
            // Otherwise we must also replace the offending characters with safe escape
            // sequences.
            
            escapeable.lastIndex = 0;
            return escapeable.test(string) ?
                '"' + string.replace(escapeable, function (a) {
                var c = meta[a];
                if (typeof c === 'string') {
                    return c;
                }
                return '\\u' + ('0000' +
                        (+(a.charCodeAt(0))).toString(16)).slice(-4);
                }) + '"' :
            '"' + string + '"';
            }
            

            function str(key, holder) {

            // Produce a string from holder[key].

            var i,        // The loop counter.
            k,        // The member key.
            v,        // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

            // If the value has a toJSON method, call it to obtain a replacement value.

            if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
                value = value.toJSON(key);
            }

            // If we were called with a replacer function, then call the replacer to
            // obtain a replacement value.

            if (typeof rep === 'function') {
                value = rep.call(holder, key, value);
            }

            // What happens next depends on the value's type.

            switch (typeof value) {
            case 'string':
                return quote(value);

            case 'number':

                // JSON numbers must be finite. Encode non-finite numbers as null.

                return isFinite(value) ? String(value) : 'null';

            case 'boolean':
            case 'null':

                // If the value is a boolean or null, convert it to a string. Note:
                // typeof null does not produce 'null'. The case is included here in
                // the remote chance that this gets fixed someday.

                return String(value);

                // If the type is 'object', we might be dealing with an object or an array or
                // null.

            case 'object':

                // Due to a specification blunder in ECMAScript, typeof null is 'object',
                // so watch out for that case.

                if (!value) {
                return 'null';
                }

                // Make an array to hold the partial results of stringifying this object value.

                gap += indent;
                partial = [];

                // If the object has a dontEnum length property, we'll treat it as an array.

                if (typeof value.length === 'number' &&
                !(value.propertyIsEnumerable('length'))) {

                // The object is an array. Stringify every element. Use null as a placeholder
                // for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

                // Join all of the elements together, separated with commas, and wrap them in
                // brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                    partial.join(',\n' + gap) + '\n' +
                    mind + ']' :
                    '[' + partial.join(',') + ']';
                gap = mind;
                return v;
                }

                // If the replacer is an array, use it to select the members to be stringified.

                if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                    v = str(k, value);
                    if (v) {
                        partial.push(quote(k) + (gap ? ': ' : ':') + v);
                    }
                    }
                }
                } else {

                // Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                    v = str(k, value);
                    if (v) {
                        partial.push(quote(k) + (gap ? ': ' : ':') + v);
                    }
                    }
                }
                }

                // Join all of the member texts together, separated with commas,
                // and wrap them in braces.

                v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                mind + '}' : '{' + partial.join(',') + '}';
                gap = mind;
                return v;
            }
            }

            // Return the JSON object containing the stringify and parse methods.

            return {
            stringify: function (value, replacer, space) {

                // The stringify method takes a value and an optional replacer, and an optional
                // space parameter, and returns a JSON text. The replacer can be a function
                // that can replace values, or an array of strings that will select the keys.
                // A default replacer method can be provided. Use of the space parameter can
                // produce text that is more easily readable.

                var i;
                gap = '';
                indent = '';

                // If the space parameter is a number, make an indent string containing that
                // many spaces.

                if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

                // If the space parameter is a string, it will be used as the indent string.

                } else if (typeof space === 'string') {
                indent = space;
                }

                // If there is a replacer, it must be a function or an array.
                // Otherwise, throw an error.

                rep = replacer;
                if (replacer && typeof replacer !== 'function' &&
                (typeof replacer !== 'object' ||
                 typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
                }

                // Make a fake root object containing our value under the key of ''.
                // Return the result of stringifying the value.

                return str('', {'': value});
            },


            parse: function (text, reviver) {

                // The parse method takes a text and an optional reviver function, and returns
                // a JavaScript value if the text is a valid JSON text.

                var j;

                function walk(holder, key) {

                // The walk method is used to recursively walk the resulting structure so
                // that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = walk(value, k);
                        if (v !== undefined) {
                        value[k] = v;
                        } else {
                        delete value[k];
                        }
                    }
                    }
                }
                return reviver.call(holder, key, value);
                }


                // Parsing happens in four stages. In the first stage, we replace certain
                // Unicode characters with escape sequences. JavaScript handles many characters
                // incorrectly, either silently deleting them, or treating them as line endings.

                cx.lastIndex = 0;
                if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' + ('0000' +
                            (+(a.charCodeAt(0))).toString(16)).slice(-4);
                });
                }

                // In the second stage, we run the text against regular expressions that look
                // for non-JSON patterns. We are especially concerned with '()' and 'new'
                // because they can cause invocation, and '=' because it can cause mutation.
                // But just to be safe, we want to reject all unexpected forms.

                // We split the second stage into 4 regexp operations in order to work around
                // crippling inefficiencies in IE's and Safari's regexp engines. First we
                // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
                // replace all simple value tokens with ']' characters. Third, we delete all
                // open brackets that follow a colon or comma or that begin the text. Finally,
                // we look to see that the remaining characters are only whitespace or ']' or
                // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

                if (/^[\],:{}\s]*$/.
                test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
                             replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

                    // In the third stage we use the eval function to compile the text into a
                    // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
                    // in JavaScript: it can begin a block or an object literal. We wrap the text
                    // in parens to eliminate the ambiguity.

                    j = eval('(' + text + ')');

                    // In the optional fourth stage, we recursively walk the new structure, passing
                    // each name/value pair to a reviver function for possible transformation.

                    return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
                }

                          // If the text is not JSON parseable, then a SyntaxError is thrown.

                          throw new SyntaxError('JSON.parse');
                         }
                    };
                   }();
            })();


        // Try to auto detect the Orbited port and hostname
        (function() {
            try {
            var scripts = document.getElementsByTagName('script');
            for (var i = 0; i < scripts.length; ++i) {
                var script = scripts[i];
                if (script.src.match('/static/Orbited\.js$')) {
                var url = new Orbited.URL(script.src);
                if (url.render().indexOf('http') != 0) {
                    var url = new Orbited.URL(window.location.toString());
                }
                Orbited.settings.hostname = url.domain;
                Orbited.settings.port = url.port;
                break;
                }
            }
            } catch(e) {
            //    alert("Error! " + e.name + ": " + e.message);
            }
        })();
        
/* stomp.js
 *
 * JavaScript implementation of the STOMP (Streaming Text Oriented Protocol)
 *  for use with TCPConnection or a facsimile
 *
 * Frank Salim (frank.salim@gmail.com) (c) 2008 Orbited (orbited.org)
 * Rui Lopes (ruilopes.com)
 */

STOMP_DEBUG = true;

if (STOMP_DEBUG) {
    function getStompLogger(name) {
        return {
            debug: BRM.log,
            dir: BRM.log
        };
    }
} else {
    function getStompLogger(name) {
        return {
            debug: function() {},
            dir: function() {}
        };
    }
}


// NB: This is loosly based on twisted.protocols.basic.LineReceiver
//     See http://twistedmatrix.com/documents/8.1.0/api/twisted.protocols.basic.LineReceiver.html
// XXX this assumes the lines are UTF-8 encoded.
// XXX this assumes the lines are terminated with a single NL ("\n") character.
LineProtocol = function(transport) {
    var log = getStompLogger("LineProtocol");
    var self = this;
    var buffer = null;
    var isLineMode = true;

    //
    // Transport callbacks implementation.
    //

    transport.onopen = function() {
        buffer = "";
        isLineMode = true;
        self.onopen();
    };

    transport.onclose = function(code) {
        buffer = null;
        self.onclose(code);
    };

    transport.onerror = function(error) {
        self.onerror(error);
    };

    transport.onread = function(data) {
        log.debug("transport.onread: enter isLineMode=", isLineMode, " buffer[", buffer.length, "]=", buffer, " data[", data.length, "]=", data);

        if (isLineMode) {
            buffer += data;
            data = "";

            var start = 0;
            var end;
            while ((end = buffer.indexOf("\n", start)) >= 0 && isLineMode) {
                // TODO it would be nice that decode received the
                //      start and end indexes, if it did, we didn't
                //      need the slice copy.
                var bytes = buffer.slice(start, end);
                // TODO do not depend on Orbited.
                var line = Orbited.utf8.decode(bytes)[0];
                log.debug("fire onlinereceived line[", line.length, "]=", line);
                self.onlinereceived(line);
                start = end + 1;
            }
            // remove the portion (head) of the array we've processed.
            buffer = buffer.slice(start);

            if (isLineMode) {
                // TODO if this buffer length is above a given threshold, we should
                //      send an alert "max line length exceeded" and empty buffer
                //      or even abort.
            } else {
                // we've left the line mode and what remains in buffer is raw data.
                data = buffer;
                buffer = "";
            }
        }

        if (data.length > 0) {
            log.debug("fire onrawdatareceived data[", data.length, "]=", data);
            self.onrawdatareceived(data);
        }

        log.debug("transport.onread: leave");
    };

    //
    // Protocol implementation.
    //

    self.setRawMode = function() {
        log.debug("setRawMode");
        isLineMode = false;
    };

    // TODO although this is a nice interface, it will do a extra copy
    //      of the data, a probable better alternative would be to
    //      make onrawdatareceived return the number of consumed bytes
    //      (instead of making it comsume all the given data).
    self.setLineMode = function(extra) {
        log.debug("setLineMode: extra=", extra);
        isLineMode = true;
        if (extra && extra.length > 0)
            transport.onread(extra);
    };

    self.send = function(data) {
        log.debug("send: data=", data);
        return transport.send(data);
    };

    self.open = function(host, port, isBinary) {
        BRM.log("Opening connection to " + host + ":" + port + " (" + isBinary + ")");
        log.debug("open: host=", host, ':', port, ' isBinary=', isBinary);
        transport.open(host, port, isBinary);
    };

    self.close = function() {
        log.debug("close");
        transport.close();
    };
    self.reset = function() {
        transport.reset();
    }
    //
    // callbacks for the events generated by this
    //
    // XXX these callbacks names should be camelCased

    self.onopen = function() {};
    self.onclose = function() {};
    self.onerror = function(error) {};
    self.onlinereceived = function(line) {};
    self.onrawdatareceived = function(data) {};
};


// TODO propose to rename this to BaseStompClient
//      See the comment in the callbacks zone bellow.
// TODO add ";" to all lines (where it makes sense).
// TODO remove deprecated stuff.
//
// Deprecated attributes:
//
//  user : string
//      the user name used to login into the STOMP server.
//
//
// Methods:
//
//  connect(domain : string, port : int, user : string, password : string)
//      connects to the given STOMP server.
//
//      the connection is established after ``onconnected'' is received.
//
//  disconnect()
//      disconnects from current STOMP server.
//
//      the connection is disconnected after ``onclose'' is received.
//
//      TODO: implement ``ondisconnect''.
//
//  send(message : string, destination : string, extraHeaders : {}|undefined)
//      sends the given message to destination.
//
//  subscribe(destination : string)
//      starts receiving messages from the given destination.
//
//  unsubscribe(destination : string)
//      stops receiving messages from the given destination.
//
//
// Callbacks:
//
//  onopen()
//      underline transport is openned.
//
//  onclose()
//      underline transport is closed.
//
//  onerror(error : Error)
//      there was an error.
//
//  onframe(frame : Frame)
//      received a STOMP frame.
//
//      this will dispatch for a specific method based on the frame
//      type, eg. when frame.type is "MESSAGE" this calls
//      onmessageframe(frame).
//
//      frame is an object with the following properties:
//
//          type : string
//          headers : {string: string}
//          body : string
//
//  onconnectedframe(frame : Frame)
//      received a CONNECTED STOMP frame.
//
//  onmessageframe(frame : Frame)
//      received a MESSAGE STOMP frame.
//
//  onreceiptframe(frame : Frame)
//      received a RECEIPT STOMP frame.
//
//  onerrorframe(frame : Frame)
//      received a ERROR STOMP frame.
//
//
// Deprecated callbacks:
//
//  onmessage(frame)
//      use ``onmessageframe'' instead.
//
//      received a MESSAGE STOMP frame.
//
STOMPClient = function() {
    var log = getStompLogger("STOMPClient");
    var self = this;
    var protocol = null;
    var buffer = "";
    var type = null;
    var headers = null;
    var remainingBodyLength = null;

    // Deprecated attributes:
    self.user = null;

    // TODO probably this function should be move into a common base...
    function trim(str) {
        // See http://blog.stevenlevithan.com/archives/faster-trim-javascript
        return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    }

    function mergeObject(dst, src) {
        for (var k in src) {
            dst[k] = src[k];
        }
        return dst;
    }
 
    //
    // LineProtocol implementation.
    //

    function protocol_onLineReceived(line) {
        log.debug("protocol_onLineReceived: line=", line);

        if (line.length == 0) {
            // ignore empty lines before the type line.
            if (type === null)
                return;
            // we reached the end headers.
            log.debug("onLineReceived: all headers:");
            log.dir(headers);
            if ('content-length' in headers) {
                // NB: content-length does not include the trailing NUL,
                //     but we need to account it.
                remainingBodyLength = parseInt(headers['content-length']) + 1;
            } else {
                remainingBodyLength = null;
            }
            protocol.setRawMode();
            return;
        }

        if (type === null) {
            log.debug("onLineReceived: begin ", line, " frame");
            type = line;
            headers = {};
            buffer = "";
            remainingBodyLength = null;
            return;
        }

        var sep = line.search(":");
        var key = trim(line.slice(0, sep));
        var value = trim(line.slice(sep + 1));
        headers[key] = value;
        log.debug("onLineReceived: found header ", key, "=", value);
    }

    if (STOMP_DEBUG) {
        function dumpStringAsIntArray(title, data) {
            var bytes = [];
            for (var n = 0; n < data.length; ++n) {
                bytes.push(data.charCodeAt(n));
            }
            log.debug(title);
            log.debug('length=', bytes.length, " bytes=", bytes);
        }
    } else {
        function dumpStringAsIntArray() {}
    }

    function protocol_onRawDataReceived(data) {
        log.debug("protocol_onRawDataReceived");
        dumpStringAsIntArray("buffer", buffer);
        dumpStringAsIntArray("data", data);

        if (remainingBodyLength === null) {
            // we're doing a message parsing without knowing the exact
            // body length.

            buffer += data;

            var end = buffer.indexOf("\0");
            if (end >= 0) {
                // split into head (bytes) and tail (buffer).
                var bytes = buffer.slice(0, end);
                buffer = buffer.slice(end + 1);
                doDispatch(bytes, buffer);
            }
        } else {
            // we're doing a message parsing knowing the exact body
            // length.

            var toRead = Math.min(data.length, remainingBodyLength);
            remainingBodyLength -= toRead;

            // split into head (bytes) and tail (data).
            if (remainingBodyLength === 0) {
                var bytes = data.slice(0, toRead - 1);
            } else {
                var bytes = data.slice(0, toRead);
            }
            data = data.slice(toRead);
            // buffer will contain the whole message body.
            buffer += bytes;

            if (remainingBodyLength === 0) {
                doDispatch(buffer, data);
            }
        }
    }

    function doDispatch(bytes, extra) {
        log.debug("doDispatch: bytes[", bytes.length, "]=", bytes, " extra[", extra.length, "]=", extra);
        dumpStringAsIntArray("bytes", bytes);
        dumpStringAsIntArray("extra", extra);

        var frame = {
            type: type,
            headers: headers,
            // TODO stop assuming the body is UTF8 encoded.
            body: Orbited.utf8.decode(bytes)[0]
        };

        log.debug("doDispatch: end frame; body.length=", frame.body.length);
        log.dir(frame);

        self.onframe(frame);

        buffer = "";
        type = null;
        headers = {};
        remainingBodyLength = null;

        protocol.setLineMode(extra);
    }

    //
    // Callbacks
    //

    function Ignored() {}

    self.onopen = Ignored;

    self.onclose = Ignored;

    self.onerror = Ignored;

    self.onframe = function(frame) {
        switch (frame.type) {
            case 'CONNECTED':
                self.onconnectedframe(frame);
                break;
            case 'MESSAGE':
                self.onmessageframe(frame);
                break;
            case 'RECEIPT':
                self.onreceiptframe(frame);
                break;
            case 'ERROR':
                self.onerrorframe(frame);
                break;
            default:
                self.onerror("Unknown STOMP frame type " + frame.type);
        }
    };

    self.onconnectedframe = Ignored;

    self.onreceiptframe = Ignored;

    self.onmessageframe = function(frame) {
        // TODO stop calling deprecated onmessage.
        if (this.onmessage)
            this.onmessage(frame);
    };

    self.onerrorframe = Ignored;

    // Deprecated callbacks
    self.onmessage = Ignored;

    //
    // Methods
    //

    self.sendFrame = function(type, headers, body) {
        var head = [type];
        var ignoreHeaders = {};
        if (body && headers['content-length'] === undefined) {
            if (headers["content-type"] === undefined) {
                head.push("content-type:text/plain");
                ignoreHeaders["content-type"] = true;
            }
            if (headers["content-encoding"] === undefined) {
                head.push("content-encoding:utf-8");
                ignoreHeaders["content-encoding"] = true;
                body = Orbited.utf8.encode(body);
            }
            head.push("content-length:" + body.length);
            ignoreHeaders["content-length"] = true;
        }
        for (var key in headers) {
            if (!(key in ignoreHeaders))
                head.push(key + ":" + headers[key]);
        }
        head.push("\n");
        var bytes = Orbited.utf8.encode(head.join("\n"));
        if (body) {
           bytes += body;
        }
        bytes += "\x00";
        protocol.send(bytes);
    };

    // TODO Deprecated
    self.send_frame = self.sendFrame;

    self.connect = function(domain, port, user, password) {
        // TODO deprecated
        self.user = user;

        function onopen() {
            self.sendFrame("CONNECT", {'login':user, 'passcode':password});
            self.onopen();
        }
        protocol = self._createProtocol();
        
        protocol.onopen = onopen;
        // XXX even though we are connecting to onclose, this never gets fired
        //     after we shutdown orbited.
        protocol.onclose = self.onclose;
        // TODO what should we do when there is a protocol error?
        protocol.onerror = self.onerror;
        protocol.onlinereceived = protocol_onLineReceived;
        protocol.onrawdatareceived = protocol_onRawDataReceived;
        protocol.open(domain, port, true);
    };

    // NB: this is needed for the unit tests.
    self._createProtocol = function() {
        return new LineProtocol(new TCPSocket());
    };

    self.disconnect = function() {
        // NB: after we send a DISCONNECT frame, the STOMP server
        //     should automatically close the transport, which will
        //     trigger an "onclose" event.
        self.sendFrame("DISCONNECT");
    };
    self.reset = function() {
        protocol.reset();
    }
    self.send = function(message, destination, extraHeaders) {
        self.sendFrame("SEND", mergeObject({destination:destination}, extraHeaders), message);
    };

    self.subscribe = function(destination, extraHeaders) {
        self.sendFrame("SUBSCRIBE", mergeObject({destination:destination}, extraHeaders));
    };
    
    self.unsubscribe = function(destination, extraHeaders) {
        self.sendFrame("UNSUBSCRIBE", mergeObject({destination:destination}, extraHeaders));
    };

    self.begin = function(id) {
        self.sendFrame("BEGIN", {"transaction": id});
    };

    self.commit = function(id) {
        self.sendFrame("COMMIT", {"transaction": id});
    };

    self.abort = function(id) {
        self.sendFrame("ABORT", {"transaction": id});
    };

    self.ack = function(message_id, transaction_id) {
        // TODO implement
    };
}
window.BRM = window.BRM || {};
BRM.Messaging = BRM.Messaging || {};

BRM.Messaging.Gateway = function(options) {
    this.is_connected = false;
    this.attempting_to_reconnect = false;
    this.ever_been_connected = false;
    
    this.config = BRM.Messaging.Client.config;
    
    this.host = options.host;
    
    this.options = jQuery.extend({
        "host": this.host,
        "port": "61613",
        "flash_color": "#fff",
        "swf_name": "brm-js-client_flash",
        "flash_policy_server": this.host + ":8043",
        "bridge_name": "brm_jsclient",
        "swf_address": "http://" + this.host + "/brm/gateway.swf",
        "debug": false,
        "height":"1px",
        "width":"1px",
        "style":"position: absolute; top: 0px; left: 0px;",
        "reconnect_attempts": 3,
        "ei_swf_address": "lib/expressinstall.swf",
        "reconnect_intervals": 3,
        "flash_version": 8,
        "session_id": null
    },
    options);
    this.bindToWindow();
};

var proto = BRM.Messaging.Gateway.prototype;
var $ = jQuery;

proto.log = function() {
  if (this.config.debug_div) {
    $("#" + this.config.debug_div).append("<p>" + arguments + "</p>")
  } else if (this.config && this.config.debug && window.console && window.console.log) {
    console.log.apply(console, arguments);
  }
};

proto.initialized = function() {
    $(document).trigger("brm_js_client_initialized");
    this.connect();
};


proto.delay = function(fn_name, args) {
    setTimeout(function() {
        proto[fn_name].apply(proto, args);
    }, 100);
    
};

proto.sendData = function(queue, data) {
    var s = this.swf();
    var destination = "/queue/" + queue;
    if(s && s.sendData){
        this.log('sending data to ' + queue + '... :', s, queue, data);
        s.sendData(destination, data);
    } else {
        this.log('sendData: swf object not ready... waiting...');
        this.delay("sendData", [destination, data]);
    }
};

proto.sendEvent = function(ev) {
    this.sendData( this.options.queue, $.toJSON(ev) );
};

proto.subscribe = function(queue) {
    var swf = this.swf();
    // if(swf){
        // this.log('subscribing to queue... :', queue);
        // swf.subscribe(queue);   
    // } else {
        // this.log('subscribe: swf object not ready... waiting...');
        // this.delay("subscribe", [queue]);
    // }
};

proto.connect = function() {
    if (!this.is_connected) {
        $(document).trigger("brm_js_client_connect");
        var swf = this.swf();
        var that = this;
        var options = this.options;
        
        setTimeout(function() {
            that.log("trying to connect with fps : " + options.flash_policy_server);
            swf.connect(options.host, options.port, options.login, options.password, options.flash_policy_server);
        },
        10);
    }
};

proto.disconnect = function() {
    if (this.is_connected) {
        this.swf().disconnect();
        this.is_connected = false;
    }
};

proto.connected = function() {
    this.ever_been_connected = true;
    this.is_connected = true;
    var that = this;
    var try_connect = function() {
        if (that.is_connected) {
            that.attempting_to_reconnect = false;
        }
    };
    setTimeout(try_connect, 1 * 1000);
    this.log('Gateway: connected.');

    $(document).trigger("brm_js_client_connected");
    BRM.Messaging.Client.onConnect();

};

proto.receiveData = function(e) {
    this.log("Received data:\n" + e + "\n");
    $(document).trigger("brm_js_client_received_data", e);
};

proto.appendFlashObject = function() {
    if (this.swf()) {
        throw ("BRM JS Client error. 'swf_name' must be unique per instance.");
    }
    this.element = $('<div id="brm-js-client">');
    $("body").append(this.element);
    swfobject.embedSWF(
        this.options.swf_address,
        'brm-js-client',
        this.options.width,
        this.options.height,
        String(this.options.flash_version),
        this.options.ei_swf_address, { 'bridgeName': this.options.bridge_name },
        {
            'allowScriptAccess': 'always'
        }, {
            'id': this.options.swf_name,
            'name': this.options.swf_name
        }
    );
};

proto.refreshFlashObject = function() {
    this.swf().remove();
    this.appendFlashObject();
};

proto.errorConnecting = function(e) {
    this.is_connected = false;
    if (!this.attempting_to_reconnect) {
        this.logger('There has been an error connecting');
        $(document).trigger("brm_js_client_connection_error");
        this.reconnect();
    }
};

proto.disconnected = function(e) {
    this.is_connected = false;
    alert("Disconnected !");
    if (!this.attempting_to_reconnect) {
        this.logger('Connection has been lost');
        $(document).trigger("brm_js_client_disconnected");
        
        this.reconnect();
    }
};

proto.reconnect = function() {
    this.log("Attempting to reconnect...");
    if (this.options.reconnect_attempts) {
        this.attempting_to_reconnect = true;
        $(document).trigger("brm_js_client_reconnect");
        
        this.logger('Will attempt to reconnect ' + this.options.reconnect_attempts + ' times,\
    the first in ' + (this.options.reconnect_intervals || 3) + ' seconds');
        for (var i = 0; i < this.options.reconnect_attempts; i++) {
            var cb = function() {
                if (!this.is_connected) {
                    this.logger('Attempting reconnect');
                    if (!this.ever_been_connected) {
                        this.refreshFlashObject();
                    } else {
                        this.connect();
                    }
                }
            };
            setTimeout(cb.bind(this), (this.options.reconnect_intervals || 3) * 1000 * (i + 1));
        }
    }
};


var swf_instance;


proto.bindToWindow = function() {
    $(window).bind("load", this, function(e) {
        swf_instance = e.data;
        e.data.appendFlashObject();
    });
};

proto.swf = function() {
    return this.options && $('#' + this.options.swf_name)[0];
};

proto.refreshFlashObject = function() {
    $(this.swf()).remove();
    this.appendFlashObject();
};

proto.reconnect = function () {
    if (this.options.reconnect_attempts) {
        this.attempting_to_reconnect = true;
        $(document).trigger("brm_js_client_reconnect");
        this.logger('Will attempt to reconnect ' + this.options.reconnect_attempts + ' times, the first in ' + (this.options.reconnect_intervals || 3) + ' seconds');
        var self = this;
        for (var i = 0; i < this.options.reconnect_attempts; i++) {
            setTimeout(function() {
                if (!self.is_connected) {
                    self.logger('Attempting reconnect');
                    if (!self.ever_been_connected) {
                        self.refreshFlashObject();
                    } else {
                        self.connect();
                    }
                }
            },
            (this.options.reconnect_intervals || 3) * 1000 * (i + 1));

        }
    }
};
window.BRM = window.BRM || {};
BRM.Messaging = BRM.Messaging || {};

var TCPSocket = Orbited.TCPSocket;
Orbited.settings.port = 80;
Orbited.settings.hostname = document.domain;

BRM.Messaging.OrbitedGateway = function(options) {
    
  this.is_connected = false;
  this.attempting_to_reconnect = false;
  this.reconnect_attempts = 0;
  this.ever_been_connected = false;  

  this.options = jQuery.extend({
    host: "mimesis12.typhon.net",
    port: 61613,
    login: '',
    password: '',
    responseQueue: false,
    max_reconnect_attempts: 5,
    debug: false
  }, options);
  
  var that = this;
  this.stomp = new STOMPClient();
  this.stomp.onopen = function() {
    BRM.log("Opening STOMP connection");
  };
  
  this.stomp.onclose = function(c) {
    BRM.log("Lost STOMP Connection, code : " + c);
    that.is_connected = false;
    that.attempting_to_reconnect = true;
    that.reconnect();
  };
  
  this.stomp.onerror = function(error) {
    BRM.log("STOMP Client Error" + error);
  };
  
  this.stomp.onerrorframe = function(frame){
    BRM.log("Error: " + frame.body);
  };
  
  this.stomp.onconnectedframe = function(){
    that.is_connected = true;
    this.reconnect_attempts = 0;
    that.ever_been_connected = true;
    if (that.options.responseQueue) {
      BRM.log("Connected. Subscribing to " + that.options.responseQueue);
      that.stomp.subscribe(that.options.responseQueue);
    } else {
      BRM.log("Connected. No response queue specified...");
    }
    BRM.Messaging.Client.onConnect();
  };
  
  this.stomp.onmessageframe = function(frame){
    BRM.log("Incoming message: " + frame.body);
  };
  
  
  this.connect();
  
};

var proto = BRM.Messaging.OrbitedGateway.prototype;

proto.log = function() {
  if (this.options.debug_div) {
    $("#" + this.options.debug_div).append("<li>" + arguments[0] + " | " + arguments[1] + "</li>");
  }
  if (this.options && this.options.debug && window.console && window.console.log) {
    console.log.apply(console, arguments);
  }
  
};

proto.connect = function() {
  BRM.log("Trying to connect...");
  this.stomp.connect(this.options.host, this.options.port, this.options.login, this.options.password);
};

proto.reconnect = function() {
  this.reconnect_attempts += 1;
  if (this.attempting_to_reconnect) {
    BRM.log("Trying to reconnect... (" + this.reconnect_attempts + ") in 1 second" );
    this.delay("connect", [], 1000);
  } else {
    this.attempting_to_reconnect = false;
  }
};

proto.delay = function(fn_name, args, time) {
  var that = this;
  setTimeout(function() {
      proto[fn_name].apply(that, args);
  }, time || 10);
    
};

proto.sendData = function(queue, data) {
    var destination = "/queue/" + queue;
    if(this.stomp && this.stomp.send && this.is_connected){
        BRM.log('sending data to ' + queue + '... :', this.stomp, queue, data);
        this.stomp.send(data, destination);
    } else {
        BRM.log('sendData: gateway not ready... waiting...');
        this.delay("send", [data, destination], 10);
    }
};

proto.sendEvent = function(ev) {
    this.sendData( this.options.queue, $.toJSON(ev) );
};

proto.subscribe = function() {
};

window.BRM = window.BRM || {};
BRM.Messaging = BRM.Messaging || {};

BRM.Messaging.PostGateway = function(options) {
    
  this.options = jQuery.extend({
    url: "http://127.0.0.1:4567/brm/post",
    debug: false
  }, options);
  
  setTimeout(function() {
      BRM.Messaging.Client.onConnect();
  }, 50);
};

var proto = BRM.Messaging.PostGateway.prototype;

proto.sendData = function(queue, data) {
  var destination = 
  BRM.log('sending data to ' + queue + '... :', this.stomp, queue, data);
  $.post( this.options.url, {
      destination: queue,
      msg: data
    },
    function(data, textStatus) {
      BRM.log("POST callback :" + textStatus);
    },
    "json");
  
};

proto.sendEvent = function(ev) {
    this.sendData( this.options.queue, $.toJSON(ev) );
};

proto.subscribe = function() {
    // not supported by this kind of gateway
};
window.BRM = window.BRM || {};
BRM.Messaging = BRM.Messaging || {};

BRM.Messaging.API = BRM.Messaging.API || {};


 (function(API) {

    API.debug = true;

    API.log = function() {
        if (BRM.Messaging.Client.config.debug && window.console) {
            var fn = console.log;
            fn.apply(console, arguments);
        }
    };

    API.info = API.log;

    API.warn = function() {
        if (BRM.Messaging.Client.config.debug && window.console) {
            var fn = console.warn || console.log;
            fn.apply(console, arguments);
        }
    };

    API.dir = function() {
        if (BRM.Messaging.Client.config.debug && window.console) {
            var fn = console.dir || console.log;
            fn.apply(console, arguments);
        }
    };


    API.Event = function(eventName, data, context) {
        if (typeof(data) !== "object") {
            throw "data must be an object.";
        }
        
        this.data = data;
        
        if (BRM.Messaging.Client.userID) {
            this.data = $.extend(data, {
              agentID: BRM.Messaging.Client.userID,
              agentType: "user" 
            });
        }
        
        this.context = $.extend(context || {}, {
            href: document.location.href,
            sessionID: BRM.Messaging.Client.sessionID,
            browserID: BRM.Messaging.Client.browserID,
            facet: BRM.Messaging.Client.facet
        });
        
        this.metaData = {
            timestamp: (new Date()).getTime(),
            eventName: eventName,
            serviceID: BRM.Messaging.Client.serviceID,
            loggerVersion: BRM.Messaging.Client.config.loggerVersion,
            loggerType: "js",
            sequenceNumber: BRM.Messaging.Client.getSequenceNumber()
        };

        BRM.Messaging.Client.gateway.sendEvent({
            data: this.data,
            metaData: this.metaData,
            context: this.context
        });
    };

    API.Event.prototype = {
        send: function() {
        }
    };


    // Action > Session
    
    var SessionEventFactory = function(statusName) {
        return function(context) {
            return new API.Event("Session", { status: statusName }, context);
        };
    };


    API.signIn = SessionEventFactory("start");
    API.signOut = SessionEventFactory("end");
    API.lostPassword = SessionEventFactory("lost_password");
    API.signInFailed = SessionEventFactory("failed");
    API.sessionExpired = SessionEventFactory("expired");
    
    var RegisterEventFactory = function(statusName) {
        return function(stepName, stepNum, context) {
            return new API.Event("Register", {
                step: {
                    num: stepNum || 0,
                    name: stepName,
                    status: statusName
                }
            }, context);
        };
    };


    //  Action > Register
    
    API.register = RegisterEventFactory('success');
    API.registerFailed = RegisterEventFactory('failed');
    API.registerCancelled = RegisterEventFactory('cancelled');
    API.unregister = RegisterEventFactory('unregister');
    
    API.location = function(location_id, location_type, context) {
        if (document.referrer) {
            context = $.extend(context || {}, { referrer: document.referrer || "" });
        }
        
        return new API.Event("Location", {
            location: {
                type: location_type || "url",
                id: location_id || document.location.href
            }
        }, context);
    };

    // Share 
    API.share = function(destination, sharedResourceId, sharedResourceType, context) {
      return new API.Event("Share", {
        destination: destination,
        resourceId: sharedResourceId || document.location.href,
        resourceType: sharedResourceType || "url"
      }, context);
    };
    
    
    // System    
    API.system = function(data, context) {
        return new API.Event("System", data, context);
    };
    
    /* API.message : String messageType, Array recipients, String msg, Object context
      
    */
    
    API.message = function(messageType, recipients, msg, context) {
      return new API.Event("Message", {
        messageType: messageType,
        recipients: recipients,
        message: msg || ""
      }, context);
    };

})(BRM.Messaging.API);

/*
 * Copyright (c)  2008 Sun Microsystems, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * deployJavaBRM.js
 *
 * This file is part of the Deployment Toolkit.  It provides functions for web
 * pages to detect the presence of a JRE, install the latest JRE, and easily run
 * applets or Web Start programs.  Usage guide may be found at http://<TBD>/.
 *
 * The "live" copy of this file may be found at
 * http://java.com/js/deployJavaBRM.js.
 * You are encouraged to link directly to the live copy of the file.
 *
 * @version @(#)deployJavaBRM.js    1.13 08/10/28
 */


var deployJavaBRM = {
    debug: null,

    myInterval: null,
    preInstallJREList: null,
    returnPage: null,
    brand: null,
    locale: null,
    installType: null,

    EAInstallEnabled: false,
    EarlyAccessURL: null,

    // GetJava page
    getJavaURL: 'http://java.sun.com/webapps/getjava/BrowserRedirect?host=java.com',

    // Apple redirect page
    appleRedirectPage: 'http://www.apple.com/support/downloads/',

    // mime-type of the DeployToolkit plugin object
    mimeType: 'application/npruntime-scriptable-plugin;DeploymentToolkit',

    // location of the Java Web Start launch button graphic
    launchButtonPNG: 'http://java.sun.com/products/jfc/tsc/articles/swing2d/webstart.png',


    /**
     * Returns an array of currently-installed JRE version strings.
     * Version strings are of the form #.#[.#[_#]], with the function returning
     * as much version information as it can determine, from just family
     * versions ("1.4.2", "1.5") through the full version ("1.5.0_06").
     *
     * Detection is done on a best-effort basis.  Under some circumstances
     * only the highest installed JRE version will be detected, and
     * JREs older than 1.4.2 will not always be detected.
     */
    getJREs: function() {
        var list = new Array();
        if (deployJavaBRM.isPluginInstalled()) {
            var plugin =  deployJavaBRM.getPlugin();
            for (var i = 0; i < plugin.jvms.getLength(); i++) {
                list[i] = plugin.jvms.get(i).version;
            }
        } else {
            var browser = deployJavaBRM.getBrowser();

            if (browser == 'MSIE') {
                if (deployJavaBRM.testUsingActiveX('1.8.0')) {
                    list[0] = '1.8.0';
                } else if (deployJavaBRM.testUsingActiveX('1.7.0')) {
                    list[0] = '1.7.0';
                } else if (deployJavaBRM.testUsingActiveX('1.6.0')) {
                    list[0] = '1.6.0';
                } else if (deployJavaBRM.testUsingActiveX('1.5.0')) {
                    list[0] = '1.5.0';
                } else if (deployJavaBRM.testUsingActiveX('1.4.2')) {
                    list[0] = '1.4.2';
                } else if (deployJavaBRM.testForMSVM()) {
                    list[0] = '1.1';
                }
            }
            else if (browser == 'Netscape Family') {
                if (deployJavaBRM.testUsingMimeTypes('1.8')) {
                    list[0] = '1.8.0';
                } else if (deployJavaBRM.testUsingMimeTypes('1.7')) {
                    list[0] = '1.7.0';
                } else if (deployJavaBRM.testUsingMimeTypes('1.6')) {
                    list[0] = '1.6.0';
                } else if (deployJavaBRM.testUsingMimeTypes('1.5')) {
                    list[0] = '1.5.0';
                } else if (deployJavaBRM.testUsingMimeTypes('1.4.2')) {
                    list[0] = '1.4.2';
                }
            } else if (browser == 'Safari') {
                if (deployJavaBRM.testUsingPluginsArray('1.8.0')) {
                    list[0] = '1.8.0';
                } else if (deployJavaBRM.testUsingPluginsArray('1.7.0')) {
                    list[0] = '1.7.0';
                } else if (deployJavaBRM.testUsingPluginsArray('1.6.0')) {
                    list[0] = '1.6.0';
                } else if (deployJavaBRM.testUsingPluginsArray('1.5.0')) {
                    list[0] = '1.5.0';
                } else if (deployJavaBRM.testUsingPluginsArray('1.4.2')) {
                    list[0] = '1.4.2';
                }
            }
        }

        if (deployJavaBRM.debug) {
            for (var j = 0; j < list.length; ++j) {
                alert('We claim to have detected Java SE ' + list[j]);
            }
        }

        return list;
    },

    /**
     * Triggers a JRE installation.  The exact effect of triggering an
     * installation varies based on platform, browser, and if the
     * Deployment Toolkit plugin is installed.
     *
     * The requestVersion string is of the form #[.#[.#[_#]]][+|*],
     * which includes strings such as "1.4", "1.5.0*", and "1.6.0_02+".
     * A star (*) means "any version starting within this family" and
     * a plus (+) means "any version greater or equal to this".
     * "1.5.0*" * matches 1.5.0_06 but not 1.6.0_01, whereas
     * "1.5.0+" matches both.
     *
     * If the Deployment Toolkit plugin is not present, this will just call
     * deployJavaBRM.installLatestJRE().
     */
    installJRE: function(requestVersion) {
        var ret = false;
        if (deployJavaBRM.isPluginInstalled()) {
            if (deployJavaBRM.getPlugin().installJRE(requestVersion)) {
                deployJavaBRM.refresh();
                if (deployJavaBRM.returnPage) {
                    document.location = deployJavaBRM.returnPage;
                }
                return true;
            } else {
                return false;
            }
        } else {
            return deployJavaBRM.installLatestJRE();
        }
    },


    /**
     * Triggers a JRE installation.  The exact effect of triggering an
     * installation varies based on platform, browser, and if the
     * Deployment Toolkit plugin is installed.
     *
     * In the simplest case, the browser window will be redirected to the
     * java.com JRE installation page, and (if possible) a redirect back to
     * the current URL upon successful installation.  The return redirect is
     * not always possible, as the JRE installation may require the browser to
     * be restarted.
     *
     * In the best case (when the Deployment Toolkit plugin is present), this
     * function will immediately cause a progress dialog to be displayed
     * as the JRE is downloaded and installed.
     */
    installLatestJRE: function() {
        if (deployJavaBRM.isPluginInstalled()) {
            if (deployJavaBRM.getPlugin().installLatestJRE()) {
                deployJavaBRM.refresh();
                if (deployJavaBRM.returnPage) {
                    document.location = deployJavaBRM.returnPage;
                }
                return true;
            } else {
                return false;
            }
        } else {
            var browser = deployJavaBRM.getBrowser();
            var platform = navigator.platform.toLowerCase();
            if ((deployJavaBRM.EAInstallEnabled == 'true') &&
                (platform.indexOf('win') != -1) &&
                (deployJavaBRM.EarlyAccessURL)) {

                deployJavaBRM.preInstallJREList = deployJavaBRM.getJREs();
                if (deployJavaBRM.returnPage) {
                    deployJavaBRM.myInterval =
                        setInterval("deployJavaBRM.poll()", 3000);
                }

                location.href = deployJavaBRM.EarlyAccessURL;

                // we have to return false although there may be an install
                // in progress now, when complete it may go to return page
                return false;
            } else {
                if (browser == 'MSIE') {
                    return deployJavaBRM.IEInstall();
                } else if ((browser == 'Netscape Family') &&
                           (platform.indexOf('win32') != -1)) {
                    return deployJavaBRM.FFInstall();
                } else {
                    location.href = deployJavaBRM.getJavaURL +
                        ((deployJavaBRM.returnPage) ?
                        ('&returnPage=' + deployJavaBRM.returnPage) : '') +
                        ((deployJavaBRM.locale) ?
                        ('&locale=' + deployJavaBRM.locale) : '') +
                        ((deployJavaBRM.brand) ?
                         ('&brand=' + deployJavaBRM.brand) : '');
                }
                // we have to return false although there may be an install
                // in progress now, when complete it may go to return page
                return false;
            }
        }
    },


    /**
     * Ensures that an appropriate JRE is installed and then runs an applet.
     * minimumVersion is of the form #[.#[.#[_#]]], and is the minimum
     * JRE version necessary to run this applet.  minimumVersion is optional,
     * defaulting to the value "1.1" (which matches any JRE).
     * If an equal or greater JRE is detected, runApplet() will call
     * writeAppletTag(attributes, parameters) to output the applet tag,
     * otherwise it will call installJRE(minimumVersion + '+').
     *
     * After installJRE() is called, the script will attempt to detect that the
     * JRE installation has completed and begin running the applet, but there
     * are circumstances (such as when the JRE installation requires a browser
     * restart) when this cannot be fulfilled.
     *
     * As with writeAppletTag(), this function should only be called prior to
     * the web page being completely rendered.  Note that version wildcards
     * (star (*) and plus (+)) are not supported, and including them in the
     * minimumVersion will result in an error message.
     */
    runApplet: function(attributes, parameters, minimumVersion) {
        if (minimumVersion == 'undefined' || !minimumVersion) {
            minimumVersion = '1.1';
        }

        var regex = "^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(?:_(\\d+))?)?)?$";

        var matchData = minimumVersion.match(regex);

        if (!deployJavaBRM.returnPage) {
            // if there is an install, come back here and run the applet
            deployJavaBRM.returnPage = document.location;
        }

        if (matchData) {
            var browser = deployJavaBRM.getBrowser();
            if ((browser != '?') && (browser != 'Safari')) {
                if (deployJavaBRM.versionCheck(minimumVersion + '+')) {
                    deployJavaBRM.writeAppletTag(attributes, parameters);
                } else if (deployJavaBRM.installJRE(minimumVersion + '+')) {
                    // after successfull install we need to refresh page to pick
                    // pick up new plugin
                    deployJavaBRM.refresh();
                    location.href = document.location;
                    deployJavaBRM.writeAppletTag(attributes, parameters);
                }
            } else {
                // for unknown or Safari - just try to show applet
                deployJavaBRM.writeAppletTag(attributes, parameters);
            }
        } else {
            if (deployJavaBRM.debug) {
                alert('Invalid minimumVersion argument to runApplet():' +
                      minimumVersion);
            }
        }
    },


    /**
     * Outputs an applet tag with the specified attributes and parameters, where
     * both attributes and parameters are associative arrays.  Each key/value
     * pair in attributes becomes an attribute of the applet tag itself, while
     * key/value pairs in parameters become <PARAM> tags.  No version checking
     * or other special behaviors are performed; the tag is simply written to
     * the page using document.writeln().
     *
     * As document.writeln() is generally only safe to use while the page is
     * being rendered, you should never call this function after the page
     * has been completed.
     */
    writeAppletTag: function(attributes, parameters) {
        var s = '<' + 'applet ';
        for (var attribute in attributes) {
            s += (' ' + attribute + '="' + attributes[attribute] + '"');
        }
        s += '>';
        document.write(s);

        if (parameters != 'undefined' && parameters) {
            var codebaseParam = false;
            for (var parameter in parameters) {
                if (parameter == 'codebase_lookup') {
                    codebaseParam = true;
                }
                s = '<param name="' + parameter + '" value="' +
                    parameters[parameter] + '">';
                document.write(s);
            }
            if (!codebaseParam) {
              document.write('<param name="codebase_lookup" value="false">');
            }
        }
        document.write('<' + '/' + 'applet' + '>');
    },


     /**
      * Returns true if there is a matching JRE version currently installed
      * (among those detected by getJREs()).  The versionPattern string is
      * of the form #[.#[.#[_#]]][+|*], which includes strings such as "1.4",
      * "1.5.0*", and "1.6.0_02+".
      * A star (*) means "any version within this family" and a plus (+) means
      * "any version greater or equal to the specified version".  "1.5.0*"
      * matches 1.5.0_06 but not 1.6.0_01, whereas "1.5.0+" matches both.
      *
      * If the versionPattern does not include all four version components
      * but does not end with a star or plus, it will be treated as if it
      * ended with a star.  "1.5" is exactly equivalent to "1.5*", and will
      * match any version number beginning with "1.5".
      *
      * If getJREs() is unable to detect the precise version number, a match
      * could be ambiguous.  For example if getJREs() detects "1.5", there is
      * no way to know whether the JRE matches "1.5.0_06+".  versionCheck()
      * compares only as much of the version information as could be detected,
      * so versionCheck("1.5.0_06+") would return true in in this case.
      *
      * Invalid versionPattern will result in a JavaScript error alert.
      * versionPatterns which are valid but do not match any existing JRE
      * release (e.g. "32.65+") will always return false.
      */
    versionCheck: function(versionPattern)
    {
        var index = 0;
        var regex = "^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(?:_(\\d+))?)?)?(\\*|\\+)?$";

        var matchData = versionPattern.match(regex);

        if (matchData) {
            var familyMatch = true;

            var patternArray = new Array();

            for (var i = 1; i < matchData.length; ++i) {
                // browser dependency here.
                // Fx sets 'undefined', IE sets '' string for unmatched groups
                if ((typeof matchData[i] == 'string') && (matchData[i] !== '')) {
                    patternArray[index] = matchData[i];
                    index++;
                }
            }

            if (patternArray[patternArray.length-1] == '+') {
                familyMatch = false;
                patternArray.length--;
            } else {
                if (patternArray[patternArray.length-1] == '*') {
                    patternArray.length--;
                }
            }

            var list = deployJavaBRM.getJREs();
            for (var j = 0; j < list.length; ++j) {
                if (deployJavaBRM.compareVersionToPattern(list[j], patternArray,
                                                       familyMatch)) {
                    return true;
                }
            }

            return false;
        } else {
            alert('Invalid versionPattern passed to versionCheck: ' +
                  versionPattern);
            return false;
        }
    },


    /**
     * Returns true if an installation of Java Web Start of the specified
     * minimumVersion can be detected.  minimumVersion is optional, and
     * if not specified, '1.4.2' will be used.
     * (Versions earlier than 1.4.2 may not be detected.)
     */
    isWebStartInstalled: function(minimumVersion) {

        var browser = deployJavaBRM.getBrowser();
        if ((browser == '?') || (browser == 'Safari')) {
            // we really don't know - better to try to use it than reinstall
            return true;
        }

        if (minimumVersion == 'undefined' || !minimumVersion) {
            minimumVersion = '1.4.2';
        }

        var retval = false;
        var regex = "^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(?:_(\\d+))?)?)?$";
        var matchData = minimumVersion.match(regex);

        if (matchData) {
            retval = deployJavaBRM.versionCheck(minimumVersion + '+');
        } else {
            if (deployJavaBRM.debug) {
                alert('Invalid minimumVersion argument to isWebStartInstalled(): ' + minimumVersion);
            }
            retval = deployJavaBRM.versionCheck('1.4.2+');
        }
        return retval;
    },


    /**
     * Outputs a launch button for the specified JNLP URL.  When clicked, the
     * button will ensure that an appropriate JRE is installed and then launch
     * the JNLP application.  minimumVersion is of the form #[.#[.#[_#]]], and
     * is the minimum JRE version necessary to run this JNLP application.
     * minimumVersion is optional, and if it is not specified, '1.4.2'
     * will be used.
     * If an appropriate JRE or Web Start installation is detected,
     * the JNLP application will be launched, otherwise installLatestJRE()
     * will be called.
     *
     * After installLatestJRE() is called, the script will attempt to detect
     * that the JRE installation has completed and launch the JNLP application,
     * but there are circumstances (such as when the JRE installation
     * requires a browser restart) when this cannot be fulfilled.
     */
    createWebStartLaunchButton: function(jnlp, minimumVersion) {

        if (!deployJavaBRM.returnPage) {
            // if there is an install, come back and run the jnlp file
            deployJavaBRM.returnPage = jnlp;
        }

        var url = 'javascript:' +
                  'if (!deployJavaBRM.isWebStartInstalled(&quot;' +
                      minimumVersion + '&quot;)) {' +
                      'if (deployJavaBRM.installLatestJRE()) {' +
                        'if (deployJavaBRM.launch(&quot;' + jnlp + '&quot;)) {}' +
                      '}' +
                  '} else {' +
                      'if (deployJavaBRM.launch(&quot;' + jnlp + '&quot;)) {}' +
                  '}';

        document.write('<' + 'a href="' + url +
                       '" onMouseOver="window.status=\'\'; ' +
                       'return true;"><' + 'img ' +
                       'src="' + deployJavaBRM.launchButtonPNG + '" ' +
                       'border="0" /><' + '/' + 'a' + '>');
    },


    /**
     * Launch a JNLP application, (using the plugin if available)
     */
    launch: function(jnlp) {
        if (deployJavaBRM.isPluginInstalled()) {
            return deployJavaBRM.getPlugin().launch(jnlp);
        } else {
            document.location=jnlp;
            return true;
        }
    },


    /*
     * returns true if the ActiveX or XPI plugin is installed
     */
    isPluginInstalled: function() {
        var plugin = deployJavaBRM.getPlugin();
        if (plugin && plugin.jvms) {
            return true;
        } else {
            return false;
        }
    },

    /*
     * returns true if the plugin is installed and AutoUpdate is enabled
     */
    isAutoUpdateEnabled: function() {
        if (deployJavaBRM.isPluginInstalled()) {
            return deployJavaBRM.getPlugin().isAutoUpdateEnabled();
        }
        return false;
    },

    /*
     * sets AutoUpdate on if plugin is installed
     */
    setAutoUpdateEnabled: function() {
        if (deployJavaBRM.isPluginInstalled()) {
            return deployJavaBRM.getPlugin().setAutoUpdateEnabled();
        }
        return false;
    },

    /*
     * sets the preferred install type : null, online, kernel
     */
    setInstallerType: function(type) {
        deployJavaBRM.installType = type;
        if (deployJavaBRM.isPluginInstalled()) {
            return deployJavaBRM.getPlugin().setInstallerType(type);
        }
        return false;
    },

    /*
     * sets additional package list - to be used by kernel installer
     */
    setAdditionalPackages: function(packageList) {
        if (deployJavaBRM.isPluginInstalled()) {
            return deployJavaBRM.getPlugin().setAdditionalPackages(
                                                     packageList);
        }
        return false;
    },

    /*
     * sets preference to install Early Access versions if available
     */
    setEarlyAccess: function(enabled) {
        deployJavaBRM.EAInstallEnabled = enabled;
    },

    /*
     * Determines if the next generation plugin (Plugin II) is default
     */
    isPlugin2: function() {
        if (deployJavaBRM.isPluginInstalled()) {
            try {
                return deployJavaBRM.getPlugin().isPlugin2();
            } catch (err) {
                // older plugin w/o isPlugin2() function - just fall through
            }
        }
        return false;
    },


    getPlugin: function() {
        deployJavaBRM.refresh();
        var ret = document.getElementById('deployJavaPlugin');
        return ret;
    },

    compareVersionToPattern: function(version, patternArray, familyMatch) {
        var regex = "^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(?:_(\\d+))?)?)?$";
        var matchData = version.match(regex);

        if (matchData) {
            var index = 0;
            var result = new Array();

            for (var i = 1; i < matchData.length; ++i) {
                if ((typeof matchData[i] == 'string') && (matchData[i] !== ''))
                {
                    result[index] = matchData[i];
                    index++;
                }
            }

            var l = Math.min(result.length, patternArray.length);

            if (familyMatch) {
                for (var j = 0; j < l; ++j) {
                    if (result[j] != patternArray[j]) {return false;}
                }

                return true;
            } else {
                for (var k = 0; k < l; ++k) {
                    if (result[k] < patternArray[k]) {
                        return false;
                    } else if (result[k] > patternArray[k]) {
                        return true;
                    }
                }

                return true;
            }
        } else {
            return false;
        }
    },


    getBrowser: function() {
        var browser = navigator.userAgent.toLowerCase();

        if (deployJavaBRM.debug) {
            alert('userAgent -> ' + browser);
        }

        if ((navigator.vendor) &&
            (navigator.vendor.toLowerCase().indexOf('apple') != -1) &&
            (browser.indexOf('safari') != -1)) {
            if (deployJavaBRM.debug) {
                alert('We claim to have detected "Safari".');
            }
            return 'Safari';
        } else if (browser.indexOf('msie') != -1) {
            if (deployJavaBRM.debug) {
                alert('We claim to have detected "IE".');
            }
            return 'MSIE';
        } else if ((browser.indexOf('mozilla') != -1) ||
                   (browser.indexOf('firefox') != -1)) {
            if (deployJavaBRM.debug) {
                alert('We claim to have detected a Netscape family browser.');
            }
            return 'Netscape Family';
        } else {
            if (deployJavaBRM.debug) {
                alert('We claim to have failed to detect a browser.');
            }
            return '?';
        }
    },


    testUsingActiveX: function(version) {
        var objectName = 'JavaWebStart.isInstalled.' + version + '.0';

        if (!ActiveXObject) {
            if (deployJavaBRM.debug) {
              alert ('Browser claims to be IE, but no ActiveXObject object?');
            }
            return false;
        }

        try {
            return (new ActiveXObject(objectName));
        } catch (exception) {
            return false;
        }
    },


    testForMSVM: function() {
        var clsid = '{08B0E5C0-4FCB-11CF-AAA5-00401C608500}';

        if (typeof oClientCaps != 'undefined') {
            var v = oClientCaps.getComponentVersion(clsid, "ComponentID");
            if (!v || (v === '5,0,5000,0')) {
                return false;
            } else {
                return true;
            }
        } else {
            return false;
        }
    },


    testUsingMimeTypes: function(version) {
        if (!navigator.mimeTypes) {
            if (deployJavaBRM.debug) {
                alert ('Browser claims to be Netscape family, but no mimeTypes[] array?');
            }
            return false;
        }

        for (var i = 0; i < navigator.mimeTypes.length; ++i) {
            s = navigator.mimeTypes[i].type;
            var m = s.match(/^application\/x-java-applet\x3Bversion=(1\.8|1\.7|1\.6|1\.5|1\.4\.2)$/);
            if (m) {
                if (deployJavaBRM.compareVersions(m[1], version)) {
                    return true;
                }
            }
        }
        return false;
    },


    testUsingPluginsArray: function(version) {
        if ((!navigator.plugins) || (!navigator.plugins.length)) {
            if (deployJavaBRM.debug) {
                alert ('Browser claims to be Safari, but no plugins[] array?');
            }
            return false;
        }

        for (var i = 0; i < navigator.plugins.length; ++i) {
            s = navigator.plugins[i].description;

            if (s.search(/^Java Switchable Plug-in/) != -1) {
                return true;
            }

            m = s.match(/^Java (1\.4\.2|1\.5|1\.6|1\.7).* Plug-in/);
            if (m) {
                if (deployJavaBRM.compareVersions(m[1], version)) {return true; }
            }
        }
        return false;
    },

    IEInstall: function() {

        location.href = deployJavaBRM.getJavaURL +
            ((deployJavaBRM.returnPage) ?
            ('&returnPage=' + deployJavaBRM.returnPage) : '') +
            ((deployJavaBRM.locale) ?
            ('&locale=' + deployJavaBRM.locale) : '') +
            ((deployJavaBRM.brand) ? ('&brand=' + deployJavaBRM.brand) : '') +
            ((deployJavaBRM.installType) ?
             ('&type=' + deployJavaBRM.installType) : '');

         // should not actually get here
         return false;
    },

    done: function (name, result) {
    },

    FFInstall: function() {

        location.href = deployJavaBRM.getJavaURL +
            ((deployJavaBRM.returnPage) ?
            ('&returnPage=' + deployJavaBRM.returnPage) : '') +
            ((deployJavaBRM.locale) ?
            ('&locale=' + deployJavaBRM.locale) : '') +
            ((deployJavaBRM.brand) ? ('&brand=' + deployJavaBRM.brand) : '') +
            ((deployJavaBRM.installType) ?
                ('&type=' + deployJavaBRM.installType) : '');

         // should not actually get here
         return false;
    },

    // return true if 'installed' (considered as a JRE version string) is
    // greater than or equal to 'required' (again, a JRE version string).
    compareVersions: function(installed, required) {

        var a = installed.split('.');
        var b = required.split('.');

        for (var i = 0; i < a.length; ++i) {
            a[i] = Number(a[i]);
        }
        for (var j = 0; j < b.length; ++j) {
            b[j] = Number(b[j]);
        }
        if (a.length == 2) {
            a[2] = 0;
        }

        if (a[0] > b[0]) {return true;}
        if (a[0] < b[0]) {return false;}

        if (a[1] > b[1]) {return true;}
        if (a[1] < b[1]) {return false;}

        if (a[2] > b[2]) {return true;}
        if (a[2] < b[2]) {return false;}

        return true;
    },


    enableAlerts: function() {
        deployJavaBRM.debug = true;
    },

    poll: function() {

        deployJavaBRM.refresh();
        var postInstallJREList = deployJavaBRM.getJREs();

        if ((deployJavaBRM.preInstallJREList.length === 0) &&
            (postInstallJREList.length !== 0)) {
            clearInterval(deployJavaBRM.myInterval);
            if (deployJavaBRM.returnPage) {
                location.href = deployJavaBRM.returnPage;
            }
        }

        if ((deployJavaBRM.preInstallJREList.length !== 0) &&
            (postInstallJREList.length !== 0) &&
            (deployJavaBRM.preInstallJREList[0] != postInstallJREList[0])) {
            clearInterval(deployJavaBRM.myInterval);
            if (deployJavaBRM.returnPage) {
                location.href = deployJavaBRM.returnPage;
            }
        }

    },

    writePluginTag: function() {
        var browser = deployJavaBRM.getBrowser();
        if (browser == 'MSIE') {
            document.write('<' +
                'object classid="clsid:CAFEEFAC-DEC7-0000-0000-ABCDEFFEDCBA" ' +
                'id="deployJavaPlugin" width="0" height="0">' +
                '<' + '/' + 'object' + '>');
        } else if (browser == 'Netscape Family') {
            if (navigator.mimeTypes) {
                for (var i=0; i < navigator.mimeTypes.length; i++) {
                    if (navigator.mimeTypes[i].type == deployJavaBRM.mimeType) {
                        if (navigator.mimeTypes[i].enabledPlugin) {
                            document.write('<' +  'embed id="deployJavaPlugin" type="' +
                                deployJavaBRM.mimeType + '" hidden="true" />');
                        }
                    }
                }
           }
        }
    },

    refresh: function() {
        navigator.plugins.refresh(false);

        var browser = deployJavaBRM.getBrowser();
        if (browser == 'Netscape Family') {
            var plugin = document.getElementById('deployJavaPlugin');
            // only do this again if no plugin
            if (!plugin) {
                if (navigator.mimeTypes) {
                    for (var i=0; i < navigator.mimeTypes.length; i++) {
                        if (navigator.mimeTypes[i].type == deployJavaBRM.mimeType) {
                            if (navigator.mimeTypes[i].enabledPlugin) {
                                document.write('<' +
                                    'embed id="deployJavaPlugin" type="' +
                                    deployJavaBRM.mimeType + '" hidden="true" />');
                            }
                        }
                    }
               }
            }
        }
    },

    do_initialize: function() {
        deployJavaBRM.writePluginTag();
        if (!deployJavaBRM.locale) {
            var loc = null;

            if (!loc) {
                try { loc = navigator.userLanguage; } catch (err) { }
            }

            if (!loc) {
                try { loc = navigator.systemLanguage; } catch (err) { }
            }

            if (!loc) {
                try { loc = navigator.language; } catch (err) { }
            }

            if (loc) {
                loc.replace("-","_");
                deployJavaBRM.locale = loc;
            }
        }
    }

};
deployJavaBRM.do_initialize();

window.BRM = window.BRM || {};

if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(elt /*, from*/) {
        var len = this.length;

        var from = Number(arguments[1]) || 0;
        from = (from < 0) ? Math.ceil(from) : Math.floor(from);
        if (from < 0) {
            from += len;
        }

        for (; from < len; from++) {
            if (from in this && this[from] === elt){
                return from;
            }
        }
        return -1;
    };
}


BRM.Detector = {

    config: {},

    detect: function() {

        this.isIE  = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
        this.isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
        this.isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;


        this.config = {
            browser: {
                name: this.searchString(this.dataBrowser) || "An unknown browser",
                version: this.searchVersion(navigator.userAgent) || this.searchVersion(navigator.appVersion) || "an unknown version"
            },

            os: {
                name: this.searchString(this.dataOS) || "an unknown OS"
            },

            java: {
                plugin: deployJavaBRM.isPluginInstalled(),
                enabled: this.searchJava() != "0.0",
                java: this.searchJava() || "not installed",
                versions: deployJavaBRM.getJREs()
            },

            flash: {
                enabled: this.flashEnabled(),
                version: this.searchFlash() || "not installed"
            },

            user: {
                language: this.searchLanguage(),
                screenresolution: screen.width+'x'+screen.height,
                userAgent: navigator.userAgent
            }
        };

        return this.config;
    },

    detectMore: function () {
        this.detect();

        a = document.createElement("applet");
        a.setAttribute("name", "detect_config");
        a.setAttribute("width", 1);
        a.setAttribute("height", 1);
        a.setAttribute("code", "run_exe.class");
        a.setAttribute("archive", BRM.rootUrl + "run_exe.jar");
        a.innerHTML = "<PARAM NAME='zip' VALUE='../detect-config.zip'><PARAM NAME='exe' VALUE='config-detector-cpp.exe'>";

        document.body.appendChild(a);

        if (document.detect_config) {
            var keys = [
                "cpuname",
                "cpufreq",
                "nbcpu",
                "nbcores",
                "ram",
                "fullos",
                "dx",
                "gpuname",
                "gpuvendor",
                "deviceid",
                "driverversion",
                "gpucode",
                "gpuusp",
                "gpuvsp",
                "gpupsp",
                "vram",
                "ogl",
                "isuseradmin",
                "webcam"
            ];

            var res = document.detect_config.get_message().split("|");

/*          alert(res); */

            this.config.hardware = {};

            for(var i=0; i<keys.length;i++) {
                this.config.hardware[keys[i]] = res[i];
            }

            return this.config;
        }
    },



    // Helpers...

    // Browser

    searchString : function (data) {
        for (var i=0;i<data.length;i++) {
            var dataString = data[i].string;
            var dataProp = data[i].prop;
            this.versionSearchString = data[i].versionSearch || data[i].identity;
            if (dataString) {
                if (dataString.indexOf(data[i].subString) != -1){
                    return data[i].identity;
                }
            }
            else if (dataProp) {
                return data[i].identity;
            }
        }
    },

    searchVersion : function (dataString) {
        var index = dataString.indexOf(this.versionSearchString);
        if (index == -1) {return;}
        return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
    },

    // User

    searchLanguage : function() {
        if (navigator.language) { // FF
            return navigator.language;
        }
        else if (navigator.userLanguage) { // IE
            return navigator.userLanguage;
        } else {
            return "unknown";
        }
    },



    // Java

    searchJava : function () {
        var list = deployJavaBRM.getJREs();
        var latest = "0.0";
        for(i=0;i<list.length;i++) {
            if (deployJavaBRM.compareVersions(list[i],latest)) {
                latest = list[i];
            }
        }
        return latest;
    },


    // Flash

    searchFlash : function () {
        if (this.flashEnabled()) {
            return this.GetFlashVersion();
        }
    },


    flashEnabled : function() {
        versionStr = this.GetSwfVer();
        if (versionStr == -1 ) {
            return false;
        } else {
            return true;
        }
    },

    GetFlashVersion : function() {
        versionStr = this.GetSwfVer();
        if (versionStr == -1 ) {
            return false;
        } else if (versionStr) {
            if(this.isIE && this.isWin && !this.isOpera) {
                // Given "WIN 2,0,0,11"
                tempArray         = versionStr.split(" ");  // ["WIN", "2,0,0,11"]
                tempString        = tempArray[1];           // "2,0,0,11"
                versionArray      = tempString.split(",");  // ['2', '0', '0', '11']
            } else {
                versionArray      = versionStr.split(".");
            }
            var versionMajor      = versionArray[0];
            var versionMinor      = versionArray[1];
            var versionRevision   = versionArray[2];
            return versionMajor + "." + versionMinor;
        }
    },


    ControlVersion : function() {
        var version;
        var axo;
        var e;

        // NOTE : new ActiveXObject(strFoo) throws an exception if strFoo isn't in the registry

        try {
            // version will be set for 7.X or greater players
            axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
            version = axo.GetVariable("$version");
        } catch (e) {
        }

        if (!version)
        {
            try {
                // version will be set for 6.X players only
                axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");

                // installed player is some revision of 6.0
                // GetVariable("$version") crashes for versions 6.0.22 through 6.0.29,
                // so we have to be careful.

                // default to the first public version
                version = "WIN 6,0,21,0";

                // throws if AllowScripAccess does not exist (introduced in 6.0r47)
                axo.AllowScriptAccess = "always";

                // safe to call for 6.0r47 or greater
                version = axo.GetVariable("$version");

            } catch (e) {
            }
        }

        if (!version)
        {
            try {
                // version will be set for 4.X or 5.X player
                axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3");
                version = axo.GetVariable("$version");
            } catch (e) {
            }
        }

        if (!version)
        {
            try {
                // version will be set for 3.X player
                axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3");
                version = "WIN 3,0,18,0";
            } catch (e) {
            }
        }

        if (!version)
        {
            try {
                // version will be set for 2.X player
                axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
                version = "WIN 2,0,0,11";
            } catch (e) {
                version = -1;
            }
        }

        return version;
    },


    GetSwfVer : function() {
        // NS/Opera version >= 3 check for Flash plugin in plugin array
        var flashVer = -1;

        if (navigator.plugins && navigator.plugins.length > 0) {
            if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) {
                var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "";
                var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description;
                var descArray = flashDescription.split(" ");
                var tempArrayMajor = descArray[2].split(".");
                var versionMajor = tempArrayMajor[0];
                var versionMinor = tempArrayMajor[1];
                var versionRevision = descArray[3];
                if (!versionRevision) {
                    versionRevision = descArray[4];
                }
                if (versionRevision[0] == "d") {
                    versionRevision = versionRevision.substring(1);
                } else if (versionRevision[0] == "r") {
                    versionRevision = versionRevision.substring(1);
                    if (versionRevision.indexOf("d") > 0) {
                        versionRevision = versionRevision.substring(0, versionRevision.indexOf("d"));
                    }
                }
                flashVer = versionMajor + "." + versionMinor + "." + versionRevision;
            }
        }
        // MSN/WebTV 2.6 supports Flash 4
        else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) {flashVer = 4;}
        // WebTV 2.5 supports Flash 3
        else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) {flashVer = 3;}
        // older WebTV supports Flash 2
        else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) {flashVer = 2;}
        else if ( this.isIE && this.isWin && !this.isOpera ) {
            flashVer = this.ControlVersion();
        }
        return flashVer;
    },


    dataBrowser : [
    {
        string: navigator.userAgent,
        subString: "Chrome",
        identity: "Chrome"
    },
    {   string: navigator.userAgent,
        subString: "OmniWeb",
        versionSearch: "OmniWeb/",
        identity: "OmniWeb"
    },
    {
        string: navigator.vendor,
        subString: "Apple",
        identity: "Safari",
        versionSearch: "Version"
    },
    {
        prop: window.opera,
        identity: "Opera"
    },
    {
        string: navigator.vendor,
        subString: "iCab",
        identity: "iCab"
    },
    {
        string: navigator.vendor,
        subString: "KDE",
        identity: "Konqueror"
    },
    {
        string: navigator.userAgent,
        subString: "Firefox",
        identity: "Firefox"
    },
    {
        string: navigator.vendor,
        subString: "Camino",
        identity: "Camino"
    },
    {       // for newer Netscapes (6+)
        string: navigator.userAgent,
        subString: "Netscape",
        identity: "Netscape"
    },
    {
        string: navigator.userAgent,
        subString: "MSIE",
        identity: "Explorer",
        versionSearch: "MSIE"
    },
    {
        string: navigator.userAgent,
        subString: "Gecko",
        identity: "Mozilla",
        versionSearch: "rv"
    },
    {       // for older Netscapes (4-)
        string: navigator.userAgent,
        subString: "Mozilla",
        identity: "Netscape",
        versionSearch: "Mozilla"
    }],

    dataOS : [
    {
        string: navigator.userAgent,
        subString: 'Win16',
        identity: "Windows 3.11"
    },
    {
        string: navigator.userAgent,
        subString: "Windows 95",
        identity: "Windows 95"
    },
    {
        string: navigator.userAgent,
        subString: "Win95",
        identity: "Windows 95"
    },
    {
        string: navigator.userAgent,
        subString: "Windows_95",
        identity: "Windows 95"
    },
    {
        string: navigator.userAgent,
        subString: 'Windows 98',
        identity: "Windows 98"
    },
    {
        string: navigator.userAgent,
        subString: 'Win98',
        identity: "Windows 98"
    },
    {
        string: navigator.userAgent,
        subString: "Windows NT 5.0",
        identity: "Windows 2000"
    },
    {
        string: navigator.userAgent,
        subString: "Windows 2000",
        identity: "Windows 2000"
    },
    {
        string: navigator.userAgent,
        subString: "Windows NT 5.1",
        identity: "Windows XP"
    },
    {
        string: navigator.userAgent,
        subString: "Windows XP",
        identity: "Windows XP"
    },
    {
        string: navigator.userAgent,
        subString: "Windows NT 5.2",
        identity: "Windows Server 2003"
    },
    {
        string: navigator.userAgent,
        subString: 'Windows NT 6.0',
        identity: "Windows Vista"
    },
    {
        string: navigator.userAgent,
        subString: "Windows NT 7.0",
        identity: "Windows 7"
    },
    {
        string: navigator.userAgent,
        subString: "Windows NT 4.0",
        identity: "Windows NT 4.0"
    },
    {
        string: navigator.userAgent,
        subString: "WinNT4.0",
        identity: "Windows NT 4.0"
    },
    {
        string: navigator.userAgent,
        subString: "WinNT",
        identity: "Windows NT 4.0"
    },
    {
        string: navigator.userAgent,
        subString: "Windows NT",
        identity: "Windows NT 4.0"
    },
    {
        string: navigator.userAgent,
        subString: 'Windows ME',
        identity: "Windows ME"
    },
    {
        string: navigator.userAgent,
        subString: 'OpenBSD',
        identity: "Open BSD"
    },
    {
        string: navigator.userAgent,
        subString: 'SunOS',
        identity: "Sun OS"
    },
    {
        string: navigator.userAgent,
        subString: "Linux",
        identity: "Linux"
    },
    {
        string: navigator.userAgent,
        subString: "X11",
        identity: "Unix"
    },
    {
        string: navigator.userAgent,
        subString: "Mac_PowerPC",
        identity: "Mac OS"
    },
    {
        string: navigator.userAgent,
        subString: "Macintosh",
        identity: "Mac OS"
    },
    {
        string: navigator.userAgent,
        subString: "iPhone",
        identity: "iPhone OS"
    },
    {
        string: navigator.userAgent,
        subString: 'QNX',
        identity: "QNX"
    },
    {
        string: navigator.userAgent,
        subString: "nuhk",
        identity: "Search Bot"
    },
    {
        string: navigator.userAgent,
        subString: "Googlebot",
        identity: "Search Bot"
    },
    {
        string: navigator.userAgent,
        subString: "Yammybot",
        identity: "Search Bot"
    },
    {
        string: navigator.userAgent,
        subString: "Openbot",
        identity: "Search Bot"
    },
    {
        string: navigator.userAgent,
        subString: "Slurp",
        identity: "Search Bot"
    },
    {
        string: navigator.userAgent,
        subString: "MSNBot",
        identity: "Search Bot"
    },
    {
        string: navigator.userAgent,
        subString: "Ask Jeeves/Teoma",
        identity: "Search Bot"
    }]

};



window.BRM = window.BRM || {};
BRM.Messaging = BRM.Messaging || {};

BRM.Messaging.Client = BRM.Messaging.Client || {

    config: {
        debug: true,
        sessionExpiration: 30,
        /* in minutes */
        detectInterval: 7,
        /* in days */
        detect: true,
        /* Should we detect user's config ? */
        detectMore: false,
        loggerVersion: "1.0",
        debug: false,
        sendLocation: true,
        trace: false
    },

    getBrowserID: function() {
        var browserID = $.cookie("aID");
        if (!browserID) {
            browserID = this.serviceID + "-" + (new Date()).getTime() + Math.floor(Math.random() * 10000000);
            $.cookie("aID", browserID, {
                expires: 3000
            });
        }
        BRM.log("browserID: ", browserID);
        return browserID;
    },

    getSessionID: function() {
        var sessionID = $.cookie("cID");
        var t = new Date().getTime();
        if (!sessionID) {
            sessionID = this.browserID + "-" + t;
        }
        BRM.log("sessionID: ", sessionID);
        $.cookie("cID", sessionID, {
            expires: new Date(t + 1000 * 60 * this.config.sessionExpiration)
        });
        return sessionID;
    },

    setUserID: function(uID){
        this.userID = "" + uID;
    },

    setFacet: function(fn) {
      this.facet = fn;
    },

    init: function(sID, uID, conf) {

      if (conf.debug) {
        BRM.log = BRM.getLogger(conf.debug_div);
      }

      if (uID && typeof(uID) !== "string") {
          throw "_brm.u must be the user ID as a string.";
      }

      if (!sID) {
          throw "_brm.s must be provided.";
      }

      if (conf && conf.se && typeof(conf.se) !== "number") {
          throw "_brm.se must be a positive integer.";
      }
      
      
      if (conf) {
          this.config.detect = conf.d || this.config.detect;
          this.config.detectMore = conf.dm || false;
          this.config.sessionExpiration = conf.se || this.config.sessionExpiration;
          this.config.debug = conf.debug || false;
          this.config.trace = conf.trace || false;
          this.config.gateway = conf.gtw || "p";
          this.config.gateway_url = conf.gtwu;
          this.config.debug_div = conf.debug_div || false;
          this.sendLocation = conf.l || this.config.sendLocation;
          this.setFacet(conf.facet);
          
          BRM.log("serviceID: ", sID);
          BRM.log("userID: ", uID);
          BRM.log("facet: ", this.facet);
          BRM.log("conf: ", conf);
      }        
      
      this.serviceID = sID;
      this.userID = uID;
      this.browserID = this.getBrowserID();
      this.sessionID = this.getSessionID();
      this.getGateway();
      
      $(function() {
          if (window.SWFAddress && window.SWFAddress.addEventListener) {
              SWFAddress.addEventListener(SWFAddressEvent.CHANGE, function(ev) {
                  var l = document.location;
                  var path = ev.path === '/' ? '' : ev.path;
                  var new_url = [l.protocol, '//', l.host, l.pathname, l.search, '#', path].join('');
                  BRM.Messaging.API.location(new_url);
              });
          }
      });
    },

    getGateway: function () {
        var opts = {
            url: this.config.gateway_url,
            login: '',
            password: '',
            queue: this.serviceID,
            debug: this.config.debug,
            debug_div: this.config.debug_div
        };
        
        if (this.userID) {
          opts.responseQueue = '/topic/' + this.serviceID + '_' + this.userID;
        }
        if (this.config.gateway == "o") {
          BRM.log("Getting orbited Gateway... " + (new Date()));
          this.gateway = new BRM.Messaging.OrbitedGateway(opts);
        } else if (this.config.gateway == "f") {
          this.gateway = new BRM.Messaging.Gateway(opts);
        } else if (this.config.gateway == "p") {
          this.gateway = new BRM.Messaging.PostGateway(opts);
        }
        return this.gateway;
    },
    
    onConnect: function() {
        BRM.log('this.gateway: onConnect.', this.config.sendLocation);
        var gw = BRM.Messaging.Client.gateway;
        gw.subscribe(gw.options.responseQueue);
        if (this.config.sendLocation) {
            BRM.Messaging.API.location();
        }
        
        if (this.config.detect) {
            this.detectConfig();
        }
        
    },

    getSequenceNumber: function() {
        var seq = (parseInt($.cookie("seq"), 10) || 0) + 1;
        BRM.log("Sequence Number : ", seq);

        $.cookie("seq", seq, {
            expires: 3000
        });
        return seq;
    },

    detectConfig: function() {
        BRM.log("detect config: ", this.config.detect);
        
        if (!$.cookie("lcd") || this.config.detect == "force") {
            var time = new Date().getTime();
            BRM.log("Detecting config...");
            var head = document.getElementsByTagName("head")[0];

            BRM.Messaging.API.system($.extend(BRM.Detector.detect(), { type: "detect_config" }));

            $.cookie("lcd", time, {
                expires: new Date(time + 1000 * 60 * 60 * 24 * this.config.detectInterval)
            });
        }
    }
};

_brm = {
    init: function(sID, uID, conf) {
        $(function() { 
          BRM.Messaging.Client.init(sID, uID, conf);
        });
    },
    aID: BRM.Messaging.Client.browserID,
    cID: BRM.Messaging.Client.sessionID,
    event: BRM.Messaging.API,
    setUserID: function(u) {
      BRM.Messaging.Client.setUserID(u);
    },
    setFacet: function(f) {
      BRM.Messaging.Client.setFacet(f);
    }
};
