if($.fn.qtip)
{
	$.fn.qtip.styles.dixie = { // Last part is the name of the style
			width: 100,
			name: 'light',
			tip: "leftBottom",
			width: {max: 300},
			border: {
				width: 3,
				radius: 2,
				color: '#879EBC'
			}
	};
	$.fn.qtip.styles.dixie_error = { // Last part is the name of the style
		width: 100,
		name: 'light',
		tip: 'leftBottom',
		width: {max: 300},
		border: {
			width: 3,
			radius: 2,
			color: '#760F12'
		}
	};	
}


function create_tooltips(obj_id,prerender){

	prerender = false;
	$("#" + obj_id + " .form_tooltip").each(function(){
			
			input_name = $(this).parent().attr('id').replace(/(.*?)_div/,'$1');
			style_name = 'dixie';

			//add the error style if the input has an error
			if(this.className.search('tooltip_error') > 0)
			{
				style_name = 'dixie_error';
			}
			$(this).parent().find("*[id='"+input_name+"_input']").qtip({
				content: {prerender: prerender,text:this.innerHTML.replace(/&nbsp;/g,' ')},
				show: "focusin",
				hide: "focusout",
				style: style_name,
				position: { adjust:{screen: true},corner: {target: "topRight", tooltip:"leftBottom"} }
			});
		});
	//checkboxes, radio buttons, and file browser buttons need the tooltip to appear on mouse over
	$("#" + obj_id + " .form_checkbox_tooltip, #" + obj_id + " .form_radio_tooltip, #" + obj_id + " .form_file_tooltip").each(function(){
		input_name = $(this).parent().attr('id').replace(/(.*?)_div/,'$1');
		style_name = 'dixie';

		//add the error style if the input has an error
		if(this.className.search('tooltip_error') > 0)
		{
			style_name = 'dixie_error';
		}
		$(this).parent().find("div,.text_input").qtip({
			
				content: {prerender: prerender,text:this.innerHTML.replace(/&nbsp;/g,' ')},
				show: "mouseover",
				hide: {delay:1000, when:"mouseout", fixed: true},
				style: style_name,
				position: { adjust:{screen: true},corner: {target: "topRight", tooltip:"leftBottom"} }
		});
	});
//	console.log("done running create_tooltips for " + obj_id);
}
function destroy_tooltips(obj_id){
//	console.log("destroying tooltips from "+obj_id);	
	$("#" + obj_id + " .form_tooltip").each(function(){
			input_name = $(this).parent().attr('id').replace(/(.*?)_div/,'$1');
//			console.log("deleting " + input_name + ' qtip');
//			console.log($(this).parent().find("*[id='"+input_name+"_input']"));
			$(this).parent().find("*[id='"+input_name+"_input']").qtip("destroy");
		});
	//because qtips were created differently for radios and checkboxes, need to destroy them differently too
	$("#" + obj_id + " .form_checkbox_tooltip, #" + obj_id + " .form_radio_tooltip").each(function(){
		$(this).parent().find("div").qtip("destroy");
	});
//	console.log("finished destroying tooltips from "+obj_id);
}
function attach_session_changer(obj_id,calling_page)
{
	if(!calling_page)
	{
		calling_page = document.referrer;//not sure if this works
	}
	$("#"+obj_id+" input:not(:checkbox), #"+obj_id+" textarea, #"+obj_id+" select").bind('change',function(e){

		try
		{
			my_function = wrapFunction(update_session,this,[this,calling_page]);
			
			onkeyupDelay(e, my_function);
		}
		catch(err)
		{
			alert('attach_session_changer() ' + err);
		}
	});
	$("#"+obj_id+" input:checkbox").bind('change',function(e){
		var values = new Object();
		var serial_values = "";
		$(this).parent().parent().find(".form_checkbox > input").each(function(){
			if(this.checked)
			{
				values[this.value] = true;	
			}
		});
		serial_values = serialize(values).replace(/&/g,"%26");

		var mythis = this;
		var my_function = function (){
			$.get(calling_page,{ajax:true,update_session:true,input:$(mythis).attr('rel'),value:serial_values});
		};
		onkeyupDelay(e, my_function);
	});
}
function update_session(obj,calling_page)
{
	//call the same page and tell it to update it's session variables
	jQuery.get(calling_page,{
		ajax:'true',
		update_session:'true',
		input: $(obj).attr('rel'),
		log:"attach_session_changer()",
		value:obj.value.replace(/&/g,'%26')
	});
}

/**
 * Allows for passing functions as variables and still passing parameters to that function
 * Example: my_function = wrapFunction(alert,this,["hurray, this is the alert text!"]);
 * setTimeout(my_function,1000);
 * Borrowed code
 * @link http://stackoverflow.com/questions/899102/how-do-i-store-javascript-functions-in-a-queue-for-them-to-be-executed-eventually/899133#899133
 * 
 * @param fn
 * @param context
 * @param params
 * @returns function
 */
wrapFunction = function(fn,context, params) {
    return function() {
        fn.apply(context, params);
    };
};
function confirm_reset(message)
{
	if(!message)
	{
		message = "Reset all form fields?";
	}
	return window.confirm(message);
}
/**focus_id is obsolete now that I'm using jQuery. Might delete it soon**/
function focus_id(obj_id)
{
	var this_input = document.getElementById(obj_id);
//	console.log(this_input.nodeName);
	this_input.focus();	
}
function error_window(content)
{
//maybe I'll do this later
}

//add a delay for any input ajax calls, if the trigger event was keyup
//this way we can reduce server loads. No one wants to have one ajax call per key stroke
//got this idea from http://forums.devshed.com/javascript-development-115/time-restraint-430177.html?&highlight=onkeyup+AJAX
var keyupCount = 0;//global variable for onkeyupDelay() and onkeyupDo()
function onkeyupDelay(event,onkeyupFunction,options)
{
	var this_options = {
			delay: 500//default delay is 500ms
	};
	jQuery.extend(this_options,options);
	
	if(event.type == "keyup")
	{//only add the delay if the event was an onkeyup event
		eventTarget = (event.srcElement || event.target);//srcElement is IE specific, everyone else uses target
		
		//define which keys will NOT trigger the onkeyup event
		var invalid_keys;

		//some input types we want certain keys to trigger the event, others we don't
		switch(eventTarget.type)
		{
		case "text":
		case "textarea":
		case "password":
			invalid_keys = { 39:'right',37:'left',16:'shift',9:'tab',13:'enter',38:'up',40:'down'};
			break;
		case "select-one":
		case "select-multiple":
			invalid_keys = { 39:'right',37:'left',16:'shift',9:'tab',13:'enter'};
			break;
		case "radio":
		case "checkbox":
			invalid_keys = {16:'shift',9:'tab',13:'enter'};
			break;
		}

		if(invalid_keys[event.keyCode] === undefined)
		{
//			if(delay === undefined) { delay = 500; }//default delay is 500ms
			keyupCount = keyupCount + 1;//increment the global counter
			var my_count = keyupCount;//assign local variable the same number as global counter
			//perform timeout
			setTimeout(
				function(){
					onkeyupDo(onkeyupFunction,my_count);
					//always set any variables used inside of setTimout to null
					//IE does not handle garbage collection correctly inside these functions
					onkeyupFunction = null; 
					my_count = null;
				}
				,this_options['delay']
			);
			
		}
	}
	else
	{//not an onkeyup event? just call the function.
		try{
			onkeyupFunction.call();
		}
		catch(err){
			console.warn(err);
		}
		
	}
}
function onkeyupDo(onkeyupFunction,currentCount)
{
	//global counter will continue to be incremented as long as the user keeps typing
	if(currentCount == keyupCount)
	{//if, by the time this function is finally called, the current count equals the global count, the user must have stopped typing
		//set the global count back to 0 and call the function
		keyupCount = 0;
		onkeyupFunction.call();
	}
}

function create_autocomplete(obj_id){
	//generate autocompletes
	$("#" + obj_id + " .autocomplete").each(function(){
		$(this).autocomplete({
			source: $(this).metadata().source,
			autoFocus:true,
			open: function(){
				updateAutoCompletePosition();
			},
			change: function (e,ui){
				//the information in the field has changed
                var originalElement = this;
                var options = $(originalElement).metadata();
                
                if(options.enforce)
                {
                	/**
                	 * if valid is null, undefined, or false, this function will empty the appropriate fields of their data and create a popup error message
                	 * @param valid:booleon
                	 */
                	function validate(valid)
                	{
                		$(originalElement).qtip("destroy");
						if(!valid)
						{//not a valid option
							//empty the field value
							originalElement.value = "";
							//add error class
							$(originalElement).addClass('form_error');
							//if another field was set using this, empty its value as well
							if(options.mapping.other_field_id)
							{
								$("#" + options.mapping.other_field_id).val('');
							}
							
							//show an error message tooltip
							$(originalElement).qtip({
								show: { ready: true },
								style: {name: 'dixie_error', tip: "leftBottom"}, 
								content:options.enforce_message,
								position: {corner: {target: "topRight", tooltip:"leftBottom"}}
							});
						}
						else
						{//valid, so remove the error class
							$(originalElement).removeClass('form_error');
		                	if(options.mapping.other_field_id && ui.item)
		                	{//set object value using the mapping option
								$("#" + options.mapping.other_field_id).val(ui.item[options.mapping.other_field_item]).trigger('change');
		                	}
						}
                	}
                	switch(typeof options.source)
                	{
                	case 'string':
						$.getJSON(options.source,{term:originalElement.value,exact:true,error_message:options.enforce_message,ajax:true},validate);
						break;
                	default:
                		validate(jQuery.inArray(originalElement.value,options.source) !== -1);
                		break;
					
                	}
                }
			},
			select: function (e,ui){
				//if they've simply click on the element or hit enter, then just change simply do the field mapping value change
				//no need to run validation yet, that will happen when they leave the element
				//this means that the object mapping value might get changed a couple times, but it avoids the problem where someone
				//may have clicked on an option and the value of that option is the same as another option, but the code is different
				//TODO: this might cause too many hits to the server, maybe look into a better way some time?
                var originalElement = this;
                var options = $(originalElement).metadata();

            	if(options.mapping.other_field_id && ui.item)
            	{//set object value using the mapping option
                	$('#' + options.mapping.other_field_id).val(ui.item[options.mapping.other_field_item]).trigger('change');
            	}
                
                
			},
			appendTo: "form.fancy_form"
		});
	});
	$(window).bind('resize scroll', updateAutoCompletePosition);
	
	
	function updateAutoCompletePosition() {
		$('.autocomplete').filter(':visible').each(function(){
				
				//screen properties
				var topEdge = $(window).scrollTop();
				var bottomEdge = $(window).height() + $(window).scrollTop();
				
				//input properties
				var iOffset = $(this).offset();
				var iHeight = $(this).height();
				
				if(iOffset.top > bottomEdge || iOffset.top + iHeight < topEdge)
				{//don't do anything if the input isn't even visible
					return;
				}
				
				var iPosition = $(this).position();
				
				
				//widget properties
				widget = $(this).autocomplete("widget");
				var wHeight = widget.height();
				var wPosition = widget.position();
				var wOffset = widget.offset();
				var wCenter = wOffset.top + (wHeight/2);
				var wBottom = wOffset.top + wHeight;
				
				var offset = 0;
				var adj;
				
				// Cut off by bottom of window
				if(iOffset.top + iHeight * 4 > bottomEdge) {
					adj = offset - wHeight - iHeight * 2;
//					console.warn(iOffset.top + iHeight * 3 + ' > '+ bottomEdge);
//					// Shifting to the top will make whole tooltip visible, or will minimize how much is cut off
					if(wPosition.top + adj > topEdge || topEdge - (wPosition.top + adj) < wBottom - bottomEdge) {
//						console.log("wPosition.top + adj",wPosition.top + " + " + adj + " = " + (wPosition.top + adj));
						widget.css('top',wPosition.top + adj);
					}
					
				}
				else if (iOffset.top + iHeight * 4 < bottomEdge && iOffset.top > wOffset.top) {
					adj = offset + wHeight + iHeight * 2;
					widget.css('top',wPosition.top + adj);
				}
		});
	}
}
