///<reference path="../TESCO.js" />
///<reference path="exception.js" />
/*

compatibility: WinIE5, WinIE5.5, WinIE6, Mozilla, Firefox, Netscape 7+, Opera 7+, Safari, Konqueror
		NOTES: 1) Does not support DOM0 events (e.g. MacIE)
			 2) Safari code added for onclick events to ensure event cancellation is supported for this event.
			This is added to the DOM0 onclick and does not retain any existing code. Therefore the original
			   event code will *not* be executed.

	Example:
	
	TESCO.system.event.attach(document.body, "click", 
		function(e) {
			//	click occured
			TESCO.system.event.detach(document.body, "click", arguments.callee);
		}
	}

*/

TESCO.system.event = new function() {
	this.NAME = "TESCO.system.event";

	var _self = this;

	var _queue = [];
	this._earlyBindEvents = null;
	var wrappedEvents = [];
	var bLoaded = false;
	var bBindComplete = false;

	var CONST_BINDING_TIMEOUT = 50;
	
	this.attach = function(o, e, f, args, early) {
	    var _callback = f;
	    var _args = args;
		var eventWrapper = function(e) {
			this.source = o;
			this.callback = _callback;
			this.args = _args;
			this.source.callback = this.callback;

			TESCO.system.event._dispatchEvent(this, e);
		}

		if (o) {
			if (typeof o == "string") {
				attachEventById(o, e, f, args, early);
			} else if (o.ISARRAY) {
				for (var item in o) {
					attachEventById(o[item], e, f, args, early);
				}
			} else {
			    if (e == "error" && o === window) {
					//  IE does not fire error event listener if errors are 
					//      suppressed, attach directly to window object
					o["on" + e] = function(message, fileName, lineNumber) {
						f({ message: message, fileName: fileName, lineNumber: lineNumber });
						//  suppress errors if not in debug
						return !TESCO.sites.Configuration.application.debug;
					}
				} else if (o.tagName && o.tagName != "IMG" && e == "load") {	// Element already loaded...
					o.callback = f;
					o.callback({ type: "load" }, args);
				} else {
					var _found = false, L = wrappedEvents.length;
					for (var i = 0; i < L && !_found; i++) {
						_found = (wrappedEvents[i] && wrappedEvents[i].f === f && wrappedEvents[i].o === o && wrappedEvents[i].e == e);
					}
					if (!_found) {
						wrappedEvents[L] = { o: o, e: e, f: f, w: eventWrapper }
						attachEvent(o, e, eventWrapper);
					}
				}
			}
		} 
	}

	this.detach = function(o, e, f) {
		if (typeof o == "string") {
			o = document.getElementById(o);
		} else if (o.ISARRAY) {
			for (var i = 0; i < o.length; i++) {
				this.detach(o[i], e, f);
			}
		}

		if (o) {
			for (var i = 0; i < wrappedEvents.length; i++) {
				if (wrappedEvents[i] && wrappedEvents[i].f === f && wrappedEvents[i].o === o && wrappedEvents[i].e == e) {
					if (o.removeEventListener) {
						o.removeEventListener(e, wrappedEvents[i].w, false);
					} else if (o.detachEvent) {
						o.detachEvent("on" + e, wrappedEvents[i].w);
					}
					delete wrappedEvents[i].w;
					delete wrappedEvents[i];
					wrappedEvents.splice(i, 1);
					break;
				}
			}
		}
	}

	this._dispatchEvent = function(eventWrapper, e) {
		var oEvent = getEvent(e);
		setPreventDefault = false;
		setStopPropagation = false;

        function _preventDefault() {
            if (!this.preventDefault) {
			    setPreventDefault = true;
				if (!this.canceldisabled) {
					this.returnValue = false;
				}
		    } else {
		        this.preventDefault();
		    }
        }

        function _stopPropagation() {
            if (!this.stopPropagation) {
			    setStopPropagation = true;
			    this.cancelBubble = true;
		    } else {
		        this.stopPropagation();
		    }
        }

		oEvent.stopEvent = function() {
			this.stop();
			this.prevent();
		}
		
		oEvent.stop = function() {
		    this.stopped = true;
		    _stopPropagation.call(this);
		}
		
		oEvent.prevent = function() {
		    this.prevented = true;
		    _preventDefault.call(this);
		}

		if (!oEvent.target && oEvent.srcElement) {
			oEvent.target = oEvent.srcElement;
		} else {
			/* Get around Safari text node bug */
			try {
				if (oEvent.target && oEvent.target.nodeType == 3) {
					oEvent.target = oEvent.target.parentNode;
				}
			} catch (err) {
				TESCO.system.Exception.handler.raise(err);
			}
		}
		
		if (!oEvent.currentTarget) {
			oEvent.currentTarget = eventWrapper.source;
		}

		if (!oEvent.pageX && oEvent.pageX !== 0) {
			oEvent.pageX = oEvent.clientX || 0;

			if (TESCO.system.browser.ie) {
				oEvent.pageX += getScroll()[1];
			}
		}

		if (!oEvent.pageY && oEvent.pageY !== 0) {
			oEvent.pageY = oEvent.clientY || 0;

			if (TESCO.system.browser.ie) {
				oEvent.pageY += getScroll()[0];
			}
		}

		if (!oEvent.relatedTarget && (oEvent.type === "mouseover") && oEvent.fromElement) {
			// || oEvent.type === "mouseenter") && oEvent.fromElement) {
			oEvent.relatedTarget = oEvent.fromElement;
		} else if (!oEvent.relatedTarget && (oEvent.type === "mouseout") && oEvent.toElement) { 
			// || oEvent.type === "mouseleave") && oEvent.toElement) {
			oEvent.relatedTarget = oEvent.toElement;
		} 

		oEvent.BUTTONLEFT = 1;
		oEvent.BUTTONMIDDLE = 2;
		oEvent.BUTTONRIGHT = 3;

		if (TESCO.system.browser.ie) {
			switch (oEvent.button) {
				case 1:
					oEvent.which = oEvent.BUTTONLEFT;
					break;
				case 2:
					oEvent.which = oEvent.BUTTONRIGHT;
					break;
				case 4:
					oEvent.which = oEvent.BUTTONMIDDLE;
					break;
			}
		} else if (TESCO.system.browser.opera) {
			switch (oEvent.button) {
				case 2:
					oEvent.which = oEvent.BUTTONRIGHT;
					break;
				case 3:
					oEvent.which = oEvent.BUTTONMIDDLE;
					break;
			}
		}

		try {
			eventWrapper.source.callback(oEvent, eventWrapper.args);
		} catch (e) {
			TESCO.system.Exception.handler.raise(e);
		} finally {
			if (TESCO.system.browser.clickBug && e.type == "click") {
				eventWrapper.source.onclick = function() { return e.returnValue; }
			}
			if (setPreventDefault) {
				oEvent.preventDefault = null;
			}
			if (setStopPropagation) {
				oEvent.stopPropagation = null;
			}
		}
	}

	function getScroll() {
		var dd = document.documentElement; db = document.body;
		if (dd && dd.scrollTop) {
			return [dd.scrollTop, dd.scrollLeft];
		} else if (db) {
			return [db.scrollTop, db.scrollLeft];
		} else {
			return [0, 0];
		}
	}

	function attachEvent(o, e, f) {
		if (o) {
			if (o.addEventListener) {
				o.addEventListener(e, f, false);
			} else if (o.attachEvent) {
				o.attachEvent("on" + e, f);
			}
			if (TESCO.system.browser.clickBug && o.onclick) {
				var onclick = o.onclick;
				o.onclick = null;
				_self.attach(o, e, onclick);
			}
		}
	}

	function attachEventById(id, e, f, args, early) {
		var oItem = document.getElementById(id);
		if (oItem) {
			_self.attach(oItem, e, f, args, early);
		} else if (early) {
			earlyBind(id, e, f, args);
		}
	}

	function getEvent(e) {
		if (e) {
			return e;
		} else if (window.event) {
			return window.event;
		} else {
			return {};
		}
	}

	function earlyBind(o, e, f, args) {
		if (!TESCO.system.event._earlyBindEvents) {
			TESCO.system.event._earlyBindEvents = [];
		}
		TESCO.system.event._earlyBindEvents[TESCO.system.event._earlyBindEvents.length] = { o: o, e: e, f: f, co: args };
		setTimeout(_attachEarlyBindEvents, CONST_BINDING_TIMEOUT);
	}

	function _attachEarlyBindEvents() {
		var earlyBindEvents = TESCO.system.event._earlyBindEvents, oItem, count = 0;
		for (var i = 0; i < earlyBindEvents.length; i++) {
			if (earlyBindEvents[i]) {
				oItem = $(earlyBindEvents[i].o);
				if (oItem) {
					if (oItem.tagName != "IMG" && earlyBindEvents[i].e == "load") {
						oItem.callback = earlyBindEvents[i].f;
						oItem.callback({ type: "load" }, earlyBindEvents[i].args);
					} else {
						_self.attach(oItem, earlyBindEvents[i].e, earlyBindEvents[i].f, earlyBindEvents[i].args);
					}

					earlyBindEvents[i].o = null;
					earlyBindEvents[i].f = null;
					earlyBindEvents[i].w = null;

					delete earlyBindEvents[i].o;
					delete earlyBindEvents[i].f;
					delete earlyBindEvents[i].w;
					delete earlyBindEvents[i];
				} else {
					count++;
				}
			}
		}
		if (!bBindComplete) {
			if (bLoaded) {
				bBindComplete = true;
				_attachEarlyBindEvents();
			} else if (count > 0) {
				setTimeout(_attachEarlyBindEvents, CONST_BINDING_TIMEOUT);
			} else {
				delete earlyBindEvents;
			}
		}
	}

	this._onload = function(e) {
		bLoaded = true;
	}

	this._onunload = function(e) {
		bLoaded = false;
		// TODO: review...
		for (var i = 0; i < wrappedEvents.length; i++) {
			if (wrappedEvents[i] && wrappedEvents[i].o) {
				if (wrappedEvents[i].o.removeEventListener) {
					wrappedEvents[i].o.removeEventListener(wrappedEvents[i].e, wrappedEvents[i].f, false);
				} else if (wrappedEvents[i].o.detachEvent) {
					wrappedEvents[i].o.detachEvent(wrappedEvents[i].e, wrappedEvents[i].f);
				}
			}
			wrappedEvents[i].o = null;
			wrappedEvents[i].f = null;
			wrappedEvents[i].w = null;

			delete wrappedEvents[i].o;
			delete wrappedEvents[i].f;
			delete wrappedEvents[i].w;
			delete wrappedEvents[i];
		}

		var earlyBindEvents = TESCO.system.event._earlyBindEvents;
		if (earlyBindEvents) {
			for (var i = 0; i < earlyBindEvents.length; i++) {
				if (earlyBindEvents[i]) {
					earlyBindEvents[i].o = null;
					earlyBindEvents[i].f = null;
					earlyBindEvents[i].w = null;

					delete earlyBindEvents[i].o;
					delete earlyBindEvents[i].f;
					delete earlyBindEvents[i].w;
					delete earlyBindEvents[i];
				}
			}
		}
	}

	_self.attach(window, "load", this._onload);
	_self.attach(window, "unload", this._onunload);
	//  capture runtime errors
	//  exception event raised in TESCO.system.Exception.handler
	_self.attach(window, "error",
	    function(error) {
	    	TESCO.system.Exception.handler.raise(error);
	    }
	);
}
