X.createSingleton('X.HoverIntentManager',
	// Constructor
	function HoverIntentManager()
	{
	    // default configuration options
		this.configDefaults =
	    {
			sensitivity: 7,
			interval: 100,
			timeout: 0,
			over: function(){},
			out: function(){}
		};
	
		// instantiate variables
		// cX, cY = current X and Y position of mouse, updated by mousemove event
		// pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
		this.currX = -1;
		this.currY = -1;
		this.prevX = -1;
		this.prevY = -1;
	},
	// Prototype Members
	{
	    // A private function for getting mouse position
	    // Called in the scope of the DOM element
		track: function(evt)
	    {
	        X.HoverIntentManager.currX = evt.pageX;
		    X.HoverIntentManager.currY = evt.pageY;
	    },
    
	    // called in the scope of the jquery element
	    compare: function(evt, config)
		{
	        this.hoverIntent_t = clearTimeout(this.hoverIntent_t);
		
			// compare mouse positions to see if they've crossed the threshold
			if ((Math.abs(X.HoverIntentManager.prevX - X.HoverIntentManager.currX) + Math.abs(X.HoverIntentManager.prevY - X.HoverIntentManager.currY)) < config.sensitivity)
			{
				this.unbind('mousemove', X.HoverIntentManager.track);
			
				// set hoverIntent state to true (so mouseOut can be called)
				this.hoverIntent_s = 1;
				return config.over.apply(this.get(0), [evt]);
			}
			else
			{
				// set previous coordinates for next time
				X.HoverIntentManager.prevX = X.HoverIntentManager.currX;
				X.HoverIntentManager.prevY = X.HoverIntentManager.currY;
			
				var me = this;
				// use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
				this.hoverIntent_t = setTimeout(function(){ X.HoverIntentManager.compare.apply(me, [evt, config]); }, config.interval);
			}
		},
	
		// A private function for delaying the mouseOut function
		// called in the scope of the jquery element
	    delay: function(evt, config)
		{
			this.hoverIntent_t = clearTimeout(this.hoverIntent_t);
			this.hoverIntent_s = 0;
			return config.out.apply(this.get(0), [evt]);
		},
	
		// Called in the scope of the DOM element
		onMouseOut: function(evt, me, config)
		{
		    // Ignore child elements
		    var relTarget = evt.toElement || evt.relatedTarget;
			while (relTarget && relTarget != this) { try { relTarget = relTarget.parentNode; } catch(ex) { relTarget = this; } }
			if (relTarget == this) { return false; }
		
			// cancel hoverIntent timer if it exists
			if (me.hoverIntent_t) { me.hoverIntent_t = clearTimeout(me.hoverIntent_t); }
		
			// unbind expensive mousemove event
			me.unbind('mousemove', X.HoverIntentManager.track);
		
			// if hoverIntent state is true, then call the mouseOut function after the specified delay
			if (me.hoverIntent_s == 1)
			{
			    me.hoverIntent_t = setTimeout(function() { X.HoverIntentManager.delay.apply(me, [evt, config]); }, config.timeout);
			}
		},
	
		// Called in the scope of the DOM element
		onMouseOver: function(evt, me, config)
		{
		    // Ignore child elements
		    var relTarget = evt.fromElement || evt.relatedTarget;
			while (relTarget && relTarget != this) { try { relTarget = relTarget.parentNode; } catch(ex) { relTarget = this; } }
			if (relTarget == this) { return false; }
		
			// cancel hoverIntent timer if it exists
			if (me.hoverIntent_t) { me.hoverIntent_t = clearTimeout(me.hoverIntent_t); }
		
			// set "previous" X and Y position based on initial entry point
			X.HoverIntentManager.prevX = evt.pageX;
			X.HoverIntentManager.prevY = evt.pageY;
		
			// update "current" X and Y position based on mousemove
			me.bind('mousemove', X.HoverIntentManager.track);
		
			// start polling interval (self-calling timeout) to compare mouse coordinates over time
			if (me.hoverIntent_s != 1)
			{
			    var timedFunc = function()
			    {
			        X.HoverIntentManager.compare.apply(me, [evt, config]);
			    }
			    me.hoverIntent_t = setTimeout(timedFunc, config.interval);
			}
		}
	}
);

jQuery.fn.hoverIntent = function(over, out)
{
	// override configuration options with user supplied object
	var config = (out)? { over: over, out: out } : over;
	config = jQuery.extend({}, X.HoverIntentManager.configDefaults, config);
    
    //return this.hover(config.over, config.out);
    
	// bind the function to the two event listeners
	return this
	    .mouseover(X.HoverIntentManager.onMouseOver.delegate(this.get(0), this, config))
	    .mouseout(X.HoverIntentManager.onMouseOut.delegate(this.get(0), this, config));
};