// Inspired by base2 and Prototype
(function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

  // The base Class implementation (does nothing)
  this.Class = function(){};
 
  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
    var _super = this.prototype;
   
    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;
   
    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;
           
            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];
           
            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            var ret = fn.apply(this, arguments);       
            this._super = tmp;
           
            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }
   
    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }
   
    // Populate our constructed prototype object
    Class.prototype = prototype;
   
    // Enforce the constructor to be what we expect
    Class.constructor = Class;

    // And make this class extendable
    Class.extend = arguments.callee;
   
    return Class;
  };
})();


CupMan = {
	wr: null,
	translations: new Map(),
	
	getServiceUrl: function(serviceName, urlConfig) {
		var urlConfig = urlConfig || {
			edition: true
		};
		if (typeof urlConfig["edition"] == "undefined") {
			urlConfig["edition"] = true;
		}
		var ids = this.wr.ids;
		if (!urlConfig.edition) {
			ids = this.wr.otherIds.noEdition;
		}
		
		if (urlConfig.ids != null) {
			ids = urlConfig.ids;
		}
		
		if (ids != "") {
			ids += ",service";
		} else {
			ids = "service";
		}
		var url = "/" + ids + "/" + serviceName;
		
		if (serviceName == "SingleSignOnService" || serviceName == "LoginService" || serviceName == "LogoutService") {
			url = "http://login.cupmanager.net" + url;
		}
		
		return url;
	},
	callService: function(serviceName, options, urlConfig) {
		
		options.url = this.getServiceUrl(serviceName, urlConfig);
		options.type = "POST";
		
		if (options.url.indexOf("http://") == 0) { // login.cupmanager.net
			options.type = "GET";
			options.dataType = "jsonp";
			options.url += "?jsonp=true";
		}
		
		var _success = options.success || function() {};
		var _error = options.error || function() {};
		
		options.success = function(data) {
			var isXML = data["nodeType"] != undefined;
			if (isXML) {
				var root = jQuery(data).children();
				var status = root.attr("status");
				if (status != 0) {
					_error(root, root.text(), status);
				} else {
					_success(root, 0);
				}
			} else {
				var status = data.status;
				if (typeof status == "undefined")
				{
					// HTML
					_success(data);
				} else { 
					if (status != 0) {
						_error(data, data.statusText, status);
					} else {
						_success(data, 0);
					}
				}
			}
		};
		options.error = function(xhr, msg, error) {
			console.log("ERROR", xhr, msg, error);
			_error(null, msg);
		};
		
		jQuery.ajax(options);
	},
	
	
	
	// Translation
	translate: function(key,value) {
		this.translations.put(key,value);
	},
	T: function(key, defaultValue) {
		
		var value = this.translations.get(key) || this.translations.get("Web.Validate." + key) || defaultValue;
		
		if (value == "" || !value) {
			console.log(key, " has no translation");
			return key;
		}
		
		return value;
	},
	
	notify: function(list) {
		for (var i=0; i<list.size(); i++) {
			var func = list.get(i);
			func();
		}
	}
}

jQuery.fn.setFlag = function(flag) {
	var flag = flag.toLowerCase();
	var url = "http://static.cupmanager.net/images/flags_iso/16/"+flag+".png";
	jQuery(this).css("background-image", "url("+url+")");
	jQuery(this).css("background-repeat", "no-repeat");
	if (jQuery(this).css("background-position") == "0% 0%") {
		
		if (jQuery(this).css("font-size") == "10px") {
			jQuery(this).css("background-position", "1px 0px");
		} else {
			jQuery(this).css("background-position", "3px 2px");
		}
	}
	if (jQuery(this).css("font-size") == "10px") {
		jQuery(this).css("padding-left", "17px");
	} else {
		jQuery(this).css("padding-left", "20px");
	}
	jQuery(this).css("border", "1px solid #ccc");
}
jQuery.fn.removeFlag = function() {
	jQuery(this).css("background", "");
	jQuery(this).css("border", "");
	jQuery(this).css("padding-left", "");
}

function convertFlagFields() {
	jQuery("[flag]").each(function() {
		jQuery(this).setFlag( jQuery(this).attr("flag") );
	});
	jQuery(".flag").each(function() {
		jQuery(this).css("padding-left", "20px");
		jQuery(this).css("border", "1px solid #ccc");
	});
	
}
jQuery(function() {
	convertFlagFields();
});


function parsePx(value)
{
	var intValue = parseInt(value.replace("px", ""));
	if( isNaN(intValue) ) {
		return 0;
	} else {
		return intValue;
	}
}


CupMan.Tooltip = Class.extend({
	init: function(source) {
		this.source = source;
		console.log("source:", source);
		
		this.element = jQuery("<div />");
		this.element.addClass("cupmanTip");
		this.element.css("left", "-9999px");
		this.element.css("top", "-9999px");
		
		this.originalMargins = [
			parsePx(this.source.css("margin-top")),
			parsePx(this.source.css("margin-right")),
			parsePx(this.source.css("margin-bottom")),
			parsePx(this.source.css("margin-left"))
		];
		console.log(this.originalMargins);
		this.margins = [
			this.originalMargins[0] - 3,
			this.originalMargins[1] - 3,
			this.originalMargins[2] - 3,
			this.originalMargins[3] - 3
		];
		
		
		this.element.appendTo( jQuery("body") );
		
		this.hideTimer = null;
		
		var thiz = this;
		
		this.element.hover(function() {
			clearTimeout(thiz.hideTimer);
		}, function() {
			thiz._hide();
		});
	},
	
	setMargins: function(margins) {
		this.source.css("margin-top",    margins[0]+"px");
		this.source.css("margin-right",  margins[1]+"px");
		this.source.css("margin-bottom", margins[2]+"px");
		this.source.css("margin-left",   margins[3]+"px");
	},
	
	show: function(where) {
		clearTimeout(this.hideTimer);
		
		this.source.addClass("tooltipped");
		this.source.addClass("tooltipped_holder");
		this.setMargins(this.margins);
		
		where = where || "below";
		var pos = this.source.offset();
		
		this.element.width( Math.max(this.source.width(), 100) );
		this.element.css("left", pos.left);
		this.element.css("top", pos.top + this.source.outerHeight() -3);
		
		
		var thiz = this;
		this.source.bind("mouseout.cupmanTip", function() {
			thiz.hide();
		});
	},
	
	hide: function() {
		this.source.removeClass("tooltipped_holder");
		var thiz = this;
		clearTimeout(this.hideTimer);
		this.hideTimer = setTimeout(function() {
			thiz._hide();
		}, 100);
	},
	
	_hide: function() {
		this.source.unbind("mouseout.cupmanTip");
		clearTimeout(this.hideTimer);
		this.element.unbind();
		this.element.remove();
		
		if (!this.source.is("tooltipped_holder")) {
			this.source.removeClass("tooltipped");
			this.setMargins(this.originalMargins);
		}
	}
});


var likeCounterData = {};

jQuery.fn.likeCounter = function() {
	var contextId = this.attr("contextId");
	var thiz = this;
	var info = this.find(".info");
	var dash= this.find(".dash");
	var link = this.find("a");
	var youLike = readCookie("CupManWebsite_liked_"+contextId) == "yes";
	
	var likes = 0;
	
	this.setLikes = function(count, texts) {
		likeCounterData[contextId] = {likes:count, texts:texts};
		
		if (youLike)  {
			count--;
		}
		if (count < 0) {
			count = 0;
		}
		
		if (youLike) { 
			info.html( texts['you'] );
			
			link.hide();
			dash.hide();
		} else {
			link.show();
			dash.show();
			
			info.html( texts['text'] );
			console.log("likes:",count);
			if (count == 0) {
				info.html("");
				dash.hide();
			}
			
		}
	};
	
	
	if ( likeCounterData[contextId] ) {
		data = likeCounterData[contextId];
		likes = data["likes"];
		
		thiz.setLikes(likes, data["texts"]);
	} else {
		CupMan.callService("website.LikeService", {
			data: {
				contextId: contextId,
				readOnly: true
			},success: function(result) {
				likes = result['likes'];
				thiz.setLikes(likes, result['texts']);
				
			}
		});
	}
	link.click(function() {
		
		CupMan.callService("website.LikeService", {
			data: {
				contextId: contextId
			},
			success: function(result) {
				youLike = true;
				likes = result['likes'];
				thiz.setLikes(likes, result['texts']);
				
				createCookie("CupManWebsite_liked_"+contextId, "yes", 10000);
			}
		});
		
		return false;
	});
};

/////////////////////////////////////////////////////
////////////////////   O L D   //////////////////////
/////////////////////////////////////////////////////





function popUp(URL)
{
	day = new Date();
	id = day.getTime();
	eval("page" + id + " = window.open(URL, '" + id + "', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=1,width=400,height=243');");
}

String.prototype.trim = function() { return this.replace(/(^\s+)/, '').replace(/(\s+$)/, ''); };

if ( typeof console == "undefined" ) {
	console = {
		log: function() {},
		trace: function() {}
	};
}


////////  Disabling elements

jQuery.fn.disable = function(state)
{
	var el = this.find("input,button,submit,select,textarea").andSelf();
	el.each(function() {
		if ( state || typeof state == "undefined" )
	    {
		   jQuery(this).addClass("disabled");
		   jQuery(this).attr("disabled", "disabled");
		   jQuery(this).attr("oldHref", jQuery(this).attr("href"));
		   jQuery(this).removeAttr("href");
		} else {
		   jQuery(this).removeClass("disabled");
		   jQuery(this).removeAttr("disabled");
		   if ( jQuery(this).attr("oldHref") )
		   {
			   jQuery(this).attr("href", jQuery(this).attr("oldHref"));
			   jQuery(this).removeAttr("oldHref");
		   }
		}
	});
}

jQuery.fn.enable = function() {
	this.disable(false);
}

jQuery.fn.disabled = function()
{
	return this.attr("disabled") == "disabled";
}

/////////// Our own ajax wrapper function

/*
jQuery.remote = function(url, data, success, failure, finall, jsonp)
{
	var options = {
	   "cache": false,
	   "data": data,
	   "dataType": jsonp?"jsonp":"xml",
	   "success": function(xmlDoc){
	   		var root = jQuery(xmlDoc).children();
	   		var status = root.attr("status");
	   		
	   		if ( status == 0 )
	   		{
	   			if ( success ) {
					success( root );
	            }
			} else {
	   			if ( failure )
	   				failure( root.text(), status, root );
	   		}
	   		if( finall && !this.finalHasRun)
            {
            	finall();
            	this.finalHasRun = true;
            }
		},
		"error": function(xhr, msg, error){
			if ( failure ) {
				failure( msg );
			}
			if( finall && !this.finalHasRun)
            {
            	finall();
            	this.finalHasRun = true;
            }
		},
		"type": "POST",
		"url": url + ( jsonp?"?jsonp=true&callback=?":"" )
	};
	jQuery.ajax(options);
}

jQuery.callService = function( serviceName, data, success, failure, finalFunc )
{
	 this.remote( "/service/"+ids+"/"+serviceName, data,
		function(root){ // Success
			var status = parseInt( root.attr("status") );
			success( root, status );
		}, function(msg, status, root){ // Failure
			failure( msg, status, root );
		},
		function(a,b,c) {
			if(finalFunc)
				finalFunc(a,b,c);
		}
    );
}

jQuery.callRemoteService = function( host, serviceName, data, success, failure, finalFunc )
{
	var url = "http://"+host+"/service/"+ids+"/"+serviceName;
	url = url + "?jsonp=true&callback=?";
	jQuery.ajax({
		url: url, 
		data: data,
		dataType: "jsonp",
		success: function(obj) {
			if (typeof obj.status == "undefined" || obj.status == 0) {
				success(obj);
			} else {
				failure(obj);
			}
			
			finalFunc(obj);
		},
		error: function(err) {
			console.log("ERROR! ", err);
			failure(err);
			finalFunc(err);
		}
	});
}



jQuery.callJSONService = function( serviceName, data, success, failure )
{
	jQuery.getJSON( "/service/"+serviceName, data, function(data) {
		success(data);
	} );
}
* 
* */

///// UTILS

var ids = "$ids";

function addOnLoad(f) 
{
	jQuery(f);
}

addOnLoad(function(){   
    
    jQuery("a[href*='liveViewer']").live('click',function() {
    	popUp( jQuery(this).attr("href") );
        return false;
    });
});

function stripHash( url )
{
	var index = url.indexOf("#");
	if ( index > -1 )
		return url.substring( 0, index );
	else
		return url;
}

function getBaseUrl() {
	alert("baseUrl() is deprecated.")
}

function getUrl() {
	alert("getUrl()");
}
function getRelUrl() {
	return window.location.pathname;
}
function buildUrl(relUrl) {
	alert("buildurl() is deprecated")
}


///// BOLDify labels for radio buttons
jQuery(function() {
	jQuery("input:radio").click(function() {
		var name = jQuery(this).attr("name");
		var allRadios = jQuery("input:radio[name='"+name+"']");
		
		allRadios.each(function() {
			var id = jQuery(this).attr("id");
			var lbl = jQuery("label[for='"+id+"']");
			if (this.checked)
				lbl.css("font-weight", "bold");
			else
				lbl.css("font-weight", "normal");
		});
	});
	jQuery("input:radio:checked").click();
});


/* COOKIES */
/* Cred: http://www.quirksmode.org/js/cookies.html */
function createCookie(name,value,days) {
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
}

function readCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return null;
}

function eraseCookie(name) {
	createCookie(name,"",-1);
}






/* TABS */
jQuery(function() {
	jQuery(".tabContainer .tabs .tab").click(function() {
		// Select tab
		jQuery(this).closest(".tabs").find(".selectedTab").removeClass("selectedTab");
		jQuery(this).addClass("selectedTab");
		
		// Show correct content
		var index = jQuery(this).closest(".tabs").find(".tab").index( jQuery(this) );
		var container = jQuery(this).closest(".tabContainer");
		container.find(".tabContent").hide();
		
		container.find(".tabContent").eq(index).show();
		
		jQuery(this).find("input:text").focus();
	});
	jQuery(".tabContainer .tabs .tab:first-child").click();
});



//////////// Errors for fields

function clearFieldErrors(_field)
{
	var field = _field;
	if ( typeof field == "object")
    {
    	field = _field;
    } else if ( typeof field == "string")
    {
    	field = jQuery("#"+_field+", ."+_field+"");
    } else {
    	field = jQuery(".field");
    }
    
    if (!field.find(".status").is(".sticky")) {
    	field.find(".statusContainer, .staticStatusContainer").hide();
    }
}

function clearAllFieldErrors(parentEl)
{
	jQuery(parentEl).find(".field").each(function(){
		clearFieldErrors( jQuery(this) );
	});
}

function _showFieldStatus(fieldId, text, clazz)
{
	var field = fieldId;
	if ( typeof fieldId == "string" ) {
	   var field = jQuery("#"+fieldId+", ."+fieldId+"");
	}
	
	if (text == null) {
		clearFieldErrors(field);
	}
	
	if (text == -1) {
		return; // Do nothing!
	}
	
	if (text == "") {
		text = "Unknown error!";
	}
	
	// Ensure that the status field exist correctly
	var statusContainer = jQuery('<div class="statusContainer"><span class="status"></span></div>');
	var status = field.find(".status");
    if (status.length == 0) {
    	field.find("*[validate]").after(statusContainer);
    } else {
    	status = status.eq(0);
    	
    	if (status.parent().is(".staticStatusContainer")) {
    		statusContainer = status.parent();
    	} else {
	    	if (status.parent().is(".statusContainer")) {
	    		status.parent().replaceWith(statusContainer);
	    	} else {
	    		status.replaceWith(statusContainer);
	    	}
    	}
    }
    
    status = statusContainer.find(".status");
    
    if ( clazz == "success" || clazz == "info"  ) {
    	if (statusContainer.is(".staticStatusContainer")) {
	    	statusContainer.removeClass("staticStatusContainerBad");
	    	statusContainer.addClass("staticStatusContainerGood");
    	} else {
	    	statusContainer.removeClass("statusContainerBad");
	    	statusContainer.addClass("statusContainerGood");
    	}
    } else {
    	if (statusContainer.is(".staticStatusContainer")) {
		    statusContainer.removeClass("staticStatusContainerGood");
	    	statusContainer.addClass("staticStatusContainerBad");
    	} else {
    		statusContainer.removeClass("statusContainerGood");
	    	statusContainer.addClass("statusContainerBad");
    	}
    }
    
    if (statusContainer.is(".statusContainer")) {
	    // Move to bottom
	    var input = field.find("*[validate]");
	    if (input.size() == 0)
	    {
	    	input = statusContainer;
	    }
	    
	    statusContainer.css("width", input.outerWidth() - 4 -2 );
    	
    	var leftDiff = statusContainer.position().left - input.position().left;
    	leftDiff -= parseInt( input.css("marginLeft").replace("px", "").replace("auto","0") );
    	
    	var topDiff = statusContainer.position().top - input.position().top;
    	topDiff -= parseInt( input.css("marginTop").replace("px", "").replace("auto","0") );
    	
	    statusContainer.css("marginLeft", -leftDiff +2 +"px");
	    statusContainer.css("marginTop", -topDiff + input.outerHeight() -1);
	}
    
    status.html(text);
    statusContainer.show();
    
    return status;
    
}
function showFieldError(fieldId, text)
{
    return _showFieldStatus(fieldId, text, "error");
}
function showFieldWarning(fieldId, text)
{
    return _showFieldStatus(fieldId, text, "warning");
}
function showFieldInfo(fieldId, text)
{
    return _showFieldStatus(fieldId, text, "info");
}
function showFieldSuccess(fieldId, text)
{
	setTimeout(function(){
		clearFieldErrors(fieldId);
	}, 2500);
    return _showFieldStatus(fieldId, text, "success");
}


/////////// Validation

function emailTest(value)
{
    var valid = (/^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i).test( value );
    return valid ? null : "Web.Validate.Email";
}

function phoneTest(value)
{
	var valid = (/^\+?(\d+(\-? ?| ?\-?)?)+$/i).test( value );
    return valid ? null : "Web.Validate.Phone";
}

function regexTest(value, regex)
{
    var valid = (regex).test( value );
    return valid ? null : "regex";
}

function notEmptyTest(value)
{
    var res = regexTest( value, /[^ ]+/ );
    return res==null ? null : "Web.Validate.NotEmpty";  
}
function nameTest(value)
{
	var res = regexTest( value, /^[^ ]+( [^ ]+)+ *$/ );
	return res==null ? null : "Web.Validate.Name";
}
function numberTest(value)
{
    var res = regexTest( value, /^\d+$/ );
    return res==null ? null : "Web.Validate.Number";
}
function postalTest(value)
{
    var res = regexTest( value, /^\d( ?\d+)+ *$/ );
    return res==null ? null : "Web.Validate.Postal";
}
function birthDateTest(value)
{
    var res = regexTest( value, /^\d{8}$/ );
    return res==null ? null : "Web.Validate.BirthDate";
}
function birthYearTest(value)
{
    var res = regexTest( value, /^\d{4}$/ );
    return res==null ? null : "Web.Validate.BirthYear";
}

function notNegativeTest(value)
{
	var res = parseInt(value) > -1;
    return res ? null : "Web.Validate.NotNegative";
}

function comboTest(value)
{
	var res = notNegativeTest(value);
	return res==null ? null : "Web.Validate.Combo";
}
function categoryComboTest(value)
{
	var res = notNegativeTest(value);
	return res==null ? null : "Web.Validate.CategoryCombo";
}

function positiveTest(value)
{
	var res = parseInt(value) > 0;
    return res ? null : "Web.Validate.Positive";
}

function noTest(value)
{
	return null;
}

function stickyTest(value,el)
{
	if (el.closest(".field").find(".sticky").length == 0)
		return null;
	else
		return -1;
}

function checkedTest(value, el) 
{
	return el.get(0).checked ? null : "checked";
}


function validateField(el, validation) // validation is optional
{
	var validate = el.attr("validate") || validation;
	if ( validate == undefined || validate == "" )
		return true;
	
	var func = validate+"Test";
	
	var notEmptyRes = notEmptyTest(el.val(), el);
	var valid = null;
	if( el.attr("required") == "true" )
	{
		if ( notEmptyRes != null ) // Tom
		{
			valid = notEmptyRes; // D� �r felet "Tom"
		} else {
			valid = eval( func )(el.val(), el); // Annars testar vi vanligt
		}
	} else {
		if ( notEmptyRes != null ) // Tom
		{
			valid = null; // Aldrig fel.
		} else {
			valid = eval( func )(el.val(), el); // Testa vanligt
		}
	}
	
	if( valid == null || el.disabled() )
    {
        clearFieldErrors( el.parents(".field") )
        el.attr("validated", "validated");
        el.addClass("valid");
        el.removeClass("invalid");
    } else {
    	var errorText = CupMan.T(valid);
    	
    	if ( valid == validate )
    	{
	    	if ( el.attr("error") )
	    		errorText = el.attr("error");
    	}
    	showFieldError( el.parents(".field"), errorText );
    	
        el.removeAttr("validated");
        el.addClass("invalid");
        el.removeClass("valid");
    }
    
    return (valid == null || el.disabled());
}

jQuery.fn.validate = function() {
	this.each(function() {
		validateField( jQuery(this) );
	});
}

function validateEvent(e)
{
	var el = jQuery(this);
	validateField(el);
}

jQuery(function(){
	jQuery(".field input[validate], .field select[validate], .field textarea[validate]").keydown( function() {
		// clearFieldErrors( jQuery(this).parents(".field") )
	});
	jQuery(".field input[validate], .field select[validate], .field textarea[validate]").change(validateEvent).keyup(validateEvent);
	
	//	jQuery(".field select[validate]").keyup(validateEvent);
	// jQuery("").change(validateEvent).blur(validateEvent);
	
	
	jQuery(".field *[validate]").blur(function() {
		if (!jQuery(this).closest(".field").find(".status").is(".sticky"))
		{
			clearFieldErrors( jQuery(this).closest(".field") );
		}
	});
	jQuery(".field *[validate]").focus(function() {
		jQuery(this).validate();
	});
	
	
	jQuery(".button-bar, .buttonBar").attr("align", "center");
	
	jQuery("input.numeric").numeric();
	
	// Hitting enter on an input:text should submit form
	/*
	jQuery(".form .field input:text, .form .field input:password, .form .field input:option").keyup(function(e){
		var event = e || window.event;
		var keyCode = event.keyCode;
		if ( keyCode == 13 )
		{
			jQuery(this).parents(".form").find("button.submit, button").eq(0).click();
		}
	});
	*/
	
	jQuery("form[action='']").attr("action", stripHash(window.location.href));
});


///// UTILS

var ids = "$ids";

function stripHash( url )
{
	var index = url.indexOf("#");
	if ( index > -1 )
		return url.substring( 0, index );
	else
		return url;
}

function getBaseUrl() {
	if ( onAjaxSite ) {
		return "#";
	}
	var selfUrl = "$baseUrl/";
	return selfUrl;
}
function getUrl() {
	var url = window.location.href;
	if ( url.indexOf("#") > -1 ) {
		url = url.substr( 0, url.indexOf("#") );
	}
	return url;
}
function getRelUrl() {
	return window.location.pathname;
}
function buildUrl(relUrl) {
	var baseUrl = getBaseUrl().substr(0, getBaseUrl().length-1);
	return baseUrl + relUrl;
}


///// BOLDify labels for radio buttons
jQuery(function() {
	jQuery("input:radio").click(function() {
		var name = jQuery(this).attr("name");
		var allRadios = jQuery("input:radio[name='"+name+"']");
		
		allRadios.each(function() {
			var id = jQuery(this).attr("id");
			var lbl = jQuery("label[for='"+id+"']");
			if (this.checked)
				lbl.css("font-weight", "bold");
			else
				lbl.css("font-weight", "normal");
		});
	});
	jQuery("input:radio:checked").click();
});

if (typeof GControl != "undefined") {

// A TextualZoomControl is a GControl that displays textual "Zoom In"
// and "Zoom Out" buttons (as opposed to the iconic buttons used in
// Google Maps).
function FancyMapControl() {
}
FancyMapControl.prototype = new GControl();

// Creates a one DIV for each of the buttons and places them in a container
// DIV which is returned as our control element. We add the control to
// to the map container and return the element for the map class to
// position properly.
FancyMapControl.prototype.initialize = function(map) {
	this.map = map;
  var container = document.createElement("div");
  
  var image_top = document.createElement("img");
  var image_bottom = document.createElement("img");
  var top = document.createElement("div");
  var bottom = document.createElement("div");
  bottom.style.paddingLeft = "21px";
  
  // Top controls:
  var up = document.createElement("div");
 
  var down = document.createElement("div");
  var left = document.createElement("div");
  var right = document.createElement("div");
  var center = document.createElement("div");
  
  // Zoom controls:
  var zoom_in = document.createElement("div");
  var zoom_out = document.createElement("div");
  
  var zoom_bar = document.createElement("div");
  var zoom_handle = document.createElement("div");
  
  this.setBarStyle_(zoom_bar,zoom_handle,map);
  this.setHandleStyle_(zoom_handle,container,zoom_bar,map);
  
  var thiz = this;
  this.setButtonStyle_(zoom_in,29,88,function(){
  
    var zoom = map.getZoom();
    if( zoom < 19 )
    {
       zoom = zoom + 1;
       map.setZoom(zoom);
       zoom_handle.style.top = thiz._barToContainer(thiz._zoomToPixel(zoom))+"px";
    }
   

  });
  this.setButtonStyle_(zoom_out,30,240,function(){
  	var zoom = map.getZoom();
  	if( zoom > 1 )
  	{
  		zoom = zoom - 1;
    	map.setZoom(zoom);
    	zoom_handle.style.top = thiz._barToContainer(thiz._zoomToPixel(zoom))+"px";
    }
  });
  
  this.setButtonStyle_(up,30,9,function(){
        map.panDirection(0,1);
    
  });
  this.setButtonStyle_(down,30,49,function(){
        map.panDirection(0,-1);
    
  });
  this.setButtonStyle_(left,9,29,function(){
        map.panDirection(1,0);
    
  });
  this.setButtonStyle_(right,50,29,function(){
        map.panDirection(-1,0);
    
  });
  //this.setButtonStyle_(center,22,22);
  
  top.appendChild(up);
  top.appendChild(down);
  top.appendChild(left);
  top.appendChild(right);
 // top.appendChild(center);
    
  bottom.appendChild(zoom_in);
  bottom.appendChild(zoom_out);
  bottom.appendChild(zoom_bar);
  bottom.appendChild(zoom_handle);
 
  image_top.src= "http://static.cupmanager.net/images/map_top.png";
  image_bottom.src= "http://static.cupmanager.net/images/map_zoom.png";                          
                                     
  top.appendChild(image_top);
  bottom.appendChild(image_bottom);
  
  container.appendChild(top);
  container.appendChild(bottom);

  map.getContainer().appendChild(container);
    
  container.style.cursor="url(http://maps.gstatic.com/intl/sv_se/mapfiles/openhand_8_8.cur), default";
    
  return container;
}

// By default, the control will appear in the top left corner of the
// map with 7 pixels of padding.
FancyMapControl.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(7, 7));
}




	FancyMapControl.prototype._screenToBar = function(y) {
		var y = y - jQuery(this.map.getContainer()).offset().top + jQuery("body").scrollTop();
		y = y - 121;
		
		// Keep within 0-125
		if (y < 0) 	 y = 0;
		if (y > 125) y = 125;
		
		return y;
	}
	
	FancyMapControl.prototype._pixelToZoom = function(y)  {
		var dy = 125 / 19;
		var zoom = Math.round(y / dy);
		
		zoom = 19 - zoom;
		
		return zoom;
	}
	
	FancyMapControl.prototype._zoomToPixel = function(zoom) {
		var dy = 125 / 19;
		zoom = 19 - zoom;
		var y = zoom*dy;
		
		return y;
	}
	
	FancyMapControl.prototype._barToContainer = function(y) {
		return 105 + y;
	}


FancyMapControl.prototype.setButtonStyle_ = function(button,x,y,callback) {
  //button.style.border = "1px solid black";
  button.style.position ="absolute";
  button.style.cursor = "pointer";
  button.style.width = "23px";
  button.style.height = "23px";
  button.style.left = x + "px";
  button.style.top = y + "px";

  GEvent.addDomListener( button, "mouseover", function(){
      this.style.backgroundImage="url(http://static.cupmanager.net/images/map_hoover.png)";
  });
  GEvent.addDomListener( button, "mouseout", function(){
       this.style.backgroundImage="none";
  });
  GEvent.addDomListener( button, "click", callback);
}
  
FancyMapControl.prototype.setBarStyle_ = function(bar,handle,map) {
  //bar.style.border = "1px solid black";
  bar.style.position ="absolute";
  bar.style.cursor = "pointer";
  bar.style.width = "10px";
  bar.style.height = "132px";
  bar.style.left = "35px";
  bar.style.top = "112px";

 var thiz = this;
 GEvent.addDomListener( bar, "click", function(point){
   var zoom = thiz._pixelToZoom(thiz._screenToBar(point.clientY));
   map.setZoom(zoom);
   
   handle.style.top = thiz._barToContainer( thiz._zoomToPixel(zoom) ) + "px";
 });
  
}
  
FancyMapControl.prototype.setHandleStyle_ = function(handle,container,zoom_bar,map) {
  
  var thiz = this;
  
  handle.style.position ="absolute";
  handle.style.width = "33px";
  handle.style.height = "19px";
  handle.style.left = "24px";
  handle.style.top = this._barToContainer(this._zoomToPixel(map.getZoom()));

  var dragged = false;
  var lastZoom = map.getZoom();

  var openHand = "url(http://maps.gstatic.com/intl/sv_se/mapfiles/openhand_8_8.cur), default";
  var closedHand = "url(http://maps.gstatic.com/intl/sv_se/mapfiles/closedhand_8_8.cur), default";

  handle.style.cursor = "url(http://maps.gstatic.com/intl/sv_se/mapfiles/openhand_8_8.cur), default";
  handle.style.backgroundImage = "url(http://static.cupmanager.net/images/map_handle.png)";
  
  var thiz = this;
  
  GEvent.addListener( map, "move", function(){
  		if( map.getZoom() != lastZoom )
  		{
  			lastZoom = map.getZoom();
  			handle.style.top = thiz._barToContainer(thiz._zoomToPixel(lastZoom))+"px";
  		}
  		
  });
 
  GEvent.addDomListener( handle, "mousedown", function(){
        handle.style.cursor = closedHand;
        container.style.cursor = closedHand;
        zoom_bar.style.cursor = closedHand;
        dragged = true;
  });
  GEvent.addDomListener( container, "mousemove", function(point){
    if( dragged )
    {
    	var y = thiz._screenToBar(point.clientY);
    	var zoom = thiz._pixelToZoom(y);
    	
    	//map.setZoom(zoom);
        
        handle.style.top = thiz._barToContainer(thiz._zoomToPixel(zoom)) + "px";
    }
  });

  GEvent.addDomListener( container, "mouseout", function(point){
   
    if( dragged && point.clientX+document.documentElement.scrollLeft - jQuery(map.getContainer()).offset().left > 80 )
     {
        handle.style.cursor = openHand;
        container.style.cursor = openHand;
        zoom_bar.style.cursor = "pointer";
        dragged = false;
        handle.style.top = (108+dy*(19-map.getZoom()))+"px";
     }     
  });

  GEvent.addDomListener( container, "mouseup", function(point){
    if( dragged )
    {
       handle.style.cursor = openHand;
       container.style.cursor = openHand;
       zoom_bar.style.cursor = "pointer";
       dragged = false;
      
       	var y = thiz._screenToBar(point.clientY);
    	var zoom = thiz._pixelToZoom(y);
    	
    	map.setZoom(zoom);
        
        handle.style.top = thiz._barToContainer(thiz._zoomToPixel(zoom)) + "px";

    }    
  });

 
   
}

function initialize() {
  if (GBrowserIsCompatible()) {
    var map = new GMap2(document.getElementById("map_canvas"));
    map.setCenter(new GLatLng(37.441944, -122.141944), 13);
    map.addControl(new FancyMapControl());
  }
}

}if (typeof GControl != "undefined")  {
	
	function ButtonMapControl() {
		this.buttons = [];
	}
	
	// To "subclass" the GControl, we set the prototype object to
	// an instance of the GControl object
	ButtonMapControl.prototype = new GControl();
	
	ButtonMapControl.prototype.addButton = function(title, callback, selected) {
		var button = {
			title: title,
			onclick: callback,
			startSelected: selected
		};
		
		this.buttons.push(button);
	}
	
	// Creates a one DIV for each of the buttons and places them in a container
	// DIV which is returned as our control element. We add the control to
	// to the map container and return the element for the map class to
	// position properly.
	ButtonMapControl.prototype.initialize = function(map) {
		console.log("init", this.buttons);
		
		this.map = map;
		
		var container = document.createElement("div");
		var thiz = this;
		
		for (var i=0; i<this.buttons.length; i++)
		{
			var button = this.buttons[i];
			
			var buttonDiv = document.createElement("div");
			var innerDiv = document.createElement("div");
			
			button.buttonDiv = buttonDiv;
			button.innerDiv = innerDiv;
			
			this.setButtonStyle_(button);
			if (button.startSelected) {
				this.setButtonStyle_selected(button);
			}
			
			container.appendChild(buttonDiv);
			buttonDiv.appendChild(innerDiv);
			innerDiv.appendChild(document.createTextNode(button.title));
			
			buttonDiv.onclick = function(button) {
				return function() {
					thiz.select(button);
					button.onclick();
				};
			}(button);
			
			if (button.startSelected) {
				button.onclick();
			}
		}
		
		map.getContainer().appendChild(container);
		return container;
	}
	
	ButtonMapControl.prototype.select = function(selected) {
		for (var i=0; i<this.buttons.length; i++) {
			var button = this.buttons[i];
			
			if (button == selected) {
				this.setButtonStyle_selected(button);
			} else {
				this.setButtonStyle_(button);
			}
		}
	}
	
	// Center the contol
	ButtonMapControl.prototype.getDefaultPosition = function() {
		var containerWidth = 90*this.buttons.length + 2*(this.buttons.length-1);
		var mapWidth = this.map.getSize().width;
		return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize( mapWidth/2 - containerWidth/2 , 5));
	}
	
	// Sets the proper CSS for the given button element.
	ButtonMapControl.prototype.setButtonStyle_ = function(button) {
	  button.buttonDiv.style.cssFloat = "left";
	  button.buttonDiv.style.styleFloat = "left";
		
	  button.buttonDiv.style.border = "1px solid #829BB5";
	  button.buttonDiv.style.background = "white";
	  
	  button.innerDiv.style.borderColor = "white #B0B0B0 #B0B0B0 white";
	  //  #829BB5
	  //button.innerDiv.style.borderColor = "white #829BB5 #829BB5 white";
	  
	  button.innerDiv.style.borderStyle = "solid";
	  button.innerDiv.style.borderWidth = "1px";
	  
	  button.buttonDiv.style.cursor = "pointer";
	  button.buttonDiv.style.width = "90px";
	  button.buttonDiv.style.textAlign = "center";
	  button.buttonDiv.style.font = "small Arial";
	  
	  button.buttonDiv.style.marginRight = "2px";
	  
	  button.innerDiv.style.color = "black";
	  button.innerDiv.style.padding = "0px";
	  button.innerDiv.style.fontWeight = "normal";
	}
	
	// Sets the proper CSS for the given button element when selected
	ButtonMapControl.prototype.setButtonStyle_selected = function(button) {
	  //button.innerDiv.style.borderColor = "#345684 #6C9DDF #6C9DDF #345684";
	  button.innerDiv.style.borderColor = "#345684 #6C9DDF #6C9DDF #345684";
	  button.innerDiv.style.fontWeight = "bold";
	}
}/*!
 * jquery.qtip. The jQuery tooltip plugin
 *
 * Copyright (c) 2009 Craig Thompson
 * http://craigsworks.com
 *
 * Licensed under MIT
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Launch  : February 2009
 * Version : 1.0.0-rc3
 * Released: Tuesday 12th May, 2009 - 00:00
 * Debug: jquery.qtip.debug.js
 */
(function($)
{
   // Implementation
   $.fn.qtip = function(options, blanket)
   {
   	
      var i, id, interfaces, opts, obj, command, config, api;

      // Return API / Interfaces if requested
      if(typeof options == 'string')
      {
      	 // Make sure API data exists if requested
         if(typeof $(this).data('qtip') !== 'object')
            $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.NO_TOOLTIP_PRESENT, false);

         // Return requested object
         if(options == 'api')
            return $(this).data('qtip').interfaces[ $(this).data('qtip').current ];
         else if(options == 'interfaces')
            return $(this).data('qtip').interfaces;
      }

      // Validate provided options
      else
      {
         // Set null options object if no options are provided
         if(!options) options = {};

         // Sanitize option data
         if(typeof options.content !== 'object' || (options.content.jquery && options.content.length > 0)) options.content = { text: options.content };
         if(typeof options.content.title !== 'object') options.content.title = { text: options.content.title };
         if(typeof options.position !== 'object') options.position = { corner: options.position };
         if(typeof options.position.corner !== 'object') options.position.corner = { target: options.position.corner, tooltip: options.position.corner };
         if(typeof options.show !== 'object') options.show = { when: options.show };
         if(typeof options.show.when !== 'object') options.show.when = { event: options.show.when };
         if(typeof options.show.effect !== 'object') options.show.effect = { type: options.show.effect };
         if(typeof options.hide !== 'object') options.hide = { when: options.hide };
         if(typeof options.hide.when !== 'object') options.hide.when = { event: options.hide.when };
         if(typeof options.hide.effect !== 'object') options.hide.effect = { type: options.hide.effect };
         if(typeof options.style !== 'object') options.style = { name: options.style };
         options.style = sanitizeStyle(options.style);

         // Build main options object
         opts = $.extend(true, {}, $.fn.qtip.defaults, options);

         // Inherit all style properties into one syle object and include original options
         opts.style = buildStyle.call({ options: opts }, opts.style);
         opts.user = $.extend(true, {}, options);
      };

      // Iterate each matched element
      return $(this).each(function() // Return original elements as per jQuery guidelines
      {
         // Check for API commands
         if(typeof options == 'string')
         {
            command = options.toLowerCase();
            interfaces = $(this).qtip('interfaces');

            // Make sure API data exists$('.qtip').qtip('destroy')
            if(typeof interfaces == 'object')
            {
               // Check if API call is a BLANKET DESTROY command
               if(blanket === true && command == 'destroy')
                  while(interfaces.length > 0) interfaces[interfaces.length-1].destroy();

               // API call is not a BLANKET DESTROY command
               else
               {
                  // Check if supplied command effects this tooltip only (NOT BLANKET)
                  if(blanket !== true) interfaces = [ $(this).qtip('api') ];

                  // Execute command on chosen qTips
                  for(i = 0; i < interfaces.length; i++)
                  {
                     // Destroy command doesn't require tooltip to be rendered
                     if(command == 'destroy') interfaces[i].destroy();

                     // Only call API if tooltip is rendered and it wasn't a destroy call
                     else if(interfaces[i].status.rendered === true)
                     {
                        if(command == 'show') interfaces[i].show();
                        else if(command == 'hide') interfaces[i].hide();
                        else if(command == 'focus') interfaces[i].focus();
                        else if(command == 'disable') interfaces[i].disable(true);
                        else if(command == 'enable') interfaces[i].disable(false);
                     };
                  };
               };
            };
         }

         // No API commands, continue with qTip creation
         else
         {
            // Create unique configuration object
            config = $.extend(true, {}, opts);
            config.hide.effect.length = opts.hide.effect.length;
            config.show.effect.length = opts.show.effect.length;

            // Sanitize target options
            if(config.position.container === false) config.position.container = $(document.body);
            if(config.position.target === false) config.position.target = $(this);
            if(config.show.when.target === false) config.show.when.target = $(this);
            if(config.hide.when.target === false) config.hide.when.target = $(this);

            // Determine tooltip ID (Reuse array slots if possible)
            id = $.fn.qtip.interfaces.length;
            for(i = 0; i < id; i++)
            {
               if(typeof $.fn.qtip.interfaces[i] == 'undefined'){ id = i; break; };
            };

            // Instantiate the tooltip
            obj = new qTip($(this), config, id);

            // Add API references
            $.fn.qtip.interfaces[id] = obj;

            // Check if element already has qTip data assigned
            if(typeof $(this).data('qtip') == 'object')
            {
               // Set new current interface id
               if(typeof $(this).attr('qtip') === 'undefined')
                  $(this).data('qtip').current = $(this).data('qtip').interfaces.length;

               // Push new API interface onto interfaces array
               $(this).data('qtip').interfaces.push(obj);
            }

            // No qTip data is present, create now
            else $(this).data('qtip', { current: 0, interfaces: [obj] });

            // If prerendering is disabled, create tooltip on showEvent
            if(config.content.prerender === false && config.show.when.event !== false && config.show.ready !== true)
            {
               config.show.when.target.bind(config.show.when.event+'.qtip-'+id+'-create', { qtip: id }, function(event)
               {
                  // Retrieve API interface via passed qTip Id
                  api = $.fn.qtip.interfaces[ event.data.qtip ];

                  // Unbind show event and cache mouse coords
                  api.options.show.when.target.unbind(api.options.show.when.event+'.qtip-'+event.data.qtip+'-create');
                  api.cache.mouse = { x: event.pageX, y: event.pageY };

                  // Render tooltip and start the event sequence
                  construct.call( api );
                  api.options.show.when.target.trigger(api.options.show.when.event);
               });
            }

            // Prerendering is enabled, create tooltip now
            else
            {
               // Set mouse position cache to top left of the element
               obj.cache.mouse = {
                  x: config.show.when.target.offset().left,
                  y: config.show.when.target.offset().top
               };

               // Construct the tooltip
               construct.call(obj);
            }
         };
      });
   };

   // Instantiator
   function qTip(target, options, id)
   {
      // Declare this reference
      var self = this;

      // Setup class attributes
      self.id = id;
      self.options = options;
      self.status = {
         animated: false,
         rendered: false,
         disabled: false,
         focused: false
      };
      self.elements = {
         target: target.addClass(self.options.style.classes.target),
         tooltip: null,
         wrapper: null,
         content: null,
         contentWrapper: null,
         title: null,
         button: null,
         tip: null,
         bgiframe: null
      };
      self.cache = {
         mouse: {},
         position: {},
         toggle: 0
      };
      self.timers = {};

      // Define exposed API methods
      $.extend(self, self.options.api,
      {
         show: function(event)
         {
         	var returned, solo;

            // Make sure tooltip is rendered and if not, return
            if(!self.status.rendered)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'show');

            // Only continue if element is visible
            if(self.elements.tooltip.css('display') !== 'none') return self;

            // Clear animation queue
            self.elements.tooltip.stop(true, false);

            // Call API method and if return value is false, halt
            returned = self.beforeShow.call(self, event);
            if(returned === false) return self;

            // Define afterShow callback method
            function afterShow()
            {
               // Call API method and focus if it isn't static
               if(self.options.position.type !== 'static') self.focus();
               self.onShow.call(self, event);

               // Prevent antialias from disappearing in IE7 by removing filter attribute
               if($.browser.msie) self.elements.tooltip.get(0).style.removeAttribute('filter');
            };

            // Maintain toggle functionality if enabled
            self.cache.toggle = 1;

            // Update tooltip position if it isn't static
            if(self.options.position.type !== 'static')
               self.updatePosition(event, (self.options.show.effect.length > 0));

            // Hide other tooltips if tooltip is solo
            if(typeof self.options.show.solo == 'object') solo = $(self.options.show.solo);
            else if(self.options.show.solo === true) solo = $('div.qtip').not(self.elements.tooltip);
            if(solo) solo.each(function(){ if($(this).qtip('api').status.rendered === true) $(this).qtip('api').hide(); });

            // Show tooltip
            if(typeof self.options.show.effect.type == 'function')
            {
               self.options.show.effect.type.call(self.elements.tooltip, self.options.show.effect.length);
               self.elements.tooltip.queue(function(){ afterShow(); $(this).dequeue(); });
            }
            else
            {
               switch(self.options.show.effect.type.toLowerCase())
               {
                  case 'fade':
                     self.elements.tooltip.fadeIn(self.options.show.effect.length, afterShow);
                     break;
                  case 'slide':
                     self.elements.tooltip.slideDown(self.options.show.effect.length, function()
                     {
                        afterShow();
                        if(self.options.position.type !== 'static') self.updatePosition(event, true);
                     });
                     break;
                  case 'grow':
                     self.elements.tooltip.show(self.options.show.effect.length, afterShow);
                     break;
                  default:
                     self.elements.tooltip.show(null, afterShow); afterShow();
                     break;
               };

               // Add active class to tooltip
               self.elements.tooltip.addClass(self.options.style.classes.active);
            };

            // Log event and return
            return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_SHOWN, 'show');
         },

         hide: function(event)
         {
            var returned;

            // Make sure tooltip is rendered and if not, return
            if(!self.status.rendered)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'hide');

            // Only continue if element is visible
            else if(self.elements.tooltip.css('display') === 'none') return self;

            // Stop show timer and animation queue
            clearTimeout(self.timers.show);
            self.elements.tooltip.stop(true, false);

            // Call API method and if return value is false, halt
            returned = self.beforeHide.call(self, event);
            if(returned === false) return self;

            // Define afterHide callback method
            function afterHide(){ self.onHide.call(self, event); };

            // Maintain toggle functionality if enabled
            self.cache.toggle = 0;

            // Hide tooltip
            if(typeof self.options.hide.effect.type == 'function')
            {
               self.options.hide.effect.type.call(self.elements.tooltip, self.options.hide.effect.length);
               self.elements.tooltip.queue(function(){ afterHide(); $(this).dequeue(); });
            }
            else
            {
               switch(self.options.hide.effect.type.toLowerCase())
               {
                  case 'fade':
                     self.elements.tooltip.fadeOut(self.options.hide.effect.length, afterHide);
                     break;
                  case 'slide':
                     self.elements.tooltip.slideUp(self.options.hide.effect.length, afterHide);
                     break;
                  case 'grow':
                     self.elements.tooltip.hide(self.options.hide.effect.length, afterHide);
                     break;
                  default:
                     self.elements.tooltip.hide(null, afterHide);
                     break;
               };

               // Remove active class to tooltip
               self.elements.tooltip.removeClass(self.options.style.classes.active);
            };

            // Log event and return
            return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_HIDDEN, 'hide');
         },

         updatePosition: function(event, animate)
         {
            var i, target, tooltip, coords, mapName, imagePos, newPosition, ieAdjust, ie6Adjust, borderAdjust, mouseAdjust, offset, curPosition, returned

            // Make sure tooltip is rendered and if not, return
            if(!self.status.rendered)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updatePosition');

            // If tooltip is static, return
            else if(self.options.position.type == 'static')
               return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.CANNOT_POSITION_STATIC, 'updatePosition');

            // Define property objects
            target = {
               position: { left: 0, top: 0 },
               dimensions: { height: 0, width: 0 },
               corner: self.options.position.corner.target
            };
            tooltip = {
               position: self.getPosition(),
               dimensions: self.getDimensions(),
               corner: self.options.position.corner.tooltip
            };

            // Target is an HTML element
            if(self.options.position.target !== 'mouse')
            {
               // If the HTML element is AREA, calculate position manually
               if(self.options.position.target.get(0).nodeName.toLowerCase() == 'area')
               {
                  // Retrieve coordinates from coords attribute and parse into integers
                  coords = self.options.position.target.attr('coords').split(',');
                  for(i = 0; i < coords.length; i++) coords[i] = parseInt(coords[i]);

                  // Setup target position object
                  mapName = self.options.position.target.parent('map').attr('name');
                  imagePos = $('img[usemap="#'+mapName+'"]:first').offset();
                  target.position = {
                     left: Math.floor(imagePos.left + coords[0]),
                     top: Math.floor(imagePos.top + coords[1])
                  };

                  // Determine width and height of the area
                  switch(self.options.position.target.attr('shape').toLowerCase())
                  {
                     case 'rect':
                        target.dimensions = {
                           width: Math.ceil(Math.abs(coords[2] - coords[0])),
                           height: Math.ceil(Math.abs(coords[3] - coords[1]))
                        };
                        break;

                     case 'circle':
                        target.dimensions = {
                           width: coords[2] + 1,
                           height: coords[2] + 1
                        };
                        break;

                     case 'poly':
                        target.dimensions = {
                           width: coords[0],
                           height: coords[1]
                        };

                        for(i = 0; i < coords.length; i++)
                        {
                           if(i % 2 == 0)
                           {
                              if(coords[i] > target.dimensions.width)
                                 target.dimensions.width = coords[i];
                              if(coords[i] < coords[0])
                                 target.position.left = Math.floor(imagePos.left + coords[i]);
                           }
                           else
                           {
                              if(coords[i] > target.dimensions.height)
                                 target.dimensions.height = coords[i];
                              if(coords[i] < coords[1])
                                 target.position.top = Math.floor(imagePos.top + coords[i]);
                           };
                        };

                        target.dimensions.width = target.dimensions.width - (target.position.left - imagePos.left);
                        target.dimensions.height = target.dimensions.height - (target.position.top - imagePos.top);
                        break;

                     default:
                        return $.fn.qtip.log.error.call(self, 4, $.fn.qtip.constants.INVALID_AREA_SHAPE, 'updatePosition');
                        break;
                  };

                  // Adjust position by 2 pixels (Positioning bug?)
                  target.dimensions.width -= 2; target.dimensions.height -= 2;
               }

               // Target is the document
               else if(self.options.position.target.add(document.body).length === 1)
               {
                  target.position = { left: $(document).scrollLeft(), top: $(document).scrollTop() };
                  target.dimensions = { height: $(window).height(), width: $(window).width() };
               }

               // Target is a regular HTML element, find position normally
               else
               {
                  // Check if the target is another tooltip. If its animated, retrieve position from newPosition data
                  if(typeof self.options.position.target.attr('qtip') !== 'undefined')
                     target.position = self.options.position.target.qtip('api').cache.position;
                  else
                     target.position = self.options.position.target.offset();

                  // Setup dimensions objects
                  target.dimensions = {
                     height: self.options.position.target.outerHeight(),
                     width: self.options.position.target.outerWidth()
                  };
               };

               // Calculate correct target corner position
               newPosition = $.extend({}, target.position);
               if(target.corner.search(/right/i) !== -1)
                  newPosition.left += target.dimensions.width;

               if(target.corner.search(/bottom/i) !== -1)
                  newPosition.top += target.dimensions.height;

               if(target.corner.search(/((top|bottom)Middle)|center/) !== -1)
                  newPosition.left += (target.dimensions.width / 2);

               if(target.corner.search(/((left|right)Middle)|center/) !== -1)
                  newPosition.top += (target.dimensions.height / 2);
            }

            // Mouse is the target, set position to current mouse coordinates
            else
            {
               // Setup target position and dimensions objects
               target.position = newPosition = { left: self.cache.mouse.x, top: self.cache.mouse.y };
               target.dimensions = { height: 1, width: 1 };
            };

            // Calculate correct target corner position
            if(tooltip.corner.search(/right/i) !== -1)
               newPosition.left -= tooltip.dimensions.width;

            if(tooltip.corner.search(/bottom/i) !== -1)
               newPosition.top -= tooltip.dimensions.height;

            if(tooltip.corner.search(/((top|bottom)Middle)|center/) !== -1)
               newPosition.left -= (tooltip.dimensions.width / 2);

            if(tooltip.corner.search(/((left|right)Middle)|center/) !== -1)
               newPosition.top -= (tooltip.dimensions.height / 2);

            // Setup IE adjustment variables (Pixel gap bugs)
            ieAdjust = ($.browser.msie) ? 1 : 0; // And this is why I hate IE...
            ie6Adjust = ($.browser.msie && parseInt($.browser.version.charAt(0)) === 6) ? 1 : 0; // ...and even more so IE6!

            // Adjust for border radius
            if(self.options.style.border.radius > 0)
            {
               if(tooltip.corner.search(/Left/) !== -1)
                  newPosition.left -= self.options.style.border.radius;
               else if(tooltip.corner.search(/Right/) !== -1)
                  newPosition.left += self.options.style.border.radius;

               if(tooltip.corner.search(/Top/) !== -1)
                  newPosition.top -= self.options.style.border.radius;
               else if(tooltip.corner.search(/Bottom/) !== -1)
                  newPosition.top += self.options.style.border.radius;
            };

            // IE only adjustments (Pixel perfect!)
            if(ieAdjust)
            {
               if(tooltip.corner.search(/top/) !== -1)
                  newPosition.top -= ieAdjust
               else if(tooltip.corner.search(/bottom/) !== -1)
                  newPosition.top += ieAdjust

               if(tooltip.corner.search(/left/) !== -1)
                  newPosition.left -= ieAdjust
               else if(tooltip.corner.search(/right/) !== -1)
                  newPosition.left += ieAdjust

               if(tooltip.corner.search(/leftMiddle|rightMiddle/) !== -1)
                  newPosition.top -= 1
            };

            // If screen adjustment is enabled, apply adjustments
            if(self.options.position.adjust.screen === true)
               newPosition = screenAdjust.call(self, newPosition, target, tooltip);

            // If mouse is the target, prevent tooltip appearing directly under the mouse
            if(self.options.position.target === 'mouse' && self.options.position.adjust.mouse === true)
            {
               if(self.options.position.adjust.screen === true && self.elements.tip)
                  mouseAdjust = self.elements.tip.attr('rel');
               else
                  mouseAdjust = self.options.position.corner.tooltip;

               newPosition.left += (mouseAdjust.search(/right/i) !== -1) ? -6 : 6;
               newPosition.top += (mouseAdjust.search(/bottom/i) !== -1) ? -6 : 6;
            }

            // Initiate bgiframe plugin in IE6 if tooltip overlaps a select box or object element
            if(!self.elements.bgiframe && $.browser.msie && parseInt($.browser.version.charAt(0)) == 6)
            {
               $('select, object').each(function()
               {
                  offset = $(this).offset();
                  offset.bottom = offset.top + $(this).height();
                  offset.right = offset.left + $(this).width();

                  if(newPosition.top + tooltip.dimensions.height >= offset.top
                  && newPosition.left + tooltip.dimensions.width >= offset.left)
                     bgiframe.call(self);
               });
            };

            // Add user xy adjustments
            newPosition.left += self.options.position.adjust.x;
            newPosition.top += self.options.position.adjust.y;

            // Set new tooltip position if its moved, animate if enabled
            curPosition = self.getPosition();
            if(newPosition.left != curPosition.left || newPosition.top != curPosition.top)
            {
               // Call API method and if return value is false, halt
               returned = self.beforePositionUpdate.call(self, event);
               if(returned === false) return self;

               // Cache new position
               self.cache.position = newPosition;

               // Check if animation is enabled
               if(animate === true)
               {
                  // Set animated status
                  self.status.animated = true;

                  // Animate and reset animated status on animation end
                  self.elements.tooltip.animate(newPosition, 200, 'swing', function(){ self.status.animated = false });
               }

               // Set new position via CSS
               else self.elements.tooltip.css(newPosition);

               // Call API method and log event if its not a mouse move
               self.onPositionUpdate.call(self, event);
               if(typeof event !== 'undefined' && event.type && event.type !== 'mousemove')
                  $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_POSITION_UPDATED, 'updatePosition');
            };

            return self;
         },

         updateWidth: function(newWidth)
         {
            var hidden;

            // Make sure tooltip is rendered and if not, return
            if(!self.status.rendered)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateWidth');

            // Make sure supplied width is a number and if not, return
            else if(newWidth && typeof newWidth !== 'number')
               return $.fn.qtip.log.error.call(self, 2, 'newWidth must be of type number', 'updateWidth');

            // Setup elements which must be hidden during width update
            hidden = self.elements.contentWrapper.siblings().add(self.elements.tip).add(self.elements.button);

            // Calculate the new width if one is not supplied
            if(!newWidth)
            {
               // Explicit width is set
               if(typeof self.options.style.width.value == 'number')
                  newWidth = self.options.style.width.value;

               // No width is set, proceed with auto detection
               else
               {
                  // Set width to auto initally to determine new width and hide other elements
                  self.elements.tooltip.css({ width: 'auto' });
                  hidden.hide();

                  // Set position and zoom to defaults to prevent IE hasLayout bug
                  if($.browser.msie)
                     self.elements.wrapper.add(self.elements.contentWrapper.children()).css({ zoom: 'normal' });

                  // Set the new width
                  newWidth = self.getDimensions().width + 1;

                  // Make sure its within the maximum and minimum width boundries
                  if(!self.options.style.width.value)
                  {
                     if(newWidth > self.options.style.width.max) newWidth = self.options.style.width.max
                     if(newWidth < self.options.style.width.min) newWidth = self.options.style.width.min
                  };
               };
            };

            // Adjust newWidth by 1px if width is odd (IE6 rounding bug fix)
            if(newWidth % 2 !== 0) newWidth -= 1;

            // Set the new calculated width and unhide other elements
            self.elements.tooltip.width(newWidth);
            hidden.show();

            // Set the border width, if enabled
            if(self.options.style.border.radius)
            {
               self.elements.tooltip.find('.qtip-betweenCorners').each(function(i)
               {
                  $(this).width(newWidth - (self.options.style.border.radius * 2));
               })
            };

            // IE only adjustments
            if($.browser.msie)
            {
               // Reset position and zoom to give the wrapper layout (IE hasLayout bug)
               self.elements.wrapper.add(self.elements.contentWrapper.children()).css({ zoom: '1' });

               // Set the new width
               self.elements.wrapper.width(newWidth);

               // Adjust BGIframe height and width if enabled
               if(self.elements.bgiframe) self.elements.bgiframe.width(newWidth).height(self.getDimensions.height);
            };

            // Log event and return
            return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_WIDTH_UPDATED, 'updateWidth');
         },

         updateStyle: function(name)
         {
            var tip, borders, context, corner, coordinates;

            // Make sure tooltip is rendered and if not, return
            if(!self.status.rendered)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateStyle');

            // Return if style is not defined or name is not a string
            else if(typeof name !== 'string' || !$.fn.qtip.styles[name])
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.STYLE_NOT_DEFINED, 'updateStyle');

            // Set the new style object
            self.options.style = buildStyle.call(self, $.fn.qtip.styles[name], self.options.user.style);

            // Update initial styles of content and title elements
            self.elements.content.css( jQueryStyle(self.options.style) );
            if(self.options.content.title.text !== false)
               self.elements.title.css( jQueryStyle(self.options.style.title, true) );

            // Update CSS border colour
            self.elements.contentWrapper.css({ borderColor: self.options.style.border.color });

            // Update tip color if enabled
            if(self.options.style.tip.corner !== false)
            {
               if($('<canvas>').get(0).getContext)
               {
                  // Retrieve canvas context and clear
                  tip = self.elements.tooltip.find('.qtip-tip canvas:first');
                  context = tip.get(0).getContext('2d');
                  context.clearRect(0,0,300,300);

                  // Draw new tip
                  corner = tip.parent('div[rel]:first').attr('rel');
                  coordinates = calculateTip(corner, self.options.style.tip.size.width, self.options.style.tip.size.height);
                  drawTip.call(self, tip, coordinates, self.options.style.tip.color || self.options.style.border.color);
               }
               else if($.browser.msie)
               {
                  // Set new fillcolor attribute
                  tip = self.elements.tooltip.find('.qtip-tip [nodeName="shape"]');
                  tip.attr('fillcolor', self.options.style.tip.color || self.options.style.border.color);
               };
            };

            // Update border colors if enabled
            if(self.options.style.border.radius > 0)
            {
               self.elements.tooltip.find('.qtip-betweenCorners').css({ backgroundColor: self.options.style.border.color });

               if($('<canvas>').get(0).getContext)
               {
                  borders = calculateBorders(self.options.style.border.radius)
                  self.elements.tooltip.find('.qtip-wrapper canvas').each(function()
                  {
                     // Retrieve canvas context and clear
                     context = $(this).get(0).getContext('2d');
                     context.clearRect(0,0,300,300);

                     // Draw new border
                     corner = $(this).parent('div[rel]:first').attr('rel')
                     drawBorder.call(self, $(this), borders[corner],
                        self.options.style.border.radius, self.options.style.border.color);
                  });
               }
               else if($.browser.msie)
               {
                  // Set new fillcolor attribute on each border corner
                  self.elements.tooltip.find('.qtip-wrapper [nodeName="arc"]').each(function()
                  {
                     $(this).attr('fillcolor', self.options.style.border.color)
                  });
               };
            };

            // Log event and return
            return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_STYLE_UPDATED, 'updateStyle');
         },

         updateContent: function(content, reposition)
         {
            var parsedContent, images, loadedImages;

            // Make sure tooltip is rendered and if not, return
            if(!self.status.rendered)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateContent');

            // Make sure content is defined before update
            else if(!content)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.NO_CONTENT_PROVIDED, 'updateContent');

            // Call API method and set new content if a string is returned
            parsedContent = self.beforeContentUpdate.call(self, content);
            if(typeof parsedContent == 'string') content = parsedContent;
            else if(parsedContent === false) return;

            // Set position and zoom to defaults to prevent IE hasLayout bug
            if($.browser.msie) self.elements.contentWrapper.children().css({ zoom: 'normal' });

            // Append new content if its a DOM array and show it if hidden
            if(content.jquery && content.length > 0)
               content.clone(true).appendTo(self.elements.content).show();

            // Content is a regular string, insert the new content
            else self.elements.content.html(content);

            // Check if images need to be loaded before position is updated to prevent mis-positioning
            images = self.elements.content.find('img[complete=false]');
            if(images.length > 0)
            {
               loadedImages = 0;
               images.each(function(i)
               {
                  $('<img src="'+ $(this).attr('src') +'" />')
                     .load(function(){ if(++loadedImages == images.length) afterLoad(); });
               });
            }
            else afterLoad();

            function afterLoad()
            {
               // Update the tooltip width
               self.updateWidth();

               // If repositioning is enabled, update positions
               if(reposition !== false)
               {
                  // Update position if tooltip isn't static
                  if(self.options.position.type !== 'static')
                     self.updatePosition(self.elements.tooltip.is(':visible'), true);

                  // Reposition the tip if enabled
                  if(self.options.style.tip.corner !== false)
                     positionTip.call(self);
               };
            };

            // Call API method and log event
            self.onContentUpdate.call(self);
            return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_CONTENT_UPDATED, 'loadContent');
         },

         loadContent: function(url, data, method)
         {
            var returned;

            // Make sure tooltip is rendered and if not, return
            if(!self.status.rendered)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'loadContent');

            // Call API method and if return value is false, halt
            returned = self.beforeContentLoad.call(self);
            if(returned === false) return self;

            // Load content using specified request type
            if(method == 'post')
               $.post(url, data, setupContent);
            else
               $.get(url, data, setupContent);

            function setupContent(content)
            {
               // Call API method and log event
               self.onContentLoad.call(self);
               $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_CONTENT_LOADED, 'loadContent');

               // Update the content
               self.updateContent(content);
            };

            return self;
         },

         updateTitle: function(content)
         {
            // Make sure tooltip is rendered and if not, return
            if(!self.status.rendered)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateTitle');

            // Make sure content is defined before update
            else if(!content)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.NO_CONTENT_PROVIDED, 'updateTitle');

            // Call API method and if return value is false, halt
            returned = self.beforeTitleUpdate.call(self);
            if(returned === false) return self;

            // Set the new content and reappend the button if enabled
            if(self.elements.button) self.elements.button = self.elements.button.clone(true);
            self.elements.title.html(content)
            if(self.elements.button) self.elements.title.prepend(self.elements.button);

            // Call API method and log event
            self.onTitleUpdate.call(self);
            return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_TITLE_UPDATED, 'updateTitle');
         },

         focus: function(event)
         {
            var curIndex, newIndex, elemIndex, returned;

            // Make sure tooltip is rendered and if not, return
            if(!self.status.rendered)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'focus');

            else if(self.options.position.type == 'static')
               return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.CANNOT_FOCUS_STATIC, 'focus');

            // Set z-index variables
            curIndex = parseInt( self.elements.tooltip.css('z-index') );
            newIndex = 6000 + $('div.qtip[qtip]').length - 1;

            // Only update the z-index if it has changed and tooltip is not already focused
            if(!self.status.focused && curIndex !== newIndex)
            {
               // Call API method and if return value is false, halt
               returned = self.beforeFocus.call(self, event);
               if(returned === false) return self;

               // Loop through all other tooltips
               $('div.qtip[qtip]').not(self.elements.tooltip).each(function()
               {
                  if($(this).qtip('api').status.rendered === true)
                  {
                     elemIndex = parseInt($(this).css('z-index'));

                     // Reduce all other tooltip z-index by 1
                     if(typeof elemIndex == 'number' && elemIndex > -1)
                        $(this).css({ zIndex: parseInt( $(this).css('z-index') ) - 1 });

                     // Set focused status to false
                     $(this).qtip('api').status.focused = false;
                  }
               })

               // Set the new z-index and set focus status to true
               self.elements.tooltip.css({ zIndex: newIndex });
               self.status.focused = true;

               // Call API method and log event
               self.onFocus.call(self, event);
               $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_FOCUSED, 'focus');
            };

            return self;
         },

         disable: function(state)
         {
            // Make sure tooltip is rendered and if not, return
            if(!self.status.rendered)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'disable');

            if(state)
            {
               // Tooltip is not already disabled, proceed
               if(!self.status.disabled)
               {
                  // Set the disabled flag and log event
                  self.status.disabled = true;
                  $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_DISABLED, 'disable');
               }

               // Tooltip is already disabled, inform user via log
               else  $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.TOOLTIP_ALREADY_DISABLED, 'disable');
            }
            else
            {
               // Tooltip is not already enabled, proceed
               if(self.status.disabled)
               {
                  // Reassign events, set disable status and log
                  self.status.disabled = false;
                  $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_ENABLED, 'disable');
               }

               // Tooltip is already enabled, inform the user via log
               else $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.TOOLTIP_ALREADY_ENABLED, 'disable');
            };

            return self;
         },

         destroy: function()
         {
            var i, returned, interfaces;

            // Call API method and if return value is false, halt
            returned = self.beforeDestroy.call(self);
            if(returned === false) return self;

            // Check if tooltip is rendered
            if(self.status.rendered)
            {
               // Remove event handlers and remove element
               self.options.show.when.target.unbind('mousemove.qtip', self.updatePosition);
               self.options.show.when.target.unbind('mouseout.qtip', self.hide);
               self.options.show.when.target.unbind(self.options.show.when.event + '.qtip');
               self.options.hide.when.target.unbind(self.options.hide.when.event + '.qtip');
               self.elements.tooltip.unbind(self.options.hide.when.event + '.qtip');
               self.elements.tooltip.unbind('mouseover.qtip', self.focus);
               self.elements.tooltip.remove();
            }

            // Tooltip isn't yet rendered, remove render event
            else self.options.show.when.target.unbind(self.options.show.when.event+'.qtip-create');

            // Check to make sure qTip data is present on target element
            if(typeof self.elements.target.data('qtip') == 'object')
            {
               // Remove API references from interfaces object
               interfaces = self.elements.target.data('qtip').interfaces;
               if(typeof interfaces == 'object' && interfaces.length > 0)
               {
                  // Remove API from interfaces array
                  for(i = 0; i < interfaces.length - 1; i++)
                     if(interfaces[i].id == self.id) interfaces.splice(i, 1)
               }
            }
            delete $.fn.qtip.interfaces[self.id];

            // Set qTip current id to previous tooltips API if available
            if(typeof interfaces == 'object' && interfaces.length > 0)
               self.elements.target.data('qtip').current = interfaces.length -1;
            else
               self.elements.target.removeData('qtip');

            // Call API method and log destroy
            self.onDestroy.call(self);
            $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_DESTROYED, 'destroy');

            return self.elements.target
         },

         getPosition: function()
         {
            var show, offset;

            // Make sure tooltip is rendered and if not, return
            if(!self.status.rendered)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'getPosition');

            show = (self.elements.tooltip.css('display') !== 'none') ? false : true;

            // Show and hide tooltip to make sure coordinates are returned
            if(show) self.elements.tooltip.css({ visiblity: 'hidden' }).show();
            offset = self.elements.tooltip.offset();
            if(show) self.elements.tooltip.css({ visiblity: 'visible' }).hide();

            return offset;
         },

         getDimensions: function()
         {
            var show, dimensions;

            // Make sure tooltip is rendered and if not, return
            if(!self.status.rendered)
               return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'getDimensions');

            show = (!self.elements.tooltip.is(':visible')) ? true : false;

            // Show and hide tooltip to make sure dimensions are returned
            if(show) self.elements.tooltip.css({ visiblity: 'hidden' }).show();
            dimensions = {
               height: self.elements.tooltip.outerHeight(),
               width: self.elements.tooltip.outerWidth()
            };
            if(show) self.elements.tooltip.css({ visiblity: 'visible' }).hide();

            return dimensions;
         }
      });
   };

   // Define priamry construct function
   function construct()
   {
      var self, adjust, content, url, data, method, tempLength;
      self = this;

      // Call API method
      self.beforeRender.call(self);

      // Set rendered status to true
      self.status.rendered = true;

      // Create initial tooltip elements
      self.elements.tooltip =  '<div qtip="'+self.id+'" ' +
         'class="qtip '+(self.options.style.classes.tooltip || self.options.style)+'"' +
         'style="display:none; -moz-border-radius:0; -webkit-border-radius:0; border-radius:0;' +
         'position:'+self.options.position.type+';">' +
         '  <div class="qtip-wrapper" style="position:relative; overflow:hidden; text-align:left;">' +
         '    <div class="qtip-contentWrapper" style="overflow:hidden;">' +
         '       <div class="qtip-content '+self.options.style.classes.content+'"></div>' +
         '</div></div></div>';

      // Append to container element
      self.elements.tooltip = $(self.elements.tooltip);
      self.elements.tooltip.appendTo(self.options.position.container)

      // Setup tooltip qTip data
      self.elements.tooltip.data('qtip', { current: 0, interfaces: [self] });

      // Setup element references
      self.elements.wrapper = self.elements.tooltip.children('div:first');
      self.elements.contentWrapper = self.elements.wrapper.children('div:first').css({ background: self.options.style.background });
      self.elements.content = self.elements.contentWrapper.children('div:first').css( jQueryStyle(self.options.style) );

      // Apply IE hasLayout fix to wrapper and content elements
      if($.browser.msie) self.elements.wrapper.add(self.elements.content).css({ zoom: 1 });

      // Setup tooltip attributes
      if(self.options.hide.when.event == 'unfocus') self.elements.tooltip.attr('unfocus', true);

      // If an explicit width is set, updateWidth prior to setting content to prevent dirty rendering
      if(typeof self.options.style.width.value == 'number') self.updateWidth();

      // Create borders and tips if supported by the browser
      if($('<canvas>').get(0).getContext || $.browser.msie)
      {
         // Create border
         if(self.options.style.border.radius > 0)
            createBorder.call(self);
         else
            self.elements.contentWrapper.css({ border: self.options.style.border.width+'px solid '+self.options.style.border.color  });

         // Create tip if enabled
         if(self.options.style.tip.corner !== false)
            createTip.call(self);
      }

      // Neither canvas or VML is supported, tips and borders cannot be drawn!
      else
      {
         // Set defined border width
         self.elements.contentWrapper.css({ border: self.options.style.border.width+'px solid '+self.options.style.border.color  });

         // Reset border radius and tip
         self.options.style.border.radius = 0;
         self.options.style.tip.corner = false;

         // Inform via log
         $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.CANVAS_VML_NOT_SUPPORTED, 'render');
      };

      // Use the provided content string or DOM array
      if((typeof self.options.content.text == 'string' && self.options.content.text.length > 0)
      || (self.options.content.text.jquery && self.options.content.text.length > 0))
         content = self.options.content.text;

      // Use title string for content if present
      else if(typeof self.elements.target.attr('title') == 'string' && self.elements.target.attr('title').length > 0)
      {
         content = self.elements.target.attr('title').replace("\\n", '<br />');
         self.elements.target.attr('title', ''); // Remove title attribute to prevent default tooltip showing
      }

      // No title is present, use alt attribute instead
      else if(typeof self.elements.target.attr('alt') == 'string' && self.elements.target.attr('alt').length > 0)
      {
         content = self.elements.target.attr('alt').replace("\\n", '<br />');
         self.elements.target.attr('alt', ''); // Remove alt attribute to prevent default tooltip showing
      }

      // No valid content was provided, inform via log
      else
      {
         content = ' ';
         $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.NO_VALID_CONTENT, 'render');
      };

      // Set the tooltips content and create title if enabled
      if(self.options.content.title.text !== false) createTitle.call(self);
      self.updateContent(content);

      // Assign events and toggle tooltip with focus
      assignEvents.call(self);
      if(self.options.show.ready === true) self.show();

      // Retrieve ajax content if provided
      if(self.options.content.url !== false)
      {
         url = self.options.content.url;
         data = self.options.content.data;
         method = self.options.content.method || 'get';
         self.loadContent(url, data, method);
      };

      // Call API method and log event
      self.onRender.call(self);
      $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_RENDERED, 'render');
   };

   // Create borders using canvas and VML
   function createBorder()
   {
      var self, i, width, radius, color, coordinates, containers, size, betweenWidth, betweenCorners, borderTop, borderBottom, borderCoord, sideWidth, vertWidth;
      self = this;

      // Destroy previous border elements, if present
      self.elements.wrapper.find('.qtip-borderBottom, .qtip-borderTop').remove();

      // Setup local variables
      width = self.options.style.border.width;
      radius = self.options.style.border.radius;
      color = self.options.style.border.color || self.options.style.tip.color;

      // Calculate border coordinates
      coordinates = calculateBorders(radius);

      // Create containers for the border shapes
      containers = {};
      for(i in coordinates)
      {
         // Create shape container
         containers[i] = '<div rel="'+i+'" style="'+((i.search(/Left/) !== -1) ? 'left' : 'right') + ':0; ' +
            'position:absolute; height:'+radius+'px; width:'+radius+'px; overflow:hidden; line-height:0.1px; font-size:1px">';

         // Canvas is supported
         if($('<canvas>').get(0).getContext)
            containers[i] += '<canvas height="'+radius+'" width="'+radius+'" style="vertical-align: top"></canvas>';

         // No canvas, but if it's IE use VML
         else if($.browser.msie)
         {
            size = radius * 2 + 3;
            containers[i] += '<v:arc stroked="false" fillcolor="'+color+'" startangle="'+coordinates[i][0]+'" endangle="'+coordinates[i][1]+'" ' +
               'style="width:'+size+'px; height:'+size+'px; margin-top:'+((i.search(/bottom/) !== -1) ? -2 : -1)+'px; ' +
               'margin-left:'+((i.search(/Right/) !== -1) ? coordinates[i][2] - 3.5 : -1)+'px; ' +
               'vertical-align:top; display:inline-block; behavior:url(#default#VML)"></v:arc>';

         };

         containers[i] += '</div>';
      };

      // Create between corners elements
      betweenWidth = self.getDimensions().width - (Math.max(width, radius) * 2);
      betweenCorners = '<div class="qtip-betweenCorners" style="height:'+radius+'px; width:'+betweenWidth+'px; ' +
         'overflow:hidden; background-color:'+color+'; line-height:0.1px; font-size:1px;">';

      // Create top border container
      borderTop = '<div class="qtip-borderTop" dir="ltr" style="height:'+radius+'px; ' +
         'margin-left:'+radius+'px; line-height:0.1px; font-size:1px; padding:0;">' +
         containers['topLeft'] + containers['topRight'] + betweenCorners;
      self.elements.wrapper.prepend(borderTop);

      // Create bottom border container
      borderBottom = '<div class="qtip-borderBottom" dir="ltr" style="height:'+radius+'px; ' +
         'margin-left:'+radius+'px; line-height:0.1px; font-size:1px; padding:0;">' +
         containers['bottomLeft'] + containers['bottomRight'] + betweenCorners;
      self.elements.wrapper.append(borderBottom);

      // Draw the borders if canvas were used (Delayed til after DOM creation)
      if($('<canvas>').get(0).getContext)
      {
         self.elements.wrapper.find('canvas').each(function()
         {
            borderCoord = coordinates[ $(this).parent('[rel]:first').attr('rel') ];
            drawBorder.call(self, $(this), borderCoord, radius, color);
         })
      }

      // Create a phantom VML element (IE won't show the last created VML element otherwise)
      else if($.browser.msie) self.elements.tooltip.append('<v:image style="behavior:url(#default#VML);"></v:image>');

      // Setup contentWrapper border
      sideWidth = Math.max(radius, (radius + (width - radius)) )
      vertWidth = Math.max(width - radius, 0);
      self.elements.contentWrapper.css({
         border: '0px solid ' + color,
         borderWidth: vertWidth + 'px ' + sideWidth + 'px'
      })
   };

   // Border canvas draw method
   function drawBorder(canvas, coordinates, radius, color)
   {
      // Create corner
      var context = canvas.get(0).getContext('2d');
      context.fillStyle = color;
      context.beginPath();
      context.arc(coordinates[0], coordinates[1], radius, 0, Math.PI * 2, false);
      context.fill();
   };

   // Create tip using canvas and VML
   function createTip(corner)
   {
      var self, color, coordinates, coordsize, path;
      self = this;

      // Destroy previous tip, if there is one
      if(self.elements.tip !== null) self.elements.tip.remove();

      // Setup color and corner values
      color = self.options.style.tip.color || self.options.style.border.color;
      if(self.options.style.tip.corner === false) return;
      else if(!corner) corner = self.options.style.tip.corner;

      // Calculate tip coordinates
      coordinates = calculateTip(corner, self.options.style.tip.size.width, self.options.style.tip.size.height);

      // Create tip element
      self.elements.tip =  '<div class="'+self.options.style.classes.tip+'" dir="ltr" rel="'+corner+'" style="position:absolute; ' +
         'height:'+self.options.style.tip.size.height+'px; width:'+self.options.style.tip.size.width+'px; ' +
         'margin:0 auto; line-height:0.1px; font-size:1px;">';

      // Use canvas element if supported
      if($('<canvas>').get(0).getContext)
          self.elements.tip += '<canvas height="'+self.options.style.tip.size.height+'" width="'+self.options.style.tip.size.width+'"></canvas>';

      // Canvas not supported - Use VML (IE)
      else if($.browser.msie)
      {
         // Create coordize and tip path using tip coordinates
         coordsize = self.options.style.tip.size.width + ',' + self.options.style.tip.size.height;
         path = 'm' + coordinates[0][0] + ',' + coordinates[0][1];
         path += ' l' + coordinates[1][0] + ',' + coordinates[1][1];
         path += ' ' + coordinates[2][0] + ',' + coordinates[2][1];
         path += ' xe';

         // Create VML element
         self.elements.tip += '<v:shape fillcolor="'+color+'" stroked="false" filled="true" path="'+path+'" coordsize="'+coordsize+'" ' +
            'style="width:'+self.options.style.tip.size.width+'px; height:'+self.options.style.tip.size.height+'px; ' +
            'line-height:0.1px; display:inline-block; behavior:url(#default#VML); ' +
            'vertical-align:'+((corner.search(/top/) !== -1) ? 'bottom' : 'top')+'"></v:shape>';

         // Create a phantom VML element (IE won't show the last created VML element otherwise)
         self.elements.tip += '<v:image style="behavior:url(#default#VML);"></v:image>';

         // Prevent tooltip appearing above the content (IE z-index bug)
         self.elements.contentWrapper.css('position', 'relative');
      };

      // Attach new tip to tooltip element
      self.elements.tooltip.prepend(self.elements.tip + '</div>');

      // Create element reference and draw the canvas tip (Delayed til after DOM creation)
      self.elements.tip = self.elements.tooltip.find('.'+self.options.style.classes.tip).eq(0);
      if($('<canvas>').get(0).getContext)
         drawTip.call(self, self.elements.tip.find('canvas:first'), coordinates, color);

      // Fix IE small tip bug
      if(corner.search(/top/) !== -1 && $.browser.msie && parseInt($.browser.version.charAt(0)) === 6)
         self.elements.tip.css({ marginTop: -4 });

      // Set the tip position
      positionTip.call(self, corner);
   };

   // Canvas tip drawing method
   function drawTip(canvas, coordinates, color)
   {
      // Setup properties
      var context = canvas.get(0).getContext('2d');
      context.fillStyle = color;

      // Create tip
      context.beginPath();
      context.moveTo(coordinates[0][0], coordinates[0][1]);
      context.lineTo(coordinates[1][0], coordinates[1][1]);
      context.lineTo(coordinates[2][0], coordinates[2][1]);
      context.fill();
   };

   function positionTip(corner)
   {
      var self, ieAdjust, paddingCorner, paddingSize, newMargin;
      self = this;

      // Return if tips are disabled or tip is not yet rendered
      if(self.options.style.tip.corner === false || !self.elements.tip) return;
      if(!corner) corner = self.elements.tip.attr('rel');

      // Setup adjustment variables
      ieAdjust = positionAdjust = ($.browser.msie) ? 1 : 0;

      // Set initial position
      self.elements.tip.css(corner.match(/left|right|top|bottom/)[0], 0);

      // Set position of tip to correct side
      if(corner.search(/top|bottom/) !== -1)
      {
         // Adjustments for IE6 - 0.5px border gap bug
         if($.browser.msie)
         {
            if(parseInt($.browser.version.charAt(0)) === 6)
               positionAdjust = (corner.search(/top/) !== -1) ? -3 : 1;
            else
               positionAdjust = (corner.search(/top/) !== -1) ? 1 : 2;
         };

         if(corner.search(/Middle/) !== -1)
            self.elements.tip.css({ left: '50%', marginLeft: -(self.options.style.tip.size.width / 2) });

         else if(corner.search(/Left/) !== -1)
            self.elements.tip.css({ left: self.options.style.border.radius - ieAdjust });

         else if(corner.search(/Right/) !== -1)
            self.elements.tip.css({ right: self.options.style.border.radius + ieAdjust });

         if(corner.search(/top/) !== -1)
            self.elements.tip.css({ top: -positionAdjust });
         else
            self.elements.tip.css({ bottom: positionAdjust });

      }
      else if(corner.search(/left|right/) !== -1)
      {
         // Adjustments for IE6 - 0.5px border gap bug
         if($.browser.msie)
            positionAdjust = (parseInt($.browser.version.charAt(0)) === 6) ? 1 : ((corner.search(/left/) !== -1) ? 1 : 2);

         if(corner.search(/Middle/) !== -1)
            self.elements.tip.css({ top: '50%', marginTop: -(self.options.style.tip.size.height / 2) });

         else if(corner.search(/Top/) !== -1)
            self.elements.tip.css({ top: self.options.style.border.radius - ieAdjust });

         else if(corner.search(/Bottom/) !== -1)
            self.elements.tip.css({ bottom: self.options.style.border.radius + ieAdjust });

         if(corner.search(/left/) !== -1)
            self.elements.tip.css({ left: -positionAdjust });
         else
            self.elements.tip.css({ right: positionAdjust });
      };

      // Adjust tooltip padding to compensate for tip
      paddingCorner = 'padding-' + corner.match(/left|right|top|bottom/)[0];
      paddingSize = self.options.style.tip.size[ (paddingCorner.search(/left|right/) !== -1) ? 'width' : 'height' ];
      self.elements.tooltip.css('padding', 0);
      self.elements.tooltip.css(paddingCorner, paddingSize);

      // Match content margin to prevent gap bug in IE6 ONLY
      if($.browser.msie && parseInt($.browser.version.charAt(0)) == 6)
      {
         newMargin = parseInt(self.elements.tip.css('margin-top')) || 0;
         newMargin += parseInt(self.elements.content.css('margin-top')) || 0;

         self.elements.tip.css({ marginTop: newMargin });
      };
   };

   // Create title bar for content
   function createTitle()
   {
      var self = this;

      // Destroy previous title element, if present
      if(self.elements.title !== null) self.elements.title.remove();

      // Create title element
      self.elements.title = $('<div class="'+self.options.style.classes.title+'">')
         .css( jQueryStyle(self.options.style.title, true) )
         .css({ zoom: ($.browser.msie) ? 1 : 0 })
         .prependTo(self.elements.contentWrapper);

      // Update title with contents if enabled
      if(self.options.content.title.text) self.updateTitle.call(self, self.options.content.title.text);

      // Create title close buttons if enabled
      if(self.options.content.title.button !== false
      && typeof self.options.content.title.button == 'string')
      {
         self.elements.button = $('<a class="'+self.options.style.classes.button+'" style="float:right; position: relative"></a>')
            .css( jQueryStyle(self.options.style.button, true) )
            .html(self.options.content.title.button)
            .prependTo(self.elements.title)
            .click(function(event){ if(!self.status.disabled) self.hide(event) });
      };
   };

   // Assign hide and show events
   function assignEvents()
   {
      var self, showTarget, hideTarget, inactiveEvents;
      self = this;

      // Setup event target variables
      showTarget = self.options.show.when.target;
      hideTarget = self.options.hide.when.target;

      // Add tooltip as a hideTarget is its fixed
      if(self.options.hide.fixed) hideTarget = hideTarget.add(self.elements.tooltip);

      // Check if the hide event is special 'inactive' type
      if(self.options.hide.when.event == 'inactive')
      {
         // Define events which reset the 'inactive' event handler
         inactiveEvents = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
         'mouseout', 'mouseenter', 'mouseleave', 'mouseover' ];

         // Define 'inactive' event timer method
         function inactiveMethod(event)
         {
            if(self.status.disabled === true) return;

            //Clear and reset the timer
            clearTimeout(self.timers.inactive);
            self.timers.inactive = setTimeout(function()
            {
               // Unassign 'inactive' events
               $(inactiveEvents).each(function()
               {
                  hideTarget.unbind(this+'.qtip-inactive');
                  self.elements.content.unbind(this+'.qtip-inactive');
               });

               // Hide the tooltip
               self.hide(event);
            }
            , self.options.hide.delay);
         };
      }

      // Check if the tooltip is 'fixed'
      else if(self.options.hide.fixed === true)
      {
         self.elements.tooltip.bind('mouseover.qtip', function()
         {
            if(self.status.disabled === true) return;

            // Reset the hide timer
            clearTimeout(self.timers.hide);
         });
      };

      // Define show event method
      function showMethod(event)
      {
         if(self.status.disabled === true) return;

         // If set, hide tooltip when inactive for delay period
         if(self.options.hide.when.event == 'inactive')
         {
            // Assign each reset event
            $(inactiveEvents).each(function()
            {
               hideTarget.bind(this+'.qtip-inactive', inactiveMethod);
               self.elements.content.bind(this+'.qtip-inactive', inactiveMethod);
            });

            // Start the inactive timer
            inactiveMethod();
         };

         // Clear hide timers
         clearTimeout(self.timers.show);
         clearTimeout(self.timers.hide);

         // Start show timer
         self.timers.show = setTimeout(function(){ self.show(event); }, self.options.show.delay);
      };

      // Define hide event method
      function hideMethod(event)
      {
         if(self.status.disabled === true) return;

         // Prevent hiding if tooltip is fixed and event target is the tooltip
         if(self.options.hide.fixed === true
         && self.options.hide.when.event.search(/mouse(out|leave)/i) !== -1
         && $(event.relatedTarget).parents('div.qtip[qtip]').length > 0)
         {
            // Prevent default and popagation
            event.stopPropagation();
            event.preventDefault();

            // Reset the hide timer
            clearTimeout(self.timers.hide);
            return false;
         };

         // Clear timers and stop animation queue
         clearTimeout(self.timers.show);
         clearTimeout(self.timers.hide);
         self.elements.tooltip.stop(true, true);

         // If tooltip has displayed, start hide timer
         self.timers.hide = setTimeout(function(){ self.hide(event); }, self.options.hide.delay);
      };

      // Both events and targets are identical, apply events using a toggle
      if((self.options.show.when.target.add(self.options.hide.when.target).length === 1
      && self.options.show.when.event == self.options.hide.when.event
      && self.options.hide.when.event !== 'inactive')
      || self.options.hide.when.event == 'unfocus')
      {
         self.cache.toggle = 0;
         // Use a toggle to prevent hide/show conflicts
         showTarget.bind(self.options.show.when.event + '.qtip', function(event)
         {
            if(self.cache.toggle == 0) showMethod(event);
            else hideMethod(event);
         });
      }

      // Events are not identical, bind normally
      else
      {
         showTarget.bind(self.options.show.when.event + '.qtip', showMethod);

         // If the hide event is not 'inactive', bind the hide method
         if(self.options.hide.when.event !== 'inactive')
            hideTarget.bind(self.options.hide.when.event + '.qtip', hideMethod);
      };

      // Focus the tooltip on mouseover
      if(self.options.position.type.search(/(fixed|absolute)/) !== -1)
         self.elements.tooltip.bind('mouseover.qtip', self.focus);

      // If mouse is the target, update tooltip position on mousemove
      if(self.options.position.target === 'mouse' && self.options.position.type !== 'static')
      {
         showTarget.bind('mousemove.qtip', function(event)
         {
            // Set the new mouse positions if adjustment is enabled
            self.cache.mouse = { x: event.pageX, y: event.pageY };

            // Update the tooltip position only if the tooltip is visible and adjustment is enabled
            if(self.status.disabled === false
            && self.options.position.adjust.mouse === true
            && self.options.position.type !== 'static'
            && self.elements.tooltip.css('display') !== 'none')
               self.updatePosition(event);
         });
      };
   };

   // Screen position adjustment
   function screenAdjust(position, target, tooltip)
   {
      var self, adjustedPosition, adjust, newCorner, overflow, corner;
      self = this;

      // Setup corner and adjustment variable
      if(tooltip.corner == 'center') return target.position // TODO: 'center' corner adjustment
      adjustedPosition = $.extend({}, position);
      newCorner = { x: false, y: false };

      // Define overflow properties
      overflow = {
         left: (adjustedPosition.left < $.fn.qtip.cache.screen.scroll.left),
         right: (adjustedPosition.left + tooltip.dimensions.width + 2 >= $.fn.qtip.cache.screen.width + $.fn.qtip.cache.screen.scroll.left),
         top: (adjustedPosition.top < $.fn.qtip.cache.screen.scroll.top),
         bottom: (adjustedPosition.top + tooltip.dimensions.height + 2 >= $.fn.qtip.cache.screen.height + $.fn.qtip.cache.screen.scroll.top)
      };

      // Determine new positioning properties
      adjust = {
         left: (overflow.left && (tooltip.corner.search(/right/i) != -1 || (tooltip.corner.search(/right/i) == -1 && !overflow.right))),
         right: (overflow.right && (tooltip.corner.search(/left/i) != -1 || (tooltip.corner.search(/left/i) == -1 && !overflow.left))),
         top: (overflow.top && tooltip.corner.search(/top/i) == -1),
         bottom: (overflow.bottom && tooltip.corner.search(/bottom/i) == -1)
      };

      // Tooltip overflows off the left side of the screen
      if(adjust.left)
      {
         if(self.options.position.target !== 'mouse')
            adjustedPosition.left = target.position.left + target.dimensions.width;
         else
            adjustedPosition.left = self.cache.mouse.x

         newCorner.x = 'Left';
      }

      // Tooltip overflows off the right side of the screen
      else if(adjust.right)
      {
         if(self.options.position.target !== 'mouse')
            adjustedPosition.left = target.position.left - tooltip.dimensions.width;
         else
            adjustedPosition.left = self.cache.mouse.x - tooltip.dimensions.width;

         newCorner.x = 'Right';
      };

      // Tooltip overflows off the top of the screen
      if(adjust.top)
      {
         if(self.options.position.target !== 'mouse')
            adjustedPosition.top = target.position.top + target.dimensions.height;
         else
            adjustedPosition.top = self.cache.mouse.y

         newCorner.y = 'top';
      }

      // Tooltip overflows off the bottom of the screen
      else if(adjust.bottom)
      {
         if(self.options.position.target !== 'mouse')
            adjustedPosition.top = target.position.top - tooltip.dimensions.height;
         else
            adjustedPosition.top = self.cache.mouse.y - tooltip.dimensions.height;

         newCorner.y = 'bottom';
      };

      // Don't adjust if resulting position is negative
      if(adjustedPosition.left < 0)
      {
         adjustedPosition.left = position.left;
         newCorner.x = false;
      };
      if(adjustedPosition.top < 0)
      {
         adjustedPosition.top = position.top;
         newCorner.y = false;
      };

      // Change tip corner if positioning has changed and tips are enabled
      if(self.options.style.tip.corner !== false)
      {
         // Determine new corner properties
         adjustedPosition.corner = new String(tooltip.corner);
         if(newCorner.x !== false) adjustedPosition.corner = adjustedPosition.corner.replace(/Left|Right|Middle/, newCorner.x);
         if(newCorner.y !== false) adjustedPosition.corner = adjustedPosition.corner.replace(/top|bottom/, newCorner.y);

         // Adjust tip if position has changed and tips are enabled
         if(adjustedPosition.corner !== self.elements.tip.attr('rel'))
            createTip.call(self, adjustedPosition.corner);
      };

      return adjustedPosition;
   };

   // Build a jQuery style object from supplied style object
   function jQueryStyle(style, sub)
   {
      var styleObj, i;

      styleObj = $.extend(true, {}, style);
      for(i in styleObj)
      {
         if(sub === true && i.search(/(tip|classes)/i) !== -1)
            delete styleObj[i];
         else if(!sub && i.search(/(width|border|tip|title|classes|user)/i) !== -1)
            delete styleObj[i];
      };

      return styleObj;
   };

   // Sanitize styles
   function sanitizeStyle(style)
   {
      if(typeof style.tip !== 'object') style.tip = { corner: style.tip };
      if(typeof style.tip.size !== 'object') style.tip.size = { width: style.tip.size, height: style.tip.size };
      if(typeof style.border !== 'object') style.border = { width: style.border };
      if(typeof style.width !== 'object') style.width = { value: style.width };
      if(typeof style.width.max == 'string') style.width.max = parseInt(style.width.max.replace(/([0-9]+)/i, "$1"));
      if(typeof style.width.min == 'string') style.width.min = parseInt(style.width.min.replace(/([0-9]+)/i, "$1"));

      // Convert deprecated x and y tip values to width/height
      if(typeof style.tip.size.x == 'number')
      {
         style.tip.size.width = style.tip.size.x;
         delete style.tip.size.x;
      };
      if(typeof style.tip.size.y == 'number')
      {
         style.tip.size.height = style.tip.size.y;
         delete style.tip.size.y;
      };

      return style;
   };

   // Build styles recursively with inheritance
   function buildStyle()
   {
      var self, i, styleArray, styleExtend, finalStyle, ieAdjust;
      self = this;

      // Build style options from supplied arguments
      styleArray = [true, {}];
      for(i = 0; i < arguments.length; i++)
         styleArray.push(arguments[i]);
      styleExtend = [ $.extend.apply($, styleArray) ];

      // Loop through each named style inheritance
      while(typeof styleExtend[0].name == 'string')
      {
         // Sanitize style data and append to extend array
         styleExtend.unshift( sanitizeStyle($.fn.qtip.styles[ styleExtend[0].name ]) );
      };

      // Make sure resulting tooltip className represents final style
      styleExtend.unshift(true, {classes:{ tooltip: 'qtip-' + (arguments[0].name || 'defaults') }}, $.fn.qtip.styles.defaults);

      // Extend into a single style object
      finalStyle = $.extend.apply($, styleExtend);

      // Adjust tip size if needed (IE 1px adjustment bug fix)
      ieAdjust = ($.browser.msie) ? 1 : 0;
      finalStyle.tip.size.width += ieAdjust;
      finalStyle.tip.size.height += ieAdjust;

      // Force even numbers for pixel precision
      if(finalStyle.tip.size.width % 2 > 0) finalStyle.tip.size.width += 1;
      if(finalStyle.tip.size.height % 2 > 0) finalStyle.tip.size.height += 1;

      // Sanitize final styles tip corner value
      if(finalStyle.tip.corner === true)
         finalStyle.tip.corner = (self.options.position.corner.tooltip === 'center') ? false : self.options.position.corner.tooltip;

      return finalStyle;
   };

   // Tip coordinates calculator
   function calculateTip(corner, width, height)
   {
      // Define tip coordinates in terms of height and width values
      var tips = {
         bottomRight:   [[0,0],              [width,height],      [width,0]],
         bottomLeft:    [[0,0],              [width,0],           [0,height]],
         topRight:      [[0,height],         [width,0],           [width,height]],
         topLeft:       [[0,0],              [0,height],          [width,height]],
         topMiddle:     [[0,height],         [width / 2,0],       [width,height]],
         bottomMiddle:  [[0,0],              [width,0],           [width / 2,height]],
         rightMiddle:   [[0,0],              [width,height / 2],  [0,height]],
         leftMiddle:    [[width,0],          [width,height],      [0,height / 2]]
      };
      tips.leftTop = tips.bottomRight;
      tips.rightTop = tips.bottomLeft;
      tips.leftBottom = tips.topRight;
      tips.rightBottom = tips.topLeft;

      return tips[corner];
   };

   // Border coordinates calculator
   function calculateBorders(radius)
   {
      var borders;

      // Use canvas element if supported
      if($('<canvas>').get(0).getContext)
      {
         borders = {
            topLeft: [radius,radius], topRight: [0,radius],
            bottomLeft: [radius,0], bottomRight: [0,0]
         };
      }

      // Canvas not supported - Use VML (IE)
      else if($.browser.msie)
      {
         borders = {
            topLeft: [-90,90,0], topRight: [-90,90,-radius],
            bottomLeft: [90,270,0], bottomRight: [90, 270,-radius]
         };
      };

      return borders;
   };

   // BGIFRAME JQUERY PLUGIN ADAPTION
   //   Special thanks to Brandon Aaron for this plugin
   //   http://plugins.jquery.com/project/bgiframe
   function bgiframe()
   {
      var self, html, dimensions;
      self = this;
      dimensions = self.getDimensions();

      // Setup iframe HTML string
      html = '<iframe class="qtip-bgiframe" frameborder="0" tabindex="-1" src="javascript:false" '+
         'style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=\'0\'); border: 1px solid red; ' +
         'height:'+dimensions.height+'px; width:'+dimensions.width+'px" />';

      // Append the new HTML and setup element reference
      self.elements.bgiframe = self.elements.wrapper.prepend(html).children('.qtip-bgiframe:first');
   };

   // Assign cache and event initialisation on document load
   $(document).ready(function()
   {
      // Setup library cache with window scroll and dimensions of document
      $.fn.qtip.cache = {
         screen: {
            scroll: { left: $(window).scrollLeft(), top: $(window).scrollTop() },
            width: $(window).width(),
            height: $(window).height()
         }
      };

      // Adjust positions of the tooltips on window resize or scroll if enabled
      var adjustTimer;
      $(window).bind('resize scroll', function(event)
      {
         clearTimeout(adjustTimer);
         adjustTimer = setTimeout(function()
         {
            // Readjust cached screen values
            if(event.type === 'scroll')
               $.fn.qtip.cache.screen.scroll = { left: $(window).scrollLeft(), top: $(window).scrollTop() };
            else
            {
               $.fn.qtip.cache.screen.width = $(window).width();
               $.fn.qtip.cache.screen.height = $(window).height();
            };

            for(i = 0; i < $.fn.qtip.interfaces.length; i++)
            {
               // Access current elements API
               var api = $.fn.qtip.interfaces[i];

               // Update position if resize or scroll adjustments are enabled
               if(api.status.rendered === true
               && (api.options.position.type !== 'static'
               || api.options.position.adjust.scroll && event.type === 'scroll'
               || api.options.position.adjust.resize && event.type === 'resize'))
               {
                  // Queue the animation so positions are updated correctly
                  api.updatePosition(event, true);
               }
            };
         }
         , 100);
      })

      // Hide unfocus toolipts on document mousedown
      $(document).bind('mousedown.qtip', function(event)
      {
         if($(event.target).parents('div.qtip').length === 0)
         {
            $('.qtip[unfocus]').each(function()
            {
               var api = $(this).qtip("api");

               // Only hide if its visible and not the tooltips target
               if($(this).is(':visible') && !api.status.disabled
               && $(event.target).add(api.elements.target).length > 1)
                  api.hide(event);
            })
         };
      })
   });

   // Define qTip API interfaces array
   $.fn.qtip.interfaces = []

   // Define log and constant place holders
   $.fn.qtip.log = { error: function(){ return this; } };
   $.fn.qtip.constants = {};

   // Define configuration defaults
   $.fn.qtip.defaults = {
      // Content
      content: {
         prerender: false,
         text: false,
         url: false,
         data: null,
         title: {
            text: false,
            button: false
         }
      },
      // Position
      position: {
         target: false,
         corner: {
            target: 'bottomRight',
            tooltip: 'topLeft'
         },
         adjust: {
            x: 0, y: 0,
            mouse: true,
            screen: false,
            scroll: true,
            resize: true
         },
         type: 'absolute',
         container: false
      },
      // Effects
      show: {
         when: {
            target: false,
            event: 'mouseover'
         },
         effect: {
            type: 'fade',
            length: 100
         },
         delay: 140,
         solo: false,
         ready: false
      },
      hide: {
         when: {
            target: false,
            event: 'mouseout'
         },
         effect: {
            type: 'fade',
            length: 100
         },
         delay: 0,
         fixed: false
      },
      // Callbacks
      api: {
         beforeRender: function(){},
         onRender: function(){},
         beforePositionUpdate: function(){},
         onPositionUpdate: function(){},
         beforeShow: function(){},
         onShow: function(){},
         beforeHide: function(){},
         onHide: function(){},
         beforeContentUpdate: function(){},
         onContentUpdate: function(){},
         beforeContentLoad: function(){},
         onContentLoad: function(){},
         beforeTitleUpdate: function(){},
         onTitleUpdate: function(){},
         beforeDestroy: function(){},
         onDestroy: function(){},
         beforeFocus: function(){},
         onFocus: function(){}
      }
   };

   $.fn.qtip.styles = {
      defaults: {
         background: 'white',
         color: '#111',
         overflow: 'hidden',
         textAlign: 'left',
         width: {
            min: 0,
            max: 250
         },
         padding: '5px 9px',
         border: {
            width: 1,
            radius: 0,
            color: '#d3d3d3'
         },
         tip: {
            corner: false,
            color: false,
            size: { width: 13, height: 13 },
            opacity: 1
         },
         title: {
            background: '#e1e1e1',
            fontWeight: 'bold',
            padding: '7px 12px'
         },
         button: {
            cursor: 'pointer'
         },
         classes: {
            target: '',
            tip: 'qtip-tip',
            title: 'qtip-title',
            button: 'qtip-button',
            content: 'qtip-content',
            active: 'qtip-active'
         }
      },
      cream: {
         border: {
            width: 3,
            radius: 0,
            color: '#F9E98E'
         },
         title: {
            background: '#F0DE7D',
            color: '#A27D35'
         },
         background: '#FBF7AA',
         color: '#A27D35',

         classes: { tooltip: 'qtip-cream' }
      },
      light: {
         border: {
            width: 3,
            radius: 0,
            color: '#E2E2E2'
         },
         title: {
            background: '#f1f1f1',
            color: '#454545'
         },
         background: 'white',
         color: '#454545',

         classes: { tooltip: 'qtip-light' }
      },
      dark: {
         border: {
            width: 3,
            radius: 0,
            color: '#303030'
         },
         title: {
            background: '#404040',
            color: '#f3f3f3'
         },
         background: '#505050',
         color: '#f3f3f3',

         classes: { tooltip: 'qtip-dark' }
      },
      red: {
         border: {
            width: 3,
            radius: 0,
            color: '#CE6F6F'
         },
         title: {
            background: '#f28279',
            color: '#9C2F2F'
         },
         background: '#F79992',
         color: '#9C2F2F',

         classes: { tooltip: 'qtip-red' }
      },
      green: {
         border: {
            width: 3,
            radius: 0,
            color: '#A9DB66'
         },
         title: {
            background: '#b9db8c',
            color: '#58792E'
         },
         background: '#CDE6AC',
         color: '#58792E',

         classes: { tooltip: 'qtip-green' }
      },
      blue: {
         border: {
            width: 3,
            radius: 0,
            color: '#ADD9ED'
         },
         title: {
            background: '#D0E9F5',
            color: '#5E99BD'
         },
         background: '#E5F6FE',
         color: '#4D9FBF',

         classes: { tooltip: 'qtip-blue' }
      }
   };
})(jQuery);
/*
 * List: a sequential, non-exclusive data structure
 * $Id: list.js 1606 2003-10-24 21:06:35Z scott $
 * Copyright (C) 2001-2003 Scott Martin (http://www.coffeeblack.org/contact/)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, it is available from the Free Software
 * Foundation, Inc. at http://www.gnu.org/copyleft/lesser.html or in writing at
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
 
/*
 * Creates a new list. This method simply initializes the internal array
 * backing this list.
 *
 * A list functions a lot like an array, but adds the ability to set,
 * insert and remove elements at an arbitrary index and automatically shift
 * the other list elements to the correct positions. Also, the first and
 * last index of a given element can be determined, and the contains() method
 * can be used to test the presence of an element before accessing it.
 *
 * List elements must be defined, but can be null. A list allows multiple 
 * duplicate elements to be added. A list can also be sorted by providing a 
 * sort function to the sort() method.
 */ 
function List()
{	
	this.elements = new Array();
}

/*
 * Gets the size of the list, the number of elements it contains.
 */
List.prototype.size = function()
{	
	return this.elements.length;
}

/*
 * Tests whether this list is currently empty (has no elements).
 */
List.prototype.isEmpty = function()
{	
	return (this.elements.length == 0);
}

/*
 * Adds an element to this list. The element must be defined and can be null.
 *
 * If no index is specified, the element is appended to the end of this list.
 * Otherwise, the element is inserted at the specified index and all elements
 * that occur after that index are shifted to the right (their indeces are
 * increased by 1). If the specified index is negative, it is increased to 0.
 * If the specified index exceeds the size of this list, it is reduced
 * to this size of this list.
 */
List.prototype.add = function(value, index)
{	
	if(value) // don't add undefined elements
	{	
		with(this)	
		{	// if index is not present or invalid,
			// append to the end of the list
			if(isNaN(index) || !checkIndex(index))
				index = elements.length;

			// shift elements after value to the right
			if(index < elements.length)
			{	
				for(var i = elements.length; i > index; i--)
					elements[i] = elements[i - 1];
			}

			elements[index] = value;
		}
	}
}


List.prototype.addAll = function(other)
{
	for (var i=0; i<other.size(); i++)
	{
		this.add( other.get(i) );
	}
}

/*
 * Sets the element at the specified index to the value of the new element.
 * The element must be defined and can be null. The index must be defined
 * and must be within the bounds of the size of this list (at least zero
 * and less than its size).
 */
List.prototype.set = function(value, index)
{	
	if(value && !isNaN(index) && this.checkIndex(index))
		this.elements[index] = value;
}

/*
 * Gets the element the specified index. If index is not defined, the
 * first element (at index 0) is returned.
 *
 * If defined, the index must be within the bounds of the size of this
 * list (at least zero and less than its size). Otherwise, this method
 * returns null.
 */
List.prototype.get = function(index)
{	
	with(this)
	{	
		if(isNaN(index))
			return first();
	
		return (checkIndex(index)) ? elements[index] : null;
	}
}

/*
 * Gets the first element in this list, if the list is not empty.
 */
List.prototype.first = function()
{	
	with(this)
	{	
		return (isEmpty()) ? null : get(0);
	}
}

/*
 * Gets the last element in this list, if the list is not empty.
 */
List.prototype.last = function()
{	
	with(this)
	{	
		return (isEmpty()) ? null : get(elements.length - 1);
	}
}

/*
 * Removes and returns the element at the specified index, if it is within
 * the bounds of this list (at least zero and less than its size). If the
 * index is not defined, 0 is used. If an element is successfully removed,
 * all elements occuring after the index of the removed element are shifted
 * left (their indeces are decreased by 1). The size of the list is then
 * decreased by 1.
 */
List.prototype.remove = function(index)
{	
	if(isNaN(index))
		index = 0;
	
	var obj = null;
		
	with(this)	
	{	
		if(checkIndex(index))
		{	
			obj = elements[index];
		
			// shift the other elements up one
			for(var j = index; j < (elements.length - 1); j++)
				elements[j] = elements[j + 1];

			// last element is invalid, chop it off
			elements.length -= 1;
		}
	}
	
	return obj;
}

/*
 * Tests whether this list contains the specified value.
 */
List.prototype.contains = function(value)
{	
	return (this.indexOf(value) != -1);
}

/*
 * Gets the first index of the specified value in this list, if it is present.
 * If not, this method returns -1.
 */
List.prototype.indexOf = function(value)
{	
	if(value)
	{	
		with(this)
		{	
			for(var i = 0; i < elements.length; i++)
				if(elements[i] == value)
					return i;
		}
	}
	
	return -1;
}

/*
 * Gets the last index of the specified value in this list, if it is present.
 * If not, this method returns -1.
 */
List.prototype.lastIndexOf = function(value)
{	
	if(value)
	{	
		with(this)
		{	
			for(var i = (elements.length - 1); i >= 0; i--)
				if(elements[i] == value)
					return i;
		}
	}

	return -1;
}

/*
 * Sorts this list according to the specified sort function. If the sort
 * function is not defined, the elements are sorted using the default
 * no-argument sort function Array.sort().
 */
List.prototype.sort = function(sortFunc)
{	
	if(sortFunc && sortFunc != null)
		this.elements.sort(sortFunc);
	else this.elements.sort();	
}

/*
 * Clears this list. This method reduces the size of the list to 0.
 */
List.prototype.clear = function()
{	
	this.elements.length = 0;
}

/*
 * Utility method that performs bounds checking on an element index.
 */
List.prototype.checkIndex = function(index)
{	
	return (index >= 0 && index < this.elements.length);
}

/*
 * Gets a string representation of this list.
 */
List.prototype.toString = function()
{	
	return "[object List]";
}
/*
 * Map: a dictionary data structure based on a hash table
 * $Id: map.js 1932 2004-04-20 05:04:18Z scott $
 * Copyright (C) 2001-2003 Scott Martin (http://www.coffeeblack.org/contact/)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, it is available from the Free Software
 * Foundation, Inc. at http://www.gnu.org/copyleft/lesser.html or in writing at
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
 
/*
 * Creates a new map. This method simply initializes the internal hash table
 * backing this map. The optional argument 'size' refers to the number
 * of buckets that will be used in this map's internal hash table. If
 * this argument is unspecified, not a number, or less than one, a
 * default value of 10 buckets is used.
 *
 * A map stores a series of elements. Each element is a key and its
 * corresponding value. Once an element is added using put(), the key can then
 * be used to retrieve its corresponding value using get(). Keys must be
 * defined and non-null, and a map does not allow duplicate keys. An attempt to
 * add an element when its key is already present results in the current
 * element's value being replaced by the new one. The values themselves must be
 * defined, but can be null. Multiple duplicate values are permitted.
 *
 * Internally, a map uses a hash table to store elements inserted into it.
 * This hash table uses several buckets as storage, and the number of
 * buckets can be specified at construction. Each bucket is a chain of 
 * elements, and the appropriate bucket location of any given element is
 * determined by computing a hash code based on the element's key. The hash 
 * table implementation used by this map uses chaining to resolve collisions.
 */
function Map(size)
{	
	this.buckets = new Array();

	if(isNaN(size) || size < 1)
		size = 10;

	for(var i = 0; i < size; i++)
		this.buckets[i] = new Bucket();
}

/*
 * Computes a hash code for an object.
 */
function hash(object)
{	
	var hashCode = 0;
	var str = object.toString();

	for(var i = 0; i < str.length; i++)
		hashCode ^= str.charCodeAt(i);

	return hashCode;
}

/*
 * Finds the appropriate bucket for a given object using its hash code.
 */
Map.prototype.bucketFor = function(obj)
{	
	with(this)
	{	
		return buckets[hash(obj) % buckets.length];
	}
}

/*
 * Gets the size of the map, the number of elements it contains.
 */
Map.prototype.size = function()
{	
	var sz = 0;

	with(this)
	{	
		for(var i = 0; i < buckets.length; i++)
			sz += buckets[i].depth;
	}

	return sz;
}

/*
 * Tests whether this map is currently empty (has no elements).
 */ 
Map.prototype.isEmpty = function()
{	
	with(this)
	{	
		for(var i = 0; i < buckets.length; i++)
			if(buckets[i].depth > 0)
				return false;		
	}	

	return true;	
}

/*
 * Gets an array of the keys in this map.
 */
Map.prototype.keys = function()
{	
	var a = new Array();
	
	with(this)
	{	
		var bucket, e;
		for(var i = 0; i < buckets.length; i++)
		{	
			bucket = buckets[i];
			
			for(e = bucket.first; e != null; e = e.next)
				a[a.length] = e.key;
		}
	}
		
	return a;
}

/*
 * Gets an array of the values in this map.
 */
Map.prototype.values = function()
{	
	var a = new Array();
	
	with(this)
	{	
		var bucket, e;
		for(var i = 0; i < buckets.length; i++)
		{	
			bucket = buckets[i];
			
			for(e = bucket.first; e != null; e = e.next)
				a[a.length] = e.value;
		}
	}
		
	return a;
}

/*
 * Tests whether this map contains the specified key.
 */
Map.prototype.containsKey = function(key)
{	
	if(key && key != null)
	{	
		with(this)
		{	
			var bucket = bucketFor(key);

			for(var e = bucket.first; e != null; e = e.next)
				if(e.key == key)
					return true;
		}
	}
			
	return false;
}

/*
 * Tests whether this map contains the specified value.
 */
Map.prototype.containsValue = function(value)
{	
	if(value)
	{	
		with(this)
		{	
			var bucket, e;

			for(var i = 0; i < buckets.length; i++)
			{	
				bucket = buckets[i];

				for(e = bucket.first; e != null; e = e.next)
					if(e.value == value)
						return true;
			}
		}
	}
	
	return false;
}

/*
 * Adds an element to this map. The key must be defined
 * and non-null. The value must be defined, but can be null.
 *
 * If a key is currently contained in this map that is the same
 * as the key to be added, the new element is inserted in place of
 * the old one and the old element's value is then returned. If the new
 * key is not already present, this method returns null.
 */
Map.prototype.put = function(key, value)
{	
	with(this)
	{	
		return (key && key != null)
			? bucketFor(key).add(key, value) : null;
	}
}

/*
 * Gets the value associated with the specified key, if any.
 * Otherwise, this method returns null.
 */
Map.prototype.get = function(key)
{	
	if(key && key != null)
	{	
		with(this)
		{	
			var bucket = bucketFor(key);

			for(var e = bucket.first; e != null; e = e.next)
				if(e.key == key)
					return e.value;
		}
	}	
	
	return null;
}

/*
 * Removes the element with the specified key from this map, if any
 * such key is present. The removed element is then returned. If no such
 * key is present in this map, this method returns null. If an element is
 * successfully removed, ths size of this map decreases by 1.
 */
Map.prototype.remove = function(key)
{	
	if(key && key != null)
	{	
		with(this)
		{	
			return bucketFor(key).remove(key);
		}		
	}
	
	return null;
}


/*
 * Clears this map. This method reduces the size of the map to 0.
 */
Map.prototype.clear = function()
{	
	with(this)
	{	
		for(var i = 0; i < buckets.length; i++)
			buckets[i].clear();
	}
}

/*
 * Gets a string representation of this map.
 */
Map.prototype.toString = function()
{	
	return "[object Map]";
}

/*
 * A hash table bucket that holds entries.
 */
function Bucket()
{	// stores the number of elements contained
	this.depth = 0;
	// the first element in the chain
	this.first = null;
}

/*
 * Adds an entry to this bucket. If an entry already existed with the same
 * key, its value is replaced and the old value is then returned.
 */
Bucket.prototype.add = function(key, value)
{	
	with(this)
	{	
		if(first != null) // look for the key
		{	
			for(var e = first; e != null; e = e.next)
			{	
				if(e.key == key) // same key already present
				{	
					var old = e.value;
					e.value = value;
					return old;
				}
			}			
		}

		// bucket empty or key not present, add a new entry
		first = new Entry(key, value, first);
		depth++;
	}

	return null;
}

/*
 * Removes an entry from this bucket. If an entry was removed,
 * its value is returned and the bucket's depth is decreased by one.
 */
Bucket.prototype.remove = function(key)
{	
	with(this)
	{	
		if(first != null)
		{	
			for(var e = first, prev = null; e != null; prev = e, e = e.next)
			{	
				if(e.key == key)
				{	
					if(prev == null) // it's the first entry
						first = e.next;
					// otherwise, link previous to next
					else prev.next = e.next;

					depth--;
					return e.value;
				}
			}
		}		
	}
	
	return null;
}

/*
 * Removes all entries from this bucket.
 */
Bucket.prototype.clear = function()
{	
	with(this)
	{	
		first = null;
		depth = 0;
	}
}

/*
 * Gets a string representation of this bucket.
 */
Bucket.prototype.toString = function()
{	
	return "[object Bucket]";
}

/*
 * A map entry. This object simply ties a key to its associated value.
 * An entry also stores a reference to the next object in the chain of
 * entries in the bucket they belong to.
 */
function Entry(k, val, nxt)
{	
	this.key = k;
	this.value = val;
	this.next = nxt ? nxt : null;
}

/*
 * Gets a string representation of this entry.
 */
Entry.prototype.toString = function()
{	
	return "[object Entry]";
}
/*
 * Set: an exclusive, non-sequential data structure
 * $Id: set.js 1553 2003-04-05 05:23:21Z scott $
 * Copyright (C) 2001-2003 Scott Martin (http://www.coffeeblack.org/contact/)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, it is available from the Free Software
 * Foundation, Inc. at http://www.gnu.org/copyleft/lesser.html or in writing at
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * Creates a new set. The 'size' argument refers to the number of buckets that
 * will be used in the hash table of the map backing this set.
 * 
 * A set is essentially a non-sequential collection of objects that does not
 * allow duplicate entries. Any attempt to add an object that is equal to an
 * object already contained by the set results in the current object being 
 * replaced by the new one.
 *
 * Internally, a set uses a map for storage, and a map stores its elements
 * non-sequentially. Therefore, the list of objects returned by its values()
 * method is not guaranteed to (and will almost never) retain the objects' 
 * original order of insertion.
 *
 * dependency: Map
 */
function Set(size)
{	
	this.map = new Map(size);
}

/*
 * The placeholder object used as a value in all sets' internal maps.
 */
Set.placeholder = new Object();

/*
 * Gets the size of this set, the number of elements it contains.
 */
Set.prototype.size = function()
{	
	return this.map.size();
}

/*
 * Tests whether this set is empty (contains no elements).
 */
Set.prototype.isEmpty = function()
{	
	return this.map.isEmpty();
}

/*
 * Clears this set, emptying it of all stored elements.
 */
Set.prototype.clear = function()
{	
	return this.map.clear();
}

/*
 * Adds an object to this set. This method returns true if the object was
 * inserted without overwriting another object that was already present, false
 * if an object was overwritten.
 */
Set.prototype.add = function(obj)
{	
	return (this.map.put(obj, Set.placeholder) == null);
}

/*
 * Tests whether an object is contained by this set.
 */
Set.prototype.contains = function(obj)
{	
	return this.map.containsKey(obj);
}

/*
 * Removes an element from this set. This method returns true if an element was
 * actually removed from this set, false if no such element was present.
 */
Set.prototype.remove = function(obj)
{	
	return (this.map.remove(obj) == Set.placeholder);
}

/*
 * Gets a list of the elements contained in this set, as an array of objects.
 * Since sets do not store their elements sequentially, this method will almost
 * certainly return an array of elements in a different order than the order in
 * which they were inserted.
 */
Set.prototype.values = function()
{	
	return this.map.keys();
}

/*
 * Gets a string representation of this set.
 */
Set.prototype.toString = function()
{	
	return "[object Set]";
}

/**
 *  jQuery Plugin highlightFade (jquery.offput.ca/highlightFade)
 *  (c) 2006 Blair Mitchelmore (offput.ca) blair@offput.ca
 */
/**
 * This is version 0.7 of my highlightFade plugin. It follows the yellow fade technique of Web 2.0 fame
 * but expands it to allow any starting colour and allows you to specify the end colour as well.
 *
 * For the moment, I'm done with this plug-in. Unless I come upon a really cool feature it should have
 * this plug-in will only receive updates to ensure future compatibility with jQuery.
 *
 * As of now (Aug. 16, 2006) the plugin has been written with the 1.0.1 release of jQuery (rev 249) which
 * is available from http://jquery.com/src/jquery-1.0.1.js
 *
 * A note regarding rgb() syntax: I noticed that most browsers implement rgb syntax as either an integer 
 * (0-255) or percentage (0-100%) value for each field, that is, rgb(i/p,i/p,i/p); however, the W3C 
 * standard clearly defines it as "either three integer values or three percentage values" [http://www.w3.org/TR/CSS21/syndata.html] 
 * which I choose to follow despite the error redundancy of the typical behaviour browsers employ.
 *
 * Changelog:
 *
 *    0.7:
 *        - Added the awesome custom attribute support written by George Adamson (slightly modified)
 *        - Removed bgColor plugin dependency seeing as attr is customizable now...
 *    0.6:
 *        - Abstracted getBGColor into its own plugin with optional test and data retrieval functions
 *        - Converted all $ references to jQuery references as John's code seems to be shifting away
 *          from that and I don't want to have to update this for a long time.
 *    0.5:
 *        - Added simple argument syntax for only specifying start colour of event
 *        - Removed old style argument syntax
 *        - Added 'interval', 'final, and 'end' properties
 *        - Renamed 'color' property to 'start'
 *        - Added second argument to jQuery.highlightFade.getBGColor to bypass the e.highlighting check
 *    0.4:
 *        - Added rgb(%,%,%) color syntax
 *    0.3:
 *        - Fixed bug when event was called while parent was also running event corrupting the
 *          the background colour of the child
 *    0.2:
 *        - Fixed bug where an unspecified onComplete function made the page throw continuous errors
 *        - Fixed bug where multiple events on the same element would speed each subsequent event
 *    0.1:
 *        - Initial Release
 * 
 * @author          Blair Mitchelmore (blair@offput.ca)
 * @version         0.5
 */
jQuery.fn.highlightFade = function(settings) {
	var o = (settings && settings.constructor == String) ? {start: settings} : settings || {};
	var d = jQuery.highlightFade.defaults;
	var i = o['interval'] || d['interval'];
	var a = o['attr'] || d['attr'];
	var ts = {
		'linear': function(s,e,t,c) { return parseInt(s+(c/t)*(e-s)); },
		'sinusoidal': function(s,e,t,c) { return parseInt(s+Math.sin(((c/t)*90)*(Math.PI/180))*(e-s)); },
		'exponential': function(s,e,t,c) { return parseInt(s+(Math.pow(c/t,2))*(e-s)); }
	};
	var t = (o['iterator'] && o['iterator'].constructor == Function) ? o['iterator'] : ts[o['iterator']] || ts[d['iterator']] || ts['linear'];
	if (d['iterator'] && d['iterator'].constructor == Function) t = d['iterator'];
	return this.each(function() {
		if (!this.highlighting) this.highlighting = {};
		var e = (this.highlighting[a]) ? this.highlighting[a].end : jQuery.highlightFade.getBaseValue(this,a) || [255,255,255];
		var c = jQuery.highlightFade.getRGB(o['start'] || o['colour'] || o['color'] || d['start'] || [255,255,128]);
		var s = jQuery.speed(o['speed'] || d['speed']);
		var r = o['final'] || (this.highlighting[a] && this.highlighting[a].orig) ? this.highlighting[a].orig : jQuery.curCSS(this,a);
		if (o['end'] || d['end']) r = jQuery.highlightFade.asRGBString(e = jQuery.highlightFade.getRGB(o['end'] || d['end']));
		if (typeof o['final'] != 'undefined') r = o['final'];
		if (this.highlighting[a] && this.highlighting[a].timer) window.clearInterval(this.highlighting[a].timer);
		this.highlighting[a] = { steps: ((s.duration) / i), interval: i, currentStep: 0, start: c, end: e, orig: r, attr: a };
		jQuery.highlightFade(this,a,o['complete'],t);
	});
};

jQuery.highlightFade = function(e,a,o,t) {
	e.highlighting[a].timer = window.setInterval(function() { 
		var newR = t(e.highlighting[a].start[0],e.highlighting[a].end[0],e.highlighting[a].steps,e.highlighting[a].currentStep);
		var newG = t(e.highlighting[a].start[1],e.highlighting[a].end[1],e.highlighting[a].steps,e.highlighting[a].currentStep);
		var newB = t(e.highlighting[a].start[2],e.highlighting[a].end[2],e.highlighting[a].steps,e.highlighting[a].currentStep);
		jQuery(e).css(a,jQuery.highlightFade.asRGBString([newR,newG,newB]));
		if (e.highlighting[a].currentStep++ >= e.highlighting[a].steps) {
			jQuery(e).css(a,e.highlighting[a].orig || '');
			window.clearInterval(e.highlighting[a].timer);
			e.highlighting[a] = null;
			if (o && o.constructor == Function) o.call(e);
		}
	},e.highlighting[a].interval);
};

jQuery.highlightFade.defaults = {
	start: [255,255,128],
	interval: 50,
	speed: 400,
	attr: 'backgroundColor'
};

jQuery.highlightFade.getRGB = function(c,d) {
	var result;
	if (c && c.constructor == Array && c.length == 3) return c;
	if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c))
		return [parseInt(result[1]),parseInt(result[2]),parseInt(result[3])];
	else if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c))
		return [parseFloat(result[1])*2.55,parseFloat(result[2])*2.55,parseFloat(result[3])*2.55];
	else if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c))
		return [parseInt("0x" + result[1]),parseInt("0x" + result[2]),parseInt("0x" + result[3])];
	else if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c))
		return [parseInt("0x"+ result[1] + result[1]),parseInt("0x" + result[2] + result[2]),parseInt("0x" + result[3] + result[3])];
	else
		return jQuery.highlightFade.checkColorName(c) || d || null;
};

jQuery.highlightFade.asRGBString = function(a) {
	return "rgb(" + a.join(",") + ")";
};

jQuery.highlightFade.getBaseValue = function(e,a,b) {
	var s, t;
	b = b || false;
	t = a = a || jQuery.highlightFade.defaults['attr'];
	do {
		s = jQuery(e).css(t || 'backgroundColor');
		if ((s  != '' && s != 'transparent') || (e.tagName.toLowerCase() == "body") || (!b && e.highlighting && e.highlighting[a] && e.highlighting[a].end)) break; 
		t = false;
	} while (e = e.parentNode);
	if (!b && e.highlighting && e.highlighting[a] && e.highlighting[a].end) s = e.highlighting[a].end;
	if (s == undefined || s == '' || s == 'transparent') s = [255,255,255];
	return jQuery.highlightFade.getRGB(s);
};

jQuery.highlightFade.checkColorName = function(c) {
	if (!c) return null;
	switch(c.replace(/^\s*|\s*$/g,'').toLowerCase()) {
		case 'aqua': return [0,255,255];
		case 'black': return [0,0,0];
		case 'blue': return [0,0,255];
		case 'fuchsia': return [255,0,255];
		case 'gray': return [128,128,128];
		case 'green': return [0,128,0];
		case 'lime': return [0,255,0];
		case 'maroon': return [128,0,0];
		case 'navy': return [0,0,128];
		case 'olive': return [128,128,0];
		case 'purple': return [128,0,128];
		case 'red': return [255,0,0];
		case 'silver': return [192,192,192];
		case 'teal': return [0,128,128];
		case 'white': return [255,255,255];
		case 'yellow': return [255,255,0];
	}
};
/*
 * SimpleModal 1.2.2 - jQuery Plugin
 * http://www.ericmmartin.com/projects/simplemodal/
 * Copyright (c) 2008 Eric Martin
 * Dual licensed under the MIT and GPL licenses
 * Revision: $Id: jquery.simplemodal.js 181 2008-12-16 16:51:44Z emartin24 $
 */

/**
 * SimpleModal is a lightweight jQuery plugin that provides a simple
 * interface to create a modal dialog.
 *
 * The goal of SimpleModal is to provide developers with a cross-browser 
 * overlay and container that will be populated with data provided to
 * SimpleModal.
 *
 * There are two ways to call SimpleModal:
 * 1) As a chained function on a jQuery object, like jQuery('#myDiv').modal();.
 * This call would place the DOM object, #myDiv, inside a modal dialog.
 * Chaining requires a jQuery object. An optional options object can be
 * passed as a parameter.
 *
 * @example jQuery('<div>my data</div>').modal({options});
 * @example jQuery('#myDiv').modal({options});
 * @example jQueryObject.modal({options});
 *
 * 2) As a stand-alone function, like jQuery.modal(data). The data parameter
 * is required and an optional options object can be passed as a second
 * parameter. This method provides more flexibility in the types of data 
 * that are allowed. The data could be a DOM object, a jQuery object, HTML
 * or a string.
 * 
 * @example jQuery.modal('<div>my data</div>', {options});
 * @example jQuery.modal('my data', {options});
 * @example jQuery.modal(jQuery('#myDiv'), {options});
 * @example jQuery.modal(jQueryObject, {options});
 * @example jQuery.modal(document.getElementById('myDiv'), {options}); 
 * 
 * A SimpleModal call can contain multiple elements, but only one modal 
 * dialog can be created at a time. Which means that all of the matched
 * elements will be displayed within the modal container.
 * 
 * SimpleModal internally sets the CSS needed to display the modal dialog
 * properly in all browsers, yet provides the developer with the flexibility
 * to easily control the look and feel. The styling for SimpleModal can be 
 * done through external stylesheets, or through SimpleModal, using the
 * overlayCss and/or containerCss options.
 *
 * SimpleModal has been tested in the following browsers:
 * - IE 6, 7
 * - Firefox 2, 3
 * - Opera 9
 * - Safari 3
 *
 * @name SimpleModal
 * @type jQuery
 * @requires jQuery v1.2.2
 * @cat Plugins/Windows and Overlays
 * @author Eric Martin (http://ericmmartin.com)
 * @version 1.2.2
 */
(function (jQuery) {
	var ie6 = jQuery.browser.msie && parseInt(jQuery.browser.version) == 6 && !window['XMLHttpRequest'],
		ieQuirks = jQuery.browser.msie && !jQuery.boxModel,
		w = [];

	/*
	 * Stand-alone function to create a modal dialog.
	 * 
	 * @param {string, object} data A string, jQuery object or DOM object
	 * @param {object} [options] An optional object containing options overrides
	 */
	jQuery.modal = function (data, options) {
		return jQuery.modal.impl.init(data, options);
	};

	/*
	 * Stand-alone close function to close the modal dialog
	 */
	jQuery.modal.close = function () {
		jQuery.modal.impl.close();
	};

	/*
	 * Chained function to create a modal dialog.
	 * 
	 * @param {object} [options] An optional object containing options overrides
	 */
	jQuery.fn.modal = function (options) {
		return jQuery.modal.impl.init(this, options);
	};

	/*
	 * SimpleModal default options
	 * 
	 * opacity: (Number:50) The opacity value for the overlay div, from 0 - 100
	 * overlayId: (String:'simplemodal-overlay') The DOM element id for the overlay div
	 * overlayCss: (Object:{}) The CSS styling for the overlay div
	 * containerId: (String:'simplemodal-container') The DOM element id for the container div
	 * containerCss: (Object:{}) The CSS styling for the container div
	 * dataCss: (Object:{}) The CSS styling for the data div
	 * zIndex: (Number: 10000) Starting z-index value
	 * close: (Boolean:true) Show closeHTML?
	 * closeHTML: (String:'<a class="modalCloseImg" title="Close"></a>') The HTML for the 
	              default close link. SimpleModal will automatically add the closeClass to this element.
	 * closeClass: (String:'simplemodal-close') The CSS class used to bind to the close event
	 * position: (Array:null) Position of container [top, left]. Can be number of pixels or percentage
	 * persist: (Boolean:false) Persist the data across modal calls? Only used for existing
	            DOM elements. If true, the data will be maintained across modal calls, if false,
				   the data will be reverted to its original state.
	 * onOpen: (Function:null) The callback function used in place of SimpleModal's open
	 * onShow: (Function:null) The callback function used after the modal dialog has opened
	 * onClose: (Function:null) The callback function used in place of SimpleModal's close
	 */
	jQuery.modal.defaults = {
		opacity: 50,
		overlayId: 'simplemodal-overlay',
		overlayCss: {},
		containerId: 'simplemodal-container',
		containerCss: {},
		dataCss: {},
		zIndex: 10000,
		close: true,
		closeHTML: '<a class="modalCloseImg" title="Close"></a>',
		closeClass: 'simplemodal-close',
		position: null,
		positioning: "absolute",
		persist: false,
		onOpen: null,
		onShow: null,
		onClose: null
	};

	/*
	 * Main modal object
	 */
	jQuery.modal.impl = {
		/*
		 * Modal dialog options
		 */
		opts: null,
		/*
		 * Contains the modal dialog elements and is the object passed 
		 * back to the callback (onOpen, onShow, onClose) functions
		 */
		dialog: {},
		/*
		 * Initialize the modal dialog
		 */
		init: function (data, options) {
			// don't allow multiple calls
			if (this.dialog.data) {
				return false;
			}

			// merge defaults and user options
			this.opts = jQuery.extend({}, jQuery.modal.defaults, options);

			// keep track of z-index
			this.zIndex = this.opts.zIndex;

			// set the onClose callback flag
			this.occb = false;

			// determine how to handle the data based on its type
			if (typeof data == 'object') {
				// convert DOM object to a jQuery object
				data = data instanceof jQuery ? data : jQuery(data);

				// if the object came from the DOM, keep track of its parent
				if (data.parent().parent().size() > 0) {
					this.dialog.parentNode = data.parent();

					// persist changes? if not, make a clone of the element
					if (!this.opts.persist) {
						this.dialog.orig = data.clone(true);
					}
				}
			}
			else if (typeof data == 'string' || typeof data == 'number') {
				// just insert the data as innerHTML
				data = jQuery('<div/>').html(data);
			}
			else {
				// unsupported data type!
				alert('SimpleModal Error: Unsupported data type: ' + typeof data);
				return false;
			}
			this.dialog.data = data.addClass('simplemodal-data').css(this.opts.dataCss);
			data = null;

			// create the modal overlay, container and, if necessary, iframe
			this.create();

			// display the modal dialog
			this.open();

			// useful for adding events/manipulating data in the modal dialog
			if (jQuery.isFunction(this.opts.onShow)) {
				this.opts.onShow.apply(this, [this.dialog]);
			}

			// don't break the chain =)
			return this;
		},
		/*
		 * Create and add the modal overlay and container to the page
		 */
		create: function () {
			// get the window properties
			w = this.getDimensions();

			// add an iframe to prevent select options from bleeding through
			if (ie6) {
				this.dialog.iframe = jQuery('<iframe src="javascript:false;"/>')
					.css(jQuery.extend(this.opts.iframeCss, {
						display: 'none',
						opacity: 0, 
						position: 'absolute', // fixed
						height: w[0],
						width: w[1],
						zIndex: this.opts.zIndex,
						top: 0,
						left: 0
					}))
					.appendTo('body');
			}

			// create the overlay
			this.dialog.overlay = jQuery('<div/>')
				.attr('id', this.opts.overlayId)
				.addClass('simplemodal-overlay')
				.css(jQuery.extend(this.opts.overlayCss, {
					display: 'none',
					opacity: this.opts.opacity / 100,
					height: w[0],
					width: w[1],
					position: ieQuirks ? 'absolute' : 'fixed',
					left: 0,
					top: 0,
					zIndex: this.opts.zIndex + 1
				}))
				.appendTo('body');

			// create the container
			this.dialog.container = jQuery('<div/>')
				.attr('id', this.opts.containerId)
				.addClass('simplemodal-container')
				.css(jQuery.extend(this.opts.containerCss, {
					display: 'none',
					position: this.opts.positioning,
					zIndex: this.opts.zIndex + 2
				}))
				.append(this.opts.close 
					? jQuery(this.opts.closeHTML).addClass(this.opts.closeClass)
					: '')
				.appendTo('body');

			this.setPosition();

			// fix issues with IE
			if (ie6 || ieQuirks) {
				this.fixIE();
			}

			// hide the data and add it to the container
			this.dialog.container.append(this.dialog.data.hide());
		},
		/*
		 * Bind events
		 */
		bindEvents: function () {
			var self = this;

			// bind the close event to any element with the closeClass class
			jQuery('.' + this.opts.closeClass).bind('click.simplemodal', function (e) {
				e.preventDefault();
				console.log("fromX");
				self.close(true);
			});

			// update window size
			jQuery(window).bind('resize.simplemodal', function () {
				// redetermine the window width/height
				w = self.getDimensions();

				// reposition the dialog
				self.setPosition();
	
				if (ie6 || ieQuirks) {
					// update the iframe & overlay
					self.dialog.iframe && self.dialog.iframe.css({height: w[0], width: w[1]});
					self.dialog.overlay.css({height: w[0], width: w[1]});
					
					self.fixIE();
				}
				else {
					// update the iframe & overlay
					self.dialog.iframe && self.dialog.iframe.css({height: w[0], width: w[1]});
					self.dialog.overlay.css({height: w[0], width: w[1]});
				}
			});
		},
		/*
		 * Unbind events
		 */
		unbindEvents: function () {
			jQuery('.' + this.opts.closeClass).unbind('click.simplemodal');
			jQuery(window).unbind('resize.simplemodal');
		},
		/*
		 * Fix issues in IE6 and IE7 in quirks mode
		 */
		fixIE: function () {
			if (jQuery.browser.msie && parseInt(jQuery.browser.version) == 8) 
				return; 
			
			
			var p = this.opts.position;

			// simulate fixed position - adapted from BlockUI
			jQuery.each([this.dialog.iframe || null, this.dialog.overlay/*, this.dialog.container*/], function (i, el) {
				if (el) {
					var bch = 'document.body.clientHeight', bcw = 'document.body.clientWidth',
						bsh = 'document.body.scrollHeight', bsl = 'document.body.scrollLeft',
						bst = 'document.body.scrollTop', bsw = 'document.body.scrollWidth',
						ch = 'document.documentElement.clientHeight', cw = 'document.documentElement.clientWidth',
						sl = 'document.documentElement.scrollLeft', st = 'document.documentElement.scrollTop',
						s = el[0].style;

					s.position = 'absolute';
					if (i < 2) {
						s.removeExpression('height');
						s.removeExpression('width');
						s.setExpression('height','' + bsh + ' > ' + bch + ' ? ' + bsh + ' : ' + bch + ' + "px"');
						s.setExpression('width','' + bsw + ' > ' + bcw + ' ? ' + bsw + ' : ' + bcw + ' + "px"');
					}
					else {
						var te, le;
						if (p && p.constructor == Array) {
							if (p[0]) {
								var top = typeof p[0] == 'number' ? p[0].toString() : p[0].replace(/px/, '');
								te = top.indexOf('%') == -1 
									? top + ' + (t = ' + st + ' ? ' + st + ' : ' + bst + ') + "px"'
									: parseInt(top.replace(/%/, '')) + ' * ((' + ch + ' || ' + bch + ') / 100) + (t = ' + st + ' ? ' + st + ' : ' + bst + ') + "px"';
							}
							if (p[1]) {
								var left = typeof p[1] == 'number' ? p[1].toString() : p[1].replace(/px/, '');
								le = left.indexOf('%') == -1 
									? left + ' + (t = ' + sl + ' ? ' + sl + ' : ' + bsl + ') + "px"'
									: parseInt(left.replace(/%/, '')) + ' * ((' + cw + ' || ' + bcw + ') / 100) + (t = ' + sl + ' ? ' + sl + ' : ' + bsl + ') + "px"';
							}
						}
						else {
							te = '(' + ch + ' || ' + bch + ') / 2 - (this.offsetHeight / 2) + (t = ' + st + ' ? ' + st + ' : ' + bst + ') + "px"';
							le = '(' + cw + ' || ' + bcw + ') / 2 - (this.offsetWidth / 2) + (t = ' + sl + ' ? ' + sl + ' : ' + bsl + ') + "px"';
						}
						s.removeExpression('top');
						s.removeExpression('left');
						s.setExpression('top', te);
						s.setExpression('left', le);
					}
				}
			});
		},
		getDimensions: function () {
			var el = jQuery(window);

			// fix a jQuery/Opera bug with determining the window height
			var h = jQuery.browser.opera && jQuery.browser.version > '9.5' && jQuery.fn.jquery <= '1.2.6' ?
				document.documentElement['clientHeight'] : 
				el.height();

			return [h, el.width()];
		},
		setPosition: function () {
			var top, left,
				hCenter = (w[0]/2) - ((this.dialog.container.height() || this.dialog.data.height())/2),
				vCenter = (w[1]/2) - ((this.dialog.container.width() || this.dialog.data.width())/2);

			if (this.opts.position && this.opts.position.constructor == Array) {
				top = this.opts.position[0] || hCenter;
				left = this.opts.position[1] || vCenter;
			} else {
				top = hCenter;
				left = vCenter;
			}
			this.dialog.container.css({left: left, top: top});
		},
		/*
		 * Open the modal dialog elements
		 * - Note: If you use the onOpen callback, you must "show" the 
		 *	        overlay and container elements manually 
		 *         (the iframe will be handled by SimpleModal)
		 */
		open: function () {
			// display the iframe
			this.dialog.iframe && this.dialog.iframe.show();

			if (jQuery.isFunction(this.opts.onOpen)) {
				// execute the onOpen callback 
				this.opts.onOpen.apply(this, [this.dialog]);
			}
			else {
				// display the remaining elements
				this.dialog.overlay.show();
				this.dialog.container.show();
				this.dialog.data.show();
			}

			// bind default events
			this.bindEvents();
		},
		/*
		 * Close the modal dialog
		 * - Note: If you use an onClose callback, you must remove the 
		 *         overlay, container and iframe elements manually
		 *
		 * @param {boolean} external Indicates whether the call to this
		 *     function was internal or external. If it was external, the
		 *     onClose callback will be ignored
		 */
		close: function (fromX) {
			// prevent close when dialog does not exist
			if (!this.dialog.data) {
				return false;
			}

			if (jQuery.isFunction(this.opts.onClose) && !this.occb) {
				// set the onClose callback flag
				this.occb = true;
				
				// execute the onClose callback
				this.opts.onClose.apply(this, [this.dialog, !!fromX]);
			}
			// else
			{
				// if the data came from the DOM, put it back
				if (this.dialog.parentNode) {
					// save changes to the data?
					if (this.opts.persist) {
						// insert the (possibly) modified data back into the DOM
						this.dialog.data.hide().appendTo(this.dialog.parentNode);
					}
					else {
						// remove the current and insert the original, 
						// unmodified data back into the DOM
						this.dialog.data.remove();
						this.dialog.orig.appendTo(this.dialog.parentNode);
					}
				}
				else {
					// otherwise, remove it
					this.dialog.data.remove();
				}

				// remove the remaining elements
				this.dialog.container.remove();
				this.dialog.overlay.remove();
				this.dialog.iframe && this.dialog.iframe.remove();

				// reset the dialog object
				this.dialog = {};
			}

			// remove the default events
			this.unbindEvents();
		}
	};
})(jQuery);
/* SpinButton control
 *
 * Adds bells and whistles to any ordinary textbox to
 * make it look and feel like a SpinButton Control.
 *
 * Originally written by George Adamson, Software Unity (george.jquery@softwareunity.com) August 2006.
 * - Added min/max options
 * - Added step size option
 * - Added bigStep (page up/down) option
 *
 * Modifications made by Mark Gibson, (mgibson@designlinks.net) September 2006:
 * - Converted to jQuery plugin
 * - Allow limited or unlimited min/max values
 * - Allow custom class names, and add class to input element
 * - Removed global vars
 * - Reset (to original or through config) when invalid value entered
 * - Repeat whilst holding mouse button down (with initial pause, like keyboard repeat)
 * - Support mouse wheel in Firefox
 * - Fix double click in IE
 * - Refactored some code and renamed some vars
 *
 * Tested in IE6, Opera9, Firefox 1.5
 * v1.0  11 Aug 2006 - George Adamson	- First release
 * v1.1     Aug 2006 - George Adamson	- Minor enhancements
 * v1.2  27 Sep 2006 - Mark Gibson		- Major enhancements
 * v1.3a 28 Sep 2006 - George Adamson	- Minor enhancements
 
 Sample usage:
 
	// Create group of settings to initialise spinbutton(s). (Optional)
	var myOptions = {
					min: 0,						// Set lower limit.
					max: 100,					// Set upper limit.
					step: 1,					// Set increment size.
					spinClass: mySpinBtnClass,	// CSS class to style the spinbutton. (Class also specifies url of the up/down button image.)
					upClass: mySpinUpClass,		// CSS class for style when mouse over up button.
					downClass: mySpinDnClass	// CSS class for style when mouse over down button.
					}
 
	jQuery(document).ready(function(){

		// Initialise INPUT element(s) as SpinButtons: (passing options if desired)
		jQuery("#myInputElement").SpinButton(myOptions);

	});
 
 */
jQuery.fn.SpinButton = function(cfg){
	return this.each(function(){

		// Apply specified options or defaults:
		// (Ought to refactor this some day to use jQuery.extend() instead)
		this.spinCfg = {
			//min: cfg && cfg.min ? Number(cfg.min) : null,
			//max: cfg && cfg.max ? Number(cfg.max) : null,
			min: cfg && !isNaN(parseFloat(cfg.min)) ? Number(cfg.min) : null,	// Fixes bug with min:0
			max: cfg && !isNaN(parseFloat(cfg.max)) ? Number(cfg.max) : null,
			step: cfg && cfg.step ? Number(cfg.step) : 1,
			page: cfg && cfg.page ? Number(cfg.page) : 10,
			upClass: cfg && cfg.upClass ? cfg.upClass : 'up',
			downClass: cfg && cfg.downClass ? cfg.downClass : 'down',
			reset: cfg && cfg.reset ? cfg.reset : this.value,
			delay: cfg && cfg.delay ? Number(cfg.delay) : 500,
			interval: cfg && cfg.interval ? Number(cfg.interval) : 100,
			_btn_width: 20,
			_btn_height: 12,
			_direction: null,
			_delay: null,
			_repeat: null
		};
		
		this.adjustValue = function(i){
			var v = (isNaN(this.value) ? this.spinCfg.reset : Number(this.value)) + Number(i);
			if (this.spinCfg.min !== null) v = Math.max(v, this.spinCfg.min);
			if (this.spinCfg.max !== null) v = Math.min(v, this.spinCfg.max);
			this.value = v;
			jQuery(this).change();
		};
		
		jQuery(this)
		.addClass(cfg && cfg.spinClass ? cfg.spinClass : 'spin-button')
		
		.mousemove(function(e){
			// Determine which button mouse is over, or not (spin direction):
			var x = e.pageX || e.x;
			var y = e.pageY || e.y;
			var el = e.target || e.srcElement;
			var direction = 
				(x > jQuery(el).offset().left + el.offsetWidth - this.spinCfg._btn_width)
				? ((y < jQuery(el).offset().top + this.spinCfg._btn_height) ? 1 : -1) : 0;
			
			if (direction !== this.spinCfg._direction) {
				// Style up/down buttons:
				switch(direction){
					case 1: // Up arrow:
						jQuery(this).removeClass(this.spinCfg.downClass).addClass(this.spinCfg.upClass);
						break;
					case -1: // Down arrow:
						jQuery(this).removeClass(this.spinCfg.upClass).addClass(this.spinCfg.downClass);
						break;
					default: // Mouse is elsewhere in the textbox
						jQuery(this).removeClass(this.spinCfg.upClass).removeClass(this.spinCfg.downClass);
				}
				
				// Set spin direction:
				this.spinCfg._direction = direction;
			}
		})
		
		.mouseout(function(){
			// Reset up/down buttons to their normal appearance when mouse moves away:
			jQuery(this).removeClass(this.spinCfg.upClass).removeClass(this.spinCfg.downClass);
			this.spinCfg._direction = null;
		})
		
		.mousedown(function(e){
			if (this.spinCfg._direction != 0) {
				// Respond to click on one of the buttons:
				var self = this;
				var adjust = function() {
					self.adjustValue(self.spinCfg._direction * self.spinCfg.step);
				};
			
				adjust();
				
				// Initial delay before repeating adjustment
				self.spinCfg._delay = window.setTimeout(function() {
					adjust();
					// Repeat adjust at regular intervals
					self.spinCfg._repeat = window.setInterval(adjust, self.spinCfg.interval);
				}, self.spinCfg.delay);
			}
		})
		
		.mouseup(function(e){
			// Cancel repeating adjustment
			window.clearInterval(this.spinCfg._repeat);
			window.clearTimeout(this.spinCfg._delay);
		})
		
		.dblclick(function(e) {
			if (jQuery.browser.msie)
				this.adjustValue(this.spinCfg._direction * this.spinCfg.step);
		})
		
		.keydown(function(e){
			// Respond to up/down arrow keys.
			switch(e.keyCode){
				case 38: this.adjustValue(this.spinCfg.step);  break; // Up
				case 40: this.adjustValue(-this.spinCfg.step); break; // Down
				case 33: this.adjustValue(this.spinCfg.page);  break; // PageUp
				case 34: this.adjustValue(-this.spinCfg.page); break; // PageDown
			}
		})

		
		.change(function(e){
			// this.adjustValue(0);
		})
		
		.keyup(function(e){
			if ( !isNaN(parseInt(jQuery(this).val())) )
				this.adjustValue(0);
		});
		
		/*
		if (this.addEventListener) {
			// Respond to mouse wheel in Firefox
			this.addEventListener('DOMMouseScroll', function(e) {
				if (e.detail > 0)
					this.adjustValue(-this.spinCfg.step);
				else if (e.detail < 0)
					this.adjustValue(this.spinCfg.step);
				
				e.preventDefault();
			}, false);
		}
		*/
	});
	
	function coord(el,prop) {
		var c = el[prop], b = document.body;
		
		while ((el = el.offsetParent) && (el != b)) {
			if (!jQuery.browser.msie || (el.currentStyle.position != 'relative'))
				c += el[prop];
		}
		
		return c;
	}
};
/*
 *
 * Copyright (c) 2006/2007 Sam Collett (http://www.texotela.co.uk)
 * Licensed under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * Version 1.0
 * Demo: http://www.texotela.co.uk/code/jquery/numeric/
 *
 * $LastChangedDate$
 * $Rev$
 */
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('r.E.W=7(c,d){c=c||".";d=q d=="7"?d:7(){};6.K(7(e){g a=e.i?e.i:e.h?e.h:0;2(a==k&&6.N.J()=="G"){5 3}f 2(a==k){5 j}g b=j;2((e.4&&a==y)||(e.4&&a==v))5 3;2((e.4&&a==t)||(e.4&&a==u))5 3;2((e.4&&a==V)||(e.4&&a==S))5 3;2((e.4&&a==R)||(e.4&&a==Q))5 3;2((e.4&&a==P)||(e.4&&a==O)||(e.L&&a==p))5 3;2(a<I||a>H){2(a==p&&6.l.F==0)5 3;2(a==c.n(0)&&6.l.o(c)!=-1){b=j}2(a!=8&&a!=9&&a!=k&&a!=D&&a!=C&&a!=M&&a!=B&&a!=A){b=j}f{2(q e.i!="z"){2(e.h==e.m&&e.m!=0){b=3}f 2(e.h!=0&&e.i==0&&e.m==0){b=3}}}2(a==c.n(0)&&6.l.o(c)==-1){b=3}}f{b=3}5 b}).x(7(){g a=r(6).w();2(a!=""){g b=T U("^\\\\d+$|\\\\d*"+c+"\\\\d+");2(!b.s(a)){d.X(6)}}});5 6}',60,60,'||if|true|ctrlKey|return|this|function||||||||else|var|keyCode|charCode|false|13|value|which|charCodeAt|indexOf|45|typeof|jQuery|exec|120|88|65|val|blur|97|undefined|46|39|36|35|fn|length|input|57|48|toLowerCase|keypress|shiftKey|37|nodeName|86|118|90|122|67|new|RegExp|99|numeric|apply'.split('|'),0,{}))

function checkSSO(callback) {
	CupMan.callService("SingleSignOnService", {
		data: {"domain": window.location.hostname},
		success: callback,
		error: callback
	});
}

function showLoginDialog(options, callback) {
	if (options['force']) {
		jQuery(".loginPopup").modal(options);
		if (typeof callback == "function") {
			callback();
		}
		return false;
	}
	
	checkSSO(function(result) {
		if (result.status == 0) {
			// Logged in
			window.location.href = window.location.href;
		}
		// Not logged in, really show it!
		jQuery(".loginPopup").modal(options);
		if (typeof callback == "function") {
			callback();
		}
	});
}

function logout(callback) {
	CupMan.callService("LogoutService", {
		data: {}, 
		success: function(){
			
		}, error: function(){
			
		}, complete: function(){
			callback();
		}
	});
}

function loginCallback(obj)
{
	var status = obj.status;
    clearFieldErrors("loginEmailField");
    clearFieldErrors("loginPasswordField");
    if ( status == 0)
    {
        // window.location.href = stripHash(window.location.href);
        // Submit form
        jQuery("#loginScreen form").submit();
    } else {
        jQuery(".loginButton").disable(false);
        if ( status == 1 )
        {
            // No user
            showFieldError("loginEmailField", CupMan.T("Web.Login.Error.NoSuchUser"));
        } else {
            // Wrong password
            showFieldError("loginPasswordField", CupMan.T("Web.Login.Error.WrongPassword"));
        }
    }
}

function setLoginPopupScreen(screen, callbackFunc, fast)
{
	clearFieldErrors("newEmailField");
	
	var callback = function() {
		jQuery.modal.impl.setPosition();
		if ( callbackFunc )
			callbackFunc();
	};
	
    if ( screen == "CreateAccount" )
    {
    	jQuery("#loginScreen").hide();
    	jQuery("#createAccountScreen").show(0, callback);
    } else {
    	jQuery("#createAccountScreen").hide()
    	jQuery("#loginScreen").show(0, callback);
    }
}

function setLoginScreen(screen)
{
    if ( screen == "Need" )
    {
        jQuery("#loginNeed").show();
        jQuery("#loginChange").hide();
    } else {
        jQuery("#loginNeed").hide();
        jQuery("#loginChange").show();
    }
}


/////////


function recoverPassword(username, method, callback)
{
	CupMan.callService("ResetPasswordService", {
		data: {'username':username, 'method':method}, 
		success: function(root) {
			var status = parseInt( root.attr("status") );
			callback(status, root.attr("has-phone")=="1", root.attr("has-email")=="1");
		},
		error: function(root) {
			
			var status = parseInt( root.attr("status") );
			callback(status, root.attr("has-phone")=="1", root.attr("has-email")=="1");
		}
	});
}


jQuery(function(){
    
    jQuery(".loginButton").click(function(){
    	var user = jQuery("#loginEmail").val();
        var pass = jQuery("#loginPassword").val();
        jQuery(".loginButton").disable(true);
        CupMan.callService("LoginService", {
        	data: {username:user, password:pass, domain: window.location.hostname}, 
        	success: loginCallback,
        	error: loginCallback
        });
        
        return false;
    });
    
    jQuery(".createAccountLink").click(function(){
        setLoginPopupScreen("CreateAccount", null, true);
        return false;
    });
    jQuery("#loginLink").click(function(){
        setLoginPopupScreen("Login", null, true);
        return false;
    });
    
    jQuery("#loginEmail").focus(function(){
    	//clearFieldErrors("loginEmailField");
    });
    
    jQuery("#loginEmail").keyup(function(){
    	clearFieldErrors("loginEmailField");
    	jQuery(".loginForgotContainer").slideUp(500);
    	jQuery("a.forgot").show();
    });
    
    jQuery("#loginScreen input").keyup(function(e) {
    	var event = e || window.event;
		var keyCode = event.keyCode;
		if ( keyCode == 13 ) {
			jQuery(this).closest("#loginScreen").find("button").click();
		}
    });
    
    jQuery("a.forgot").click(function(){
    	recoverPassword( jQuery("#loginEmail").val(), "test", function(status, hasPhone, hasEmail){
    		if ( status == 1 ) {
                showFieldError("loginEmailField", CupMan.T("Web.Login.Error.NoSuchUser"));
                jQuery("a.forgotByEmail").disable( true );
                jQuery("a.forgotBySms").disable( true );
    		} else {
    			clearFieldErrors("loginEmailField");
    			
    			jQuery(".loginForgotContainer").slideDown(500);
		    	jQuery("a.forgot").hide();
		    	
    			jQuery("a.forgotByEmail").disable( !hasEmail );
    			jQuery("a.forgotBySms").disable( !hasPhone );
    		}
   	    });
   	    return false;
    });
    
    jQuery("a.forgotByEmail").click(function(){
    	if( !jQuery(this).hasClass("disabled") )
    	{
	        recoverPassword(jQuery("#loginEmail").val(), "email", function(){
	            jQuery("#loginEmail").keyup();
	            showFieldSuccess("loginEmailField", CupMan.T("Web.Login.ResetEmailMsg"));
	        });
    	}
    	return false;
    });
    jQuery("a.forgotBySms").click(function(){
        if( !jQuery(this).hasClass("disabled") )
        {
	        recoverPassword(jQuery("#loginEmail").val(), "sms", function(){
	            jQuery("#loginEmail").keyup();
	            showFieldSuccess("loginEmailField", CupMan.T("Web.Login.ResetSmsMsg"));
	        });
        }
        return false;
    });
    jQuery("a.forgotCancel").click(function(){
    	jQuery("#loginEmail").keyup();
    	return false;
    });
    
    
    //// NEW ACCOUNT SECTION
    
    jQuery("#newEmail").keyup(function(){
    	
    	var emailExistsFunc = function() {
    		
			jQuery(".createAccountButton").hide();
			jQuery(".switchToLoginButton").show();
			
			jQuery("#newName").val( CupMan.T("Registration.LoginPopup.NameInput_EmailExists") ).disable();
    	};
    	var emailNotExistsFunc = function() {
    		jQuery(".createAccountButton").show();
			jQuery(".switchToLoginButton").hide();
			
			jQuery("#newName").val("").enable();
    	};
    	
    	var valid = validateField( jQuery("#newEmail").attr("validate", "email") );
    	if ( valid )
    	{
    		recoverPassword( jQuery("#newEmail").val(), "test", function(status, hasPhone, hasEmail){
	    		if ( status == 0 )
	    		{
	    			/*
	    			var errorElement = showFieldError("newEmailField", CupMan.T("Web.Login.Error.UserAlreadyExists"));
	    			errorElement.addClass("sticky");
	    			errorElement.find("a").click(function(){
	    				errorElement.removeClass("sticky");
	    				clearFieldErrors("newEmailField");
	    				jQuery("#loginEmail").val( jQuery("#newEmail").val() );
	    				setLoginPopupScreen("Login");
	    				setLoginScreen("Need");
	    				return false;
	    			});
	    			*/
	    			emailExistsFunc();
	    		} else {
	    			/*
	    			jQuery("#newEmail").closest(".field").find(".status").removeClass("sticky");
	    			clearFieldErrors("newEmailField");
	    			validateField(jQuery("#newEmail"));
	    			*/
	    			emailNotExistsFunc();
	    		}
	    		
	    		jQuery("#newName, .createAccountButton").disable( status == 0 );
	    	});
    	} else {
    		jQuery("#newName, .createAccountButton").disable( false );
    		emailNotExistsFunc();
    	}
    });
    
    jQuery(".switchToLoginButton").click(function() {
    	jQuery("#loginEmail").val( jQuery("#newEmail").val() );
		setLoginPopupScreen("Login");
		setLoginScreen("Need");
		
		jQuery("#loginPassword").focus();
		
		return false;
    });
    
    jQuery(".createAccountButton").click(function(){
    	var name = jQuery("#newName").val();
    	jQuery(".createAccountButton").disable(true);
    	var email = jQuery("#newEmail").val();
    	
    	
    	var completeFunc = function(root, status) {
    		console.log("completeFunc");
            clearFieldErrors("createAccountScreen");
            
            jQuery(".createAccountButton").disable( status == 0 );
            
            if (status == 1)
            {
            	// Bad name
            	validateField(jQuery("#newName"));
            	// showFieldError("newNameField", "You have to enter a name (both your first and last)");
            } else if (status == 2) {
            	// Bad email
            	validateField(jQuery("#newEmail"), "email");
            	//showFieldError("newEmailField", "Badly formatted email address.");
            } else if (status == 3) {
                // Email exists
                var errorElement = showFieldError("newEmailField", CupMan.T("Web.Login.Error.UserAlreadyExists"));
    			errorElement.find("a").click(function(){
    				clearFieldErrors("newEmailField");
    				jQuery("#loginEmail").val( jQuery("#newEmail").val() );
    				setLoginPopupScreen("Login");
    				setLoginScreen("Need");
    				return false;
    			});
            } else if (status == 4) {
                // Unknown
                showFieldError("createAccountButtonField", root.text());
            } else {
            	showFieldSuccess("createAccountButtonField", CupMan.T("Web.Login.Msg.AccountCreated"));
            	window.location.reload();
            }
		};
    	
    	
    	CupMan.callService("CreateAccountService", {
    		data: {"name": name, "email": email}, 
    		success: function(root,status) {
    			completeFunc(root,status);
    		},
    		error: function(root,msg,status) {
    			completeFunc(root,status);
    		}
    	});
    });
    
});






/*
 * jQuery autoResize (textarea auto-resizer)
 * @copyright James Padolsey http://james.padolsey.com
 * @version 1.04
 */

(function($){
    
    $.fn.autoResize = function(options) {
        // Just some abstracted details,
        // to make plugin users happy:
        var settings = $.extend({
            onResize : function(){},
            animate : true,
            animateDuration : 150,
            animateCallback : function(){},
            extraSpace : 20,
            limit: 1000
        }, options);
        
        // Only textarea's auto-resize:
        this.filter('textarea').each(function(){
            
                // Get rid of scrollbars and disable WebKit resizing:
            var textarea = $(this).css({resize:'none','overflow':'hidden'}),
            
                // Cache original height, for use later:
                origHeight = textarea.height(),
                
                // Need clone of textarea, hidden off screen:
                clone = (function(){
                    
                    // Properties which may effect space taken up by chracters:
                    var props = ['height','width','lineHeight','textDecoration','letterSpacing'],
                        propOb = {};
                        
                    // Create object of styles to apply:
                    $.each(props, function(i, prop){
                        propOb[prop] = textarea.css(prop);
                    });
                    
                    // Clone the actual textarea removing unique properties
                    // and insert before original textarea:
                    return textarea.clone().appendTo($("body")).removeAttr('id').removeAttr('name').css({
                        position: 'absolute',
                        top: 0,
                        left: -9999,
                        fontFamily: textarea.css("font-family"),
                        fontSize: textarea.css("font-size")
                    }).css(propOb).attr('tabIndex','-1'); //.insertBefore(textarea);
					
                })(),
                lastScrollTop = null,
                updateSize = function() {
                	console.log('ar2');
					// Prepare the clone:
                    clone.height(0).width($(this).width()).val($(this).val()).scrollTop(10000);
					clone.css({
						fontFamily: $(this).css("font-family"),
                        fontSize: $(this).css("font-size")
					});
					console.log('ar3');
                    
                    // Find the height of text:
                    var scrollTop = Math.max(clone.scrollTop(), origHeight) + settings.extraSpace,
                        toChange = $(this).add(clone);
					console.log('ar4',lastScrollTop, scrollTop,settings.extraSpace,origHeight,clone.scrollTop());
                    
					// Don't do anything if scrollTip hasen't changed:
                    if (lastScrollTop === scrollTop) { return; }
                    lastScrollTop = scrollTop;
					console.log('ar5');
                    
                    // Check for limit:
                    if ( scrollTop >= settings.limit ) {
                        $(this).css('overflow-y','');
                        return;
                    }
                    console.log('ar6');
                    
                    // Fire off callback:
                    settings.onResize.call(this);
					
					// Either animate or directly apply height:
                    settings.animate && textarea.css('display') === 'block' ?
                        toChange.stop().animate({height:scrollTop}, settings.animateDuration, settings.animateCallback)
                        : toChange.height(scrollTop);
                    
                };
            
            // Bind namespaced handlers to appropriate events:
            textarea
                .unbind('.dynSiz')
                .bind('keyup.dynSiz', updateSize)
                .bind('keydown.dynSiz', updateSize)
                .bind('change.dynSiz', updateSize);
            
        });
        
        // Chain:
        return this;
        
    };
    
    
    
})(jQuery);

(function($){

    $.fn.autoGrowInput = function(o) {

        o = $.extend({
            maxWidth: 1000,
            minWidth: 0,
            comfortZone: 15
        }, o);

        this.filter('input:text').each(function(){

            var minWidth = o.minWidth || $(this).width(),
                val = '',
                input = $(this),
                testSubject = $('<span/>').css({
                    position: 'absolute',
                    top: -99999,
                    left: -99999,
                    width: 'auto',
                    fontSize: input.css('fontSize'),
                    fontFamily: input.css('fontFamily'),
                    fontWeight: input.css('fontWeight'),
                    letterSpacing: input.css('letterSpacing'),
                    whiteSpace: 'nowrap'
                }),
                check = function() {
					console.log("Chcekcing size");
					
                    // if (val === (val = input.val())) {return;}

					val = input.val();
					
                    // Enter new content into testSubject
                    var escaped = val.replace(/&/g, '&amp;').replace(/\s/g,'&nbsp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
                    testSubject.html(escaped);
					testSubject.css({
	                    position: 'absolute',
	                    top: -99999,
	                    left: -99999,
	                    width: 'auto',
	                    fontSize: input.css('fontSize'),
	                    fontFamily: input.css('fontFamily'),
	                    fontWeight: input.css('fontWeight'),
	                    letterSpacing: input.css('letterSpacing'),
	                    whiteSpace: 'nowrap'
	                });
					
                    // Calculate new width + whether to change
                    var testerWidth = testSubject.width(),
                        newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
                        currentWidth = input.width(),
                        isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
                                             || (newWidth > minWidth && newWidth < o.maxWidth);
					
					console.log(escaped, testerWidth, newWidth, currentWidth, isValidWidthChange);
					
                    // Animate width
                    if (isValidWidthChange) {
                        input.width(newWidth);
                    }

                };

            testSubject.insertAfter(input);

            $(this).bind('keyup keydown blur update', check);

        });

        return this;

    };

})(jQuery);
//-------------------------------------------------
//		Quick Pager jquery plugin
//		Created by dan and emanuel @geckonm.com
//		www.geckonewmedia.com
// 
//		v1.1
//		18/09/09 * bug fix by John V - http://blog.geekyjohn.com/
//-------------------------------------------------

(function($) {
	    
	$.fn.quickPager = function(options) {
	
		var defaults = {
			pageSize: 10,
			currentPage: 1,
			holder: null,
			pagerLocation: "after"
		};
		
		var options = $.extend(defaults, options);
		
		
		return this.each(function() {
	
						
			var selector = $(this);	
			var pageCounter = 1;
			
			if (!selector.parent().is(".simplePagerContainer")) {
				selector.wrap("<div class='simplePagerContainer'></div>");
			}
			
			selector.children().each(function(i){ 
					
				if(i < pageCounter*options.pageSize && i >= (pageCounter-1)*options.pageSize) {
				$(this).addClass("simplePagerPage"+pageCounter);
				}
				else {
					$(this).addClass("simplePagerPage"+(pageCounter+1));
					pageCounter ++;
				}	
				
			});
			
			// show/hide the appropriate regions 
			selector.children().hide();
			selector.children(".simplePagerPage"+options.currentPage).show();
			
			if(pageCounter <= 1) {
				return;
			}
			
			//Build pager navigation
			var pageNav = "<ul class='simplePagerNav'>";	
			for (i=1;i<=pageCounter;i++){
				if (i==options.currentPage) {
					pageNav += "<li class='currentPage simplePageNav"+i+"'><a rel='"+i+"' href='#'>"+i+"</a></li>";	
				}
				else {
					pageNav += "<li class='simplePageNav"+i+"'><a rel='"+i+"' href='#'>"+i+"</a></li>";
				}
			}
			pageNav += "</ul>";
			
			var oldNav = selector.data("simplePagerNav");
			if (oldNav) {
				oldNav.remove();
			}
			pageNav = jQuery(pageNav);
			selector.data("simplePagerNav", pageNav);
			
			if(!options.holder) {
				switch(options.pagerLocation)
				{
				case "before":
					selector.before(pageNav);
				break;
				case "both":
					selector.before(pageNav);
					selector.after(pageNav);
				break;
				default:
					selector.after(pageNav);
				}
			}
			else {
				$(options.holder).append(pageNav);
			}
			
			//pager navigation behaviour
			selector.parent().find(".simplePagerNav a").click(function() {
					
				//grab the REL attribute 
				var clickedLink = $(this).attr("rel");
				options.currentPage = clickedLink;
				
				if(options.holder) {
					$(this).parent("li").parent("ul").parent(options.holder).find("li.currentPage").removeClass("currentPage");
					$(this).parent("li").parent("ul").parent(options.holder).find("a[rel='"+clickedLink+"']").parent("li").addClass("currentPage");
				}
				else {
					//remove current current (!) page
					$(this).parent("li").parent("ul").parent(".simplePagerContainer").find("li.currentPage").removeClass("currentPage");
					//Add current page highlighting
					$(this).parent("li").parent("ul").parent(".simplePagerContainer").find("a[rel='"+clickedLink+"']").parent("li").addClass("currentPage");
				}
				
				//hide and show relevant links
				selector.children().hide();			
				selector.find(".simplePagerPage"+clickedLink).show();
				
				return false;
			});
		});
	}
	

})(jQuery);


Date.prototype.getDayOfYear = function() {
	var onejan = new Date(this.getFullYear(),0,1);
	return Math.ceil((this - onejan) / 86400000);
};

jQuery.fn.updateTime = function() {
	jQuery(this).each(function() {
		var time = jQuery(this).attr("time");
		jQuery(this).html( CupMan.Name.formatNiceDate(new Date(parseInt(time))) );
	});
};

jQuery(function() {
	var refreshTimeFunc = function() {
		jQuery(".time[time]").updateTime();
		setTimeout(refreshTimeFunc, 60*1000);
	};
	refreshTimeFunc();
});


CupMan.Name = {
	// These are translated in util.vm
	months: {},
	days: {},
	Formats: {},
	
	format: function(format, name) {
		var func = CupMan.Name.Formats[format];
		return func(name);
	},
	
	switsh: function(value, map) {
		if (map[value]) {
			return map[value];
		}
		return map['default'];
	},
	formatNiceDate: function(date) {
		var /*Date*/ today = new Date();
		
		var /*int*/ diffMillis = today.getTime() - date.getTime();
		var /*int*/ diffMinutes = Math.floor(diffMillis/(1000*60));
		var /*int*/ diffHours = Math.floor(diffMinutes/60);
		var /*int*/ diffDays = Math.floor(diffHours/24);
		var /*int*/ diffWeeks = Math.floor(diffDays/7);
		var /*boolean*/ sameDay = today.getDayOfYear() == date.getDayOfYear();
		var /*boolean*/ sameYear = today.getFullYear() == date.getFullYear();
		var /*int*/ nDays = today.getDayOfYear() - date.getDayOfYear();
		
		if( diffMinutes <= 1 )
		{
			// Alldelles nyss
			return CupMan.Name.format("Common.Date.Tiny");
		} else if( diffMinutes < 60 )
		{
			var name = {
				minutes: diffMinutes
			};
			return CupMan.Name.format("Common.Date.Minutes", name);
			// for %d minuter sedan
		} else if( diffHours < 4 )
		{
			var name = {
				hours: diffHours
			};
			return CupMan.Name.format("Common.Date.Hours", name);
			// for %d timmar sedan
		} else if( diffDays < 1 && sameDay )
		{
			var name = {
				ampm: (date.getHours() < 12 ? "am" : "pm")
			};
			return CupMan.Name.format("Common.Date.Today", name);
			// I morse...
		} else if( diffDays < 4 && nDays < 3 )
		{
			var name = {
				days: nDays,
				ampm: (date.getHours() < 12 ? "am" : "pm")
			};
			return CupMan.Name.format("Common.Date.Days", name);
			// Igar / I forrgar (morse/kvall)
			
		} else if( diffWeeks < 1 )
		{
			var name = {
				weekday: CupMan.Name.days[date.getDay()],
				ampm: (date.getHours() < 12 ? "am" : "pm")
			};
			return CupMan.Name.format("Common.Date.ThisWeekDay", name);
			// I fredags (morse/kvall)
			
		} else if( diffWeeks < 2 )
		{
			var name = {
				weekday: CupMan.Name.days[date.getDay()],
				ampm: (date.getHours() < 12 ? "am" : "pm")
			};
			return CupMan.Name.format("Common.Date.LastWeekDay", name);
			//Forra fredagen
			
		} else if( diffWeeks < 5 )
		{
			var name = {
				"weeks": diffWeeks
			};
			return CupMan.Name.format("Common.Date.Weeks", name);
			//for %d veckor sedan
			
		} else if( sameYear || diffWeeks < 40 ) 
		{
			var name = {
				day: date.getDate(),
				month: CupMan.Name.months[date.getMonth()]
			};
			return CupMan.Name.format("Common.Date.Month", name);
			//den 12 Jan
			
		} else 
		{
			var name = {
				day: date.getDate(),
				month: CupMan.Name.months[date.getMonth()],
				year: date.getFullYear()
			}
			return CupMan.Name.format("Common.Date.MonthYear", name);
			//12 Jan 2008
			
		}
	}
};