﻿if(typeof LOC == 'undefined') {
	var LOC = {};
}

(function() {
	var doc;
	
	// Feature testing support
	var reFeaturedMethod = new RegExp('^function|object$', 'i');
	
	// Test for properties of host objects that are never callable (e.g. document nodes, elements)
	var isRealObjectProperty = function(o, p) {
		return !!(typeof o[p] == 'object' && o[p]);
	};
	
	LOC.isRealObjectProperty = isRealObjectProperty;
	
	// Test for host object properties that are typically callable (e.g. document.getElementById) or known to be callable in some implementations (e.g. document.all in Safari)
	// which may be of type function, object (IE and possibly others) or unknown (IE ActiveX methods)
	var isHostMethod = function(o, m) {
		var t = typeof o[m];
		return !!((reFeaturedMethod.test(t) && o[m]) || t == 'unknown');
	};
	
	LOC.isHostMethod = isHostMethod;
	
	// Test for object or function types. Used when the property will be assigned to a variable (e.g. el = document.all) or type converted.
	// Similar to isHostMethod, but does not allow unknown types, which are known to throw errors when evaluated.
	var isHostObjectProperty = function(o, p) {
		var t = typeof o[p];
		return !!(reFeaturedMethod.test(t) && o[p]);
	};
	
	LOC.isHostObjectProperty = isHostObjectProperty;
	
	// Test multiple API properties
	
	var areFeatures = function() {
		var i = arguments.length;
		while(i--) {
			if(!LOC[arguments[i]]) {return false;}
		}
		return true;
	};
	
	LOC.areFeatures = areFeatures;
	
	// for in filter
	var isOwnProperty = function(o, p) {
		return typeof o.constructor.prototype[p] == 'undefined';
	};
	
	LOC.isOwnProperty = isOwnProperty;
	
	if(isRealObjectProperty(this, 'document')) {
		doc = this.document;
	}
	
	var elementUniqueID = (function() {
		var uid = 0;
		return function(el) {
			return el.id || (el.id = 'LOC_' + uid++);
		};
	})();
	
	// Events
	if(doc && Function.prototype.call) {
		var global = this;
		
		var addListener, removeListener, normalizeListener, prepareListener, clearListeners, listeners, cache = {};
		
		normalizeListener = function(el, fn, options) {
			var context = el;
			if(typeof options == 'object') {
				if(options.context) {
					context = options.context;
					delete options.context;
				}
				if(options.element) {
					options.element = el;
				}
			}
			return function(e) {
				var r = fn.call(context, (e || global.event), options);
				if(r === false) {
					preventDefault(e);
				}
			};
		};
		
		var unloadDone = false, fnID = 0;
		
		prepareListener = function(el, ev, fn, fnNormalized, options) {
			var uid = elementUniqueID(el);
			if(!cache[uid]) {
				cache[uid] = {};
			}
			if(!cache[uid][ev]) {
				cache[uid][ev] = {};
			}
			cache[uid][ev][++fnID] = fnNormalized;
			return fnID;
		};
		
		clearListeners = function() {
			var i = listeners.length, uid, ev, fnID;
			while(i--) {
				uid = elementUniqueID(listeners[i].el);
				ev = listeners[i].ev;
				fnID = listeners[i].fnID;
				if(cache[uid] && cache[uid][ev] && cache[uid][ev][fnID]) {
					listeners[i].el.detachEvent('on' + ev, listeners[i].fn);
				}
			}
			listeners = cache = null;
			global.detachEvent('onunload', clearListeners);
		};
		
		addListener = (function() {
			if(isHostMethod(doc, 'addEventListener')) {
				return function(el, ev, fn, options) {
					var fnNormalized = normalizeListener(el, fn, options);
					var fnID = prepareListener(el, ev, fn, fnNormalized, options);
					el.addEventListener(ev, fnNormalized, false);
					return fnID;
				};
			}
			if(isHostMethod(doc, 'attachEvent')) {
				return function(el, ev, fn, options) {
					var fnNormalized = normalizeListener(el, fn, options);
					var fnID = prepareListener(el, ev, fn, fnNormalized, options);
					el.attachEvent('on' + ev, fnNormalized);
					if(!unloadDone) {
						listeners = [];
						global.attachEvent('onunload', clearListeners);
						unloadDone = true;
					}
					if(!(ev == 'unload' && el == global)) {
						listeners[listeners.length] = {el: el, ev: ev, fn: fnNormalized, fnID: fnID};
					}
					return fnID;
				};
			}
		})();
		
		window.addEvent = LOC.addListener = addListener;
		
		removeListener = (function() {
			if(isHostMethod(doc, 'removeEventListener')) {
				return function(el, ev, fnID) {
					var uid = elementUniqueID(el), l;
					if(cache[uid] && (l = cache[uid][ev])) {
						if(fnID && l[fnID]) {
							el.removeEventListener(ev, l[fnID], false);
							delete cache[uid][ev][fnID];
							return fnID;
						} else if(!fnID) {
							var d = [];
							for(var i in l) {
								if(isOwnProperty(l, i)) {
									el.removeEventListener(ev, l[i], false);
									d[d.length] = i;
								}
							}
							delete cache[uid][ev];
							return d.join(';');
						} else {
							return null;
						}
					}
					return null;
				};
			}
			if(isHostMethod(doc, 'detachEvent')) {
				return function(el, ev, fn) {
					var uid = elementUniqueID(el), l;
					if(cache[uid] && (l = cache[uid][ev])) {
						if(fnID && l[fnID]) {
							el.detachEvent('on' + ev, l[fnID]);
							delete cache[uid][ev][fnID];
							return fnID;
						} else if(!fnID) {
							var d = [];
							for(var i in l) {
								if(isOwnProperty(l, i)) {
									el.detachEvent('on' + ev, l[i]);
									d[d.length] = i;
								}
							}
							delete cache[uid][ev];
							return d.join(';');
						} else {
							return null;
						}
					}
					return null;
				};
			}
		})();
		
		window.removeEvent = LOC.removeListener = removeListener;
		
		var preventDefault, stopPropagation, getEventTarget, getEventRelatedTarget;
		
		if(addListener) {
			preventDefault = function(e) {
				if(e.preventDefault) {
					e.preventDefault();
				}
				if(global.event) {
					global.event.returnValue = false;
				}
				return false;
			};
			
			LOC.preventDefault = preventDefault;
			
			stopPropagation = function(e) {
				e.cancelBubble = true;
				if(e.stopPropagation) {
					e.stopPropagation();
				}
			};
			
			LOC.stopPropagation = stopPropagation;
			
			getEventTarget = function(e) {
				return (e.target) ? (e.target.nodeType == 3) ? e.target.parentNode : e.target : e.srcElement;
			};
			
			LOC.getEventTarget = getEventTarget;
			
			getEventRelatedTarget = function(e) {
				if(e.relatedTarget) {
					return (e.relatedTarget.nodeType == 3) ? e.relatedTarget.parentNode : e.relatedTarget;
				}
				if(e.srcElement) {
					if(e.srcElement == e.fromElement) {return e.toElement;}
					if(e.srcElement == e.toElement) {return e.fromElement;}
				}
				return null;
			};
			
			LOC.getEventRelatedTarget = getEventRelatedTarget;
		}
	}
	
})();
