// jQuery plug-in:
(function($) {
	/**
	 * Global tooltip index.
	 */
	document.tooltips = {};
	
	/**
	 * Current opened tooltip layer.
	 */
	var currentOpenTooltip = null;
	
	/**
	 * This method wraps the given data with the HTML structure of a tooltip layer.
	 * The data will be used as the inner content of the layer.
	 * 
	 * @param data
	 * 			the data
	 * @return the wrapped HTML
	 */
	/*private*/ function wrapHTML(data) {
		var htmlWrap = '<div class="ms-tel offscreen">'
					+ '<div class="ms-tooltip">'
					+ '<div class="ms-tooltip-head"><div class="ms-tooltip-head-inner"></div></div>'
					+ '<div class="ms-tooltip-body"><div class="ms-tooltip-body-inner">'
					+ data
					+ '</div></div>'
					+ '<div class="ms-tooltip-foot"><div class="ms-tooltip-foot-inner"></div></div>'
					+ '</div>'
					+ '<img class="ms-tooltip-arrow" src="" alt="" />'
					+ '</div>';
		return $(htmlWrap);
	}
	
	/**
	 * This method checks if a DIV element identified by the appender parameter
	 * is available within the DOM tree. If not, it creates such an element and
	 * appends it to the DOM's body.
	 * 
	 * @param appender
	 * 			A string containing the element id used to identify the tooltip
	 * 			container.
	 * @return void
	 */
	/*private*/ function ensureAppenderAvailability(appender) {
		var concreteAppender = $(appender);
		if(concreteAppender.length < 1) {
			var newHtml = '<div id="' 
				+ appender.replace(/#/, '') 
				+ '" style="left: 0; top: 0; width: 100%; z-index: 1900;" />';
			concreteAppender = $(newHtml);
			concreteAppender.appendTo('body');
		}
	}
	
	/**
	 * This plug-in requires to operate on relative tooltip references only, 
	 * since they are used to identify them within the 
	 * <code>document.tooltips</code> object. In IE6 references are inserted
	 * as absolute URLs into the DOM (a#href). Thus the absolute part of an
	 * URL is removed here.
	 * @param src
	 * 			The link to cope with.
	 * @return String
	 * 			...containing a relative reference
	 */
	/*private*/ function makeSrcRelative(src) {
		if(src.indexOf('/content') > 0) {
			var startIndex = src.indexOf('/content'); 
			src = src.substring(startIndex);
		}
		return src;
	}

	/**
	 * The following function represents the interface to the server.
	 * It sends a request and defines a method to handle the data.
	 * 
	 * @param src
	 * 			the tooltip source
	 * @param appender
	 * 			the element to which the layer will be appended
	 */
	/*private*/ function loadTooltip(src, appender) {
		ensureAppenderAvailability(appender);
		src = makeSrcRelative(src);
		if(!document.tooltips[src]) {
			// Set a value in any case to avoid redundant
			// requests through racing condition:
			document.tooltips[src] = "loading...";
			
			// Starting the request:
			window.embGetLayerManager()._loadLayer(
				function(ignore1, bodyContents, ignore2, ignore3, ignore4) {
					var payload = wrapHTML(bodyContents);
					payload.appendTo(appender);
					if(typeof(window.msSltLinkRewriter) == "object") {
						var element = $(appender)[0];
						if (element) {
							window.msSltLinkRewriter.replaceSltLinksStartingFrom(element);
						}
					}
					if (payload) {
						document.tooltips[src] = payload;
					} else {
						// Set the tooltip to false to allow a new try
						document.tooltips[src] = false;
					}
				},
				src.replace(/\.html/gi, ".layerxml.html"),
				{ silentLoading : true }
			);
		}		
	}

	/**
	 * This method gets called when the user enters the tooltip layer.
	 * The timer to set the layer to visibility hidden will be canceled.
	 * 
	 * @param event 
	 * 			event arguments
	 */
	/*private*/ function layerMouseOverHandler(event) {
		// Get tooltip and set status to open
		tooltip = $(event.currentTarget);
		tooltip.data('tooltip-open', true);
		if (tooltip.data('timerId')) {
			window.clearTimeout(tooltip.data('timerId'));
			tooltip.data('timerId', null);
		}

		// Get connected link and set hover style
		if (tooltip.data('href') && !tooltip.data('href').hasClass('tooltipHover')) {
			tooltip.data('href').addClass('tooltipHover');
		}
	}
	
	/**
	 * This method gets called when the user leaves the tooltip layer.
	 * The layer will be set to visibility hidden.
	 * 
	 * @param event 
	 * 			event arguments
	 */
	/*private*/ function layerMouseOutHandler(event) {
		// Get tooltip and set status to close
		tooltip = $(event.currentTarget);
		tooltip.data('tooltip-open', false);
		
		// Get connected link and reset style
		if (tooltip.data('href') && tooltip.data('href').hasClass('tooltipHover')) {
			tooltip.data('href').removeClass('tooltipHover');
		}
		
		// Get delay for disappear
		var delay = tooltip.data('tooltip-closedelay');
		if (!delay) delay = 2000;
		
		// Set callback for layer closing
		var timerId = window.setTimeout(function() {
			if (!tooltip.data('tooltip-open')) {
				tooltip.addClass('offscreen');
				if (currentOpenTooltip == tooltip) {
					currentOpenTooltip = null;
				}
			}
		}, delay);
		tooltip.data('timerId', timerId);
	}
	
	/**
	 * Calculates the size of the tooltip layer.
	 * @param tooltip
	 * 			The tooltip to calculate the size for.
	 * @param opts
	 * 			The tooltip options.
	 * @return void
	 */
	/*private*/ function calcAndSetLayerSize(tooltip, opts) {
		// use the standard width, if no image is contained
		var innerWidth = opts.standardWidth;
		
		var tooltipBodyInner = tooltip.find('.ms-tooltip-body-inner'); 
		var media = tooltipBodyInner.find('img');
		if(media.length == 0) {
			media = tooltipBodyInner.find('object');
		}
		if(media.length > 0) { // the media width overrides the standard width
			innerWidth = media.width();
			
			// ensure a reasonable width:
			if (innerWidth > opts.maxWidth) {
				innerWidth = opts.maxWidth;
			} else if (innerWidth < opts.minWidth) {
				innerWidth = opts.minWidth;
			}
		}
		
		tooltipBodyInner.width(innerWidth);
		
		// handle browser specific width:		
		if ($.browser.msie && $.browser.version == "7.0") {
			tooltip.find('.ms-tooltip-head-inner').width(innerWidth+29);
			tooltip.find('.ms-tooltip-foot-inner').width(innerWidth+21);
		}
		
		if ($.browser.msie && $.browser.version == "6.0") {
			tooltip.find('.ms-tooltip').width(innerWidth+37);
		}
	}
	
	/*private*/ function outhandler(event) {
		// Reset current event target and forward call
		var element = $(event.currentTarget);
		event.currentTarget = document.tooltips[makeSrcRelative(element.attr('href'))];
		layerMouseOutHandler(event);
	}
	
	/**
	 * This method gets called when the user moves the mouse over the tooltip link.
	 * The layer will be set to the correct position and then shown.
	 * 
	 * @param event
	 * 			event arguments
	 */
	/*private*/ function overhandler(event) {
		// Close current open layer
		if (currentOpenTooltip) {
			// Deactivate events and timers for this layer
			currentOpenTooltip.unbind();
			if (currentOpenTooltip.data('timerId')) {
				window.clearTimeout(currentOpenTooltip.data('timerId'));
				currentOpenTooltip.data('timerId', null);
			}
			
			// Get connected link and reset hover style
			if (currentOpenTooltip.data('href') && currentOpenTooltip.data('href').hasClass('tooltipHover')) {
				currentOpenTooltip.data('href').removeClass('tooltipHover');
			}
			
			// Hide this layer
			currentOpenTooltip.data('tooltip-open', false);
			currentOpenTooltip.addClass('offscreen');
		}
		
		// Get tooltip
		var element = $(event.currentTarget);
		var tooltip = document.tooltips[makeSrcRelative(element.attr('href'))];
		var opts = element.data('tooltip-options');

		// Set properties for tooltip
		tooltip.data('tooltip-open', true);
		tooltip.data('tooltip-closedelay', opts.closeDelay);
		tooltip.data('href', element);
		tooltip.bind('mouseout', layerMouseOutHandler);
		tooltip.bind('mouseover', layerMouseOverHandler);
		
		// Set tooltipHover class and add handler to link element
		element.addClass("tooltipHover");
		
		// Calculate size of layer
		calcAndSetLayerSize(tooltip, opts);
		
		// handle browser specific display
		if ($.browser.msie && $.browser.version == "6.0") {
			//fix head distance 
			tooltip.find('.ms-tooltip-head-inner').css("marginBottom", "-5px");
			
			//use GIF images instead of PNG
			tooltip.find('.ms-tooltip-body').css("background-image", "url(/img/tt-body.gif)");
			tooltip.find('.ms-tooltip-head').css("background-image", "url(/img/tt-top.gif)");
			tooltip.find('.ms-tooltip-foot').css("background-image", "url(/img/tt-bottom.gif)");
			
			tooltip.find('.ms-tooltip-head-inner').css("background-image", "url(/img/tt-top-end.gif)");
			tooltip.find('.ms-tooltip-foot-inner').css("background-image", "url(/img/tt-bottom-end.gif)");
		}
		
		// Calculate layer position
		var isOnTopOfLink = isLayerOnTopOfElement(element);
		
		// Set margin to cover the link (for the outhandler to work)
		var innerTooltip = tooltip.find('.ms-tooltip');
		if (isOnTopOfLink) {
			innerTooltip.css( {
				'padding-top' : '', //reset
				'padding-bottom' : ''
			});
		} else {
			innerTooltip.css( {
				'padding-top' : '',
				'padding-bottom' : '' //reset
			});
		}
		
		// Set the tooltip to the right position
		var top = calcTopPosition(element, tooltip);
		var left = calcLeftPosition(element, tooltip);
		tooltip.css( {
			'top' : top,
			'left' : left
		});
		
		// Remove offscreen class to make it visible
		tooltip.removeClass('offscreen');
		currentOpenTooltip = tooltip;

		// Set arrow image and calculate position
		var arrow = tooltip.find('.ms-tooltip-arrow');
		var arrowLeft = calcArrowLeftPosition(element, tooltip);
		if(isOnTopOfLink) {
			if ($.browser.msie && $.browser.version == "6.0") {
				arrow.attr("src", "/img/tel_arrowd.gif");
			}
			else {
				arrow.attr("src", "/img/tel_arrowd.png");
			}
			arrow.css( {
				'top' : '', //reset
				'bottom' : (opts.arrowHeight - 52),
				'left' : arrowLeft
			});
		} else {
			if ($.browser.msie && $.browser.version == "6.0") {
				arrow.attr("src", "/img/tel_arrowu.gif");
			}
			else {
				arrow.attr("src", "/img/tel_arrowu.png");
			}
			arrow.css( {
				'top' : (- opts.arrowHeight + 9),
				'bottom' : '', //reset
				'left' : arrowLeft
			});
		}
	}

	/**
	 * Calculates the position of the layer. Returns true if the layer should
	 * be opened above the given element or false, otherwise.
	 * 
	 * @param element
	 * 			the element that opens the layer
	 * @return true or false
	 */
	/*private*/ function isLayerOnTopOfElement(element) {
		var topSpace = element.offset().top - $(window).scrollTop();
		var bottomSpace = $(window).height() - topSpace - element.height();
		return (topSpace > bottomSpace);
	}
	
	/**
	 * Calculates the top position of the layer.
	 * 
	 * @param element
	 * 			the link element
	 * @param tooltip
	 * 			the tooltip layer
	 * @return the top offset for the layer
	 */
	/*private*/ function calcTopPosition(element, tooltip) {
		var opts = element.data('tooltip-options');
		var top = 0;

		if (isLayerOnTopOfElement(element)) {
			top = element.offset().top - tooltip.height() - 18;
		} else {
			top = element.offset().top + element.height() - opts.arrowHeight +54;
		}
		
		return top;
	}

	/**
	 * Calculates the left position of the layer.
	 * 
	 * @param element
	 * 			the link element
	 * @param tooltip
	 * 			the tooltip layer
	 * @return the left offset for the layer
	 */
	/*private*/ function calcLeftPosition(element, tooltip) {
		var left = (element.width() / 2) 
				 + element.offset().left
				 - (tooltip.width() / 2);
		
		if(left < 0) {
			left = 0;
		} else if (left > 1005 - tooltip.width()) {
			// we use 1005, because of the margin/padding of the tooltip!
			left = 1005 - tooltip.width();
		}
		
		return left;
	}

	/**
	 * Calculates the left position of the arrow.
	 * 
	 * @param element
	 * 			the link element
	 * @param tooltip
	 * 			the tooltip layer
	 * @return the left offset for the arrow
	 */
	/*private*/ function calcArrowLeftPosition(element, tooltip) {
		var opts = element.data('tooltip-options');

		var arrowLeft = element.offset().left
					  + (element.width() / 2)
					  - tooltip.offset().left
					  - opts.arrowWidth;
		
		return arrowLeft;
	}

	/**
	 * This method activates the tooltip behaviour for all given elements.
	 * Please make sure the elements provide a layerxml!
	 */
	/*public*/ $.fn.enableTooltipBehaviour = function(options) {
		return this.each(function(index) {
			var opts = $.extend({},
					$.fn.enableTooltipBehaviour.defaults, options);
			
			// Bind options and events
			$(this).data('tooltip-options', opts);
			$(this).bind('mouseover', overhandler);
			$(this).bind('mouseout', outhandler);
			
			// Load the tool tip immediately from the server:
			loadTooltip($(this).attr('href'), opts.layerAppender);
		});
	};
	
	/**
	 * Default values for the tooltip.
	 */
	/*public*/ $.fn.enableTooltipBehaviour.defaults = {
		minWidth :       38,
		standardWidth : 241,
		maxWidth :      465,
		arrowWidth :     32,
		arrowHeight :    32,
		closeDelay :   1500,
		layerAppender : '#ms-tooltip-veil'
	};
})(jQuery);

// Apply plugin to all tooltips on the page
jQuery(function() { 
	jQuery('a.tooltip').enableTooltipBehaviour(); 
});
