/**
 * jQuery-tooltip extension for ernstings-family.
 *
 * @requires jquery.hoverIntent <http://cherne.net/brian/resources/jquery.hoverIntent.html>
 */
;(function($) {
	$.efTooltip = {
		//{{{ globals and default-settings
		/**
		 * id of the div-container that holds the tooltip contents. An
		 * additional conatiner with <containerId>_shadow will be created.
		 */
		containerId : 'tooltip',
		
		/**
		 * enables debug-output to the firebug-console
		 */
		DEBUG : false,
		
		// instance-specific settings and default-values
		defaults: {
			/**
			 * @cfg {String|jQuery|Function} contents
			 *   a html-string, a jQuery-Object or a function that is called to
			 *   generate the contents for the element. If set, an existing
			 *   title-attribute will be ignored.
			 * 
			 * If a function is specified as argument, it is called with the
			 * event that triggered the tooltip as argument and is executed in
			 * the scope of the owner-element. It must return the contents as
			 * String, jQuery-Object or DOM-Element.
			 */
			contents : null,
			
			/**
			 * @cfg {String} extraClass
			 *   an extra css-class to be added to the tooltip's
			 *   content-container.
			 */
			extraClass : null,
			
			/**
			 * @cfg {int} maxWidth
			 *   maximum width of the wrapper-div
			 */
			maxWidth : 250,
			
			/**
			 * @cfg {int} posOffsetX
			 *   offset of the tooltip from Mouseposition
			 */
			posOffsetX : 3,
			
			/**
			 * @cfg {int} posOffsetY
			 *   offset of the tooltip from Mouseposition
			 */
			posOffsetY : 3,
			
			// hoverIntent-settings
			/**
			 * @cfg {int} sensitivity
			 *   An amount of pixels the mouse might move within the given
			 *   interval without being recognized as an actual movement.
			 */
			sensitivity: 10,
			
			/**
			 * @cfg {int} interval
			 *   The time (milliseconds) beetween two mouse-coordinate
			 *   evaluation. If the mouse stands still (see sensitivity) for
			 *   this amount of time the tooltip is activated.
			 */
			interval: 200,
			
			/**
			 * @cfg {int} timeout
			 *   The timeout beetween the leaving of the tooltip-area and the
			 *   closing of the tooltip. Reentering the tooltip within this
			 *   period cancels the timeout.
			 */
			timeout: 100
		},
		//}}}
		hide : function(callback) { _hide(null, callback); }
	};
	
	// jQuery plugin-methods
	$.fn.extend({
		//{{{ efTooltip(settings)
		/**
		 * adds tooltip-behaviour to a jQuery-Object (representing a single or a set of elements)
		 *
		 * @scope jQuery
		 * @param {Object} setings  the setings-object.
		 *    Settings: see above
		 */
		efTooltip: function(settings) {
			// apply defaults
			settings = $.extend({}, $.efTooltip.defaults, settings);
			
			settings.over = _show;
			settings.out = _hide;
			
			// setup helpers.
			if(helpers === null) { _createHelpers(settings); }
			
			return this.data('tooltipSettings', settings)
				.each(_initTooltipDataForElement)
				.hoverIntent(settings)
				.bind('mouseover.tracking mouseout.tracking', _toggleTracking);
		}
		//}}}
	});
	//}}}
	
	//{{{ private members
	/**
	 * container-object for helper-elements.
	 *   helpers.wrap: container for all elements
	 *   helpers.ct: content-Container
	 *   helpers.shadow: shadow-div
	 *
	 * @var {Object}
	 */
	var helpers = null;
	/**
	 * current mouse-position used to position the tooltip.
	 * @var {int}
	 */
	var mX, mY;
	
	//{{{ _initTooltipDataForElement()
	/**
	 * initialize the tooltip for a dom-element.
	 *
	 * @param {Object} settings  the settings for this tooltip.
	 * @scope {DomElement}
	 */
	function _initTooltipDataForElement() {
		var settings = $(this).data("tooltipSettings");
		// copy tooltip into its own expando and remove the title
		this.tooltipText = this.title;
		$(this).removeAttr("title");
		// also remove alt attribute to prevent default tooltip in IE
		this.alt = "";
	};
	//}}}

	//{{{ _createHelpers(settings)
	/**
	 * creates the helper-elements, that is the dom-elements required to show
	 * the tooltips.
	 */
	function _createHelpers(settings) {
		var cId = $.efTooltip.containerId;
		helpers = {};
		helpers.wrap = $(
			  '<div id="' + cId + '_wrap">'
			+   '<div id="' + cId + '"></div>'
			+   '<div id="' + cId + '_shadow" ></div>'
			+ '</div>'
		).css({width : settings.maxWidth + 'px'}).appendTo('body').hide();
		
		helpers.ct = $('#' + cId, helpers.wrap);
		helpers.shadow = $('#' + cId + '_shadow', helpers.wrap);
	};
	//}}}
	
	//{{{ _updateHelpers(dom, ev, s)
	/**
	 * updates the helpers before shown.
	 *
	 * @param  {DomElement} dom  the element for which the tooltip should be shown
	 * @param  {EventObject} ev  the mouseover-event
	 * @param  {Object} s        the settings-object for the tooltip
	 */
	function _updateHelpers(dom, ev, s) {
		var contents = dom.tooltipText;
		if(s.contents !== null) {
			if(typeof s.contents === 'function') {
				contents = s.contents.call(dom, ev);
			} else {
				contents = s.contents;
			}
		}
		
;;;		__dir({updateHelpers : {dom:dom, ev:ev, s:s, contents:contents}});
		
		helpers.ct.empty().append(contents);
		helpers.ct.removeClass();
		
		if(s.extraClass !== null) { helpers.ct.addClass(s.extraClass); }
		
		// mX and mY define the mousePosition and are set updated in _trackMouse
		helpers.wrap.css({left: mX + s.posOffsetX, top: mY + s.posOffsetY, width: s.maxWidth});
		
		// set to hidden so we can read out width and height
		helpers.wrap.css({visibility: 'hidden', display: 'block'});
		var ctWidth = helpers.ct.outerWidth(),
			ctHeight = helpers.ct.outerHeight();
		var screenWidth = $("html").width(),
			screenHeight = $("html").height();
		var tipLeft = mX + s.posOffsetX,
			tipTop = mY + s.posOffsetY;
		if(screenWidth < tipLeft + ctWidth + 5) {
			helpers.wrap.css({left: mX - s.posOffsetX - ctWidth + "px"});
		}
		if(screenHeight < tipTop + ctHeight + 5) {
			helpers.wrap.css({top: mY - s.posOffsetY - ctHeight + "px"});
		}
		helpers.shadow.css({width:ctWidth,height:ctHeight});
		helpers.wrap.css({width:ctWidth});
		helpers.wrap.css({display: 'none', visibility: 'visible'});
	};
	//}}}

	//{{{ _show(ev)
	/**
	 * @scope {DomElement}      the element for which the tooltip should be shown
	 * @param {EventObject} ev  the event
	 */
	function _show(ev) {
;;;		__log('show');
		// attach move-listener
		var s=$(this).data('tooltipSettings');
		_updateHelpers(this, ev, s);
		
		// mouseover and -out events for the tooltip must be delegated to the
		// element that holds the tooltip. Therefor we need to swap the target
		// and relatedTarget/fromElement/toElement-properties.
		var holder = this;
		helpers.wrap.bind( 'mouseover mouseout', function(e) {
			var tmp = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
			e.relatedTarget = e.target;
			e.target = tmp;
			
			$(holder).triggerHandler(e.type, e);
		});
		
		// show container
		helpers.wrap.show();
		return false;
	};
	//}}}
	
	//{{{ _hide(ev)
	/**
	 * @scope DOMElement  the element for which
	 * @param EventObject  the event
	 */
	function _hide(ev, callback) {
;;;		__log('hide');
		if(!helpers.wrap.is(":visible")) {
			return;
		}
		helpers.wrap.fadeOut(200, callback);
		helpers.wrap.unbind('mouseover mouseout');
	};
	//}}}
	
	//{{{ _toggleTracking(ev)
	function _toggleTracking(ev) {
		if(ev.type=='mouseover') {
			$(this).bind('mousemove.tracking', _trackMouse );
		} else {
			$(this).unbind('mousemove.tracking');
		}
	};
	//}}}
	//{{{ _trackMouse(e)
	function _trackMouse(e) {
		mX=e.pageX;
		mY=e.pageY;
	};
	//}}}
	
	// debugging-functions
;;;	function __log() {
;;;		if($.efTooltip.DEBUG === false) return;
;;;
;;;		if(console && console.log) {
;;;			console.log.apply(this, arguments);
;;;		}
;;;	}
	
;;;	function __dir() {
;;;		if($.efTooltip.DEBUG === false) return;
;;;
;;;		if(console && console.dir) {
;;;			console.dir.apply(this, arguments);
;;;		}
;;;	}
	//}}}
})(jQuery);

