/*
 * jQuery clueTip plugin
 * Version 0.9.8  (05/22/2008)
 * @requires jQuery v1.1.4+
 * @requires Dimensions plugin (for jQuery versions < 1.2.5)
 *
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 * http://plugins.learningjquery.com/cluetip/
 *
 */
;(function($)
{
	var $cluetip, $cluetipInner, $cluetipOuter, $cluetipTitle, $cluetipArrows, $dropShadow, imgCount;
	$.fn.cluetip = function(js, options) {
		if (typeof js == 'object') {
			options = js;
			js = null;
		}
		return this.each(function(index) {
			var $this = $(this);

			// support metadata plugin (v1.0 and 2.0)
			var opts = $.extend(false, {}, $.fn.cluetip.defaults, options || {}, $.metadata ? $this.metadata() : $.meta ? $this.data() : {});

			// start out with no contents (for ajax activation)
			var cluetipContents = false;
			var cluezIndex = parseInt(opts.cluezIndex, 10)-1;
			var isActive = false, closeOnDelay = 0;

			// create the cluetip divs
			if (!$('#cluetip').length) {
				$cluetipInner = $('<div id="cluetip-inner"></div>');
				$cluetipTitle = $('<h3 id="cluetip-title"></h3>');
				$cluetipOuter = $('<div id="cluetip-outer"></div>').append($cluetipInner).prepend($cluetipTitle);
				$cluetip = $('<div id="cluetip"></div>').css({zIndex: opts.cluezIndex})
				.append($cluetipOuter).append('<div id="cluetip-extra"></div>')[insertionType](insertionElement).hide();
				$('<div id="cluetip-waitimage"></div>').css({position: 'absolute', zIndex: cluezIndex-1})
				.insertBefore('#cluetip').hide();
				$cluetip.css({position: 'absolute', zIndex: cluezIndex});
				$cluetipOuter.css({position: 'relative', zIndex: cluezIndex+1});
				$cluetipArrows = $('<div id="cluetip-arrows" class="cluetip-arrows"></div>').css({zIndex: cluezIndex+1}).appendTo('#cluetip');
			}
			var dropShadowSteps = (opts.dropShadow) ? +opts.dropShadowSteps : 0;
			if (!$dropShadow) {
				$dropShadow = $([]);
				for (var i=0; i < dropShadowSteps; i++) {
					$dropShadow = $dropShadow.add($('<div></div>').css({zIndex: cluezIndex-i-1, opacity:.1, top: 1+i, left: 1+i}));
				};
				$dropShadow.css({position: 'absolute', backgroundColor: '#000'})
				.prependTo($cluetip);
			}
			var tipAttribute = $this.attr(opts.attribute), ctClass = opts.cluetipClass;
			if (!tipAttribute && !opts.splitTitle && !js) return true;
			// if hideLocal is set to true, on DOM ready hide the local content that will be displayed in the clueTip
			if (opts.local && opts.hideLocal) { $(tipAttribute + ':first').hide(); }
			var tOffset = parseInt(opts.topOffset, 10), lOffset = parseInt(opts.leftOffset, 10);
			// vertical measurement variables
			var tipHeight, wHeight;
			var defHeight = isNaN(parseInt(opts.height, 10)) ? 'auto' : (/\D/g).test(opts.height) ? opts.height : opts.height + 'px';
			var sTop, linkTop, posY, tipY, mouseY, baseline;
			// horizontal measurement variables
			var tipInnerWidth = isNaN(parseInt(opts.width, 10)) ? 275 : parseInt(opts.width, 10);
			var tipWidth = tipInnerWidth + (parseInt($cluetip.css('paddingLeft'))||0) + (parseInt($cluetip.css('paddingRight'))||0) + dropShadowSteps;
			var linkWidth = this.offsetWidth;
			var linkLeft, posX, tipX, mouseX, winWidth;

			// parse the title
			var tipParts;
			var tipTitle = (opts.attribute != 'title') ? $this.attr(opts.titleAttribute) : '';
			if (opts.splitTitle) {
				if(tipTitle == undefined) {tipTitle = '';}
				tipParts = tipTitle.split(opts.splitTitle);
				tipTitle = tipParts.shift();
			}
			var localContent;

		/***************************************
		* ACTIVATION
		****************************************/

		//activate clueTip
		var activate = function(event) {
			if (!opts.onActivate($this)) {
				return false;
			}
			isActive = true;
			$cluetip.removeClass().css({width: tipInnerWidth});
			if (tipAttribute == $this.attr('href')) {
				$this.css('cursor', opts.cursor);
			}
			$this.attr('title','');
			if (opts.hoverClass) {
				$this.addClass(opts.hoverClass);
			}
			linkTop = posY = $this.offset().top;
			linkLeft = $this.offset().left;
			mouseX = event.pageX;
			mouseY = event.pageY;
			if ($this[0].tagName.toLowerCase() != 'area') {
				sTop = $(document).scrollTop();
				winWidth = $(window).width();
			}
			// position clueTip horizontally
			if (opts.positionBy == 'fixed') {
				posX = linkWidth + linkLeft + lOffset;
				$cluetip.css({left: posX});
			} else {
				posX = (linkWidth > linkLeft && linkLeft > tipWidth)
					|| linkLeft + linkWidth + tipWidth + lOffset > winWidth
					? linkLeft - tipWidth - lOffset
					: linkWidth + linkLeft + lOffset;
				if ($this[0].tagName.toLowerCase() == 'area' || opts.positionBy == 'mouse' || linkWidth + tipWidth > winWidth) { // position by mouse
					if (mouseX + 20 + tipWidth > winWidth) {
						$cluetip.addClass(' cluetip-' + ctClass);
						posX = (mouseX - tipWidth - lOffset) >= 0 ? mouseX - tipWidth - lOffset - parseInt($cluetip.css('marginLeft'),10) + parseInt($cluetipInner.css('marginRight'),10) :  mouseX - (tipWidth/2);
					} else {
						posX = mouseX + lOffset;
					}
				}
				var pY = posX < 0 ? event.pageY + tOffset : event.pageY;
				$cluetip.css({left: (posX > 0 && opts.positionBy != 'bottomTop') ? posX : (mouseX + (tipWidth/2) > winWidth) ? winWidth/2 - tipWidth/2 : Math.max(mouseX - (tipWidth/2),0)});
			}
				wHeight = $(window).height();

			/***************************************
			* load a string from cluetip method's first argument
			***************************************/
			if (js) {
				$cluetipInner.html(js);
				cluetipShow(pY);
			}
			/***************************************
			* load the title attribute only (or user-selected attribute).
			* clueTip title is the string before the first delimiter
			* subsequent delimiters place clueTip body text on separate lines
			***************************************/

			else if (tipParts) {
				var tpl = tipParts.length;
				for (var i=0; i < tpl; i++){
					if (i == 0) {
						$cluetipInner.html(tipParts[i]);
					} else {
						$cluetipInner.append('<div class="split-body">' + tipParts[i] + '</div>');
					}
				};
				cluetipShow(pY);
			}
			/***************************************
			* load external file via ajax
			***************************************/

			else if (!opts.local && tipAttribute.indexOf('#') != 0) {
				if (cluetipContents && opts.ajaxCache) {
					$cluetipInner.html(cluetipContents);
					cluetipShow(pY);
				}
				else {
					var ajaxSettings = opts.ajaxSettings;
					ajaxSettings.url = tipAttribute;
					ajaxSettings.beforeSend = function() {
						$cluetipOuter.children().empty();
						if (opts.waitImage) {
							$('#cluetip-waitimage')
							.css({top: mouseY+20, left: mouseX+20})
							.show();
						}
					};
				 ajaxSettings.error = function() {
						if (isActive) {
							$cluetipInner.html('<i>sorry, the contents could not be loaded</i>');
						}
					};
					ajaxSettings.success = function(data) {
						cluetipContents = opts.ajaxProcess(data);
						if (isActive) {
							$cluetipInner.html(cluetipContents);
						}
					};
					ajaxSettings.complete = function() {
						imgCount = $('#cluetip-inner img').length;
								if (imgCount && !$.browser.opera) {
									$('#cluetip-inner img').load(function() {
										imgCount--;
										if (imgCount<1) {
												$('#cluetip-waitimage').hide();
											if (isActive) cluetipShow(pY);
										}
									});
								} else {
										$('#cluetip-waitimage').hide();
									if (isActive) cluetipShow(pY);
								}
					};
					$.ajax(ajaxSettings);
				}

			/***************************************
			* load an element from the same page
			***************************************/
			} else if (opts.local){
				var $localContent = $(tipAttribute + ':first');
				var localCluetip = $.fn.wrapInner ? $localContent.wrapInner('<div class="inner_local"></div>').children().clone(true) : $localContent.html();
				$.fn.wrapInner ? $cluetipInner.empty().append(localCluetip) : $cluetipInner.html(localCluetip);
				cluetipShow(pY);
			}
		};

		// get dimensions and options for cluetip and prepare it to be shown
		var cluetipShow = function(bpY) {
			$cluetip.addClass('cluetip-' + ctClass);

			if (opts.truncate) {
				var $truncloaded = $cluetipInner.text().slice(0,opts.truncate) + '...';
				$cluetipInner.html($truncloaded);
			}
			function doNothing() {}; //empty function
			tipTitle ? $cluetipTitle.show().html(tipTitle) : (opts.showTitle) ? $cluetipTitle.show().html('&nbsp;') : $cluetipTitle.hide();
			if (opts.sticky) {
				var $closeLink = $('<div id="cluetip-close"><a href="#">' + opts.closeText + '</a></div>');
				(opts.closePosition == 'bottom') ? $closeLink.appendTo($cluetipInner) : (opts.closePosition == 'title') ? $closeLink.prependTo($cluetipTitle) : $closeLink.prependTo($cluetipInner);
				$closeLink.click(function() {
					cluetipClose();
					return false;
				});
				if (opts.mouseOutClose) {
					if ($.fn.hoverIntent && opts.hoverIntent) {
						$cluetip.hoverIntent({
							over: doNothing,
							timeout: opts.hoverIntent.timeout,
							out: function() { $closeLink.trigger('click'); }
						});
					} else {
						$cluetip.hover(doNothing,
						function() {$closeLink.trigger('click'); });
					}
				} else {
					$cluetip.unbind('mouseout');
				}
			}
			// now that content is loaded, finish the positioning
			var direction = '';
			$cluetipOuter.css({overflow: defHeight == 'auto' ? 'visible' : 'auto', height: defHeight});
			tipHeight = defHeight == 'auto' ? Math.max($cluetip.outerHeight(),$cluetip.height()) : parseInt(defHeight,10);
			tipY = posY;
			baseline = sTop + wHeight;
			if (opts.positionBy == 'fixed') {
				tipY = posY - opts.dropShadowSteps + tOffset;
			} else if ( (posX < mouseX && Math.max(posX, 0) + tipWidth > mouseX) || opts.positionBy == 'bottomTop') {
				if (posY + tipHeight + tOffset > baseline && mouseY - sTop > tipHeight + tOffset) {
					tipY = mouseY - tipHeight - tOffset;
					direction = 'top';
				} else {
					tipY = mouseY + tOffset;
					direction = 'bottom';
				}
			} else if ( posY + tipHeight + tOffset > baseline ) {
				tipY = (tipHeight >= wHeight) ? sTop : baseline - tipHeight - tOffset;
			} else if ($this.css('display') == 'block' || $this[0].tagName.toLowerCase() == 'area' || opts.positionBy == "mouse") {
				tipY = bpY - tOffset;
			} else {
				tipY = posY - opts.dropShadowSteps;
			}
			if (direction == '') {
				posX < linkLeft ? direction = 'left' : direction = 'right';
			}
			$cluetip.css({top: tipY + 'px'}).removeClass().addClass('clue-' + direction + '-' + ctClass).addClass(' cluetip-' + ctClass);
			if (opts.arrows) { // set up arrow positioning to align with element
				var bgY = (posY - tipY - opts.dropShadowSteps);
				$cluetipArrows.css({top: (/(left|right)/.test(direction) && posX >=0 && bgY > 0) ? bgY + 'px' : /(left|right)/.test(direction) ? 0 : ''}).show();
			} else {
				$cluetipArrows.hide();
			}

			// (first hide, then) ***SHOW THE CLUETIP***
			$dropShadow.hide();
			$cluetip.hide()[opts.fx.open](opts.fx.open != 'show' && opts.fx.openSpeed);
			if (opts.dropShadow) $dropShadow.css({height: tipHeight, width: tipInnerWidth}).show();
			if ($.fn.bgiframe) { $cluetip.bgiframe(); }
			// trigger the optional onShow function
			if (opts.delayedClose > 0) {
				closeOnDelay = setTimeout(cluetipClose, opts.delayedClose);
			}
			opts.onShow($cluetip, $cluetipInner);

		};

		/***************************************
			 =INACTIVATION
		-------------------------------------- */
		var inactivate = function() {
			isActive = false;
			$('#cluetip-waitimage').hide();
			if (!opts.sticky || (/click|toggle/).test(opts.activation) ) {
				cluetipClose();
				clearTimeout(closeOnDelay);
			};
			if (opts.hoverClass) {
				$this.removeClass(opts.hoverClass);
			}
			$('.cluetip-clicked').removeClass('cluetip-clicked');
		};
		// close cluetip and reset some things
		var cluetipClose = function() {
			$cluetipOuter
			.parent().hide().removeClass().end()
			.children().empty();
			if (tipTitle) {
				$this.attr(opts.titleAttribute, tipTitle);
			}
			$this.css('cursor','');
			if (opts.arrows) $cluetipArrows.css({top: ''});
		};

		/***************************************
			 =BIND EVENTS
		-------------------------------------- */
		// activate by click
			if ( (/click|toggle/).test(opts.activation) ) {
				$this.click(function(event) {
					if ($cluetip.is(':hidden') || !$this.is('.cluetip-clicked')) {
						activate(event);
						$('.cluetip-clicked').removeClass('cluetip-clicked');
						$this.addClass('cluetip-clicked');

					} else {
						inactivate(event);

					}
					this.blur();
					return false;
				});
			// activate by focus; inactivate by blur
			} else if (opts.activation == 'focus') {
				$this.focus(function(event) {
					activate(event);
				});
				$this.blur(function(event) {
					inactivate(event);
				});
			// activate by hover
			// clicking is returned false if cluetip url is same as href url
			} else {
				$this.click(function() {
					if ($this.attr('href') && $this.attr('href') == tipAttribute && !opts.clickThrough) {
						return false;
					}
				});
				//set up mouse tracking
				var mouseTracks = function(evt) {
					if (opts.tracking == true) {
						var trackX = posX - evt.pageX;
						var trackY = tipY ? tipY - evt.pageY : posY - evt.pageY;
						$this.mousemove(function(evt) {
							$cluetip.css({left: evt.pageX + trackX, top: evt.pageY + trackY });
						});
					}
				};
				if ($.fn.hoverIntent && opts.hoverIntent) {
					$this.mouseover(function() {$this.attr('title',''); })
					.hoverIntent({
						sensitivity: opts.hoverIntent.sensitivity,
						interval: opts.hoverIntent.interval,
						over: function(event) {
							activate(event);
							mouseTracks(event);
						},
						timeout: opts.hoverIntent.timeout,
						out: function(event) {inactivate(event); $this.unbind('mousemove');}
					});
				} else {
					$this.hover(function(event) {
						activate(event);
						mouseTracks(event);
					}, function(event) {
						inactivate(event);
						$this.unbind('mousemove');
					});
				}
			}
		});
	};

	$.fn.cluetip.defaults = {  // set up default options
		width:            275,      // The width of the clueTip
		height:           'auto',   // The height of the clueTip
		cluezIndex:       97,       // Sets the z-index style property of the clueTip
		positionBy:       'auto',   // Sets the type of positioning: 'auto', 'mouse','bottomTop', 'fixed'
		topOffset:        15,       // Number of px to offset clueTip from top of invoking element
		leftOffset:       15,       // Number of px to offset clueTip from left of invoking element
		local:            false,    // Whether to use content from the same page for the clueTip's body
		hideLocal:        true,     // If local option is set to true, this determines whether local content
									// to be shown in clueTip should be hidden at its original location
		attribute:        'rel',    // the attribute to be used for fetching the clueTip's body content
		titleAttribute:   'title',  // the attribute to be used for fetching the clueTip's title
		splitTitle:       '',       // A character used to split the title attribute into the clueTip title and divs
									// within the clueTip body. more info below [6]
		showTitle:        true,     // show title bar of the clueTip, even if title attribute not set
		cluetipClass:     'default',// class added to outermost clueTip div in the form of 'cluetip-' + clueTipClass.
		hoverClass:       '',       // class applied to the invoking element onmouseover and removed onmouseout
		waitImage:        true,     // whether to show a "loading" img, which is set in jquery.cluetip.css
		cursor:           'help',
		arrows:           false,    // if true, displays arrow on appropriate side of clueTip
		dropShadow:       true,     // set to false if you don't want the drop-shadow effect on the clueTip
		dropShadowSteps:  6,        // adjusts the size of the drop shadow
		sticky:           false,    // keep visible until manually closed
		mouseOutClose:    false,    // close when clueTip is moused out
		activation:       'hover',  // set to 'click' to force user to click to show clueTip
									// set to 'focus' to show on focus of a form element and hide on blur
		clickThrough:     false,    // if true, and activation is not 'click', then clicking on link will take user to the link's href,
									// even if href and tipAttribute are equal
		tracking:         false,    // if true, clueTip will track mouse movement (experimental)
		delayedClose:     0,        // close clueTip on a timed delay (experimental)
		closePosition:    'top',    // location of close text for sticky cluetips; can be 'top' or 'bottom' or 'title'
		closeText:        'Close',  // text (or HTML) to to be clicked to close sticky clueTips
		truncate:         0,        // number of characters to truncate clueTip's contents. if 0, no truncation occurs

		// effect and speed for opening clueTips
		fx: {
			open:       'show', // can be 'show' or 'slideDown' or 'fadeIn'
			openSpeed:  ''
		},

		// settings for when hoverIntent plugin is used
		hoverIntent: {
			sensitivity:  3,
			interval:     50,
			timeout:      0
		},

		// function to run just before clueTip is shown.
		onActivate:       function(e) {return true;},

		// function to run just after clueTip is shown.
		onShow:           function(ct, c){},

		// whether to cache results of ajax request to avoid unnecessary hits to server
		ajaxCache:        true,

		// process data retrieved via xhr before it's displayed
		ajaxProcess:      function(data) {
			data = data.replace(/<s(cript|tyle)(.|\s)*?\/s(cript|tyle)>/g, '').replace(/<(link|title)(.|\s)*?\/(link|title)>/g,'');
			return data;
		},

		// can pass in standard $.ajax() parameters, not including error, complete, success, and url
		ajaxSettings: {
			dataType: 'html'
		},
		debug: false
	};


	var insertionType = 'appendTo', insertionElement = 'body';
	$.cluetip = {};
	$.cluetip.setup = function(options) {
		if (options && options.insertionType && (options.insertionType).match(/appendTo|prependTo|insertBefore|insertAfter/)) {
			insertionType = options.insertionType;
		}
		if (options && options.insertionElement) {
			insertionElement = options.insertionElement;
		}
	};

})(jQuery);
