/*
*
*	The Ultimate addEvent function by Stephen Stchur
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Cross-browser eventing library
*
*	Requires:
*	Nothing
*
*	License: (Ms-PL):  http://www.codeplex.com/gimme/license
*	See also: license.txt
*
*/

var xb = {};
(function()
{
	var GUIDCounter = 0;
	var evtHash = {};
	var pseudoEvents =
	{
		'mouseenter': function(_fn, _useCapture, _listening)
		{
			var f = mouseEnter(_fn);
			_listening ? xb.addEvent(this, 'mouseover', f, _useCapture, false) : xb.removeEvent(this, 'mouseover', f, _useCapture, false);
			f = null;		
		},
		
		'mouseleave': function(_fn, _useCapture, _listening)
		{
			var f = mouseEnter(_fn);
			_listening ? xb.addEvent(this, 'mouseout', f, _useCapture, false) : xb.removeEvent(this, 'mouseout', f, _useCapture, false);
			f = null;		
		}
	};
	
	xb.Helper =
	{
		getObjectGUID: getObjectGUID,
		storeHandler: storeHandler,
		retrieveHandler: retrieveHandler,
		isAnAncestorOf: isAnAncestorOf,
		mouseEnter: mouseEnter
	};	
	
	xb.addEvent = function()
	{
		if (typeof document.addEventListener !== 'undefined')
		{
			return w3c_addEvent;
		}
		else if (typeof document.attachEvent !== 'undefined')
		{
			return ie_addEvent;
		}
		else
		{
			// no modern event support I guess :-(
			// (you could use DOM 0 here if you really wanted to)
			return function() {};
		}
		
		function w3c_addEvent(_elem, _evtName, _fn, _useCapture, _directCall)
		{
			var eventFn = pseudoEvents[_evtName];
						
			if (typeof eventFn === 'function' && _directCall !== false)
			{
				eventFn.call(_elem, _fn, _useCapture, true);
			}
			else
			{
				_elem.addEventListener(_evtName, _fn, _useCapture);
			}
		}
		
		function ie_addEvent(_elem, _evtName, _fn, _useCapture, _directCall)
		{
			var eventFn = pseudoEvents[_evtName];
					
			if (typeof eventFn === 'function' && _directCall !== false)
			{
				eventFn.call(_elem, _fn, _useCapture, true);
			}
			else
			{
				// create a key to identify this element/event/function combination
				var key = generateHandlerKey(_elem, _evtName, _fn);
				
				// if this element/event/combo has already been wired up, just return
				var f = evtHash[key];
				if (typeof f !== 'undefined')
				{
					return;
				}
				
				// create a helper function to fix IE's lack of standards support
				f = function(e)
				{
					// map .target to .srcElement
					e.target = e.srcElement;
					
					// map .relatedTarget to either .toElement or .fromElement
					if (_evtName == 'mouseover') { e.relatedTarget = e.fromElement; }
					else if (_evtName == 'mouseout') { e.relatedTarget = e.toElement; }
						
					e.preventDefault = function() { e.returnValue = false; };
					e.stopPropagation = function() { e.cancelBubble = true; };

					// call the actual function, using entity (the element) as the 'this' object
					_fn.call(_elem, e);
					
					// null out these properties to prevent memory leaks
					e.target = null;
					e.relatedTarget = null;
					e.preventDefault = null;
					e.stopPropagation = null;
					e = null;
				};
				
				// add the helper function to the event hash
				evtHash[key] = f;

				// hook up the event (IE style)
				_elem.attachEvent('on' + _evtName, f);
				
				key = null;
				f = null;
			}
		}
	}();
	
	xb.removeEvent = function()
	{
		if (typeof document.removeEventListener !== 'undefined')
		{
			return w3c_removeEvent;
		}
		else if (typeof document.detachEvent !== 'undefined')
		{
			return ie_removeEvent;
		}
		else
		{
			// no modern event support I guess :-(
			return function() {};
		}
		
		function w3c_removeEvent(_elem, _evtName, _fn, _useCapture, _directCall)
		{
			var eventFn = pseudoEvents[_evtName];
						
			if (typeof eventFn === 'function' && _directCall !== false)
			{
				eventFn.call(_elem, _fn, _useCapture, false);
			}
			else
			{
				_elem.removeEventListener(_evtName, _fn, _useCapture);
			}
		}
		
		function ie_removeEvent(_elem, _evtName, _fn, _useCapture, _directCall)
		{
			var eventFn = pseudoEvents[_evtName];
						
			if (typeof eventFn === 'function' && _directCall !== false)
			{
				eventFn.call(_elem, _fn, _useCapture, false);
			}
			else
			{
				var key = generateHandlerKey(_elem, _evtName, _fn);
				var f = evtHash[key];
				if (typeof f !== 'undefined')
				{
					// unhook the event and delete the element from the event hash
					_elem.detachEvent('on' + _evtName, f);
					delete evtHash[key];
				}

				// prevent IE memory leaks
				key = null;
				f = null;
			}
			
			return this;
		}
	
	}();

	xb.defineEvent = function(_evtName, _logicFn)
	{
		pseudoEvents[_evtName] = _logicFn;
	};

	
	// Helper Functions
	function storeHandler(_key, _handler)
	{
		evtHash[_key] = _handler;
	}
	
	function retrieveHandler(_key)
	{
		return evtHash[_key];
	}
	
	function generateHandlerKey(_elem, _evtName, _handler)
	{
		return '{' + getObjectGUID(_elem) + '/' + _evtName + '/' + getObjectGUID(_handler) + '}';	
	}
	
	function isAnAncestorOf(_ancestor, _descendant, _generation)
	{
		if (_ancestor === _descendant) { return false; }
		
		var gen = 0;
		while (_descendant && _descendant != _ancestor)
		{
			gen++;
			_descendant = _descendant.parentNode;
		}
		
		_generation = _generation || gen;
		return _descendant === _ancestor && _generation === gen;		
	}
	
	function mouseEnter(_fn)
	{	
		var key = xb.Helper.getObjectGUID(_fn);
		var f = evtHash[key];
		if (typeof f === 'undefined')
		{
			f = evtHash[key] = function(_evt)
			{
				var relTarget = _evt.relatedTarget;
				if (this === relTarget || isAnAncestorOf(this, relTarget)) { return; }
		
				_fn.call(this, _evt);
			};
		}
		return f;	
	}
	
	function getObjectGUID(_elem)
	{
		if (_elem === window)
		{
			return 'theWindow';
		}
		else if (_elem === document)
		{
			return 'theDocument';
		}
		else if (typeof _elem.uniqueID !== 'undefined')
		{
			return _elem.uniqueID;
		}

		var ex = '__$$GUID$$__';
		if (typeof _elem[ex] === 'undefined')
		{
			_elem[ex] = ex + GUIDCounter++;
		}
		return _elem[ex];
	}

})();
