/*
 * Autocomplete - jQuery plugin 1.0.2
 *
 * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
 *
 */

;(function($) {
$.fn.extend({
	autocomplete: function(urlOrData, options) {
		var isUrl = typeof urlOrData == "string";
		options = $.extend({}, $.Autocompleter.defaults, {
			url: isUrl ? urlOrData : null,
			data: isUrl ? null : urlOrData,
			delay: isUrl ? $.Autocompleter.defaults.delay : 10,
			max: options && !options.scroll ? 10 : 150
		}, options);
		
		// if highlight is set to false, replace it with a do-nothing function
		options.highlight = options.highlight || function(value) { return value; };
		
		// if the formatMatch option is not specified, then use formatItem for backwards compatibility
		options.formatMatch = options.formatMatch || options.formatItem;
		
		return this.each(function() {
			new $.Autocompleter(this, options);
		});
	},
	result: function(handler) {
		return this.bind("result", handler);
	},
	search: function(handler) {
		return this.trigger("search", [handler]);
	},
	flushCache: function() {
		return this.trigger("flushCache");
	},
	setOptions: function(options){
		return this.trigger("setOptions", [options]);
	},
	unautocomplete: function() {
		return this.trigger("unautocomplete");
	}
});

$.Autocompleter = function(input, options) {

	var KEY = {
		UP: 38,
		DOWN: 40,
		DEL: 46,
		TAB: 9,
		RETURN: 13,
		ESC: 27,
		COMMA: 188,
		PAGEUP: 33,
		PAGEDOWN: 34,
		BACKSPACE: 8
	};

	// Create $ object for input element
	var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);

	var timeout;
	var previousValue = "";
	var cache = $.Autocompleter.Cache(options);
	var hasFocus = 0;
	var lastKeyPressCode;
	var config = {
		mouseDownOnSelect: false
	};
	var select = $.Autocompleter.Select(options, input, selectCurrent, config);
	
	var blockSubmit;
	
	// prevent form submit in opera when selecting with return key
	$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
		if (blockSubmit) {
			blockSubmit = false;
			return false;
		}
	});
	// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
	$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
		// track last key pressed
		lastKeyPressCode = event.keyCode;
		switch(event.keyCode) {
		
			case KEY.UP:
				event.preventDefault();
				if ( select.visible() ) {
					select.prev();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.DOWN:
				event.preventDefault();
				if ( select.visible() ) {
					select.next();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.PAGEUP:
				event.preventDefault();
				if ( select.visible() ) {
					select.pageUp();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.PAGEDOWN:
				event.preventDefault();
				if ( select.visible() ) {
					select.pageDown();
				} else {
					onChange(0, true);
				}
				break;
			
			// matches also semicolon
			case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
			case KEY.TAB:
			case KEY.RETURN:
			  var input = $input;
				if(input.val().indexOf("@") > -1 && input.val().indexOf(".") > -1){
					input.trigger("result", [input.val(), input.val()]);
					return true;
				} // check for email input
				if( selectCurrent() ) {
					// stop default to prevent a form submit, Opera needs special handling
					event.preventDefault();
					blockSubmit = true;
					return false;
				}
				break;
				
			case KEY.ESC:
				//select.hide();
				break;
				
			default:
				clearTimeout(timeout);
				timeout = setTimeout(onChange, options.delay);
				break;
		}
	}).focus(function(){
		// track whether the field has focus, we shouldn't process any
		// results if the field no longer has focus
		hasFocus++;
	}).blur(function() {
		hasFocus = 0;
		if (!config.mouseDownOnSelect) {
			hideResults();
		}
	}).click(function() {
		// show select when clicking in a focused field
		if ( hasFocus++ > 1 && !select.visible() ) {
			onChange(0, true);
		}
	}).bind("search", function() {
		// TODO why not just specifying both arguments?
		var fn = (arguments.length > 1) ? arguments[1] : null;
		function findValueCallback(q, data) {
			var result;
			if( data && data.length ) {
				for (var i=0; i < data.length; i++) {
					if( data[i].result.toLowerCase() == q.toLowerCase() ) {
						result = data[i];
						break;
					}
				}
			}
			if( typeof fn == "function" ) fn(result);
			else $input.trigger("result", result && [result.data, result.value]);
		}
		$.each(trimWords($input.val()), function(i, value) {
			request(value, findValueCallback, findValueCallback);
		});
	}).bind("flushCache", function() {
		cache.flush();
	}).bind("setOptions", function() {
		$.extend(options, arguments[1]);
		// if we've updated the data, repopulate
		if ( "data" in arguments[1] )
			cache.populate();
	}).bind("unautocomplete", function() {
		select.unbind();
		$input.unbind();
		$(input.form).unbind(".autocomplete");
	});
	
	
	function selectCurrent() {
		var selected = select.selected();
		if( !selected )
			return false;
		
		var v = selected.result;
		previousValue = v;
		
		if ( options.multiple ) {
			var words = trimWords($input.val());
			if ( words.length > 1 ) {
				v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
			}
			v += options.multipleSeparator;
		}
		
		$input.val(v);
		hideResultsNow();
		$input.trigger("result", [selected.data, selected.value]);
		return true;
	}
	
	function onChange(crap, skipPrevCheck) {
		if( lastKeyPressCode == KEY.DEL ) {
			//select.hide();
			return;
		}
		
		var currentValue = $input.val();
		
		if ( !skipPrevCheck && currentValue == previousValue )
			return;
		
		previousValue = currentValue;
		
		currentValue = lastWord(currentValue);
		if ( currentValue.length >= options.minChars) {
			$input.addClass(options.loadingClass);
			if (!options.matchCase)
				currentValue = currentValue.toLowerCase();
			request(currentValue, receiveData, hideResultsNow);
		} else {
			stopLoading();
			//select.hide();
		}
	};
	
	function trimWords(value) {
		if ( !value ) {
			return [""];
		}
		var words = value.split( options.multipleSeparator );
		var result = [];
		$.each(words, function(i, value) {
			if ( $.trim(value) )
				result[i] = $.trim(value);
		});
		return result;
	}
	
	function lastWord(value) {
		if ( !options.multiple )
			return value;
		var words = trimWords(value);
		return words[words.length - 1];
	}
	
	// fills in the input box w/the first match (assumed to be the best match)
	// q: the term entered
	// sValue: the first matching result
	function autoFill(q, sValue){
		// autofill in the complete box w/the first match as long as the user hasn't entered in more data
		// if the last user key pressed was backspace, don't autofill
		if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
			// fill in the value (keep the case the user has typed)
			$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
			// select the portion of the value not typed by the user (so the next character will erase)
			$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
		}
	};

	function hideResults() {
		clearTimeout(timeout);
		timeout = setTimeout(hideResultsNow, 200);
	};

	function hideResultsNow() {
		var wasVisible = select.visible();
		select.hide();
		clearTimeout(timeout);
		stopLoading();
		if (options.mustMatch) {
			// call search and run callback
			$input.search(
				function (result){
					// if no value found, clear the input box
					if( !result ) {
						if (options.multiple) {
							var words = trimWords($input.val()).slice(0, -1);
							$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
						}
						else
							$input.val( "" );
					}
				}
			);
		}
		if (wasVisible)
			// position cursor at end of input field
			$.Autocompleter.Selection(input, input.value.length, input.value.length);
	};

	function receiveData(q, data) {
		if ( data && data.length && hasFocus ) {
			stopLoading();
			select.display(data, q);
			autoFill(q, data[0].value);
			select.show();
		} else {
			hideResultsNow();
		}
	};

	function request(term, success, failure) {
		if (!options.matchCase)
			term = term.toLowerCase();
		var data = cache.load(term);
		// recieve the cached data
		if (data && data.length) {
			success(term, data);
		// if an AJAX url has been supplied, try loading the data now
		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
			
			var extraParams = {
				timestamp: +new Date()
			};
			$.each(options.extraParams, function(key, param) {
				extraParams[key] = typeof param == "function" ? param() : param;
			});
			
			$.ajax({
				// try to leverage ajaxQueue plugin to abort previous requests
				mode: "abort",
				// limit abortion to this input
				port: "autocomplete" + input.name,
				dataType: options.dataType,
				url: options.url,
				data: $.extend({
					q: lastWord(term),
					limit: options.max
				}, extraParams),
				success: function(data) {
					var parsed = options.parse && options.parse(data) || parse(data);
					cache.add(term, parsed);
					success(term, parsed);
				}
			});
		} else {
			// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
			select.emptyList();
			failure(term);
		}
	};
	
	function parse(data) {
		var parsed = [];
		var rows = data.split("\n");
		for (var i=0; i < rows.length; i++) {
			var row = $.trim(rows[i]);
			if (row) {
				row = row.split("|");
				parsed[parsed.length] = {
					data: row,
					value: row[0],
					result: options.formatResult && options.formatResult(row, row[0]) || row[0]
				};
			}
		}
		return parsed;
	};

	function stopLoading() {
		$input.removeClass(options.loadingClass);
	};

};

$.Autocompleter.defaults = {
	inputClass: "ac_input",
	resultsClass: "ac_results",
	loadingClass: "ac_loading",
	minChars: 0,
	delay: 400,
	matchCase: false,
	matchSubset: true,
	matchContains: false,
	cacheLength: 10,
	max: 100,
	mustMatch: false,
	extraParams: {},
	selectFirst: true,
	formatItem: function(row) { return row[0]; },
	formatMatch: null,
	autoFill: false,
	width: 0,
	multiple: false,
	multipleSeparator: ", ",
	highlight: function(value, term) {
		return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
	},
    scroll: true,
    scrollHeight: 180
};

$.Autocompleter.Cache = function(options) {

	var data = {};
	var length = 0;
	
	function matchSubset(s, sub) {
		if (!options.matchCase) 
			s = s.toLowerCase();
		var i = s.indexOf(sub);
		if (i == -1) return false;
		return i == 0 || options.matchContains;
	};
	
	function add(q, value) {
		if (length > options.cacheLength){
			flush();
		}
		if (!data[q]){ 
			length++;
		}
		data[q] = value;
	}
	
	function populate(){
		
		if( !options.data ) return false;
		// track the matches
		var stMatchSets = {},
			nullData = 0;

		// no url was specified, we need to adjust the cache length to make sure it fits the local data store
		if( !options.url ) options.cacheLength = 1;
		
		// track all options for minChars = 0
		stMatchSets[""] = [];
		
		// loop through the array and create a lookup structure
		for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
			var rawValue = options.data[i];
			// if rawValue is a string, make an array otherwise just reference the array
			rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
			
			var value = options.formatMatch(rawValue, i+1, options.data.length);
			if ( value === false )
				continue;
				
			var firstChar = value.charAt(0).toLowerCase();
			// if no lookup array for this character exists, look it up now
			if( !stMatchSets[firstChar] ) 
				stMatchSets[firstChar] = [];

			// if the match is a string
			var row = {
				value: value,
				data: rawValue,
				result: options.formatResult && options.formatResult(rawValue) || value
			};
			
			// push the current match into the set list
			stMatchSets[firstChar].push(row);

			// keep track of minChars zero items
			if ( nullData++ < options.max ) {
				stMatchSets[""].push(row);
			}
			
		};

		// add the data items to the cache
		$.each(stMatchSets, function(i, value) {
			// increase the cache size
			options.cacheLength++;
			// add to the cache
			add(i, value);
		});
	}
	
	// populate any existing data
	setTimeout(populate, 25);
	
	function flush(){
		data = {};
		length = 0;
	}
	
	return {
		flush: flush,
		add: add,
		populate: populate,
		load: function(q) {
			if (!options.cacheLength || !length)
				return null;
			/* 
			 * if dealing w/local data and matchContains than we must make sure
			 * to loop through all the data collections looking for matches
			 */
			if( !options.url && options.matchContains ){
				// track all matches
				var csub = [];
				// loop through all the data grids for matches
				for( var k in data ){
					// don't search through the stMatchSets[""] (minChars: 0) cache
					// this prevents duplicates
					if( k.length > 0 ){
						var c = data[k];
						$.each(c, function(i, x) {
							// if we've got a match, add it to the array
							if (matchSubset(x.value, q)) {
								csub.push(x);
							}
						});
					}
				}				
				return csub;
			} else 
			// if the exact item exists, use it
			if (data[q]){
				return data[q];
			} else
			if (options.matchSubset) {
				for (var i = q.length - 1; i >= options.minChars; i--) {
					var c = data[q.substr(0, i)];
					if (c) {
						var csub = [];
						$.each(c, function(i, x) {
							if (matchSubset(x.value, q)) {
								csub[csub.length] = x;
							}
						});
						return csub;
					}
				}
			}
			return null;
		}
	};
};

$.Autocompleter.Select = function (options, input, select, config) {
	var CLASSES = {
		ACTIVE: "ac_over"
	};
	
	var listItems,
		active = -1,
		data,
		term = "",
		needsInit = true,
		element,
		list;
	
	// Create results
	function init() {
		
		if (!needsInit)
			return;
		element = $("<div/>")
		.hide()
		.addClass(options.resultsClass)
		.css("position", "absolute")
		.appendTo(document.body);
	
		list = $("<ul/>").appendTo(element).mouseover( function(event) {
			if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
	            active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
			    $(target(event)).addClass(CLASSES.ACTIVE);            
	        }
		}).click(function(event) {
			$(target(event)).addClass(CLASSES.ACTIVE);
			select();
			// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
			input.focus();
			return false;
		}).mousedown(function() {
			config.mouseDownOnSelect = true;
		}).mouseup(function() {
			config.mouseDownOnSelect = false;
		});
		if( options.width > 0 )
			element.css("width", options.width);
			
		needsInit = false;
	} 
	
	function target(event) {
		var element = event.target;
		while(element && element.tagName != "LI")
			element = element.parentNode;
		// more fun with IE, sometimes event.target is empty, just ignore it then
		if(!element)
			return [];
		return element;
	}

	function moveSelect(step) {
		listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
		movePosition(step);
        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
        if(options.scroll) {
            var offset = 0;
            listItems.slice(0, active).each(function() {
				offset += this.offsetHeight;
			});
            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
            } else if(offset < list.scrollTop()) {
                list.scrollTop(offset);
            }
        }
	};
	
	function movePosition(step) {
		active += step;
		if (active < 0) {
			active = listItems.size() - 1;
		} else if (active >= listItems.size()) {
			active = 0;
		}
	}
	
	function limitNumberOfItems(available) {
		return options.max && options.max < available
			? options.max
			: available;
	}
	
	function fillList() {
		list.empty();
		var max = limitNumberOfItems(data.length);
		for (var i=0; i < max; i++) {
			if (!data[i])
				continue;
			var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
			if ( formatted === false )
				continue;
			var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
			$.data(li, "ac_data", data[i]);
		}
		listItems = list.find("li");
		if ( options.selectFirst ) {
			listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
			active = 0;
		}
		// apply bgiframe if available
		if ( $.fn.bgiframe )
			list.bgiframe();
	}
	
	return {
		display: function(d, q) {
			init();
			data = d;
			term = q;
			fillList();
		},
		next: function() {
			moveSelect(1);
		},
		prev: function() {
			moveSelect(-1);
		},
		pageUp: function() {
			if (active != 0 && active - 8 < 0) {
				moveSelect( -active );
			} else {
				moveSelect(-8);
			}
		},
		pageDown: function() {
			if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
				moveSelect( listItems.size() - 1 - active );
			} else {
				moveSelect(8);
			}
		},
		hide: function() {
			element && element.hide();
			listItems && listItems.removeClass(CLASSES.ACTIVE);
			active = -1;
		},
		visible : function() {
			return element && element.is(":visible");
		},
		current: function() {
			return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
		},
		show: function() {
			var offset = $(input).offset();
			element.css({
				width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
				top: offset.top + input.offsetHeight,
				left: offset.left
			}).show();
            if(options.scroll) {
                list.scrollTop(0);
                list.css({
					maxHeight: options.scrollHeight,
					overflow: 'auto'
				});
				
                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
					var listHeight = 0;
					listItems.each(function() {
						listHeight += this.offsetHeight;
					});
					var scrollbarsVisible = listHeight > options.scrollHeight;
                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
					if (!scrollbarsVisible) {
						// IE doesn't recalculate width when scrollbar disappears
						listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
					}
                }
                
            }
		},
		selected: function() {
			var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
			return selected && selected.length && $.data(selected[0], "ac_data");
		},
		emptyList: function (){
			list && list.empty();
		},
		unbind: function() {
			element && element.remove();
		}
	};
};

$.Autocompleter.Selection = function(field, start, end) {
	if( field.createTextRange ){
		var selRange = field.createTextRange();
		selRange.collapse(true);
		selRange.moveStart("character", start);
		selRange.moveEnd("character", end);
		selRange.select();
	} else if( field.setSelectionRange ){
		field.setSelectionRange(start, end);
	} else {
		if( field.selectionStart ){
			field.selectionStart = start;
			field.selectionEnd = end;
		}
	}
	field.focus();
};

})(jQuery);

/*
 * jqDnR - Minimalistic Drag'n'Resize for jQuery.
 *
 * Copyright (c) 2007 Brice Burgess <bhb@iceburg.net>, http://www.iceburg.net
 * Licensed under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * $Version: 2007.08.19 +r2
 */

(function($){
$.fn.jqDrag=function(h){return i(this,h,'d');};
$.fn.jqResize=function(h){return i(this,h,'r');};
$.jqDnR={dnr:{},e:0,
drag:function(v){
 if(M.k == 'd')E.css({left:M.X+v.pageX-M.pX,top:M.Y+v.pageY-M.pY});
 else E.css({width:Math.max(v.pageX-M.pX+M.W,0),height:Math.max(v.pageY-M.pY+M.H,0)});
  return false;},
stop:function(){E.css('opacity',M.o);$().unbind('mousemove',J.drag).unbind('mouseup',J.stop);}
};
var J=$.jqDnR,M=J.dnr,E=J.e,
i=function(e,h,k){return e.each(function(){h=(h)?$(h,e):e;
 h.bind('mousedown',{e:e,k:k},function(v){var d=v.data,p={};E=d.e;
 // attempt utilization of dimensions plugin to fix IE issues
 if(E.css('position') != 'relative'){try{E.position(p);}catch(e){}}
 M={X:p.left||f('left')||0,Y:p.top||f('top')||0,W:f('width')||E[0].scrollWidth||0,H:f('height')||E[0].scrollHeight||0,pX:v.pageX,pY:v.pageY,k:d.k,o:E.css('opacity')};
 E.css({opacity:0.8});$().mousemove($.jqDnR.drag).mouseup($.jqDnR.stop);
 return false;
 });
});},
f=function(k){return parseInt(E.css(k))||false;};
})(jQuery);


/*
 * jqModal - Minimalist Modaling with jQuery
 *   (http://dev.iceburg.net/jquery/jqModal/)
 *
 * Copyright (c) 2007,2008 Brice Burgess <bhb@iceburg.net>
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 * 
 * $Version: 03/01/2009 +r14
 */
(function($) {
$.fn.jqm=function(o){
var p={
overlay: 50,
overlayClass: 'jqmOverlay',
closeClass: 'jqmClose',
trigger: '.jqModal',
ajax: F,
ajaxText: '',
target: F,
modal: F,
toTop: F,
onShow: F,
onHide: F,
onLoad: F
};
return this.each(function(){if(this._jqm)return H[this._jqm].c=$.extend({},H[this._jqm].c,o);s++;this._jqm=s;
H[s]={c:$.extend(p,$.jqm.params,o),a:F,w:$(this).addClass('jqmID'+s),s:s};
if(p.trigger)$(this).jqmAddTrigger(p.trigger);
});};

$.fn.jqmAddClose=function(e){return hs(this,e,'jqmHide');};
$.fn.jqmAddTrigger=function(e){return hs(this,e,'jqmShow');};
$.fn.jqmShow=function(t){return this.each(function(){t=t||window.event;$.jqm.open(this._jqm,t);});};
$.fn.jqmHide=function(t){return this.each(function(){t=t||window.event;$.jqm.close(this._jqm,t)});};

$.jqm = {
hash:{},
open:function(s,t){var h=H[s],c=h.c,cc='.'+c.closeClass,z=(parseInt(h.w.css('z-index'))),z=(z>0)?z:9999,o=$('<div></div>').css({height:'100%',width:'100%',position:'fixed',left:0,top:0,'z-index':z-1,opacity:c.overlay/100});if(h.a)return F;h.t=t;h.a=true;h.w.css('z-index',z);
 if(c.modal) {if(!A[0])L('bind');A.push(s);}
 else if(c.overlay > 0)h.w.jqmAddClose(o);
 else o=F;

 h.o=(o)?o.addClass(c.overlayClass).prependTo('body'):F;
 if(ie6){$('html,body').css({height:'100%',width:'100%'});if(o){o=o.css({position:'absolute'})[0];for(var y in {Top:1,Left:1})o.style.setExpression(y.toLowerCase(),"(_=(document.documentElement.scroll"+y+" || document.body.scroll"+y+"))+'px'");}}

 if(c.ajax) {var r=c.target||h.w,u=c.ajax,r=(typeof r == 'string')?$(r,h.w):$(r),u=(u.substr(0,1) == '@')?$(t).attr(u.substring(1)):u;
  r.html(c.ajaxText).load(u,function(){if(c.onLoad)c.onLoad.call(this,h);if(cc)h.w.jqmAddClose($(cc,h.w));e(h);});}
 else if(cc)h.w.jqmAddClose($(cc,h.w));

 if(c.toTop&&h.o)h.w.before('<span id="jqmP'+h.w[0]._jqm+'"></span>').insertAfter(h.o);	
 (c.onShow)?c.onShow(h):h.w.show();e(h);return F;
},
close:function(s){var h=H[s];if(!h.a)return F;h.a=F;
 if(A[0]){A.pop();if(!A[0])L('unbind');}
 if(h.c.toTop&&h.o)$('#jqmP'+h.w[0]._jqm).after(h.w).remove();
 if(h.c.onHide)h.c.onHide(h);else{h.w.hide();if(h.o)h.o.remove();} return F;
},
params:{}};
var s=0,H=$.jqm.hash,A=[],ie6=$.browser.msie&&($.browser.version == "6.0"),F=false,
i=$('<iframe src="javascript:false;document.write(\'\');" class="jqm"></iframe>').css({opacity:0}),
e=function(h){if(ie6)if(h.o)h.o.html('<p style="width:100%;height:100%"/>').prepend(i);else if(!$('iframe.jqm',h.w)[0])h.w.prepend(i); f(h);},
f=function(h){try{$(':input:visible',h.w)[0].focus();}catch(_){}},
L=function(t){$()[t]("keypress",m)[t]("keydown",m)[t]("mousedown",m);},
m=function(e){var h=H[A[A.length-1]],r=(!$(e.target).parents('.jqmID'+h.s)[0]);if(r)f(h);return !r;},
hs=function(w,t,c){return w.each(function(){var s=this._jqm;$(t).each(function() {
 if(!this[c]){this[c]=[];$(this).click(function(){for(var i in {jqmShow:1,jqmHide:1})for(var s in this[i])if(H[this[i][s]])H[this[i][s]].w[i](this);return F;});}this[c].push(s);});});};
})(jQuery);

﻿/// <reference path="jquery.js"/>
/*
* resizable
* version: 1.0.0 (05/15/2009)
* @ jQuery v1.2.*
*
* Licensed under the GPL:
*   http://gplv3.fsf.org
*
* Copyright 2008, 2009 Jericho [ thisnamemeansnothing[at]gmail.com ] 
*/
//(function($) {
    $.extend($.fn, {
        getCss: function(key) {
            var v = parseInt(this.css(key));
            if (isNaN(v))
                return false;
            return v;
        }
    });
    $.fn.resizable = function(opts) {
        var ps = $.extend({
            handler: null,
            min: { width: 0, height: 0 },
            max: { width: $(document).width(), height: $(document).height() },
            onResize: function() { },
            onStop: function() { }
        }, opts);
        var resize = {
            resize: function(e) {
                var resizeData = e.data.resizeData;
								//alert(resizeData.offTop);
                var w = Math.min(Math.max(e.pageX - resizeData.offLeft + resizeData.width, resizeData.min.width), ps.max.width);
                var h = Math.min(Math.max(resizeData.offTop - e.pageY + resizeData.height, resizeData.min.height), ps.max.height);
                resizeData.target.css({
                    width: w,
                    height: h
                });
                resizeData.onResize(e);
            },
            stop: function(e) {
                e.data.resizeData.onStop(e);

                document.body.onselectstart = function() { return true; }
                e.data.resizeData.target.css('-moz-user-select', '');

                $().unbind('mousemove', resize.resize)
                    .unbind('mouseup', resize.stop);
            }
        }
        return this.each(function() {
            var me = this;
            var handler = null;
            if (typeof ps.handler == 'undefined' || ps.handler == null)
                handler = $(me);
            else
                handler = (typeof ps.handler == 'string' ? $(ps.handler, this) : ps.handle);
            handler.bind('mousedown', { e: me }, function(s) {
                var target = $(s.data.e);
                var resizeData = {
                    width: target.width() || target.getCss('width'),
                    height: target.height() || target.getCss('height'),
                    offLeft: s.pageX,
                    offTop: s.pageY,
                    onResize: ps.onResize,
                    onStop: ps.onStop,
                    target: target,
                    min: ps.min,
                    max: ps.max
                }

                document.body.onselectstart = function() { return false; }
                target.css('-moz-user-select', 'none');

                $().bind('mousemove', { resizeData: resizeData }, resize.resize)
                    .bind('mouseup', { resizeData: resizeData }, resize.stop);
            });
        });
    }
//})(jQuery); 

/*
 * 	loopedSlider 0.5.6 - jQuery plugin
 *	written by Nathan Searles	
 *	http://nathansearles.com/loopedslider/
 *
 *	Copyright (c) 2009 Nathan Searles (http://nathansearles.com/)
 *	Dual licensed under the MIT (MIT-LICENSE.txt)
 *	and GPL (GPL-LICENSE.txt) licenses.
 *
 *	Built for jQuery library
 *	http://jquery.com
 *	Compatible with jQuery 1.3.2+
 *
 */

/*
 *	markup example for $("#loopedSlider").loopedSlider();
 *
 *	<div id="loopedSlider">	
 *		<div class="container">
 *			<div class="slides">
 *				<div><img src="01.jpg" alt="" /></div>
 *				<div><img src="02.jpg" alt="" /></div>
 *				<div><img src="03.jpg" alt="" /></div>
 *				<div><img src="04.jpg" alt="" /></div>
 *			</div>
 *		</div>
 *		<a href="#" class="previous">previous</a>
 *		<a href="#" class="next">next</a>	
 *	</div>
 *
*/

if(typeof jQuery != 'undefined') {
	jQuery(function($) {
		$.fn.extend({
			loopedSlider: function(options) {
				var settings = $.extend({}, $.fn.loopedSlider.defaults, options);
			
				return this.each(
					function() {
					if($.fn.jquery < '1.3.2') {return;}
					var $t = $(this);
					var o = $.metadata ? $.extend({}, settings, $t.metadata()) : settings;
					
					var distance = 0;
					var times = 1;
					var slides = $(o.slides,$t).children().size();
					var width = $(o.slides,$t).children().outerWidth();
					var position = 0;
					var active = false;
					var number = 0;
					var interval = 0;
					var restart = 0;
					var pagination = $("."+o.pagination+" li a",$t);

					if(o.addPagination && !$(pagination).length){
						var buttons = slides;
						$($t).append("<ul class="+o.pagination+">");
						$(o.slides,$t).children().each(function(){
							if (number<buttons) {
								$("."+o.pagination,$t).append("<li><a rel="+(number+1)+" href=\"#\" >"+(number+1)+"</a></li>");
								number = number+1;
							} else {
								number = 0;
								return false;
							}
							$("."+o.pagination+" li a:eq(0)",$t).parent().addClass("active");
						});
						$("."+o.pagination,$t).css("width", (buttons * 17) + "px" )
						pagination = $("."+o.pagination+" li a",$t);
					} else {
						$(pagination,$t).each(function(){
							number=number+1;
							$(this).attr("rel",number);
							$(pagination.eq(0),$t).parent().addClass("active");
						});
					}

					if (slides===1) {
						$(o.slides,$t).children().css({position:"absolute",left:position,display:"block"});
						return;
					}

					$(o.slides,$t).css({width:(slides*width)});

					$(o.slides,$t).children().each(function(){
						$(this).css({position:"absolute",left:position,display:"block"});
						position=position+width;
					});

					$(o.slides,$t).children(":eq("+(slides-1)+")").css({position:"absolute",left:-width});

					if (slides>3) {
						$(o.slides,$t).children(":eq("+(slides-1)+")").css({position:"absolute",left:-width});
					}

					if(o.autoHeight){autoHeight(times);}

					$(".next",$t).click(function(){
						if(active===false) {
							animate("next",true);
							if(o.autoStart){
								if (o.restart) {autoStart();}
								else {clearInterval(sliderIntervalID);}
							}
						} return false;
					});

					$(".previous",$t).click(function(){
						if(active===false) {	
							animate("prev",true);
							if(o.autoStart){
								if (o.restart) {autoStart();}
								else {clearInterval(sliderIntervalID);}
							}
						} return false;
					});

					if (o.containerClick) {
						$(o.container,$t).click(function(){
							if(active===false) {
								animate("next",true);
								if(o.autoStart){
									if (o.restart) {autoStart();}
									else {clearInterval(sliderIntervalID);}
								}
							} return false;
						});
					}

					$("ul.pagination li a").click(function(){
						
						if ($(this).parent().hasClass("active")) {return false;}
						else {
							times = $(this).attr("rel");
							$("ul.pagination li a").parent().siblings().removeClass("active");
							$(this).parent().addClass("active");
							animate("fade",times);
							if(o.autoStart){
								if (o.restart) {autoStart();}
								else {clearInterval(sliderIntervalID);}
							}
						} return false;
					});

					if (o.autoStart) {
						sliderIntervalID = setInterval(function(){
							if(active===false) {animate("next",true);}
						},o.autoStart);
						function autoStart() {
							if (o.restart) {
							clearInterval(sliderIntervalID,interval);
							clearTimeout(restart);
								restart = setTimeout(function() {
									interval = setInterval(	function(){
										animate("next",true);
									},o.autoStart);
								},o.restart);
							} else {
								sliderIntervalID = setInterval(function(){
									if(active===false) {animate("next",true);}
								},o.autoStart);
							}
						};
					}

					function current(times) {
						if(times===slides+1){times = 1;}
						if(times===0){times = slides;}
						$(pagination,$t).parent().siblings().removeClass("active");
						$(pagination+"[rel='" + (times) + "']",$t).parent().addClass("active");
					};

					function autoHeight(times) {
						if(times===slides+1){times=1;}
						if(times===0){times=slides;}	
						var getHeight = $(o.slides,$t).children(":eq("+(times-1)+")",$t).outerHeight();
						$(o.container,$t).animate({height: getHeight},o.autoHeight);					
					};		

					function animate(dir,clicked){	
						active = true;	
						switch(dir){
							case "next":
								times = times+1;
								distance = (-(times*width-width));
								current(times);
								if(o.autoHeight){autoHeight(times);}
								if(slides<3){
									if (times===3){$(o.slides,$t).children(":eq(0)").css({left:(slides*width)});}
									if (times===2){$(o.slides,$t).children(":eq("+(slides-1)+")").css({position:"absolute",left:width});}
								}
								$(o.slides,$t).animate({left: distance}, o.slidespeed,function(){
									if (times===slides+1) {
										times = 1;
										$(o.slides,$t).css({left:0},function(){$(o.slides,$t).animate({left:distance})});							
										$(o.slides,$t).children(":eq(0)").css({left:0});
										$(o.slides,$t).children(":eq("+(slides-1)+")").css({ position:"absolute",left:-width});				
									}
									if (times===slides) $(o.slides,$t).children(":eq(0)").css({left:(slides*width)});
									if (times===slides-1) $(o.slides,$t).children(":eq("+(slides-1)+")").css({left:(slides*width-width)});
									active = false;
								});					
								break; 
							case "prev":
								times = times-1;
								distance = (-(times*width-width));
								current(times);
								if(o.autoHeight){autoHeight(times);}
								if (slides<3){
									if(times===0){$(o.slides,$t).children(":eq("+(slides-1)+")").css({position:"absolute",left:(-width)});}
									if(times===1){$(o.slides,$t).children(":eq(0)").css({position:"absolute",left:0});}
								}
								$(o.slides,$t).animate({left: distance}, o.slidespeed,function(){
									if (times===0) {
										times = slides;
										$(o.slides,$t).children(":eq("+(slides-1)+")").css({position:"absolute",left:(slides*width-width)});
										$(o.slides,$t).css({left: -(slides*width-width)});
										$(o.slides,$t).children(":eq(0)").css({left:(slides*width)});
									}
									if (times===2 ) $(o.slides,$t).children(":eq(0)").css({position:"absolute",left:0});
									if (times===1) $(o.slides,$t).children(":eq("+ (slides-1) +")").css({position:"absolute",left:-width});
									active = false;
								});
								break;
							case "fade":
								times = [times]*1;
								distance = (-(times*width-width));
								current(times);
								if(o.autoHeight){autoHeight(times);}
								$(o.slides,$t).children().fadeOut(o.fadespeed, function(){
									$(o.slides,$t).css({left: distance});
									$(o.slides,$t).children(":eq("+(slides-1)+")").css({left:slides*width-width});
									$(o.slides,$t).children(":eq(0)").css({left:0});
									if(times===slides){$(o.slides,$t).children(":eq(0)").css({left:(slides*width)});}
									if(times===1){$(o.slides,$t).children(":eq("+(slides-1)+")").css({ position:"absolute",left:-width});}
									$(o.slides,$t).children().fadeIn(o.fadespeed);
									active = false;
								});
								break; 
							default:
								break;
							}					
						};
					}
				);
			}
		});
		$.fn.loopedSlider.defaults = {
			container: ".container", //Class/id of main container. You can use "#container" for an id.
			slides: ".slides", //Class/id of slide container. You can use "#slides" for an id.
			pagination: "pagination", //Class name of parent ul for numbered links. Don't add a "." here.
			containerClick: true, //Click slider to goto next slide? true/false
			autoStart: 0, //Set to positive number for true. This number will be the time between transitions.
			restart: 0, //Set to positive number for true. Sets time until autoStart is restarted.
			slidespeed: 300, //Speed of slide animation, 1000 = 1second.
			fadespeed: 200, //Speed of fade animation, 1000 = 1second.
			autoHeight: 0, //Set to positive number for true. This number will be the speed of the animation.
			addPagination: false //Add pagination links based on content? true/false
		};
	});
}

// $("img").error(function(){
// 	alert('p');
// 	$(this).attr("src", "/images/img/missing_mini.png");
// });

function showDelayed() {
	$(".delay").removeClass("delay");
}
$(window).load(function(){
	showDelayed();
});
setTimeout("showDelayed()", 3000);

$(function(){
	// delays display of the Aller font-face to avoid visible transform

	setup_login();
	// user menu responds to both mouseenter and click (for touch screens)
	$("#user_menu_link .user, #user_menu img.arrow").mouseenter(function(){
		$("#menu_border").show();
	}).click(function(){
		$("#menu_border").show();
	});
	// mouseleave or blur 
	$("#menu_border").mouseleave(function(){
		$("#menu_border").hide();
	}).blur(function(){
		$("#menu_border").hide();
	});
	// hides overlay
	// $("#overlay").live("click",function(){
	// 	$(this).hide();
	// });
	
	$("._public").click(function(){
		show_signup();
		return false;
	});
	
	$(".search_text").focus(function(){
		$(this).attr("value","").css({color:"#222"});
	});
	if ($(".search_text").attr("value") == ""){
		$(".search_text").attr("value", "search")
	}
	$(".close_modal").click(function(){
		$(this).parent().jqmHide();
		return false;
	});
	$(".close_me").click(function(){
		$(this).parent().slideUp(100);
		return false;
	});
	$(".follow.delete").live("click",function(){
		var widget = $(this).parent();
		var user_id = $(this).attr("user_id");
		$.post($(this).attr("href"), {_method:"delete"}, function(result){
			widget.after(result).remove();
			notify($("#notice"),"You're no longer following",100,100,3000,100);
		},"script");
		return false;
	});
	$(".follow").live("click",function(){
		var widget = $(this).parent();
		//var user_id = $(this).attr("user_id");
		$.get($(this).attr("href"), function(result){
			widget.after(result).remove();
			notify($("#notice"),"You're now following",100,100,3000,100);
		},"script");
		return false;
	});
	var notice = $("#notice");
	if(notice.length > 0 && notice.html().trim().length > 0){
		notify("#notice",null,100,200,4400,180);
		$("#notice").click(function(){
			$(this).slideUp(100);
		});
	}
	$(".toggle_invitables").toggle(function(){
		$(".invitables").slideDown(100);
	},function(){
		$(".invitables").slideUp(100);
	});
});

///////////////////////////
// ADDING PEOPLE TO CONVOS
//////////////////////////

function invite_convo_friend(user_id){
	$('#ac_input').attr({disabled: true, value: "adding..."});
	$.post("/conversations/add_user", {conversation_id:convo_id, user_id: user_id},
		function(data){
			if (data.beginsWith("error")){
				alert(data);
				$("#invite_explain").html("Sorry! We could not add user.");
			}
			else
			{
				//$('[convo_members_id=' + convo_id + ']').append(data);
				$('.invited').append(data);
				$(".invitables [user_id=" + user_id +"]").hide();
				//$("#invite_explain").hide().html("convo.us friend has been added to the convo.").fadeIn(400);
				notify($("#notice"),"Added to the convo",100,100,3000,100);
				$('#ac_input').attr({disabled: false, value: ""});
				pageTracker._trackPageview("/user_added_to_convo");
			}
	});
}

function show_working(text){
	$("#overlay").show();
	$("#working").prepend(text).show();
	return false;
}
////////////////////////////////////////
///////// AUTOCOMPLETE INVITER /////////
////////////////////////////////////////
function auto_complete_init(){
	search_box = $('.q');
	//flush_auto_complete();
	//search_box.unbind();
	if(search_box.length > 0){
		ac_setup(convo_friends);
		ac_result();
	}
}
function ac_setup(friends_array){
	//alert("");
	//flush_auto_complete();
	search_box.autocomplete(friends_array, 
	{
		matchContains: true,
		formatItem: formatItem,
		formatResult: formatResult,
		formatMatch:formatMatch
	});
	//search_box.click();
}

function ac_result(){
	var user_id;
	search_box.result(function(event,user,formatted){
		
		//if(selected_icon.attr("id") == "convo_icon"){
			if(convo_friends.length == 1){
				convo_friends = "";
				flush_auto_complete();
			}
			else {
				convo_friends = removeItem(convo_friends,user);
			}
			$(this).autocomplete().trigger('setOptions',{
				data: convo_friends
			});	

			user_id = search_box.val();
			search_box.val("");
			invite_convo_friend(user_id);
		}); // end result
		$('#ac_input').attr({disabled: false, value: ""});
}

function formatItem(row){
	return row[0].split("|")[1];
}
function formatResult(row){
	return row[0].split("|")[0];
}
function formatMatch(row){
	return row[0].split("> ")[1];
}

function flush_auto_complete(){
	search_box.flushCache();
}
//remove item (string or number) from an array
function removeItem(originalArray, itemToRemove) {
	var j = 0;
	while (j < originalArray.length) {
	//	alert(originalArray[j]);
		if (originalArray[j] == itemToRemove) {
			originalArray.splice(j, 1);
		} else { j++; }
	}
	return originalArray;
}
auto_complete_init();
// END ADD USERS TO CONVOS


function setup_login() {
	if($("#user_session_email").length > 0) {
		
		if($("#user_session_email").attr("value") == "") {
			$("#user_session_email").addClass("email");
		}
		if($("#user_session_password").attr("value") == "") {
			$("#user_session_password").addClass("password");
		}
		$(".email").click(function(){$(this).removeClass("email")}).focus(function(){$(this).removeClass("email");});
		$(".password").click(function(){$(this).removeClass("password")}).focus(function(){$(this).removeClass("password")});
	}
}
function check_auth() {
	if(auth_state == "_public") {
		show_signup();
		return false;
	}
}

function show_signup(){
		$(".login_modal").jqm().jqmShow(); //.jqmShow();
		return false;
}

function image_viewer() {
	
	//$(".slide_viewer img").yoxview();
	$(".image_viewer img").css("cursor","pointer").click(function(){
			$("body").append('<div class="overlay full_screen"></div><div id="viewer" class="full_screen"><img id="overlay_image" src="' + $(this).attr("src") + '"> </div>');
			var img = $("#overlay_image");
			var winheight = $(window).height();
			var maxheight = img.height();
			if(maxheight > (winheight * .95)) {
				maxheight = (parseInt(winheight) * .95) + "px";
				img.css("height",maxheight)
			}
			var winwidth = $(window).width();
			var maxwidth = img.width();
			if(maxwidth > (winwidth * .8)) {
				maxwidth = (parseInt(winwidth) * .8) + "px";
				img.css("width",maxwidth)
			}
			img.css("left", (winwidth - maxwidth)/2  + 'px');
			img.css("top", (winheight - maxheight)/2  + 'px');
			window.scrollTo(0,0);
			img.click(function(){
				$(".overlay").remove();
				$("#viewer").remove();
			});
			
		});
}

function display_wysiwyg(obj,live_preview, width_editor, height_editor, float_editor){
	if(obj.css("display") == "none") {
		return false;
	}
	obj.wysiwyg({
		resizeOptions: {},
		livePreview: live_preview,
		widthEditor: width_editor,
		heightEditor: height_editor,
		floatEditor: float_editor,
	    controls: {
	      strikeThrough : { visible : true },
	      underline     : { visible : true },
	      justifyLeft   : { visible : true },
	      justifyCenter : { visible : true },
	      justifyRight  : { visible : true },
	      justifyFull   : { visible : true },
		  
	      indent  : { visible : true },
	      outdent : { visible : true },

	      insertOrderedList    : { visible : true },
	      insertUnorderedList  : { visible : true },
	      insertHorizontalRule : { visible : true }
	    },
	    events: {
	      click : function(e)
	      {
	        if ($('#click-inform:checked').length > 0)
	        {
	          e.preventDefault();
	          alert('You have clicked jWysiwyg content!');
	        }
	      }
	    }
	 });
}

function notify(page_element,message,fade_in,fade_out,delay,hide_speed)
{	
	var e = $(page_element);
	if(message) e.html(message);
	e.slideDown(fade_in);
	setTimeout(function(){
		e.slideUp(hide_speed,
				  function(){
					  e.html("");
					  e.hide()})}, 
	   delay);
}

// GLOBAL FUNCTIONS //
String.prototype.beginsWith = function(t, i) { if (i==false) { return
(t == this.substring(0, t.length)); } else { return (t.toLowerCase()
== this.substring(0, t.length).toLowerCase()); } }

String.prototype.endsWith = function(t, i) { if (i==false) { return (t
== this.substring(this.length - t.length)); } else { return
(t.toLowerCase() == this.substring(this.length -
t.length).toLowerCase()); } }
String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g,"");
}
String.prototype.ltrim = function() {
	return this.replace(/^\s+/,"");
}
String.prototype.rtrim = function() {
	return this.replace(/\s+$/,"");
}

// END GLOBAL FUNCTIONS //

// UTILS //
// rails auth token enabled in jquery
$(document).ajaxSend(function(event, request, settings) {
	if (typeof(AUTH_TOKEN) == "undefined") return;
	 // settings.data is a serialized string like "foo=bar&baz=boink" (or null)
	  settings.data = settings.data || "";
	  settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(AUTH_TOKEN);
});
// END UTILS //

// jCAROUSEL //
/*
* @cat Plugins/Image Gallery
* @author Ganeshji Marwaha/ganeshread@gmail.com
*/
var selected_image = 0;
(function($) {                                          // Compliant with jquery.noConflict()
$.fn.jCarouselLite = function(o) {
    o = $.extend({
        btnPrev: null,
        btnNext: null,
        btnGo: null,
        mouseWheel: false,
        auto: null,

        speed: 200,
        easing: null,
		default_width: false,
		default_height: false,
        vertical: false,
        circular: true,
        visible: 3,
        start: 0,
        scroll: 1,

        beforeStart: null,
        afterEnd: null
    }, o || {});

    return this.each(function() {                           // Returns the element collection. Chainable.
        var running = false, animCss=o.vertical?"top":"left", sizeCss=o.vertical?"height":"width";
        var div = $(this), ul = $("ul", div), tLi = $("li", ul), tl = tLi.size(), v = o.visible;

        if(o.circular) {
            ul.prepend(tLi.slice(tl-v-1+1).clone())
              .append(tLi.slice(0,v).clone());
            o.start += v;
        }

        var li = $("li", ul), itemLength = li.size(), curr = o.start;
        div.css("visibility", "visible");

        li.css({overflow: "hidden", float: o.vertical ? "none" : "left"});
        ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
        div.css({overflow: "hidden", position: "relative", "z-index": "2", left: "0px"});
		if(o.default_width != false) {
			
        	var liSize = o.default_width; 
		}
		else
		{	
			var liSize = o.vertical ? height(li) : width(li);   // Full li size(incl margin)-Used for animation
		}
        var ulSize = liSize * itemLength;                   // size of full ul(total length, not just for the visible items)
        var divSize = liSize * v;                           // size of entire div(total length for just the visible items)
		if(o.default_width != false) {
			li.css({width:o.default_width, height: o.default_height});
		} 
		else 
		{
			li.css({width: li.width(), height: li.height()});
		}
        
        ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));

        div.css(sizeCss, divSize+"px");                     // Width of the DIV. length of visible images

        if(o.btnPrev)
            $(o.btnPrev).click(function() {
                return go(curr-o.scroll);
            });

        if(o.btnNext)
            $(o.btnNext).click(function() {
                return go(curr+o.scroll);
            });

        if(o.btnGo)
            $.each(o.btnGo, function(i, val) {
                $(val).click(function() {
                    return go(o.circular ? o.visible+i : i);
                });
            });

        if(o.mouseWheel && div.mousewheel)
            div.mousewheel(function(e, d) {
                return d>0 ? go(curr-o.scroll) : go(curr+o.scroll);
            });

        if(o.auto)
            setInterval(function() {
                go(curr+o.scroll);
            }, o.auto+o.speed);

        function vis() {
            return li.slice(curr).slice(0,v);
        };

        function go(to) {
						selected_image = to;
            if(!running) {

                if(o.beforeStart)
                    o.beforeStart.call(this, vis());

                if(o.circular) {            // If circular we are in first or last, then goto the other end
                    if(to<=o.start-v-1) {           // If first, then goto last
                        ul.css(animCss, -((itemLength-(v*2))*liSize)+"px");
                        // If "scroll" > 1, then the "to" might not be equal to the condition; it can be lesser depending on the number of elements.
                        curr = to==o.start-v-1 ? itemLength-(v*2)-1 : itemLength-(v*2)-o.scroll;
                    } else if(to>=itemLength-v+1) { // If last, then goto first
                        ul.css(animCss, -( (v) * liSize ) + "px" );
                        // If "scroll" > 1, then the "to" might not be equal to the condition; it can be greater depending on the number of elements.
                        curr = to==itemLength-v+1 ? v+1 : v+o.scroll;
                    } else curr = to;
                } else {                    // If non-circular and to points to first or last, we just return.
                    if(to<0 || to>itemLength-v) return;
                    else curr = to;
                }                           // If neither overrides it, the curr will still be "to" and we can proceed.

                running = true;

                ul.animate(
                    animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
                    function() {
                        if(o.afterEnd)
                            o.afterEnd.call(this, vis());
                        running = false;
                    }
                );
                // Disable buttons when the carousel reaches the last/first, and enable when not
                if(!o.circular) {
                    $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
                    $( (curr-o.scroll<0 && o.btnPrev)
                        ||
                       (curr+o.scroll > itemLength-v && o.btnNext)
                        ||
                       []
                     ).addClass("disabled");
                }

            }
            return false;
        };
    });
};
function css(el, prop) {
    return parseInt($.css(el[0], prop)) || 0;
};
function width(el) {
    return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
};
function height(el) {
    return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
};

})(jQuery);
// END jCAROUSEL //

var convo_show_page_load = function(){
	$(function(){		
		$(".follow").live("click", function(){
			check_auth();
			return false;
		});
		image_viewer();
	});
	
}

function load_awards() {
   $(".viewer .award_widget div.award").css("cursor","pointer").click(function(){
      var selected_award = $(this);
      var selected_form = $(this).find(".award_form");
      var c = $(".award_widget .remaining").html();
      
      if(c == "0") {
         alert("You've used your daily quota of awards.");
         $(".award_widget .remaining").css({color: "darkred", fontWeight:"bold"});
       }
       else
       {
         $(".award_widget .status").show();
         $.post(selected_form.attr("action"), selected_form.serialize(), function(vote_count){
		         selected_award.find(".count .total").html(vote_count);
		         c = c - 1;
		         $(".award_widget .remaining").html(c);
		         $(".award_widget .status").hide();
         });
       }

    }).hover(function(){
      $(this).addClass("hilited").find(".upvoter").show();
      $(".award_icon").css({opacity:1});

    }, function(){
      $(this).removeClass("hilited").find(".upvoter").hide();
      $(".award_icon").css({opacity:.5});
    });
    $(".remaining_label").hover(function(){
      $(".remaining_info").fadeIn(200);
    },function(){
      $(".remaining_info").fadeOut(100);
    });
	 $(".award_badge").hover(function(){
				$(this).parent().next().html($(this).attr("rel"));
		},function(){
				$(this).parent().next().html("&nbsp;");
		});
}

/* 
 * Auto Expanding Text Area (1.2.2)
 * by Chrys Bader (www.chrysbader.com)
 * chrysb@gmail.com
 *
 * Version 1.2.3 update
 * by Richard Vallee
 * richardvallee@gmail.com
 *
 * Special thanks to:
 * Jake Chapa - jake@hybridstudio.com
 * John Resig - jeresig@gmail.com
 *
 * Copyright (c) 2008 Chrys Bader (www.chrysbader.com)
 * Licensed under the GPL (GPL-LICENSE.txt) license. 
 *
 *
 * NOTE: This script requires jQuery to work.  Download jQuery at www.jquery.com
 *
 */
 
(function(jQuery) {
		  
	var self = null;
 
	jQuery.fn.autogrow = function(o)
	{	
		return this.each(function() {
			new jQuery.autogrow(this, o);
		});
	};
	

    /**
     * The autogrow object.
     *
     * @constructor
     * @name jQuery.autogrow
     * @param Object e The textarea to create the autogrow for.
     * @param Hash o A set of key/value pairs to set as configuration properties.
     * @cat Plugins/autogrow
     */
	
	jQuery.autogrow = function (e, o)
	{
		this.options		  	= o || {};
		this.dummy			  	= null;
		this.interval	 	  	= null;
		this.line_height	  	= this.options.lineHeight || parseInt(jQuery(e).css('line-height'));
		this.min_height		  	= this.options.minHeight || parseInt(jQuery(e).css('min-height'));
		this.max_height		  	= this.options.maxHeight || parseInt(jQuery(e).css('max-height'));;
		this.textarea		  	= jQuery(e);
		this.expand_tolerance	= (!isNaN(this.options.expandTolerance) && this.options.expandTolerance > 0) ? this.options.expandTolerance : 4;
		
		if(isNaN(this.line_height))
		  this.line_height = 0;
		
		// Only one textarea activated at a time, the one being used
		this.init();
	};
	
	jQuery.autogrow.fn = jQuery.autogrow.prototype = {
    	autogrow: '1.2.3'
  	};
	
 	jQuery.autogrow.fn.extend = jQuery.autogrow.extend = jQuery.extend;
	
	jQuery.autogrow.fn.extend({
		init: function() {			
			var self = this;			
			this.textarea.css({overflow: 'hidden', display: 'block'});
			this.textarea.bind('focus', function() { self.startExpand() } ).bind('blur', function() { self.stopExpand() });
			this.checkExpand();	
		},
						 
		startExpand: function() {				
		  var self = this;
			this.interval = window.setInterval(function() {self.checkExpand()}, 400);
		},
		
		stopExpand: function() {
			clearInterval(this.interval);	
		},
		
		checkExpand: function() {
			if (this.dummy == null) {
				this.dummy = jQuery('<div></div>');
				this.dummy.css({
					'font-size'  : this.textarea.css('font-size'),
					'font-family': this.textarea.css('font-family'),
					'width'      : this.textarea.css('width'),
					'padding'    : this.textarea.css('padding'),
					'line-height': this.line_height + 'px',
					'overflow-x' : 'hidden',
					'position'   : 'absolute',
					'top'        : 0,
					'left'		 : -9999
					}).appendTo('body');
			} else {
				// If the dummy was already created, show it as it is hidden after expansion
				this.dummy.show();
			}
			
			// Strip HTML tags
			var html = this.textarea.val().replace(/(<|>)/g, '');
			
			// IE is different, as per usual
			if (jQuery.browser.msie) {
				html = html.replace(/\n/g, '<BR/>new');
			}
			else {
				html = html.replace(/\n/g, '<br/>new');
			}
			
			if (this.dummy.html() != html) {
				this.dummy.html(html);	
				if (this.max_height > 0 && (this.dummy.height() + (this.expand_tolerance*this.line_height) > this.max_height)) {
					this.textarea.css('overflow-y', 'auto');
					if (this.textarea.height() < this.max_height) {
						this.textarea.animate({height: (this.max_height + (this.expand_tolerance*this.line_height)) + 'px'}, 100);	
					}
				}
				else {
					this.textarea.css('overflow-y', 'hidden');
					if (this.textarea.height() < this.dummy.height() + (this.expand_tolerance*this.line_height) || (this.dummy.height() < this.textarea.height())) {	
						if (this.dummy.height() < this.min_height) {
							this.textarea.animate({height: (this.min_height + (this.expand_tolerance*this.line_height)) + 'px'}, 100);	
						} else {
							this.textarea.animate({height: (this.dummy.height() + (this.expand_tolerance*this.line_height)) + 'px'}, 100);	
						}
					}
				}
			}

			// Hide the dummy, as otherwise it overflows the body when the content is long
			this.dummy.hide();
		}
						 
	 });
})(jQuery);





function check_is_url(){
	var remote_url = $(".url_input").attr("value");
	if(remote_url.beginsWith("http://")){
		$(".poster").append('<img class="loading_anim" src="/images/img/feed_item_loading.gif">');
		import_from_url(remote_url);
	} else {
		alert("not a link");
	}
}

function clear_input(input_obj, value){
	if(input_obj.attr("value") == value) {
		input_obj.attr("value", "");
	}
}

function import_from_url(remote_url){
	
	$("#link_excerpt").html("");
	
	function updateElement(element, new_value){
		element.show().html(new_value);
	}
	
	// function updateExcerpt(new_value){
	// 	$("#link_excerpt").show().html(new_value);
	// }
	
	$.getJSON("/import_from_url", {url:remote_url},function(result){
		
		build_thumbnails(result.image_thumbs);
		
		// INPLACE EDITORS - CAN BE DRIED UP YO.
		$("#link_excerpt").html(result.excerpt).click(function(){
			$(this).hide();
			var excerpt_text = $(this).html().trim();
			$("#link_excerpt_editor").show().select().attr("value",excerpt_text.replace(/<br>/g,"\n")).focus().blur(function(){			
				updateElement($("#link_excerpt"),$(this).attr("value").replace(/\n/g,"<br>"));
				$(this).hide();
			});
		});
		$("#link_title").html(result.title).click(function(){
			$(this).hide();
			var title_text = $(this).html().trim();
			$("#link_title_editor").show().select().attr("value",title_text).focus().blur(function(){			
				updateElement($("#link_title"),$(this).attr("value"));
				$(this).hide();
			}).bind("keypress", function(e) {
					
		            if (e.keyCode == 13) {		
						updateElement($("#link_title"),$(this).attr("value"));
						$(this).hide();
						return false;
					}
				});
		});
		//$("#edit_option_title").attr("value", result.title);
		//$("#link_summary").html(result.summary);
		$("#link_summary").html(result.summary).click(function(){
			$(this).hide();
			var summary_text = $(this).html().trim();
			$("#link_summary_editor").show().select().attr("value",summary_text).focus().blur(function(){			
				updateElement($("#link_summary"),$(this).attr("value"));
				$(this).hide();
			}).bind("keypress", function(e) {
		            if (e.keyCode == 13) {		
						updateElement($("#link_summary"),$(this).attr("value"));
						$(this).hide();
						return false;
					}
				});
		});
		//$("#edit_option_summary").attr("value", result.summary);
		var domain_part = remote_url.split('/')[2].split(".").join("."); //[-2..-1].join(".") || url
		if(domain_part == "") {domain_part = remote_url}
		$("#link_domain").html(domain_part).attr("href",remote_url);
		// ASSIGN HIDDEN INPUTS
		$("#post_link .hidden_convo_title").attr("value",result.title);
		$("#post_link .hidden_convo_summary").attr("value",result.summary);
		
	});
}

var image_count;
var max_thumbs;
var thumb_count;
var thumbs_array;
var images_array;
var images_select;
function thumbs_init() {
	image_count = 0;
	max_thumbs = 25;
	thumb_count = 0;
	thumbs_array = new Array();
	images_array = new Array();
	images_select = new Array();
}

function build_thumbnails(image_thumbs){
	thumbs_init();
	image_count = image_thumbs.length;
	if (image_count > max_thumbs) {
		image_count = max_thumbs;
	}
	var thumbs_html = "";
	
	for ( var i=0, len=image_count; i<len; ++i ){

		var thumb = new Image();
		
		var img_url = image_thumbs[i];
		thumb.src = img_url;
		thumbs_array.push(thumb);
		thumb = "";
	}
	// PROVIDES TIME FOR IMAGE DOWNLOAD - NEEDED FOR DETERMINING THE WIDTH/HEIGHT
	setTimeout("process_thumbs()", 2200);
	
}

function process_thumbs() {
	$("form#post_link").slideDown(300, function(){
		$(".loading_anim").remove();
	});
	for ( var i=0, len=image_count; i<len; ++i ){
		var thumb = thumbs_array[i];
		var img_url = thumb.src;
		if(thumb.height > 90 && thumb.width > 120){
			
			if($.inArray(img_url, images_select) == -1){
				images_array.push('<li><img src="' + img_url + '" /></li>');
				images_select.push(img_url);
			}	
		}
	}
	thumb_count = images_array.length;
	if(images_array.length > 0){
		
		$("#thumb_wrapper").show();
		update_thumb_count();
		thumbs_html = images_array.join("");
		$("#thumb_selector ul").prepend(thumbs_html);
		$("#thumb_wrapper").show();
		$("#post_link .hidden_convo_thumb").attr("value", images_select[0]);
		$("#thumb_selector").jCarouselLite({
		  btnNext: ".next_thumb",
		  btnPrev: ".prev_thumb",
			visible: 1,
			scroll: 1,
			circular: false,
			default_width: 380,
			default_height: 252,
			auto: false,
			afterEnd: function(){
				thumb = images_select[selected_image];
				update_thumb_count();
				$("#post_link .hidden_convo_thumb").attr("value", thumb);
				return false;
			}
		});
	} else
	{
		$("#post_link .hidden_convo_thumb").attr("value", "");
		$("#thumb_wrapper").hide();
	}
}

function update_thumb_count(){
	var c = selected_image + 1;
	$(".thumb_counter").html(c + " of " + thumb_count);
}

function clear_inputs(){
	// $("#thoughts_text_preview").html("");
	$("#thoughts_summary").html("");
	$("#thoughts_title").html("title");
}

function show_live_preview(){

}

function load_thumbnails(){

	// $("iframe#thumb_frame").contents().find("#thumbnail_browser img.thumb").dblclick(function(){
	// 	$("#thoughts_images").append($(this));
	// 	return false;
	// });

}


var iframeLoaded = false;
var thumbCount;
var lastThumbCount;
var checkedOnce = false;
function check_iframe(){

	if($("iframe#thumb_frame").contents().find("#page_loaded").attr("loaded") == "yes") {
		
		if(iframeLoaded == false) {	
			load_thumbnails();
			iframeLoaded = true;
		}
	
	}
	thumbCount = $("iframe#thumb_frame").contents().find("#thumb_count").attr("count");

	if(thumbCount != lastThumbCount && checkedOnce == true) {
		//console.log("thumbcount changed / iframe loaded");
		load_thumbnails();
		
		//lastThumbCount = thumbCount;
	}
	lastThumbCount = thumbCount;
	checkedOnce = true;
}

function prep_for_post() {
	var thumbs = $("iframe#thumb_frame").contents().find("#thumbnail_browser img.thumb");
	var arr = [];
	thumbs.each(function(){
		arr.push($(this).attr("thumb_id"));
	});
	if(arr.length > 0) {
		$(".hidden_thumb_ids").attr("value", arr.join(","));
	}	
	$("#post_thoughts .hidden_convo_title").attr("value", $("#title_input").attr("value"));
}

function load_post_console() {
	
	$(function(){

		//CKEDITOR.instances.conversation_body_editor.setData("");
		// $(".cke_top").hide();
		// $("#title_input").click(function(){
		// 	$(".cke_top").slideUp(150);
		// });
		// $("#post_thoughts iframe").contents().find("body").click(function(){
		// 	$(".cke_top").show();
		// });
		// DEFAULT NOTICES AND CLEARING TEXT INPUTS
		var url_notice = "paste full webpage url";
		// var commentary_notice = "add your commentary";
		var url_input = $(".url_input");
		// var commentary_input = $("#commentary");
		iframeChecker = setInterval("check_iframe()", 1000);
		
		$("#thoughts_images img").live("click",function(){
			$("iframe#thumb_frame").contents().find("#thumbnail_browser .thumbs").prepend($(this).removeClass("selected"));
		});

		
		$("img.close_poster").click(function(){
			$(".post_link").hide(100);
			$(".post_thoughts").hide(100);
		});
		$("#link_share").click(function(){
			if($(".post_link").css("display") == "block") {
				$(".post_link").hide();
			} else {
				$(".post_thoughts").hide();
				//$(".post_media").hide();
				$(".post_link").show(150);
				$("#post_thoughts  input[type='hidden']").attr("disabled", "disabled");
				$("#post_link input[type='hidden']").removeAttr("disabled");
			}
			return false;
		});
		$("#thoughts_share").click(function(){

			$("#add_images_label").toggle(function(){
				$("#thumbnails_outer").slideDown(100);
			},function(){
				$("#thumbnails_outer").slideUp(100);
			});
			if($(".post_thoughts").css("display") == "block") {
				$(".post_thoughts").hide();
			} else {

				$(".post_link").hide();

				clear_inputs();
				$(".post_thoughts").show(150,function(){
					// display_wysiwyg($("#thoughts_text"), $("#thoughts_text_preview"), $("#object_width"), 
					// 				$("#object_height"),  $("#object_align"));
					// $(".wysiwyg").width(666);
					// $(".wysiwyg iframe").width(650);
					// $("#thoughts_text").wysiwyg("setContent", "");
					$("#title_input").attr("value", "");
					$(".hidden_thumb_ids").attr("value", "");
					$("#post_link  input[type='hidden']").attr("disabled", "disabled");
					$("#post_thoughts input[type='hidden']").removeAttr("disabled");
					$("#object_editor select#object_align").val('none');

				

								
				});
			}
			return false;
		});
		
		// commentary_input.autogrow({expandTolerance:1});
		url_input.attr("value", url_notice);
		// commentary_input.attr("value", commentary_notice);
		
		url_input.live("click", function(){
			clear_input($(this), url_notice);
			$(this).removeClass("small_grey_italic");
		}).live("focus", function(){
			clear_input(url_input, url_notice);
		});
		
		// commentary_input.live("click", function(){
		// 	clear_input($(this), commentary_notice);
		// 	$(this).removeClass("big_grey_italic");
		// }).live("focus", function(){
		// 	clear_input(commentary_input, commentary_notice);
		// });
		
		url_input.live('paste', function(e){
			var text = $(this).attr("value");
			$("form#post_link").hide();
			setTimeout("check_is_url()",200);
		});
		
		// LIVE PREVIEW OF COMMENTARY
		// commentary_input.keyup(function(){
		//             $('#commentary_preview').text(commentary_input.attr("value")); //.replace(/\n/g, "<br/>"));
		//         });
		$("#title_input").keyup(function(){
			//show_live_preview();
			$("#thoughts_title").html($(this).attr("value"));	
		});

		
		// HANDLE FORMS -- SUBMISSION

		$("#post_thoughts input[type='text']").bind("keypress", function(e) {
            if (e.keyCode == 13) {
                 return false;
            }
         });
		// $("#save_draft").click(function(){
		// 	if($("#title_input").attr("value").trim() == "") {
		// 		alert("Why no title?");
		// 		return false; 
		// 	}
		// 	alert($("#post_thoughts iframe").contents().find("body").html());
		// 	return false;
		// 	show_working("saving draft ...");
		// 	prep_for_post();
		// 	$.post($("#post_thoughts").attr("action"), $("#post_thoughts").serialize(), function(message){
		// 		alert(message);
		// 	});
		// 	return false;	
		// });
		$("#post_thoughts").submit(function(){
			if($("#title_input").attr("value").trim() == "") {
				alert("Why no title?");
				return false; 
			}
			show_working("saving convo ...");
			prep_for_post();
		});
		
		$("#post_link").submit(function(){
			show_working("saving convo ...");
			$("#link_title_editor").blur();
			if($("#no_thumb:checked").val() != null){
				$(".hidden_convo_thumb","#post_link").attr("value", "");
			}
			$(".hidden_convo_summary","#post_link").attr("value", $("#link_summary").text());
			
			$(".hidden_convo_title","#post_link").attr("value", $("#link_title").html());

			// var cm = $("#commentary").attr("value");
			// 			if(cm == commentary_notice){cm = ""}
			// 			$(".hidden_convo_commentary","#post_link").attr("value", cm);
			$(".hidden_convo_excerpt","#post_link").attr("value", $("#link_excerpt").html());
			$(".hidden_convo_url","#post_link").attr("value", url_input.attr("value"));
			// return false;
		});
		function is_link(){
			if($(".post_link").css("display") == "block") {
				return true;
			}
			return false;
		}
		
		// EDIT OPTIONS MODAL
		$(".show_edit_options").click(function(){

			if (is_link() ==  false) {
				$("#edit_option_title").hide();
				$("#edit_option_title_label").hide();
			}
			else 
			{
				$("#edit_option_title").show();
				$("#edit_option_title_label").show();
			}
			$("#edit_options_modal").jqm().jqmShow();
		});
		$("#close_modal").click(function(){
			$("#edit_options_modal").jqmHide();
		});
		$("#update_options").click(function(){
			var tags = $("#edit_option_tags").attr("value");
			var summary = $("#edit_option_summary").attr("value");
			if (is_link ==  true) {
				//var title = $("#edit_option_title").attr("value");
				//$(".hidden_convo_title").attr("value",title);
				$("#link_title").html(title);
			}
			
			$(".hidden_convo_tags").attr("value",tags);
			$(".hidden_convo_summary").attr("value",summary);
			$(".link_summary").html(summary);
			var privacy = $("input[@name='privacy']:checked").val();
			$(".hidden_convo_access").attr("value",privacy);
			$("#edit_options_modal").jqmHide();
		});
		//$("#thoughts_share").click();

	});
}

/* http://keith-wood.name/gChart.html
   Google Chart interface for jQuery v1.3.1.
   See API details at http://code.google.com/apis/chart/.
   Written by Keith Wood (kbwood{at}iinet.com.au) September 2008.
   Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and 
   MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. 
   Please attribute the author if you use it. */

/* Request a chart from Google charts.
   $('div selector').gchart({type: 'pie', series: [$.gchart.series([101, 84])]});
*/

(function($) { // Hide scope, no $ conflict

/* Google Charting manager. */
function GChart() {
	this._defaults = {
		width: 0, // Width of the chart
		height: 0, // Height of the chart
		format: 'png', // Returned format: png, gif
		usePost: false, // True to POST instead of GET - for larger charts with more data
		margins: null, // The minimum margins (pixels) around the chart:
			// all or [left/right, top/bottom] or [left, right, top, bottom]
		title: '', // The title of the chart
		titleColor: '', // The colour of the title
		titleSize: 0, // The font size of the title
		opacity: 0, // Make the entire chart semi-transparent (0.0-1.0 or 0-100)
		backgroundColor: null, // The background colour for the entire image
		chartColor: null, // The background colour for the chart area
		legend: '', // The location of the legend: top, topVertical,
			// bottom, bottomVertical, left, right, or '' for none
		legendOrder: 'normal', // The order of items within a legend: normal, reverse, automatic
		legendSize: null, // The minimum size (pixels) of the legend: [width, height]
		type: 'pie3D', // Type of chart requested: line, lineXY, sparkline,
			// barHoriz, barVert, barHorizGrouped, barVertGrouped, pie, pie3D (default),
			// pieConcentric, venn, scatter, radar, radarCurved, map, meter, qrCode, formula
		encoding: '', // Type of data encoding: text (default), scaled, simple, extended
		series: [this.series('Hello World', [60, 40])], // Details about the values to be plotted
		visibleSeries: 0, // The number of series that are directly displayed, 0 for all
		dataLabels: [], // Labels for the values across all the series
		axes: [], // Definitions for the various axes, each entry is either
			// a string of the axis name or a GChartAxis object
		ranges: [], // Definitions of ranges for the chart, each entry is an object with
			// vertical (boolean), color (string), start (number, 0-1),
			// and end (number, 0-1) attributes
		markers: [], // Definitions of markers for the chart, each entry is an object with
			// shape (arrow, circle, cross, diamond, down, flag, horizontal,
			// number, plus, sparkfill, sparkline, square, text, vertical),
			// color (string), series (number), item (number), size (number),
			// priority (number), text (string), positioned (boolean),
			// placement (string or string[]), offsets (number[2])
		icons: [], // Definitions of dynamic icons for the chart, each entry is an object with
			// name (string), data (string), series (number), item (number), zIndex (number),
			// position (number[2]), offsets (number[2])
		minValue: 0, // The minimum value of the data, $.gchart.calculate to calculate from data
		maxValue: 100, // The maximum value of the data, $.gchart.calculate to calculate from data
		gridSize: [], // The x and y spacings between grid lines
		gridLine: [], // The line and gap lengths for the grid lines
		gridOffsets: [], // The x and y offsets for the grid lines
		extension: {}, // Any custom extensions to the Google chart parameters
		// Bar charts -------------
		barWidth: null, // The width of each bar (pixels) or 'a' for automatic or 'r' for relative
		barSpacing: null, // The space (pixels) between bars in a group
		barGroupSpacing: null, // The space (pixels) between groups of bars
		barZeroPoint: null, // The position (0.0 to 1.0) of the zero-line
		// Pie charts -------------
		pieOrientation: 0, // The angle (degrees) of orientation from the positive x-axis
		// Maps -------------------
		mapArea: 'world', // The general area to show: world,
			// africa, asia, europe, middle_east, south_america, usa
		mapRegions: [], // List of country/state codes to plot
		mapDefaultColor: 'bebebe', // The colour for non-plotted countries/states
		mapColors: ['blue', 'red'], // The colour range for plotted countries/states
		// QR Code ----------------
		qrECLevel: null, // Error correction level: low, medium, quarter, high
		qrMargin: null, // Margin (squares) around QR code, default is 4
		// Callback
		onLoad: null, // Function to call when loaded
		provideJSON: false // True to return JSON description of chart with the onLoad callback
	};
};

/* The name of the data property that holds the instance settings. */
var PROP_NAME = 'gChart';
/* Translations of text colour names into chart values. */
var COLOURS = {aqua: '008080', black: '000000', blue: '0000ff', fuchsia: 'ff00ff', gray: '808080',
	green: '008000', grey: '808080', lime: '00ff00', maroon: '800000', navy: '000080',
	olive: '808000', orange: 'ffa500', purple: '800080', red: 'ff0000', silver: 'c0c0c0',
	teal: '008080', transparent: '00000000', white: 'ffffff', yellow: 'ffff00'};
/* Mapping from plugin chart types to Google chart types. */
var CHART_TYPES = {line: 'lc', lineXY: 'lxy', sparkline: 'ls',
	barHoriz: 'bhs', barVert: 'bvs', barHorizGrouped: 'bhg', barVertGrouped: 'bvg',
	pie: 'p', pie3D: 'p3', pieConcentric: 'pc', venn: 'v', scatter: 's',
	radar: 'r', radarCurved: 'rs', map: 't', meter: 'gom', qrCode: 'qr', formula: 'tx',
	lc: 'lc', lxy: 'lxy', ls: 'ls', bhs: 'bhs', bvs: 'bvs', bhg: 'bhg', bvg: 'bvg',
	p: 'p', p3: 'p3', pc: 'pc', v: 'v', s: 's',
	r: 'r', rs: 'rs', t: 't', gom: 'gom', qr: 'qr', tx: 'tx'};
/* Mapping from plugin shape types to Google chart shapes. */
var SHAPES = {annotation: 'A', arrow: 'a', candlestick: 'F', circle: 'o', cross: 'x',
	diamond: 'd', down: 'v', errorbar: 'E', flag: 'f', financial: 'F', horizbar: 'H',
	horizontal: 'h', number: 'N', plus: 'c', rectangle: 'C', sparkfill: 'B',
	sparkline: 'D', sparkslice: 'b', square: 's', text: 't', vertical: 'V'};
/* Mapping from plugin priority names to chart priority codes. */
var PRIORITIES = {behind: -1, below: -1, normal: 0, above: +1, inFront: +1, '-': -1, '+': +1};
/* Mapping from plugin gradient names to angles. */
var GRADIENTS = {diagonalDown: -45, diagonalUp: 45, horizontal: 0, vertical: 90,
	dd: -45, du: 45, h: 0, v: 90};
/* Mapping from plugin alignment names to chart alignment codes. */
var ALIGNMENTS = {left: -1, center: 0, centre: 0, right: +1, l: -1, c: 0, r: +1};
/* Mapping from plugin drawing control names to chart drawing control codes. */
var DRAWING = {line: 'l', ticks: 't', both: 'lt'};
/* Mapping from legend order names to chart drawing control codes. */
var ORDERS = {normal: 'l', reverse: 'r', automatic: 'a', '': '', l: 'l', r: 'r', a: 'a'};
/* Mapping from marker placement names to chart drawing placement codes. */
var PLACEMENTS = {barbase: 's', barcenter: 'c', barcentre: 'c', bartop: 'e', bottom: 'b',
	center: 'h', centre: 'h', left: 'l', middle: 'v', right: 'r', top: 't',
	b: 'b', c: 'c', e: 'e', h: 'h', l: 'l', r: 'r', s: 's', t: 't', v: 'v'};

/* Characters to use for encoding schemes. */
var SIMPLE_ENCODING = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var EXTENDED_ENCODING = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.';

$.extend(GChart.prototype, {
	/* Class name added to elements to indicate already configured with Google charting. */
	markerClassName: 'hasGChart',
	
	/* Marker value to indicate min/max calculation from data. */
	calculate: -0.123,
	
	/* Possible values for bar width. */
	barWidthAuto: 'a', // Automatic resize to fill
	barWidthRelative: 'r', // Spacings are relative to bars (0.0 - 1.0)
	
	/* Possible values for number format. */
	formatFloat: 'f',
	formatPercent: 'p',
	formatScientific: 'e',
	formatCurrency: 'c',

	/* Override the default settings for all Google chart instances.
	   @param  options  (object) the new settings to use as defaults */
	setDefaults: function(options) {
		extendRemove(this._defaults, options || {});
	},

	/* Create a new data series.
	   @param  label       (string, optional) the label for this series
	   @param  data        (number[]) the data values for this series
	   @param  colour      (string or string[]) the colour(s) for this series
	   @param  fillColour  (string, optional) the fill colour for this series or
	                       (object, optional) fill slice with attributes color and range ('start:end') or
						   (object[], optional) array of above
	   @param  minValue    (number, optional with maxValue) the minimum value for this series
	   @param  maxValue    (number, optional with minValue) the maximum value for this series
	   @param  thickness   (number) the thickness (pixels) of the line for this series
	   @param  segments    (number[2]) the line and gap lengths (pixels) for this series
	   @return  (object) the new series object */
	series: function(label, data, colour, fillColour, minValue, maxValue, thickness, segments) {
		if ($.isArray(label)) { // Optional label
			segments = thickness;
			thickness = maxValue;
			maxValue = minValue;
			minValue = fillColour;
			fillColour = colour;
			colour = data;
			data = label;
			label = '';
		}
		if (typeof colour == 'number') { // Optional colour/fillColour
			segments = maxValue;
			thickness = minValue;
			maxValue = fillColour;
			minValue = colour;
			fillColour = null;
			colour = null;
		}
		if (typeof fillColour == 'number') { // Optional fillColour
			segments = thickness;
			thickness = maxValue;
			maxValue = minValue;
			minValue = fillColour;
			fillColour = null;
		}
		if ($.isArray(maxValue)) { // Optional min/max values
			segments = maxValue;
			thickness = minValue;
			maxValue = null;
			minValue = null;
		}
		return {label: label, data: data || [], color: colour || '',
			fillColor: fillColour, minValue: minValue, maxValue: maxValue,
			lineThickness: thickness, lineSegments: segments};
	},

	/* Load series data from CSV.
	   Include a header row if fields other than data required.
	   Use these names - label, color, fillColor, minValue, maxValue,
	   lineThickness, lineSegmentLine, lineSegmentGap - for series attributes.
	   Data columns should be labelled ynn, where nn is a sequential number.
	   For X-Y line charts, include xnn columns before corresponding ynn.
	   @param  csv  (string or string[]) the series data in CSV format
	   @return  (object[]) the series definitions */
	seriesFromCsv: function(csv) {
		var seriesData = [];
		if (!$.isArray(csv)) {
			csv = csv.split('\n');
		}
		if (!csv.length) {
			return seriesData;
		}
		var xyData = false;
		var sColumns = [];
		var xColumns = [];
		var fields = ['label', 'color', 'fillColor', 'minValue', 'maxValue',
			'lineThickness', 'lineSegmentLine', 'lineSegmentGap'];
		$.each(csv, function(i, line) {
			var cols = line.split(',');
			if (i == 0 && isNaN(parseFloat(cols[0]))) { // Header row
				$.each(cols, function(i, val) {
					if ($.inArray(val, fields) > -1) { // Note the positions of the columns
						sColumns[i] = val;
					}
					else if (val.match(/^x\d+$/)) { // Column with x-coordinate
						xColumns[i] = val;
					}
				});
			}
			else {
				var series = {};
				var data = [];
				var saveX = null;
				$.each(cols, function(i, val) {
					if (sColumns[i]) { // Non-data value
						var pos = $.inArray(sColumns[i], fields);
						series[sColumns[i]] = (pos > 2 ? $.gchart._numeric(val, 0) : val);
					}
					else if (xColumns[i]) { // X-coordinate
						saveX = (val ? $.gchart._numeric(val, -1) : null);
						xyData = true;
					}
					else {
						var y = $.gchart._numeric(val, -1);
						data.push(saveX != null ? [saveX, y] : y);
						saveX = null;
					}
				});
				if (series.lineSegmentLine != null && series.lineSegmentGap != null) {
					series.lineSegments = [series.lineSegmentLine, series.lineSegmentGap];
					series.lineSegmentLine = series.lineSegmentGap = null;
				}
				seriesData.push($.extend(series, {data: data}));
			}
		});
		return (xyData ? this.seriesForXYLines(seriesData) : seriesData);
	},

	/* Load series data from XML. All attributes are optional except point/@y.
	   <data>
	     <series label="" color="" fillColor="" minValue="" maxValue="" lineThickness="" lineSegments="">
	       <point x="" y=""/>
	       ...
	     </series>
	     ...
	   </data>
	   @param  xml  (string or Document) the XML containing the series data
	   @return  (object[]) the series definitions */
	seriesFromXml: function(xml) {
		if ($.browser.msie && typeof xml == 'string') {
			var doc = new ActiveXObject('Microsoft.XMLDOM');
			doc.validateOnParse = false;
			doc.resolveExternals = false;
			doc.loadXML(xml);
			xml = doc;
		}
		xml = $(xml);
		var seriesData = [];
		var xyData = false;
		try {
			xml.find('series').each(function() {
				var series = $(this);
				var data = [];
				series.find('point').each(function() {
					var point = $(this);
					var x = point.attr('x');
					if (x != null) {
						xyData = true;
						x = $.gchart._numeric(x, -1);
					}
					y = $.gchart._numeric(point.attr('y'), -1);
					data.push(x ? [x, y] : y);
				});
				var segments = series.attr('lineSegments');
				if (segments) {
					segments = segments.split(',');
					for (var i = 0; i < segments.length; i++) {
						segments[i] = $.gchart._numeric(segments[i], 1);
					}
				}
				seriesData.push({label: series.attr('label'), data: data,
					color: series.attr('color'), fillColor: series.attr('fillColor'),
					minValue: $.gchart._numeric(series.attr('minValue'), null),
					maxValue: $.gchart._numeric(series.attr('maxValue'), null),
					lineThickness: $.gchart._numeric(series.attr('lineThickness'), null),
					lineSegments: segments});
			});
		}
		catch (e) {
			// Ignore
		}
		return (xyData ? this.seriesForXYLines(seriesData) : seriesData);
	},

	/* Force a value to be numeric.
	   @param  val      (string) the value to convert
	   @param  whenNaN  (number) value to use if not numeric
	   @return  (number) the numeric equivalent or whenNaN if not numeric */
	_numeric: function(val, whenNaN) {
		val = parseFloat(val);
		return (isNaN(val) ? whenNaN : val);
	},

	/* Prepare series for a line XY chart.
	   @param  series  (object[]) the details of the points to plot,
	                   each data value may be an array of two points
	   @return  (object[]) the transformed series
	   @deprecated  in favour of seriesForXYLines */
	lineXYSeries: function(series) {
		return this.seriesForXYLines(series);
	},

	/* Prepare series for a line XY chart.
	   @param  series  (object[]) the details of the points to plot,
	                   each data value may be an array of two points
	   @return  (object[]) the transformed series */
	seriesForXYLines: function(series) {
		var xySeries = [];
		for (var i = 0; i < series.length; i++) {
			var xNull = !$.isArray(series[i].data[0]);
			var xData = (xNull ? [null] : []);
			var yData = [];
			for (var j = 0; j < series[i].data.length; j++) {
				if (xNull) {
					yData.push(series[i].data[j]);
				}
				else {
					xData.push(series[i].data[j][0]);
					yData.push(series[i].data[j][1]);
				}
			}
			xySeries.push($.gchart.series(series[i].label, xData, series[i].color,
				series[i].fillColor, series[i].minValue, series[i].maxValue,
				series[i].lineThickness, series[i].lineSegments));
			xySeries.push($.gchart.series(series[i].label, yData, '',
				series[i].fillColor, series[i].minValue, series[i].maxValue,
				series[i].lineThickness, series[i].lineSegments));
		}
		return xySeries;
	},

	/* Prepare options for a scatter chart.
	   @param  values   (number[][]) the coordinates of the points:
	                    [0] is the x-coord, [1] is the y-coord, [2] is the percentage size
	   @param  labels   (string[]) the labels for the groups (optional)
	   @param  colours  (string[]) the colours for the labels (optional)
	   @param  options  (object) additional settings (optional)
	   @return  (object) the configured options object */
	scatter: function(values, labels, colours, options) {
		if (!$.isArray(labels)) {
			options = labels;
			colours = null;
			labels = null;
		}
		var series = [[], [], []];
		for (var i = 0; i < values.length; i++) {
			series[0][i] = values[i][0];
			series[1][i] = values[i][1];
			series[2][i] = values[i][2] || 100;
		}
		options = options || {};
		if (labels) {
			options.extension = {chdl: labels.join('|')};
		}
		if (colours) {
			colours = $.map(colours, function(v, i) {
				return $.gchart.color(v);
			});
			$.extend(options.extension, {chco: colours.join('|')});
		}
		return $.extend({}, options,
			{type: 'scatter', series: [$.gchart.series(series[0]),
				$.gchart.series(series[1]), $.gchart.series(series[2])]});
	},

	/* Prepare options for a Venn diagram.
	   @param  size1       (number) the relative size of the first circle
	   @param  size2       (number) the relative size of the second circle
	   @param  size3       (number) the relative size of the third circle
	   @param  overlap12   (number) the overlap between circles 1 and 2
	   @param  overlap13   (number) the overlap between circles 1 and 3
	   @param  overlap23   (number) the overlap between circles 2 and 3
	   @param  overlap123  (number) the overlap between all circles
	   @param  options     (object) additional settings (optional)
	   @return  (object) the configured options object */
	venn: function(size1, size2, size3, overlap12, overlap13, overlap23, overlap123, options) {
		return $.extend({}, options || {}, {type: 'venn', series:
			[$.gchart.series([size1, size2, size3, overlap12, overlap13, overlap23, overlap123])]});
	},

	/* Prepare options for a Google meter.
	   @param  text      (string or string[]) the text to show on the arrow (optional)
	   @param  values    (number or number[] or [] of these) the position(s) of the arrow(s)
	   @param  maxValue  (number) the maximum value for the meter (optional, default 100)
	   @param  colours   (string[]) the colours to use for the band (optional)
	   @param  labels    (string[]) labels appearing beneath the meter (optional)
	   @param  styles    (number[][4]) the styles of each series' arrows:
	                     width, dash, space, arrow size (optional)
	   @param  options   (object) additional settings (optional)
	   @return  (object) the configured options object */
	meter: function(text, values, maxValue, colours, labels, styles, options) {
		if (typeof text != 'string' && !$.isArray(text)) {
			options = styles;
			styles = labels;
			labels = colours;
			colours = maxValue;
			maxValue = values;
			values = text;
			text = '';
		}
		if (typeof maxValue != 'number') {
			options = styles;
			styles = labels;
			labels = colours;
			colours = maxValue;
			maxValue = null;
		}
		if (!$.isArray(colours)) {
			options = styles;
			styles = labels;
			labels = colours;
			colours = null;
		}
		if (!$.isArray(labels)) {
			options = styles;
			styles = labels;
			labels = null;
		}
		if (!$.isArray(styles)) {
			options = styles;
			styles = null;
		}
		values = ($.isArray(values) ? values : [values]);
		var multi = false;
		for (var i = 0; i < values.length; i++) {
			multi = multi || $.isArray(values[i]);
		}
		var ss = (multi ? [] : [$.gchart.series(values)]);
		if (multi) {
			for (var i = 0; i < values.length; i++) {
				ss.push($.gchart.series($.isArray(values[i]) ? values[i] : [values[i]]));
			}
		}
		values = ss;
		if (colours) {
			var cs = '';
			$.each(colours, function(i, v) {
				cs += ',' + $.gchart.color(v);
			});
			colours = cs.substr(1);
		}
		if (styles) {
			var ls = ['', ''];
			$.each(styles, function(i, v) {
				v = ($.isArray(v) ? v : [v]);
				ls[0] += '|' + $.gchart.color(v.slice(0, 3).join(','));
				ls[1] += '|' + (v[3] || 15);
			});
			styles = ls[0].substr(1) + ls[1];
		}
		var axis = (labels && labels.length ?  $.gchart.axis('y', labels) : null);
		return $.extend({}, options || {}, {type: 'meter',
			maxValue: maxValue || 100, series: values,
			dataLabels: ($.isArray(text) ? text : [text || ''])},
			(colours ? {extension: {chco: colours}} : {}),
			(axis ? {axes: [axis]} : {}),
			(styles ? {extension: {chls: styles}} : {}));
	},

	/* Prepare options for a map chart.
	   @param  mapArea        (string) the region of the world to show (optional)
	   @param  values         (object) the countries/states to plot -
	                          attributes are country/state codes and values
	   @param  defaultColour  (string) the colour for regions without values (optional)
	   @param  colour         (string or string[]) the starting colour or
	                          gradient colours for rendering values (optional)
	   @param  endColour      (string) the ending colour for rendering values (optional)
	   @param  options        (object) additional settings (optional)
	   @return  (object) the configured options object */
	map: function(mapArea, values, defaultColour, colour, endColour, options) {
		if (typeof mapArea == 'object') { // Optional mapArea
			options = endColour;
			endColour = colour;
			colour = defaultColour;
			defaultColour = values;
			values = mapArea;
			mapArea = 'world';
		}
		if (typeof defaultColour == 'object') {
			options = defaultColour;
			endColour = null;
			colour = null;
			defaultColour = null;
		}
		else if (typeof colour == 'object' && !$.isArray(colour)) {
			options = colour;
			endColour = null;
			colour = null;
		}
		else if (typeof endColour == 'object') {
			options = endColour;
			endColour = null;
		}
		var mapRegions = [];
		var data = [];
		var i = 0;
		for (var name in values) {
			mapRegions[i] = name;
			data[i] = values[name];
			i++;
		}
		return $.extend({}, options || {}, {type: 'map',
			mapArea: mapArea, mapRegions: mapRegions,
			mapDefaultColor: defaultColour || $.gchart._defaults.mapDefaultColor,
			mapColors: ($.isArray(colour) ? colour : [colour || $.gchart._defaults.mapColors[0],
			endColour || $.gchart._defaults.mapColors[1]]),
			series: [$.gchart.series('', data)]});
	},

	/* Prepare options for generating a QR Code.
	   @param  text      (object) the QR code settings or
	                     (string) the text to encode
	   @param  encoding  (string) the encoding scheme (optional)
	   @param  ecLevel   (string) the error correction level: l, m, q, h (optional)
	   @param  margin    (number) the margin around the code (optional)
	   @return  (object) the configured options object */
	qrCode: function(text, encoding, ecLevel, margin) {
		var options = {};
		if (typeof text == 'object') {
			options = text;
		}
		else { // Individual fields
			options = {dataLabels: [text], encoding: encoding,
				qrECLevel: ecLevel, qrMargin: margin};
		}
		options.type = 'qrCode';
		if (options.text) {
			options.dataLabels = [options.text];
			options.text = null;
		}
		return options;
	},

	/* Generate a Google chart color.
	   @param  r  (string) colour name or '#hhhhhh' or
	              (number) red value (0-255)
	   @param  g  (number) green value (0-255) or
	              (number) alpha value (0-255, optional) if r is name
	   @param  b  (number) blue value (0-255)
	   @param  a  (number) alpha value (0-255, optional)
	   @return  (string) the translated colour */
	color: function(r, g, b, a) {
		var checkRange = function(value) {
			if (typeof value == 'number' && (value < 0 || value > 255)) {
				throw 'Value out of range (0-255) ' + value;
			}
		};
		var twoDigits = function(value) {
			return (value.length == 1 ? '0' : '') + value;
		};
		if (typeof r == 'string') {
			checkRange(g);
			return (r.match(/^#([A-Fa-f0-9]{2}){3,4}$/) ? r.substring(1) :
				(COLOURS[r] || r) + (g ? twoDigits(g.toString(16)) : ''));
		}
		checkRange(r);
		checkRange(g);
		checkRange(b);
		checkRange(a);
		return twoDigits(r.toString(16)) + twoDigits(g.toString(16)) +
			twoDigits(b.toString(16)) + (a ? twoDigits(a.toString(16)) : '');
	},

	/* Create a simple linear gradient definition for a background.
	   @param  angle    (string or number) the angle of the gradient from positive x-axis
	   @param  colour1  (string[]) an array of colours or
	                    (string) the starting colour
	   @param  colour2  (string, optional) the ending colour
	   @return  (object) the gradient definition */
	gradient: function(angle, colour1, colour2) {
		var colourPoints = [];
		if ($.isArray(colour1)) {
			var step = 1 / (colour1.length - 1);
			for (var i = 0; i < colour1.length; i++) {
				colourPoints.push([colour1[i], Math.round(i * step * 100) / 100]);
			}
		}
		else {
			colourPoints = [[colour1, 0], [colour2, 1]];
		}
		return {angle: angle, colorPoints: colourPoints};
	},

	/* Create a colour striping definition for a background.
	   @param  angle    (string or number) the angle of the stripes from positive x-axis
	   @param  colours  (string[]) the colours to stripe
	   @return  (object) the stripe definition */
	stripe: function(angle, colours) {
		var colourPoints = [];
		var width = Math.round(100 / colours.length) / 100;
		for (var i = 0; i < colours.length; i++) {
			colourPoints.push([colours[i], width]);
		}
		return {angle: angle, striped: true, colorPoints: colourPoints};
	},

	/* Create a range definition.
	   @param  vertical  (boolean, optional) true if vertical, false if horizontal
	   @param  colour    (string) the marker's colour
	   @param  start     (number) the starting point for the range (0.0 to 1.0)
	   @param  end       (number, optional) the ending point for the range (0.0 to 1.0)
	   @return  (object) the range definition */
	range: function(vertical, colour, start, end) {
		if (typeof vertical == 'string') { // Optional vertical
			end = start;
			start = colour;
			colour = vertical;
			vertical = false;
		}
		return {vertical: vertical, color: colour, start: start, end: end};
	},

	/* Create a marker definition.
	   @param  shape       (string) the marker shape
	   @param  colour      (string) the marker's colour
	   @param  series      (number) the series to which the marker applies
	   @param  item        (number or string or number[2 or 3], optional)
	                       the item in the series to which it applies or 'all' or
	                       'everyn' or 'everyn[s:e]' or [start, end, every]
	   @param  size        (number, optional) the size (pixels) of the marker or
	                       (string) 'thickness:length' for horizline or vertical
	   @param  priority    (string or number, optional) the rendering priority
	   @param  text        (string, optional) the display text for a text type marker
	   @param  positioned  (boolean, optional) true to absolutely position the marker
	   @param  placement   (string or string[], optional) placement locations
	   @param  offsets     (number[2], optional) pixel offsets, horizontal and vertical
	   @return  (object) the marker definition */
	marker: function(shape, colour, series, item, size, priority, text,
			positioned, placement, offsets) {
		if (typeof size == 'boolean') {
			offsets = text;
			placement = priority;
			positioned = size;
			text = null;
			priority = null;
			size = null;
		}
		if ($.isArray(size)) {
			if (typeof size[0] == 'string') {
				offsets = priority;
				placement = size;
			}
			else {
				offsets = size;
				placement = null;
			}
			positioned = null;
			text = null;
			priority = null;
			size = null;
		}
		if (typeof priority == 'boolean') {
			offsets = positioned;
			placement = text;
			positioned = priority;
			text = null;
			priority = null;
		}
		if ($.isArray(priority)) {
			if (typeof priority[0] == 'string') {
				offsets = text;
				placement = priority;
			}
			else {
				offsets = priority;
				placement = null;
			}
			positioned = null;
			text = null;
			priority = null;
		}
		if (typeof text == 'boolean') {
			offsets = placement;
			placement = positioned;
			positioned = text;
			text = null;
		}
		if ($.isArray(text)) {
			if (typeof text[0] == 'string') {
				offsets = positioned;
				placement = text;
			}
			else {
				offsets = text;
				placement = null;
			}
			positioned = null;
			text = null;
		}
		if ($.isArray(positioned)) {
			if (typeof positioned[0] == 'string') {
				offsets = placement;
				placement = positioned;
			}
			else {
				offsets = positioned;
				placement = null;
			}
			positioned = null;
		}
		if ($.isArray(placement) && typeof placement[0] != 'string') {
			offsets = placement;
			placement = null;
		}
		return {shape: shape, color: colour, series: series,
			item: (item || item == 0 ? item : -1), size: size || 10,
			priority: (priority != null ? priority : 0), text: text,
			positioned: positioned, placement: placement, offsets: offsets};
	},

	/* Create a dynamic icon definition.
	   @param  name      (string) the name of the icon to use
	   @param  data      (string) the icon's encoded parameters
	   @param  series    (number, optional) the series to which the icon applies
	   @param  item      (number or string or number[2 or 3], optional)
	                     the item in the series to which it applies or 'all' (default)
	                     or 'everyn' or [start, end, every]
	   @param  zIndex    (number, optional) the z-index (-1.0 to 1.0)
	   @param  position  (number[2], optional) an absolute chart position (0.0 to 1.0)
	   @param  offsets   (number[2], optional) pixel offsets
	   @return  (object) the icon definition */
	icon: function(name, data, series, item, zIndex, position, offsets) {
		if ($.isArray(series)) {
			offsets = item;
			position = series;
			zIndex = null;
			item = null;
			series = null;
		}
		if ($.isArray(zIndex)) {
			offsets = position;
			position = zIndex;
			zIndex = null;
		}
		return {name: name, data: data, series: series || 0, item: (item || item == 0 ? item : 'all'),
			zIndex: zIndex, position: position, offsets: offsets};
	},

	/* Create a number format for a marker.
	   @param  type        (object) containing all these settings or
	                       (string) 'f' for floating point, 'p' for percentage,
	                       'e' for scientific notation, 'c<CUR>' for currency (as specified by CUR)
	   @param  prefix      (string, optional) text appearing before the number
	   @param  suffix      (string, optional - can only be present if prefix is present)
	                       text appearing after the number
	   @param  precision   (number, optional) the number of decimal places
	   @param  showX       (boolean, optional) true to show the x-value, false for the y-value
	   @param  zeroes      (boolean, optional - can only be present if showX is present)
	                       true to display trailing zeroes
	   @param  separators  (boolean, optional - can only be present if showX and zeroes are present)
	                       true to display group separators
	   @return  (string) the format definition */
	numberFormat: function(type, prefix, suffix, precision, showX, zeroes, separators) {
		var format = initNumberFormat(type, prefix, suffix, precision, showX, zeroes, separators);
		return format.prefix + '*' + format.type + format.precision + (format.zeroes ? 'z' : '') +
			(format.separators ? 's' : '') + (format.showX ? 'x' : '') + '*' + format.suffix;
	},

	/* Create an axis definition.
	   @param  axis           (string) the axis position: top, bottom, left, right
	   @param  labels         (string[]) the labels for this axis
	   @param  positions      (number[], optional) the positions of the labels
	   @param  rangeStart     (number, optional with next two) start of range
	   @param  rangeEnd       (number, optional with above) end of range
	   @param  rangeInterval  (number, optional with above) interval between values in the range
	   @param  colour         (string, optional) the axis colour
	   @param  alignment      (string, optional) the labels' alignment
	   @param  size           (number, optional) the labels' size
	   @param  format         (object, optional) the labels' number format options
	   @return  (object) the axis definition */
	axis: function(axis, labels, positions, rangeStart,
			rangeEnd, rangeInterval, colour, alignment, size, format) {
		return new GChartAxis(axis, labels, positions, rangeStart,
			rangeEnd, rangeInterval, colour, alignment, size, format);
	},
	
	/* Determine the region within a chart.
	   @param  event     (MouseEvent) the mouse event contining the cursor position
	   @param  jsonData  (object) the JSON description of the chart
	   @return  (object) the current region details (type, series, and point) or null if none */
	findRegion: function(event, jsonData) {
		if (!jsonData || !jsonData.chartshape) {
			return null;
		}
		var decodeName = function(name) {
			var matches = name.match(/([^\d]+)(\d+)(?:_(\d)+)?/);
			return {type: matches[1], series: parseInt(matches[2]), point: parseInt(matches[3] || -1)};
		};
		var offset = $(event.target).offset();
		var x = event.pageX - offset.left;
		var y = event.pageY - offset.top;
		for (var i = 0; i < jsonData.chartshape.length; i++) {
			var shape = jsonData.chartshape[i];
			switch (shape.type) {
				case 'RECT':
					if (shape.coords[0] <= x && x <= shape.coords[2] &&
							shape.coords[1] <= y && y <= shape.coords[3]) {
						return decodeName(shape.name);
					}
					break;
				case 'POLY':
					if ($.gchart._insidePolygon(shape.coords, x, y)) {
						return decodeName(shape.name);
					}
					break;
			}
		}
		return null;
	},

	/* Determine whether a point is within a polygon.
	   Ray casting algorithm adapted from http://ozviz.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/.
	   @param  coords  (number[]) the polygon coords as [x1, y1, x2, y2, ...]
	   @param  x       (number) the point's x-coord
	   @param  y       (number) the point's y-coord
	   @return  (boolean) true if the point is inside, false if not */
	_insidePolygon: function(coords, x, y) {
		var counter = 0;
		var p1 = [coords[0], coords[1]];
		for (var i = 2; i <= coords.length; i += 2) {
			var p2 = [coords[i % coords.length], coords[i % coords.length + 1]];
			if (y > Math.min(p1[1], p2[1]) && y <= Math.max(p1[1], p2[1])) {
				if (x <= Math.max(p1[0], p2[0]) && p1[1] != p2[1]) {
					var xinters = (y - p1[1]) * (p2[0] - p1[0]) / (p2[1] - p1[1]) + p1[0];
					if (p1[0] == p2[0] || x <= xinters) {
						counter++;
					}
				}
			}
			p1 = p2;
		}
		return (counter % 2 != 0);
	},

	/* Attach the Google chart functionality to a div.
	   @param  target   (element) the containing division
	   @param  options  (object) the settings for this Google chart instance (optional) */
	_attachGChart: function(target, options) {
		target = $(target);
		if (target.is('.' + this.markerClassName)) {
			return;
		}
		target.addClass(this.markerClassName);
		options = options || {};
		var width = options.width || parseInt(target.css('width'), 10);
		var height = options.height || parseInt(target.css('height'), 10);
		var allOptions = $.extend({}, this._defaults, options,
			{width: width, height: height});
		$.data(target[0], PROP_NAME, allOptions);
		this._updateChart(target[0], allOptions);
	},

	/* Reconfigure the settings for a Google charting div.
	   @param  target   (element) the containing division
	   @param  name     (object) the new settings for this Google chart instance or
	                    (string) the name of a single option
	   @param  value    (any, optional) the option's value */
	_changeGChart: function(target, name, value) {
		var options = name || {};
		if (typeof name == 'string') {
			options = {};
			options[name] = value;
		}
		var curOptions = $.data(target, PROP_NAME);
		extendRemove(curOptions || {}, options);
		$.data(target, PROP_NAME, curOptions);
		this._updateChart(target, curOptions);
	},

	/* Remove the Google charting functionality from a div.
	   @param  target  (element) the containing division */
	_destroyGChart: function(target) {
		target = $(target);
		if (!target.is('.' + this.markerClassName)) {
			return;
		}
		target.removeClass(this.markerClassName).empty();
		$.removeData(target[0], PROP_NAME);
	},

	/* Generate the Google charting request with the new settings.
	   @param  options  (object) the new settings for this Google chart instance
	   @return  (string) the Google chart URL */
	_generateChart: function(options) {
		var type = CHART_TYPES[options.type] || 'p3';
		var encoding = this['_' + options.encoding + 'Encoding'] ||
			this['_textEncoding'];
		var labels = '';
		for (var i = 0; i < options.dataLabels.length; i++) {
			labels += '|' + encodeURIComponent(options.dataLabels[i] || '');
		}
		labels = (labels.length == options.dataLabels.length ? '' : labels);
		var legends = '';
		var colours = '';
		var hasColour = false;
		var lines = '';
		for (var i = 0; i < options.series.length; i++) {
			legends += '|' + encodeURIComponent(options.series[i].label || '');
			var clrs = '';
			if (type != 'lxy' || i % 2 == 0) {
				var sep = ',';
				$.each(($.isArray(options.series[i].color) ? options.series[i].color :
						[options.series[i].color]), function(i, v) {
					var colour = $.gchart.color(v || '');
					if (colour) {
						hasColour = true;
					}
					clrs += sep + (colour || '000000');
					sep = '|';
				});
			}
			colours += (hasColour ? clrs : '');
			if (type.substr(0, 1) == 'l' && options.series[i].lineThickness &&
					$.isArray(options.series[i].lineSegments)) {
				lines += '|' + options.series[i].lineThickness + ',' +
					options.series[i].lineSegments.join(',');
			}
		}
		var include = function(name, value) {
			return (value ? name + value : '');
		};
		var addSize = function() {
			options.width = Math.max(10, Math.min(options.width, 1000));
			options.height = Math.max(10, Math.min(options.height, 1000));
			if (type != 't' && options.width * options.height > 300000) {
				options.height = Math.floor(300000 / options.width);
			}
			return (type != 't' ? '&chs=' + options.width + 'x' + options.height :
				'&chs=' + Math.min(440, options.width) + 'x' + Math.min(220, options.height));
		};
		var addMargins = function() {
			var margins = options.margins;
			margins = (margins == null ? null :
				(typeof margins == 'number' ? [margins, margins, margins, margins] :
				(!$.isArray(margins) ? null :
				(margins.length == 4 ? margins :
				(margins.length == 2 ? [margins[0], margins[0], margins[1], margins[1]] : null)))));
			return (!margins ? '' : '&chma=' + margins.join(',') +
				(!options.legendSize || options.legendSize.length != 2 ? '' :
				'|' + options.legendSize.join(',')));
		};
		var qrOptions = function() {
			return include('&choe=', options.encoding) +
				(options.qrECLevel || options.qrMargin ?
				'&chld=' + (options.qrECLevel ? options.qrECLevel.charAt(0) : 'l') +
				(options.qrMargin != null ? '|' + options.qrMargin : '') : '') +
				(labels ? '&chl=' + labels.substr(1) : '');
		};
		var mapOptions = function() {
			var colours = '';
			for (var i = 0; i < options.mapColors.length; i++) {
				colours += ',' + $.gchart.color(options.mapColors[i]);
			}
			return '&chtm=' + (options.mapArea || 'world') +
				'&chd=' + encoding.apply($.gchart, [options]) +
				(options.mapRegions && options.mapRegions.length ?
				'&chld=' + options.mapRegions.join('') : '') +
				'&chco=' + $.gchart.color(options.mapDefaultColor) + colours;
		};
		var pieOptions = function() {
			return (options.pieOrientation ?
				'&chp=' + (options.pieOrientation / 180 * Math.PI) : '') +
				standardOptions();
		};
		var standardOptions = function() {
			return '&chd=' + encoding.apply($.gchart, [options]) +
				(labels ? '&chl=' + labels.substr(1) : '');
		};
		var addBarSizings = function() {
			return (type.substr(0, 1) != 'b' ? '' : (options.barWidth == null ? '' :
				'&chbh=' + options.barWidth +
				(options.barSpacing == null ? '' : ',' + (options.barWidth == $.gchart.barWidthRelative ?
				Math.min(Math.max(options.barSpacing, 0.0), 1.0) : options.barSpacing) +
				(options.barGroupSpacing == null ? '' : ',' + (options.barWidth == $.gchart.barWidthRelative ?
				Math.min(Math.max(options.barGroupSpacing, 0.0), 1.0) : options.barGroupSpacing)))) +
				(options.barZeroPoint == null ? '' : '&chp=' + options.barZeroPoint));
		};
		var addLineStyles = function() {
			return (type.charAt(0) == 'l' && lines ? '&chls=' + lines.substr(1) : '');
		};
		var addColours = function() {
			return (colours.length > options.series.length ? '&chco=' + colours.substr(1) : '');
		};
		var addTitle = function() {
			return include('&chtt=', encodeURIComponent(options.title)) +
			(options.titleColor || options.titleSize ?
			'&chts=' + ($.gchart.color(options.titleColor) || '000000') + ',' +
			(options.titleSize || 20) : '');
		};
		var addBackground = function(area, background) {
			if (background == null) {
				return '';
			}
			if (typeof background == 'string') {
				return area + ',s,' + $.gchart.color(background);
			}
			var bg = area + ',l' + (background.striped ? 's' : 'g') + ',' +
				(GRADIENTS[background.angle] != null ?
				GRADIENTS[background.angle] : background.angle);
			for (var i = 0; i < background.colorPoints.length; i++) {
				bg += ',' + $.gchart.color(background.colorPoints[i][0]) +
					',' + background.colorPoints[i][1];
			}
			return bg;
		};
		var addBackgrounds = function() {
			var opacity = (!options.opacity ? null : '000000' +
				Math.floor(options.opacity / (options.opacity > 1 ? 100 : 1) * 255).toString(16));
			if (opacity && opacity.length < 8) {
				opacity = '0' + opacity;
			}
			var backgrounds = addBackground('|a', opacity) +
				addBackground('|bg', options.backgroundColor) +
				addBackground('|c', options.chartColor);
			return (backgrounds ? '&chf=' + backgrounds.substr(1) : '');
		};
		var addGrids = function() {
			return (options.gridSize.length == 0 ? '' :
				'&chg=' + options.gridSize[0] + ',' + options.gridSize[1] +
				(options.gridLine.length == 0 ? '' :
				',' + options.gridLine[0] + ',' + options.gridLine[1] +
				(options.gridOffsets.length == 0 ? '' :
				',' + options.gridOffsets[0] + ',' + options.gridOffsets[1])));
		};
		var addLegends = function() {
			var order = (options.legendOrder && options.legendOrder.match(/^\d+(,\d+)*$/) ?
				options.legendOrder : ORDERS[options.legendOrder]) || '';
			return (!options.legend || legends.length <= options.series.length ? '' :
				'&chdl=' + legends.substr(1) + include('&chdlp=',
				options.legend.charAt(0) + (options.legend.indexOf('V') > -1 ? 'v' : '') +
				include('|', order)));
		};
		var addExtensions = function() {
			var params = '';
			for (var name in options.extension) {
				params += '&' + name + '=' + options.extension[name];
			}
			return params;
		};
		var format = options.format || 'png';
		return 'http://chart.apis.google.com/chart?' +
			(format != 'png' ? 'chof=' + format + '&' : '') +
			'cht=' + type + addSize() + addMargins() +
			(type == 'qr' ? qrOptions() : (type == 't' ? mapOptions() :
			(type.charAt(0) == 'p' ? pieOptions() : standardOptions()))) +
			addBarSizings() + addLineStyles() + addColours() + addTitle() +
			this._addAxes(options) + addBackgrounds() + addGrids() +
			this._addMarkers(options) + this._addIcons(options) + addLegends() + addExtensions();
	},

	/* Generate axes parameters.
	   @param  options  (object) the current instance settings
	   @return  (string) the axes parameters */
	_addAxes: function(options) {
		var axes = '';
		var axesLabels = '';
		var axesPositions = '';
		var axesRanges = '';
		var axesStyles = '';
		var axesTicks = '';
		for (var i = 0; i < options.axes.length; i++) {
			var axisDef = (typeof options.axes[i] == 'string' ?
				new GChartAxis(options.axes[i]) : options.axes[i]);
			var axis = axisDef.axis().charAt(0);
			axes += ',' + (axis == 'b' ? 'x' : (axis == 'l' ? 'y' : axis));
			if (axisDef.labels()) {
				var labels = '';
				for (var j = 0; j < axisDef.labels().length; j++) {
					labels += '|' + encodeURIComponent(axisDef.labels()[j] || '');
				}
				axesLabels += (labels ? '|' + i + ':' + labels : '');
			}
			if (axisDef.positions()) {
				var positions = '';
				for (var j = 0; j < axisDef.positions().length; j++) {
					positions += ',' + axisDef.positions()[j];
				}
				axesPositions += (positions ? '|' + i + positions : '');
			}
			if (axisDef.range()) {
				var range = axisDef.range();
				axesRanges += '|' + i + ',' + range[0] + ',' + range[1] +
					(range[2] ? ',' + range[2] : '');
			}
			if (axisDef.style() || axisDef.drawing() || axisDef.ticks() || axisDef.format()) {
				var style = axisDef.style() || {};
				var ticks = axisDef.ticks() || {};
				axesStyles += '|' + i +
					(axisDef.format() ? 'N' + this.numberFormat(axisDef.format()) : '') + ',' +
					$.gchart.color(style.color || 'gray') + ',' +
					(style.size || 10) + ',' + 
					(ALIGNMENTS[style.alignment] || style.alignment || 0) +
					(!axisDef.drawing() && !ticks.color ? '' : ',' +
					(DRAWING[axisDef.drawing()] || axisDef.drawing() || 'lt') +
					(ticks.color ? ',' + $.gchart.color(ticks.color) : ''));
			}
			if (axisDef.ticks() && axisDef.ticks().length) {
				axesTicks += '|' + i + ',' + axisDef.ticks().length;
			}
		}
		return (!axes ? '' : '&chxt=' + axes.substr(1) +
			(!axesLabels ? '' : '&chxl=' + axesLabels.substr(1)) +
			(!axesPositions ? '' : '&chxp=' + axesPositions.substr(1)) +
			(!axesRanges ? '' : '&chxr=' + axesRanges.substr(1)) +
			(!axesStyles ? '' : '&chxs=' + axesStyles.substr(1)) +
			(!axesTicks ? '' : '&chxtc=' + axesTicks.substr(1)));
	},

	/* Generate markers parameters.
	   @param  options  (object) the current instance settings
	   @return  (string) the markers parameters */
	_addMarkers: function(options) {
		var markers = '';
		var decodeItem = function(item, positioned) {
			if (item == 'all') {
				return -1;
			}
			if (typeof item == 'string') {
				var matches = /^every(\d+)(?:\[(\d+):(\d+)\])?$/.exec(item);
				if (matches) {
					var every = parseInt(matches[1], 10);
					return (matches[2] && matches[3] ?
						(positioned ? Math.max(0.0, Math.min(1.0, matches[2])) : matches[2]) + ':' +
						(positioned ? Math.max(0.0, Math.min(1.0, matches[3])) : matches[3]) + ':' +
						every : -every);
				}
			}
			if ($.isArray(item)) {
				item = $.map(item, function(v, i) {
					return (positioned ? Math.max(0.0, Math.min(1.0, v)) : v);
				});
				return item.join(':') + (item.length < 2 ? ':' : '');
			}
			return item;
		};
		var escapeText = function(value) {
			return value.replace(/,/g, '\\,');
		};
		for (var i = 0; i < options.markers.length; i++) {
			var marker = options.markers[i];
			var shape = SHAPES[marker.shape] || marker.shape;
			var placement = '';
			if (marker.placement) {
				var placements = $.makeArray(marker.placement);
				for (var j = 0; j < placements.length; j++) {
					placement += PLACEMENTS[placements[j]] || '';
				}
			}
			markers += '|' + (marker.positioned ? '@' : '') + shape +
				('AfNt'.indexOf(shape) > -1 ? escapeText(marker.text || '') : '') + ',' +
				$.gchart.color(marker.color) + ',' +
				marker.series + ',' + decodeItem(marker.item, marker.positioned) +
				',' + marker.size + ',' + (PRIORITIES[marker.priority] != null ?
				PRIORITIES[marker.priority] : marker.priority) +
				(placement || marker.offsets ? ',' + placement +
				':' + (marker.offsets && marker.offsets[0] ? marker.offsets[0] : '') +
				':' + (marker.offsets && marker.offsets[1] ? marker.offsets[1] : '') : '');
		}
		for (var i = 0; i < options.ranges.length; i++) {
			markers += '|' + (options.ranges[i].vertical ? 'R' : 'r') + ',' +
				$.gchart.color(options.ranges[i].color) + ',0,' +
				options.ranges[i].start + ',' +
				(options.ranges[i].end || options.ranges[i].start + 0.005);
		}
		for (var i = 0; i < options.series.length; i++) {
			if (options.series[i].fillColor) {
				var fills = ($.isArray(options.series[i].fillColor) ?
					options.series[i].fillColor : [options.series[i].fillColor]);
				for (var j = 0; j < fills.length; j++) {
					if (typeof fills[j] == 'string') {
						markers += '|b,' + $.gchart.color(options.series[i].fillColor) +
							',' + i + ',' + (i + 1) + ',0';
					}
					else {
						var props = ($.isArray(fills[j]) ? fills[j] : [fills[j].color, fills[j].range]);
						markers += '|B,' + $.gchart.color(props[0]) +
							',' + i + ',' + props[1] + ',0';
					}
				}
			}
		}
		return (markers ? '&chm=' + markers.substr(1) : '');
	},

	/* Generate dynamic icon parameters.
	   @param  options  (object) the current instance settings
	   @return  (string) the icons parameters */
	_addIcons: function(options) {
		var icons = '';
		var decodeItem = function(item) {
			if (item == 'all') {
				return item;
			}
			if (typeof item == 'string') {
				if (/^every(\d+)$/.exec(item)) {
					return item.replace(/every/, 'every,');
				}
			}
			if ($.isArray(item)) {
				return 'range,' + item.join(',');
			}
			return item;
		};
		for (var i = 0; i < options.icons.length; i++) {
			var icon = options.icons[i];
			icons += '|y;s=' + icon.name + ';d=' + icon.data +
				(icon.position ? '' : ';ds=' + icon.series + ';dp=' + decodeItem(icon.item)) +
				(icon.zIndex ? ';py=' + icon.zIndex : '') + 
				(icon.position ? ';po=' + icon.position.join(',') : '') + 
				(icon.offsets ? ';of=' + icon.offsets.join(',') : '');
		}
		return (icons ? '&chem=' + icons.substr(1) : '');
	},

	/* Update the Google charting div with the new settings.
	   @param  target   (element) the containing division
	   @param  options  (object) the new settings for this Google chart instance */
	_updateChart: function(target, options) {
		options._src = this._generateChart(options);
		if (options.usePost) {
			var form = '<form action="http://chart.apis.google.com/chart?' +
				Math.floor(Math.random() * 1e8) + '" method="POST">';
			var pattern = /(\w+)=([^&]*)/g;
			var match = pattern.exec(options._src);
			while (match) {
				form += '<input type="hidden" name="' + match[1] + '" value="' +
					($.inArray(match[1], ['chdl', 'chl', 'chtt', 'chxl']) > -1 ?
					decodeURIComponent(match[2]) : match[2]) + '">';
				match = pattern.exec(options._src);
			}
			form += '</form>';
			target = $(target);
			target.empty();
			var ifr = $('<iframe></iframe>').appendTo(target).css({width: '100%', height: '100%'});
			var doc = ifr.contents()[0]; // Write iframe directly
			doc.open();
			doc.write(form);
			doc.close();
			ifr.show().contents().find('form').submit();	
		}
		else {
			var img = $(new Image()); // Prepare to load chart image in background
			img.load(function() { // Once loaded...
				$(target).find('img').remove().end().append(this); // Replace
				if (options.onLoad) {
					if (options.provideJSON) { // Retrieve JSON details
						$.getJSON(options._src + '&chof=json&callback=?', 
							function(data) {
								options.onLoad.apply(target, [$.gchart._normaliseRects(data)]);
							});
					}
					else {
						options.onLoad.apply(target, []);
					}
				}
			});
			$(img).attr('src', options._src);
		}
	},

	/* Ensure that rectangle coords go from min to max.
	   @param  jsonData  (object) the JSON description of the chart
	   @return  (object) the normalised JSON description */
	_normaliseRects: function(jsonData) {
		if (jsonData && jsonData.chartshape) {
			for (var i = 0; i < jsonData.chartshape.length; i++) {
				var shape = jsonData.chartshape[i];
				if (shape.type == 'RECT') {
					if (shape.coords[0] > shape.coords[2]) {
						var temp = shape.coords[0];
						shape.coords[0] = shape.coords[2];
						shape.coords[2] = temp;
					}
					if (shape.coords[1] > shape.coords[3]) {
						var temp = shape.coords[1];
						shape.coords[1] = shape.coords[3];
						shape.coords[3] = temp;
					}
				}
			}
		}
		return jsonData;
	},

	/* Encode all series with text encoding.
	   @param  options  (object) the settings for this Google chart instance
	   @return  (string) the encoded series data */
	_textEncoding: function(options) {
		var minValue = (options.minValue == $.gchart.calculate ?
			this._calculateMinValue(options.series) : options.minValue);
		var maxValue = (options.maxValue == $.gchart.calculate ?
			this._calculateMaxValue(options.series) : options.maxValue);
		var data = '';
		for (var i = 0; i < options.series.length; i++) {
			data += '|' + this._textEncode(options.series[i], minValue, maxValue);
		}
		return 't' + (options.visibleSeries || '') + ':' + data.substr(1);
	},

	/* Encode values in text format: numeric 0.0 to 100.0, comma separated, -1 for null
	   @param  series    (object) details about the data values to encode
	   @param  minValue  (number) the minimum possible data value
	   @param  maxValue  (number) the maximum possible data value
	   @return  (string) the encoded data values */
	_textEncode: function(series, minValue, maxValue) {
		minValue = (series.minValue != null ? series.minValue : minValue);
		maxValue = (series.maxValue != null ? series.maxValue : maxValue);
		var factor = 100 / (maxValue - minValue);
		var data = '';
		for (var i = 0; i < series.data.length; i++) {
			data += ',' + (series.data[i] == null || isNaN(series.data[i]) ? '-1' :
				Math.round(factor * (series.data[i] - minValue) * 100) / 100);
		}
		return data.substr(1);
	},

	/* Encode all series with scaled text encoding.
	   @param  options  (object) the settings for this Google chart instance
	   @return  (string) the encoded series data */
	_scaledEncoding: function(options) {
		var minValue = (options.minValue == $.gchart.calculate ?
			this._calculateMinValue(options.series) : options.minValue);
		var maxValue = (options.maxValue == $.gchart.calculate ?
			this._calculateMaxValue(options.series) : options.maxValue);
		var data = '';
		var minMax = '';
		for (var i = 0; i < options.series.length; i++) {
			data += '|' + this._scaledEncode(options.series[i], minValue);
			minMax += ',' + (options.series[i].minValue != null ?
				options.series[i].minValue : minValue) +
				',' + (options.series[i].maxValue != null ?
				options.series[i].maxValue : maxValue);
		}
		return 't' + (options.visibleSeries || '') + ':' + data.substr(1) +
			'&chds=' + minMax.substr(1);
	},

	/* Encode values in text format: numeric min to max, comma separated, min - 1 for null
	   @param  series    (object) details about the data values to encode
	   @param  minValue  (number) the minimum possible data value
	   @return  (string) the encoded data values */
	_scaledEncode: function(series, minValue) {
		minValue = (series.minValue != null ? series.minValue : minValue);
		var data = '';
		for (var i = 0; i < series.data.length; i++) {
			data += ',' + (series.data[i] == null || isNaN(series.data[i]) ?
				(minValue - 1) : series.data[i]);
		}
		return data.substr(1);
	},

	/* Encode all series with simple encoding.
	   @param  options  (object) the settings for this Google chart instance
	   @return  (string) the encoded series data */
	_simpleEncoding: function(options) {
		var minValue = (options.minValue == $.gchart.calculate ?
			this._calculateMinValue(options.series) : options.minValue);
		var maxValue = (options.maxValue == $.gchart.calculate ?
			this._calculateMaxValue(options.series) : options.maxValue);
		var data = '';
		for (var i = 0; i < options.series.length; i++) {
			data += ',' + this._simpleEncode(options.series[i], minValue, maxValue);
		}
		return 's' + (options.visibleSeries || '') + ':' + data.substr(1);
	},

	/* Encode values in simple format: single character,
	   banded-62 as A-Za-z0-9, _ for null.
	   @param  series    (object) details about the data values to encode
	   @param  minValue  (number) the minimum possible data value
	   @param  maxValue  (number) the maximum possible data value
	   @return  (string) the encoded data values */
	_simpleEncode: function(series, minValue, maxValue) {
		minValue = (series.minValue != null ? series.minValue : minValue);
		maxValue = (series.maxValue != null ? series.maxValue : maxValue);
		var factor = 61 / (maxValue - minValue);
		var data = '';
		for (var i = 0; i < series.data.length; i++) {
			data += (series.data[i] == null || isNaN(series.data[i]) ? '_' :
				SIMPLE_ENCODING.charAt(Math.round(factor * (series.data[i] - minValue))));
		}
		return data;
	},

	/* Encode all series with extended encoding.
	   @param  options  (object) the settings for this Google chart instance
	   @return  (string) the encoded series data */
	_extendedEncoding: function(options) {
		var minValue = (options.minValue == $.gchart.calculate ?
			this._calculateMinValue(options.series) : options.minValue);
		var maxValue = (options.maxValue == $.gchart.calculate ?
			this._calculateMaxValue(options.series) : options.maxValue);
		var data = '';
		for (var i = 0; i < options.series.length; i++) {
			data += ',' + this._extendedEncode(options.series[i], minValue, maxValue);
		}
		return 'e' + (options.visibleSeries || '') + ':' + data.substr(1);
	},

	/* Encode values in extended format: double character,
	   banded-4096 as A-Za-z0-9-., __ for null.
	   @param  series    (object) details about the data values to encode
	   @param  minValue  (number) the minimum possible data value
	   @param  maxValue  (number) the maximum possible data value
	   @return  (string) the encoded data values */
	_extendedEncode: function(series, minValue, maxValue) {
		minValue = (series.minValue != null ? series.minValue : minValue);
		maxValue = (series.maxValue != null ? series.maxValue : maxValue);
		var factor = 4095 / (maxValue - minValue);
		var encode = function(value) {
			return EXTENDED_ENCODING.charAt(value / 64) +
				EXTENDED_ENCODING.charAt(value % 64);
		};
		var data = '';
		for (var i = 0; i < series.data.length; i++) {
			data += (series.data[i] == null || isNaN(series.data[i]) ? '__' :
				encode(Math.round(factor * (series.data[i] - minValue))));
		}
		return data;
	},

	/* Determine the minimum value amongst the data values.
	   @param  series  (object[]) the series to examine
	   @return  (number) the minimum value therein */
	_calculateMinValue: function(series) {
		var minValue = 99999999;
		for (var i = 0; i < series.length; i++) {
			var data = series[i].data;
			for (var j = 0; j < data.length; j++) {
				minValue = Math.min(minValue, (data[j] == null ? 99999999 : data[j]));
			}
		}
		return minValue;
	},

	/* Determine the maximum value amongst the data values.
	   @param  series  (object[]) the series to examine
	   @return  (number) the maximum value therein */
	_calculateMaxValue: function(series) {
		var maxValue = -99999999;
		for (var i = 0; i < series.length; i++) {
			var data = series[i].data;
			for (var j = 0; j < data.length; j++) {
				maxValue = Math.max(maxValue, (data[j] == null ? -99999999 : data[j]));
			}
		}
		return maxValue;
	}
});

/* The definition of a chart axis.
   @param  axis           (string) the axis position: top, bottom, left, right
   @param  labels         (string[]) the labels for this axis
   @param  positions      (number[], optional) the positions of the labels
   @param  rangeStart     (number, optional with next two) start of range
   @param  rangeEnd       (number, optional with above) end of range
   @param  rangeInterval  (number, optional with above) interval between values in the range
   @param  colour         (string, optional) the axis colour
   @param  alignment      (string, optional) the labels' alignment
   @param  size           (number, optional) the labels' size
   @param  format         (object, optional) the labels' number format options */
function GChartAxis(axis, labels, positions, rangeStart, rangeEnd, rangeInterval,
		colour, alignment, size, format) {
	if (typeof labels == 'number') { // Range instead of labels/positions
		format = alignment;
		size = colour;
		alignment = rangeInterval;
		colour = rangeEnd;
		rangeInterval = rangeStart;
		rangeEnd = positions;
		rangeStart = labels;
		positions = null;
		labels = null;
	}
	else if (!$.isArray(positions)) { // Optional positions
		format = size;
		size = alignment;
		alignment = colour;
		colour = rangeInterval;
		rangeInterval = rangeEnd;
		rangeEnd = rangeStart;
		rangeStart = positions;
		positions = null;
	}
	if (typeof rangeStart == 'string') { // Optional rangeStart/rangeEnd/rangeInterval
		format = colour;
		size = rangeInterval;
		alignment = rangeEnd;
		colour = rangeStart;
		rangeInterval = null;
		rangeEnd = null;
		rangeStart = null;
	}
	if (typeof rangeInterval == 'string') { // Optional rangeInterval
		format = size;
		size = alignment;
		alignment = colour;
		colour = rangeInterval;
		rangeInterval = null;
	}
	if (typeof alignment == 'number') { // Optional alignment
		format = size;
		size = alignment;
		alignment = null;
	}
	this._axis = axis;
	this._labels = labels;
	this._positions = positions;
	this._range = (rangeStart != null ? [rangeStart, rangeEnd, rangeInterval] : null);
	this._color = colour;
	this._alignment = alignment;
	this._size = size;
	this._drawing = null;
	this._tickColor = null;
	this._tickLength = null;
	this._format = format;
}

$.extend(GChartAxis.prototype, {

	/* Get/set the axis position.
	   @param  axis  (string) the axis position: top, bottom, left, right
	   @return  (GChartAxis) the axis object or
	            (string) the axis position (if no parameters specified) */
	axis: function(axis) {
		if (arguments.length == 0) {
			return this._axis;
		}
		this._axis = axis;
		return this;
	},
	
	/* Get/set the axis labels.
	   @param  labels  (string[]) the labels for this axis
	   @return  (GChartAxis) the axis object or
	            (string[]) the axis labels (if no parameters specified) */
	labels: function(labels) {
		if (arguments.length == 0) {
			return this._labels;
		}
		this._labels = labels;
		return this;
	},

	/* Get/set the axis label positions.
	   @param  positions  (number[]) the positions of the labels
	   @return  (GChartAxis) the axis object or
	            (number[]) the axis label positions (if no parameters specified) */
	positions: function(positions) {
		if (arguments.length == 0) {
			return this._positions;
		}
		this._positions = positions;
		return this;
	},

	/* Get/set the axis range.
	   @param  rangeStart     (number) start of range
	   @param  rangeEnd       (number) end of range
	   @param  rangeInterval  (number, optional) interval between values in the range
	   @return  (GChartAxis) the axis object or
	            (number[3]) the axis range start, end, and interval (if no parameters specified) */
	range: function(start, end, interval) {
		if (arguments.length == 0) {
			return this._range;
		}
		this._range = [start, end, interval];
		return this;
	},

	/* Get/set the axis style.
	   @param  colour     (string) the axis colour
	   @param  alignment  (string, optional) the labels' alignment
	   @param  size       (number, optional) the labels' size
	   @return  (GChartAxis) the axis object or
	            (object) the axis style with attributes color, alignment, and size
				(if no parameters specified) */
	style: function(colour, alignment, size) {
		if (arguments.length == 0) {
			return (!this._color && !this._alignment && !this._size ? null :
				{color: this._color, alignment: this._alignment, size: this._size});
		}
		this._color = colour;
		this._alignment = alignment;
		this._size = size;
		return this;
	},

	/* Get/set the axis drawing control.
	   @param  drawing  (string) the drawing control: line, ticks, both
	   @return  (GChartAxis) the axis object or
	            (string) the axis drawing control (if no parameters specified) */
	drawing: function(drawing) {
		if (arguments.length == 0) {
			return this._drawing;
		}
		this._drawing = drawing;
		return this;
	},

	/* Get/set the axis tick style.
	   @param  colour  (string) the colour of the tick marks
	   @param  length  (number, optional) the length of the tick marks,
	                   negative values draw inside the chart or
					   (string, optional) list of lengths, comma-separated
	   @return  (GChartAxis) the axis object or
	            (object) the axis tick style with attributes color and length
				(if no parameters specified) */
	ticks: function(colour, length) {
		if (arguments.length == 0) {
			return (!this._tickColor && !this._tickLength ? null :
				{color: this._tickColor, length: this._tickLength});
		}
		this._tickColor = colour;
		this._tickLength = length;
		return this;
	},

	/* Get/set the number format for the axis.
	   @param  type        (object) containing all these settings or
	                       (string) 'f' for floating point, 'p' for percentage,
	                       'e' for scientific notation, 'c<CUR>' for currency (as specified by CUR)
	   @param  prefix      (string, optional) text appearing before the number
	   @param  suffix      (string, optional - can only be present if prefix is present)
	                       text appearing after the number
	   @param  precision   (number, optional) the number of decimal places
	   @param  showX       (boolean, optional) true to show the x-value, false for the y-value
	   @param  zeroes      (boolean, optional - can only be present if showX is present)
	                       true to display trailing zeroes
	   @param  separators  (boolean, optional - can only be present if showX and zeroes are present)
	                       true to display group separators
	   @return  (GChartAxis) the axis object or
	            (object) the axis format (if no parameters specified) */
	format: function(type, prefix, suffix, precision, showX, zeroes, separators) {
		if (arguments.length == 0) {
			return this._format;
		}
		this._format = initNumberFormat(type, prefix, suffix, precision, showX, zeroes, separators);
		return this;
	}
});

/* Initialise a number format specification. */
function initNumberFormat(type, prefix, suffix, precision, showX, zeroes, separators) {
	if (typeof type == 'object') {
		return type;
	}
	if (typeof prefix == 'number') {
		separators = showX;
		zeroes = precision;
		showX = suffix;
		precision = prefix;
		suffix = '';
		prefix = '';
	}
	if (typeof prefix == 'boolean') {
		separators = precision;
		zeroes = suffix;
		showX = prefix;
		precision = 0;
		suffix = '';
		prefix = '';
	}
	if (typeof suffix == 'number') {
		separators = zeroes;
		zeroes = showX;
		showX = precision;
		precision = suffix;
		suffix = '';
	}
	if (typeof suffix == 'boolean') {
		separators = showX;
		zeroes = precision;
		showX = suffix;
		precision = 0;
		suffix = '';
	}
	if (typeof precision == 'boolean') {
		separators = zeroes;
		zeroes = showX;
		showX = precision;
		precision = 0;
	}
	return {type: type, prefix: prefix || '', suffix: suffix || '', precision: precision || '',
		showX: showX || false, zeroes: zeroes || false, separators: separators || false};
}

/* jQuery extend now ignores nulls!
   @param  target  (object) the object to extend
   @param  props   (object) the new attributes to add
   @return  (object) the updated object */
function extendRemove(target, props) {
	$.extend(target, props);
	for (var name in props) {
		if (props[name] == null) {
			target[name] = null;
		}
	}
	return target;
}

/* Attach the Google chart functionality to a jQuery selection.
   @param  command  (string) the command to run (optional, default 'attach')
   @param  options  (object) the new settings to use for these Google chart instances
   @return  (jQuery object) for chaining further calls */
$.fn.gchart = function(options) {
	var otherArgs = Array.prototype.slice.call(arguments, 1);
	if (options == 'current') {
		return $.gchart['_' + options + 'GChart'].
			apply($.gchart, [this[0]].concat(otherArgs));
	}
	return this.each(function() {
		if (typeof options == 'string') {
			$.gchart['_' + options + 'GChart'].
				apply($.gchart, [this].concat(otherArgs));
		}
		else {
			$.gchart._attachGChart(this, options);
		}
	});
};

/* Initialise the Google chart functionality. */
$.gchart = new GChart(); // singleton instance

})(jQuery);


/* 
 * Auto Expanding Text Area (1.2.2)
 * by Chrys Bader (www.chrysbader.com)
 * chrysb@gmail.com
 *
 * Special thanks to:
 * Jake Chapa - jake@hybridstudio.com
 * John Resig - jeresig@gmail.com
 *
 * Copyright (c) 2008 Chrys Bader (www.chrysbader.com)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 *
 * NOTE: This script requires jQuery to work.  Download jQuery at www.jquery.com
 *
 */
 
(function(jQuery) {
		  
	var self = null;
 
	jQuery.fn.autogrow = function(o)
	{	
		return this.each(function() {
			new jQuery.autogrow(this, o);
		});
	};
	

    /**
     * The autogrow object.
     *
     * @constructor
     * @name jQuery.autogrow
     * @param Object e The textarea to create the autogrow for.
     * @param Hash o A set of key/value pairs to set as configuration properties.
     * @cat Plugins/autogrow
     */
	
	jQuery.autogrow = function (e, o)
	{
		this.options		  	= o || {};
		this.dummy			  	= null;
		this.interval	 	  	= null;
		this.line_height	  	= this.options.lineHeight || parseInt(jQuery(e).css('line-height'));
		this.min_height		  	= this.options.minHeight || parseInt(jQuery(e).css('min-height'));
		this.max_height		  	= this.options.maxHeight || parseInt(jQuery(e).css('max-height'));;
		this.textarea		  	= jQuery(e);
		
		if(this.line_height == NaN)
		  this.line_height = 0;
		
		// Only one textarea activated at a time, the one being used
		this.init();
	};
	
	jQuery.autogrow.fn = jQuery.autogrow.prototype = {
    autogrow: '1.2.2'
  };
	
 	jQuery.autogrow.fn.extend = jQuery.autogrow.extend = jQuery.extend;
	
	jQuery.autogrow.fn.extend({
						 
		init: function() {			
			var self = this;			
			this.textarea.css({overflow: 'hidden', display: 'block'});
			this.textarea.bind('focus', function() { self.startExpand() } ).bind('blur', function() { self.stopExpand() });
			this.checkExpand();	
		},
						 
		startExpand: function() {				
		  var self = this;
			this.interval = window.setInterval(function() {self.checkExpand()}, 400);
		},
		
		stopExpand: function() {
			clearInterval(this.interval);	
		},
		
		checkExpand: function() {
			
			if (this.dummy == null)
			{
				this.dummy = jQuery('<div></div>');
				this.dummy.css({
												'font-size'  : this.textarea.css('font-size'),
												'font-family': this.textarea.css('font-family'),
												'width'      : this.textarea.css('width'),
												'padding'    : this.textarea.css('padding'),
												'line-height': this.line_height + 'px',
												'overflow-x' : 'hidden',
												'position'   : 'absolute',
												'top'        : 0,
												'left'		 : -9999
												}).appendTo('body');
			}
			
			// Strip HTML tags
			var html = this.textarea.val().replace(/(<|>)/g, '');
			
			// IE is different, as per usual
			if ($.browser.msie)
			{
				html = html.replace(/\n/g, '<BR>new');
			}
			else
			{
				html = html.replace(/\n/g, '<br>new');
			}
			
			if (this.dummy.html() != html)
			{
				this.dummy.html(html);	
				
				if (this.max_height > 0 && (this.dummy.height() + this.line_height > this.max_height))
				{
					this.textarea.css('overflow-y', 'auto');	
				}
				else
				{
					this.textarea.css('overflow-y', 'hidden');
					if (this.textarea.height() < this.dummy.height() + this.line_height || (this.dummy.height() < this.textarea.height()))
					{	
						this.textarea.animate({height: (this.dummy.height() + this.line_height) + 'px'}, 100);	
					}
				}
			}
		}
						 
	 });
})(jQuery);