/**
 * fusionLib JavaScript library to implement event management methods.
 *
 * @see http://developer.apple.com/library/safari/ for iPhone events
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Copyright (c) 2007 - 2013 fusionLib. All rights reserved.
 */

(function() {
	var eventList = [], // Registered events
		readyFunctions = [], // Ready functions
		isReady = false; // Flags if document ready

	/**
	 * Private function to normalise events.
	 *
	 * @param element object The object the event assigned to.
	 * @param event event The native event object.
	 * @return event The event object.
	 */
	function fixEvent(element, event) {
		if(!event) var event = window.event;

		if(typeof event.target === 'undefined')
			event.target = event.srcElement || element;

		if(event.target && event.target.nodeType === 3)
			event.target = event.target.parentNode;

		var doc = document.documentElement, body = document.body;
		event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
		event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);

		if(!event.preventDefault)
			event.preventDefault = function() { event.returnValue = false; };

		if(!event.stopPropagation)
			event.stopPropagation = function() { event.cancelBubble = true; };

		event.originalEvent = event; // jQuery compatibility
		return event;
	}

	/**
	 * Private handler to process ready events.
	 */
	function doReady() {
		if(!isReady) {
			// Make sure body exists
			if(!document.body)
				setTimeout(doReady, 15);

			isReady = true;

			// Run all ready functions
			for(var i in readyFunctions)
				readyFunctions[i]();
		}
	}

	/**
	 * Private event handler.
	 */
	function DOMContentLoaded() {
		if(document.addEventListener) {
			document.removeEventListener('DOMContentLoaded', DOMContentLoaded, false);
			doReady();
		}
		else if(document.attachEvent) {
			if(document.readyState === 'complete') {
				document.detachEvent('onreadystatechange', DOMContentLoaded);
				doReady();
			}
		}
	}

	fusionLib.extend({
		/**
		 * Bind an event handler to an event on an object.
		 *
		 * @param event string The event to bind to e.g. click.
		 * @param data map Optional map of data passed to event handler.
		 * @param handler function The function to handle the event, if it returns false propagation is stopped.
		 * @return fusionLib This object.
		 */
		bind: function(event, data, handler) {
			if(typeof data === 'function') {
				handler = data;
				data = undefined;
			}

			return this.each(function() {
				// If already added ignore
				for(var i in eventList) {
					if(eventList[i].element == this && eventList[i].event == event && eventList[i].handler == handler)
						return;
				}

				// Create responder and remember
				var responder = function(event) { fixEvent(this, event); event.data = data; return handler.call(this, event); };
				eventList.push({element: this, event: event, handler: handler, responder: responder});

				// Add the event
				if(this.addEventListener)
					this.addEventListener(event, responder, false);
				else if(this.attachEvent)
					this.attachEvent('on' + event, responder);
			});
		},

		/**
		 * Unbind an event handler from an event on an object.
		 *
		 * @param event string The event to unbind to e.g. click.
		 * @param handler function The function previously used to handle the event.
		 * @return fusionLib This object.
		 */
		unbind: function(event, handler) {
			var unbindall = arguments.length == 1;
			return this.each(function() {
				// Get the cached responder
				var i, n = 0;
				for(i=0;i<eventList.length;i++) {
					if(eventList[i].element == this && eventList[i].event == event) {
						if(unbindall || eventList[i].handler == handler) {
							// Remove the event
							if(this.removeEventListener)
								this.removeEventListener(event, eventList[i].responder, false);
							else if(this.detachEvent)
								this.detachEvent('on' + event, eventList[i].responder);
						}
					}
					else
						eventList[n++] = eventList[i];
				}
				eventList.length = n;
			});
		},

		/**
		 * Trigger and event.
		 *
		 * @param event string The name of the event.
		 * @param data array Optional data for the event.
		 * @return fusionLib This object.
		 */
		trigger: function(event, data) {
			return this.each(function() {
				var element = this, evt;
				if(element == document && !element.dispatchEvent)
					element = document.documentElement;

				// For IE
				if(document.createEventObject) {
					evt = document.createEventObject();
					fixEvent(element, evt);
					evt.data = data;

					// Run any handlers
					for(var i in eventList) {
						if(eventList[i].element == element && eventList[i].event == event)
							eventList[i].handler(evt);
					}

					element.fireEvent('on' + event, evt)
				}
				else {
					evt = document.createEvent('HTMLEvents');
					fixEvent(element, evt);
					evt.initEvent(event, true, true);
					evt.eventName = event;
					evt.data = data;
					element.dispatchEvent(evt);
				}
			});
		},

		/**
		 * Submit a form.
		 *
		 * @return fusionLib This object.
		 */
		submit: function() {
			return this.each(function() {
				$fl(this).trigger('submit');
				this.submit();
			});
		},

		/**
		 * Register a function to be called once the page is loaded.
		 *
		 * @param fn function The function to call.
		 * @return fusionLib This object.
		 */
		ready: function(fn) {
			if(isReady)
				fn();
			else
				readyFunctions.push(fn);
			return this;
		}
	});

	// Register unload event to remove all events
	if(!document.addEventListener) {
		fusionLib(window).bind('unload', function() {
			for(var i=0;i<eventList.length;i++)
				eventList[i].element.detachEvent('on' + eventList[i].event, eventList[i].responder);
			eventList.length = 0;
		});
	}

	// Set up ready event processing
	if(document.readyState === 'complete')
		doReady();
	else if(document.addEventListener) {
		document.addEventListener('DOMContentLoaded', DOMContentLoaded, false );
		window.addEventListener('load', doReady, false);
	}
	else if(document.attachEvent) {
		document.attachEvent('onreadystatechange', DOMContentLoaded);
		window.attachEvent('onload', doReady);
	}
})();