/**
 * This Object helps us by caching event handlers. That way we can stop observing and reset more easily
 * Note: custom events should be prepended with an underscore
 * 
 * Usage Examples:
 * EM.set("my_element_id", {click: myobject.myfunction.bind(myobject)});
 * EM.set("my_element_id", {keyup: {action: myobject.myfunction.bind(myobject), wait: 2}});
 */
function EventManager() {
	this._observers = new Hash();
}
EventManager.prototype = {
	_observers: null,
	/**
	 * Start Observers
	 * if no dom_id is specified, all observers are started
	 * 
	 * @param (string) [optional] dom_element
	 */
	start: function(dom_element, event) {
		var observers;
		if(dom_element != undefined) {
			var dom_id = $(dom_element).id;
			observers = new Hash();
			var tmp = this.getObservers().get(dom_id);
			if(event != undefined) {
				var tmp2 = new Hash();
				tmp2.set(event, tmp.get(event));
				observers.set(dom_id, tmp2);				
			} else {
				observers.set(dom_id, tmp);
			}
		} else {
			observers = this.getObservers();
		}
		observers.each(function(q) {
			var element = $(q.key);
			if(element == undefined) {
				return; // can't do anything if the element doesn't exist
			}			
			var hash = q.value;
			$H(hash).each(function(p) {
				var event = p.key;
				var o = p.value;
				event = event.replace(/^_/, ":"); // events prefixed with _ are custom, we change it to a colon for Prototype
				var action = o.action;
				if(o.dispatch == false) {
					o.dispatch_action = o.action;
				} else {
					o.dispatch_action = this._dispatchEvent.bindAsEventListener(this, element, o);
				}
				if(o.wait != undefined) {
					o.setTimer = function() {
						this.clearTimer();
						this.timer = window.setTimeout(this.dispatch_action, this.wait*1000);
					}
					o.clearTimer = function(){
						if (this.timer != undefined) {
							window.clearTimeout(this.timer);
							delete(this.timer);
						}
					}
					o.set_timer_action = o.setTimer.bind(o);					
					o.observed_action = o.set_timer_action;
				} else {
					o.observed_action = o.dispatch_action;
				}
				element.observe(event, o.observed_action);
			}, this);
		}, this);
	},
	/**
	 * Stop Observers
	 * If no dom id is specified, all observers are stopped
	 * 
	 * @param (string) [optional] dom_element
	 */
	stopObserving: function(dom_element, event) {
		var observers;
		if(dom_element != undefined) {
			var dom_id = $(dom_element).id;
			observers = new Hash();
			var tmp = this.getObservers().get(dom_id);
			if(event != undefined) {
				var tmp2 = new Hash();
				tmp2.set(event, tmp.get(event));
				observers.set(dom_id, tmp2);				
			} else {
				observers.set(dom_id, tmp);
			}
		} else {
			observers = this.getObservers();
		}
		observers.each(function(q) {
			var element = $(q.key);
			if(element == undefined) {
				return; // can't do anything if the element doesn't exist
			}
			var hash = q.value;
			$H(hash).each(function(p) {
				var event = p.key;
				var o = p.value;
				event = event.replace(/^_/, ":"); // events prefixed with _ are custom, we change it to a colon for Prototype
				if (o.wait != undefined) {
					o.clearTimer();
				}
				element.stopObserving(event, o.observed_action);
			});
		});		
	},
	/**
	 * The obj should hold events to observe as attributes and actions as values.
	 * If a delay is required the value should be an object with action and wait(seconds) attributes set
	 * Examples:
	 * set("my_element_id", {click: myobject.myfunction.bind(myobject)});
	 * set("my_element_id", {keyup: {action: myobject.myfunction.bind(myobject), wait: 2, dispatch: true}});
	 *
	 * @param {String} dom_id
	 * @param {Object} obj
	 */
	
	set: function(dom_element, obj) {
		var dom_id;
		if(typeof(dom_element) == "object") {
			dom_id = $(dom_element).identify();
		} else {
			dom_id = dom_element;
		}
		obj = $H(obj);
		obj.each(function(p) {
			if (typeof(p.value) == "function") {
				p.value = {action: p.value};
			}
			obj.set(p.key, p.value);
		});
		if(this.getObservers().include(dom_id)) {
			Object.extend(this.getObservers().get(dom_id), obj);		
		} else {				
			this.getObservers().set(dom_id, obj);
		}
	},
	/**
	 * Remove observers
	 * @param {string} [optional] dom_id
	 */
	unset: function(dom_element, event) {
		var observers = this.getObservers();
		if (dom_element != undefined) {
			var dom_id = $(dom_element).id;
			if(event == undefined && observers.keys().include(dom_id)) {
				this.stopObserving(dom_id);				
				observers.unset(dom_id); // all events for element
			} else if(event != undefined && observers.get(dom_id).keys().include(event)) {
				this.stopObserving(dom_id, event);				
				observers.get(dom_id).unset(event); // specific event
			} else {
			}			
		} else {
			this.stopObserving();
			this._observers = new Hash(); // all events
		}
	},
	/**
	 * Resets the observers
	 */
	reset: function() {
		this.stopObserving();
		this.start();
	},
	/**
	 * Returns the Hash of Observers
	 */
	getObservers: function() {
		return this._observers;
	},
	_dispatchEvent: function(event, el, hashed_object) {
		event.stop();
		var callback = hashed_object.action;		
		switch(el.tagName.toLowerCase()) {
			case 'a':				
				callback(el.href, el);
				break;
			case 'select':
				var frm 	= el.up("form");
				var submit 	= false;
				this._dispatchForm(frm, submit, el, callback);
				break;			
			case 'input':			
				var frm 	= el.up("form");
				var submit 	= el.type == "submit" ? el.name : false;
				this._dispatchForm(frm, submit, el, callback);
				break;
			case 'form':
				var frm 	= el;
				var submit 	= false;
				this._dispatchForm(frm, submit, el, callback);
				break;
			default:
				callback(el, event);
				break;
		}
	},
	_dispatchForm: function(frm, submit, element, callback) {
		var action 		= frm.action.length > 0 ? frm.action : window.location.href.split(/\?/).first();
		var serialized 	= frm.serialize({submit: submit});
		callback(action+"?"+serialized, element, action, serialized.toQueryParams());		
	}
};

/*
 * Fire method prepends colon automatically since fire can only be used for non-native events anyway
 * It's smart and checks for colons and underscores too
 */
//KLUDGE must be a better way of extending this
var super_method = Element.Methods.fire;
Object.extend(Element.Methods, {
    fire: function(element, eventName, memo) {
		// convert underscore to colon
		eventName.replace(/^_/, ":"); 
		// prepend colon if need be
		if (!eventName.match(/^:/)) {
			eventName = ":" + eventName;
		}
		super_method(element, eventName, memo);
	}
});
Element.addMethods();

