/**
 * @class eventStack
 * 
 * Another approach for event-handling in javascript.
 * First: I really really like the new way Prototype.js solves event-handling, 
 * but I needed more of a grid/stack or map to handle larger amounts of events and elements.
 * Also; according to the prototype-manual events can only be bound with .observe if the target element 
 * has been added to the DOM.. never ran into it coz I prepared for it before, 
 * but doing without this preparation allows for cleaner, better manageable, readable code.
 * Anyway; this class is meant to be extended from or used as a mixin (which term you prefer), 
 * you should redefine the events-hash in your own class to match the events you'd like to monitor 
 * and call this.initEvents() from your constructor to start monitoring.
 * 
 * Assign events by using: 
 * this.observe(elem, eventType, callBackFunction.bindAsEventListener(this)) 
 * or 
 * this.observeBySelector(cssSelector, eventType, CallBackFunction.bindAsEventListener(this))
 * 
 * @constructor => NONE, implemented in childClasses
 * @author Ivo Toby, www.i-v-o.nl 2008
 * @version 1.0
 * 
 */


	var eventStack = Class.create({
		// definition of possible and default events, should be refined in the class you mix this class in, this is a LOT of events
		events : $H({
			click:$H({}),
			abort:$H({}),
			blur:$H({}),
			change:$H({}),
			dblclick:$H({}),
			dragdrop:$H({}),
			error:$H({}),
			focus:$H({}),
			keydown:$H({}),
			keypress:$H({}),
			keyup:$H({}),
			mousedown:$H({}),
			mousemove:$H({}),
			mouseout:$H({}),
			mouseover:$H({}),
			mouseup:$H({}),
			move:$H({}),
			reset:$H({}),
			resize:$H({}),
			select:$H({}),
			submit:$H({})
		}),

		/**
		 * Start observing the document.body or window-object for the events defined above.
		 */
		initEvents : function(reset){
			if (!reset) reset = false;
			this.events.keys().each(
				(function(event){
					if ( (!this.events.get(event).observing) || (reset) ) {
						//if (this.is_ie()){
							//$(document).observe(event,  this.handleEvent.bindAsEventListener(this));
						//	window[event] = this.handleEvent.bindAsEventListener(this);
						//}else{
							//Event.observe(window, event, this.handleEvent.bindAsEventListener(this));
						//}
						$(document).observe(event,  this.handleEvent.bindAsEventListener(this));
						this.events.get(event).observing = true;
					}
				}).bind(this)
			)
		},
		
		
		/**
		 * Handle any event and dispatch it (using _handleEvent) to the right object
		 */
		handleEvent : function(event){
			var target = event.element();
			var type = event.type;
			this._handleEvent(target, type, event);	
		},
		
		_handleEvent : function(elem, type, event){
			if (!event) event = '';
			var id = Try.these(
				function(){return $(elem).identify()},
				function(){return elem}
			)
			var events = this.events.get(type);
			if (!(events instanceof Hash)) return; // no events defined on this type
			if (events.keys().length == 0) return;
			if (events.get(id) instanceof Object){
				$A(events.get(id)).each(
					(function(func){
						if (func.timeout){
							var tmp = function(){func(event)}
							setTimeout(tmp, func.timeout);
						}else{
							func(event);
						}
					}).bind(this)
				)
			}
		},
		
		
		/** 
		 * Fire a custom event; ie; not one that's defined in this.events by default
		 */
		fireEvent : function(elem, type, event){
			if (!event){
				var obj = new Object();
				obj.element = function(){return elem};
			}else{
				var obj = event;
			}
			this._handleEvent(elem, type, obj);
		},

		/**
		 * Adds a reference to a function callBack in the event stack
		 * Note::: You can create events to elements that do not exist yet by simply passing their elem-id as a string, 
		 * You can also add elements without ID, just simple node without attributes; the function will create a unique id for it by itself. 
		 * For applying events to css-selectors please use observeBySelector
		 * 
		 * @param elem string, object  Can either be a string referencing an element, a string referencing an element that doesn't exist or an DOMelement
		 * @param type string 	The event-type that should be monitored
		 * @param callBack function (binded) function to execute upon event-fireing.
		 * @param timeout integer (optional) timeout in miliseconds (executed after event firing) 
		 * @return function, extended with property 'guid', this property can be used to reference to the event (ie; remove the event)
		 */
		observe : function(elem, type, callBack, timeout){
			callBack.guid = parseInt(Math.random()*1000000);
			var id = Try.these(
				function(){return $(elem).identify()},
				function(){return elem}
			)
			if (!this.events.get(type)) this.events.set(type, $H({})); //  create hash for event if there isn't one
			if (! (this.events.get(type).get(id) instanceof Array) ) this.events.get(type).set(id, []); // create array for elem if there isn't one
			this.events.get(type).get(id).push(callBack);
			if (timeout) callBack.timeout = timeout;
			return callBack;
		},
		
		/**
		 * Adds a reference to the function Callback in the event stack on a entire nodelist, defined by a CSS-selector
		 * @param cssSelector string, object  'prototype.js'-style css-selector ($$());
		 * @param type string 	The event-type that should be monitored
		 * @param callBack function (binded) function to execute upon event-fireing.
		 * @return Array This function returns the callBacks as an Array
		 */
		observeBySelector : function(cssSelector, type, callBack, timeout){
			var callBacks = [];
			$$(cssSelector).each(
				(function(elem){
					callBacks.push(this.observe(elem, type, callBack, timeout));	
				}).bind(this)
			);
			return callBacks;
		},
		
		/**
		 * Remove the event-listener from the stack by using the guid-property
		 * @param elem  string, object  Can either be a string referencing an element, a string referencing an element that doesn't exist or an DOMelement
		 * @param type string 	The event-type that should be removed
		 * @param callBack function (OPTIONAL); the callback to be removed from the stack; if NO callback is provided the complete stack of elem's events (within type) will be removed.
		 * @return boolean indication whether operation was sucessfull or not.
		 */
		
		deobserve : function(elem, type, callBack){
			var id = Try.these(
				function(){return $(elem).identify()},
				function(){return elem}
			)
			if (!callBack) callBack = '';
			if (!this.events.get(type)) return;
			if (! (this.events.get(type).get(id) instanceof Array) ) return;
			if (callBack) {
				// only remove callback.guid from the event-stack
				if (!callBack.guid)  return false; // CANNOT remove callback; no GUID found.
				var out = new Array();
				$A(this.events.get(type).get(id)).each(
					function(call){
						if (call.guid != callBack.guid){
							 out.push(call)
						} 
					}
				)
				this.events.get(type).set(id, out);
				return true;
			}else{
				// clear the complete eventstack of the type/elem-combi
				this.events.get(type).set(id, new Array());
				return true;
			}
		},
		
		/**
		 * Remove listeners from the stack from a cssSelector
		 */		
		deobserveBySelector : function(cssSelector, type, callBack){
			$$(cssSelector).each(
				(function(elem){
					this.deobserve(elem, type, callBack);	
				}).bind(this)
			);
		}
	
		
	})