(function($) {
	$.ymkBoxMapper = {
		defaults : {
			setup: {
				// method to be used for inserting the ymkBoxMapper into the DOM.
				// Permitted values are 'appendTo', 'prependTo', 'insertBefore', and 'insertAfter'
				insertionType: 'insertAfter',
				// element in the DOM the plugin will reference when inserting the clueTip.
				insertionElement: '#content_placeholder'
			},
		
			ymkBoxMapperzIndex : 97,	// Sets the z-index style property of the ymkBoxMapper
			local : true, 				// Whether to use content from the same page for the ymkBoxMapper's body
			localPrefix : null, 		// string to be prepended to the tip attribute if local is true
			localIdSuffix : null,	 	// string to be appended to the ymkBoxMapper content element's id if local is true
			hideLocal : true, 			// If local option is set to true, this determines whether local content
										// to be shown in ymkBoxMapper should be hidden at its original location
			attribute : 'data-in', 		// the attribute to be used for fetching the ymkBoxMapper's body content
			ymkBoxMapperClass: 'default',	// class added to outermost clueTip div in the form of 'cluetip-' + clueTipClass.
			waitImage : true,			// whether to show a "loading" img, which is set in jquery.ymkBoxMapper.css
			cursor : 'help',
			arrows : false,				// if true, displays arrow on appropriate side of ymkBoxMapper
			sticky : true,				// keep visible until manually closed
			mouseOutClose : false,		// close when ymkBoxMapper is moused out
			activation : 'hover',		// set to 'click' to force user to click to show ymkBoxMapper
										// set to 'focus' to show on focus of a form element and hide on blur
			clickThrough : true,		// 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
			delayedClose : 0, 			// close ymkBoxMapper on a timed delay (experimental)
			closePosition : 'top', 		// location of close text for sticky ymkBoxMappers; can be 'top' or 'bottom' or 'title'
			closeText : 'Close', 		// text (or HTML) to to be clicked to close sticky ymkBoxMappers
			truncate : 0, 				// number of characters to truncate ymkBoxMapper's contents. if 0, no truncation occurs

			// effect and speed for opening ymkBoxMappers
			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
			},

			// short-circuit function to run just before ymkBoxMapper is shown.
			onActivate : function(e) {return true;},
			// function to run just after ymkBoxMapper is shown.
			onShow : function(ct, ci) {},
			// function to run just after ymkBoxMapper is hidden.
			onHide : function(ct, ci) {},
			// 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(/<(script|style|title)[^<]+<\/(script|style|title)>/gm,'').replace(/<(link|meta)[^>]+>/g, '');
				return data;
			},

			// can pass in standard $.ajax() parameters. Callback functions, such as beforeSend,
			// will be queued first within the default callbacks.
			// The only exception is error, which overrides the default
			ajaxSettings : {
				// error: function(ct, ci) { /* override default error callback */ },
				// beforeSend: function(ct, ci) { /* called first within default beforeSend callback */ },
				dataType : 'html'
			},
			debug : false

		}
	};
	var $ymkBoxMapper, $ymkBoxMapperInner, $ymkBoxMapperOuter, $ymkBoxMapperTitle, $ymkBoxMapperArrows, $ymkBoxMapperWait, $dropShadow, imgCount,
		standardClasses = 'ui-widget ui-widget-content ui-ymkBoxMapper',
		$ymkLastHoverClass;


	$.fn.ymkBoxMapper = function(js, options) {
		if (typeof js == 'object') {
			options = js;
			js = null;
		}
		if (js == 'destroy') {
			$(document).unbind('.ymkBoxMapper');
			$('#ymkBoxMapper').remove();
			$.removeData(this, 'title');
			$.removeData(this, 'ymkBoxMapper');
			return this.unbind('.ymkBoxMapper');
		}

		// merge per-call options with defaults
		options = $.extend(true, {}, $.ymkBoxMapper.defaults, options || {});

		/** =create ymkBoxMapper divs * */
		var insertionType = (/appendTo|prependTo|insertBefore|insertAfter/).test(options.setup.insertionType) ? options.setup.insertionType : 'appendTo',
			insertionElement = options.setup.insertionElement || 'body';

		if (!$('#ymkBoxMapper').length) {
			$( ['<div id="ymkBoxMapper">',
				'<div id="ymkBoxMapper-outer" class="ui-ymkBoxMapper-outer">',
				'<div id="ymkBoxMapper-inner" class="ui-ymkBoxMapper-content"></div>',
				'</div>',
				//'<div id="ymkBoxMapper-extra"></div>',
				//'<div id="ymkBoxMapper-arrows" class="ymkBoxMapper-arrows"></div>',
				'</div>' ].join(''))
				[insertionType](insertionElement).hide();

			var ymkBoxMapperzIndex = +options.ymkBoxMapperzIndex;

			$ymkBoxMapperParent = $(insertionElement);
			$ymkBoxMapper = $('#ymkBoxMapper');
			$ymkBoxMapperOuter = $('#ymkBoxMapper-outer').css({zIndex : ymkBoxMapperzIndex});
			$ymkBoxMapperInner = $('#ymkBoxMapper-inner');
			//$ymkBoxMapperTitle = $('#ymkBoxMapper-title');
			//$ymkBoxMapperArrows = $('#ymkBoxMapper-arrows');
			$ymkBoxMapperWait = $('<div id="ymkBoxMapper-waitimage"></div>')
				.insertBefore($ymkBoxMapper).hide();
		}
		var ymkBoxMapperPadding = (parseInt($ymkBoxMapper.css('paddingLeft'), 10) || 0)	+ (parseInt($ymkBoxMapper.css('paddingRight'), 10) || 0);


		this.each(function(index) {
			var link = this,
				$link = $(this),
				// support metadata plugin (v1.0 and 2.0)
				opts = $.extend(true, {}, options, $.metadata ? $link.metadata() : $.meta ? $link.data() : {}),
				// start out with no contents (for ajax activation)
				ymkBoxMapperContents = false,
				isActive = false,
				closeOnDelay = 0,
				tipAttribute = $link.attr(opts.attribute),
				ctClass = opts.ymkBoxMapperClass;

				ymkBoxMapperzIndex = +opts.ymkBoxMapperzIndex;
				$link.data('ymkBoxMapper', {title : link.title,zIndex : ymkBoxMapperzIndex});

				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 ymkBoxMapper
				if (opts.local && opts.localPrefix) {tipAttribute = opts.localPrefix + tipAttribute;}
				if (opts.local && opts.hideLocal && tipAttribute) {$(tipAttribute + ':first').hide();}
				
				// parse the title
				var tipParts;
				var tipTitle = (opts.attribute != 'title') ? $link.attr(opts.titleAttribute) : '';
				if (opts.splitTitle) {
					if (tipTitle == undefined) {tipTitle = '';}
					tipParts = tipTitle.split(opts.splitTitle);
					tipTitle = tipParts.shift();
				}
				if (opts.escapeTitle) {
					tipTitle = tipTitle.replace(/&/g,'&amp;').replace(/>/g,'&gt;').replace(/</g,'&lt;');
				}
		
				var localContent;
				function returnFalse() {return false;}

				/***************************************
				 * ACTIVATION
				 ****************************************/

				// activate ymkBoxMapper
				var activate = function(event) {
					var continueOn = opts.onActivate($link);
					if (continueOn === false) {
						return false;
					}
					isActive = true;
					if (tipAttribute == $link.attr('href')) {
						$link.css('cursor', opts.cursor);
					}
					if (opts.hoverClass) {
						$link.addClass(opts.hoverClass);
					}
					
					/***************************************
					 * load a string from ymkBoxMapper method's first argument
					 ***************************************/
					if (js) {
						if (typeof js == 'function') {
							js = js.call(link);
						}
						$ymkBoxMapperInner.html(js);
						ymkBoxMapperShow(pY);
					}
					/***************************************
					 * load the title attribute only (or user-selected attribute).
					 * ymkBoxMapper title is the string before the first delimiter
					 * subsequent delimiters place ymkBoxMapper body text on separate lines
					 ***************************************/

					else if (tipParts) {
						var tpl = tipParts.length;
						$ymkBoxMapperInner.html(tpl ? tipParts[0] : '');
						if (tpl > 1) {
							for ( var i = 1; i < tpl; i++) {
								$ymkBoxMapperInner.append('<div class="split-body">' + tipParts[i] + '</div>');
							}
						}
						ymkBoxMapperShow(pY);
					}
					/***************************************
					 * load external file via ajax
					 ***************************************/

					else if (!opts.local && tipAttribute.indexOf('#') !== 0) {
						if (/\.(jpe?g|tiff?|gif|png)(?:\?.*)?$/i.test(tipAttribute)) {
							$ymkBoxMapperInner.html('<img src="' + tipAttribute + '" alt="' + tipTitle + '" />');
							ymkBoxMapperShow(pY);
						} else {
							var optionBeforeSend = opts.ajaxSettings.beforeSend,
							optionError = opts.ajaxSettings.error,
							optionSuccess = opts.ajaxSettings.success,
							optionComplete = opts.ajaxSettings.complete;
							var ajaxSettings = {
								cache : false, // force requested page not to be cached by browser
								url : tipAttribute,
								beforeSend : function(xhr) {
									if (optionBeforeSend) {optionBeforeSend.call(link, xhr,	$ymkBoxMapper, $ymkBoxMapperInner);}
									$ymkBoxMapperOuter.children().empty();
									if (opts.waitImage) {
										$ymkBoxMapperWait
												.css({zIndex: $link.data('ymkBoxMapper').zIndex - 1})
												.show();
									}
								},
								error : function(xhr, textStatus) {
									if (isActive) {
										if (optionError) {
											optionError.call(link, xhr,textStatus, $ymkBoxMapper,$ymkBoxMapperInner);
										} else {
											$ymkBoxMapperInner.html('<i>sorry, the contents could not be loaded</i>');
										}
									}
								},
								success : function(data, textStatus) {
									ymkBoxMapperContents = opts.ajaxProcess.call(link, data);
									if (isActive) {
										if (optionSuccess) {optionSuccess.call(link, data,textStatus, $ymkBoxMapper,$ymkBoxMapperInner);}
										$ymkBoxMapperInner.html(ymkBoxMapperContents);
									}
								},
								complete : function(xhr, textStatus) {
									if (optionComplete) {optionComplete.call(link, xhr,textStatus, $ymkBoxMapper,$ymkBoxMapperInner);}
									var imgs = $ymkBoxMapperInner[0].getElementsByTagName('img');
									imgCount = imgs.length;
									for ( var i = 0, l = imgs.length; i < l; i++) {
										if (imgs[i].complete) {
											imgCount--;
										}
									}
									if (imgCount && !$.browser.opera) {
										$(imgs).bind('load error', function() {
											imgCount--;
											if (imgCount < 1) {
												$ymkBoxMapperWait.hide();
												if (isActive) { ymkBoxMapperShow(pY); }
											}
										});
									} else {
										$ymkBoxMapperWait.hide();
										if (isActive) { ymkBoxMapperShow(pY); }
									}
								}
							};
							var ajaxMergedSettings = $.extend(true, {},opts.ajaxSettings, ajaxSettings);

							$.ajax(ajaxMergedSettings);
						}

						/***************************************
						 * load an element from the same page
						 ***************************************/
					} else if (opts.local) {

						var $localContent = $(tipAttribute + (/#\S+$/.test(tipAttribute) ? '': ':eq(' + index + ')')).clone(true).removeClass('tag_info_src').show();
						if (opts.localIdSuffix) {
							$localContent.attr('id', $localContent[0].id + opts.localIdSuffix);
						}
						$ymkBoxMapperInner.html($localContent);
						ymkBoxMapperShow(0);
					}
				};

				// get dimensions and options for ymkBoxMapper and prepare it to be shown
				var ymkBoxMapperShow = function(bpY) {
					$ymkBoxMapper.addClass('ymkBoxMapper-' + ctClass);
					if (opts.truncate) {
						var $truncloaded = $ymkBoxMapperInner.text().slice(0,opts.truncate) + '...';
						$ymkBoxMapperInner.html($truncloaded);
					}

					function doNothing() {}; // empty function

					if (opts.sticky) {
						var $closeLink = $('<div id="ymkBoxMapper-close"><a href="#">' + opts.closeText + '</a></div>');
						(opts.closePosition == 'bottom') ? $closeLink.appendTo($ymkBoxMapperInner): (opts.closePosition == 'title') ? $closeLink.prependTo($ymkBoxMapperTitle) : $closeLink.prependTo($ymkBoxMapperInner);
						$closeLink.bind('click.ymkBoxMapper', function() {
							ymkBoxMapperClose();
							return false;
						});
						if (opts.mouseOutClose) {
							$ymkBoxMapper.bind('mouseleave.ymkBoxMapper', function() {
								ymkBoxMapperClose();
							});
						} else {
							$ymkBoxMapper.unbind('mouseleave.ymkBoxMapper');
						}
					}
					// now that content is loaded, finish the positioning
					var direction = '';
					$ymkBoxMapperOuter.css( {zIndex : $link.data('ymkBoxMapper').zIndex});
					// add classes
					var dynamicClasses = ' clue-' + ctClass + ' ymkBoxMapper-' + ctClass;
					$ymkBoxMapper.attr( {'className' : standardClasses + dynamicClasses});
					// set up arrow positioning to align with element
//					if (opts.arrows) {
//						var bgY = (posY - tipY - opts.dropShadowSteps);
//						$ymkBoxMapperArrows.css({top : (/(left|right)/.test(direction) && posX >= 0 && bgY > 0) ? bgY + 'px': /(left|right)/.test(direction) ? 0: ''}).show();
//					} else {
//						$ymkBoxMapperArrows.hide();
//					}

					// (first hide, then) ***SHOW THE ymkBoxMapper***
					// handle dropshadow divs first
//					$dropShadow = createDropShadows(opts);
//					if ($dropShadow && $dropShadow.length) {
//						$dropShadow.hide().css( {height : tipHeight,width : tipInnerWidth,zIndex : $link.data('ymkBoxMapper').zIndex - 1}).show();
//					}
					$ymkBoxMapperParent.hide();
					$ymkBoxMapper.hide()[opts.fx.open](opts.fx.openSpeed || 0);
//					if ($.fn.bgiframe) {$ymkBoxMapper.bgiframe();}
					// delayed close (not fully tested)
					if (opts.delayedClose > 0) {
						closeOnDelay = setTimeout(ymkBoxMapperClose,opts.delayedClose);
					}
					// trigger the optional onShow function
					opts.onShow.call(link, $ymkBoxMapper, $ymkBoxMapperInner);
					$('.' + $ymkLastHoverClass).removeClass($ymkLastHoverClass);
					if ($link.attr('data-hover-style')) {
						$ymkLastHoverClass = $link.attr('data-hover-style');
						$link.addClass($ymkLastHoverClass);
					} else if ($link.attr('data-hover-on')){
						$ymkLastHoverClass = $($link.attr('data-hover-on')).attr('data-hover-style');
						$($link.attr('data-hover-on')).addClass($ymkLastHoverClass);
					}
				};

				/***************************************
				   =INACTIVATION
				***************************************/
				var inactivate = function(event) {
					isActive = false;
					$ymkBoxMapperWait.hide();
					if (!opts.sticky || (/click|toggle/).test(opts.activation)) {
						ymkBoxMapperClose();
						clearTimeout(closeOnDelay);
					}
					if (opts.hoverClass) {
						$link.removeClass(opts.hoverClass);
					}
				};
				// close ymkBoxMapper and reset some things
				var ymkBoxMapperClose = function() {
					$ymkBoxMapperOuter.parent().hide().removeClass();
					$ymkBoxMapperParent.hide()[opts.fx.open](opts.fx.openSpeed || 0);
					opts.onHide.call(link, $ymkBoxMapper, $ymkBoxMapperInner);
					$link.removeClass('ymkBoxMapper-clicked');
					if (tipTitle) {
						$link.attr(opts.titleAttribute, tipTitle);
					}
					$link.css('cursor', '');
					if (opts.arrows) {
						$ymkBoxMapperArrows.css( {top : ''});
					}
					$('.' + $ymkLastHoverClass).removeClass($ymkLastHoverClass);
				};

				$(document).bind('hideymkBoxMapper', function(e) {
					ymkBoxMapperClose();
				});
				/***************************************
				   =BIND EVENTS
				 ***************************************/
				// activate by click
				if ((/click|toggle/).test(opts.activation)) {
					$link.bind('click.ymkBoxMapper', function(event) {
						if ($ymkBoxMapper.is(':hidden')	|| !$link.is('.ymkBoxMapper-clicked')) {
							activate(event);
							$('.ymkBoxMapper-clicked').removeClass('ymkBoxMapper-clicked');
							$link.addClass('ymkBoxMapper-clicked');
						} else {
							inactivate(event);
						}
						return false;
					});
					// activate by focus; inactivate by blur
				} else if (opts.activation == 'focus') {
					$link.bind('focus.ymkBoxMapper', function(event) {
						$link.attr('title', '');
						activate(event);
					});
					$link.bind('blur.ymkBoxMapper', function(event) {
						$link.attr('title', $link.data('thisInfo').title);
						inactivate(event);
					});
					// activate by hover
				} else {
					// clicking is returned false if clickThrough option is set to false
					$link[opts.clickThrough ? 'unbind' : 'bind']('click.ymkBoxMapper', returnFalse);
					// 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;
							$link.bind('mousemove.ymkBoxMapper', function(evt) {
								$ymkBoxMapper.css( {left : evt.pageX + trackX,top : evt.pageY + trackY});
							});
						}
					};
					if ($.fn.hoverIntent && opts.hoverIntent) {
						$link.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);$link.unbind('mousemove.ymkBoxMapper');}
						});
					} else {
						$link.bind('mouseenter.ymkBoxMapper', function(event) {
							activate(event);
							mouseTracks(event);
						})
						.bind('mouseleave.ymkBoxMapper', function(event) {
							inactivate(event);
							$link.unbind('mousemove.ymkBoxMapper');
						});
					}

					$link.bind('mouseover.ymkBoxMapper', function(event) {
						$link.attr('title', '');
					}).bind('mouseleave.ymkBoxMapper', function(event) {
						$link.attr('title', $link.data('ymkBoxMapper').title);
					});
				}
			});
		return this;
	};

	$.fn.ymkBoxMapper.defaults = $.ymkBoxMapper.defaults;

})(jQuery);
