/**
 * CG custom event system
 * 
 * The main differences between this event system and the native JS event
 * system or the jQuery and Prototype custom event implementation are:
 *
 *  - completely independent from the DOM
 * 	- handlers are called "asynchronously" (using setTimeout) in order to
 * 		not lock up the browser
 * 	- custom events are objects
 *  - subscribers can set a priority that determines where they are called
 * 		in the handler queue
 * 
 * This file defines the CG.Event class, namely it's constructor function
 * and prototype object.
 * 
 * @requires jQuery
 * @requires CG
 */

require(['jQuery', 'CG'], 'CG.Event');

(function($) {

	/**
	 * CG.Event class constructor
	 * 
	 * @param object scope Optional scope that each handler will be called with.
	 * 						Defaults to the window object
	 */
	CG.Event = function(scope) {
		
		/**
		 * Contains the default scope for any handler call
		 * 
		 * @access private
		 * @var object
		 */
		this._scope = scope || window;
		
		/**
		 * Contains references to the handlers for this event.  Handlers
		 * are called in order, so they should be spliced into a certain 
		 * position if necessary
		 * 
		 * @access private
		 * @var array
		 */
		this._handlers = [];
		
		/**
		 * States whether the event is currently running through it's list 
		 * of handlers
		 * 
		 * @access private
		 * @var boolean
		 */
		this._firing = false;
	};
	
	/**
	 * CG.Event class prototype object
	 */
	CG.Event.prototype = {

		/**
		 * Add a handler
		 * 
		 * The handler is added to the event handler list in the position
		 * determined by the priority param. Handlers can be removed by
		 * calling the unsubscribe method and are called when the fire
		 * method is called. Handlers that have already been added will be
		 * ignored.
		 * 
		 * @access public
		 * @param function handler The function to be added to the list
		 * @param int priority Determines the placement in the handler list
		 * @param object scope What "this" refers to when the fn is called
		 * @return CG.Event
		 */
		subscribe: function(handler, priority, scope) {
			if(this.checkHandler(handler) === -1) {
				var scope = scope || this._scope,
					priority = priority || 0,
					ind = this._handlers.length;

				$.each(this._handlers, function(index, h) {
					if(h.p > priority) {
						ind = index;
						return false;
					}
				});

				this._handlers.splice(ind, 0, {f: handler, p: priority, s: scope});
			}

			return this;
		},
		

		/**
		 * Remove a handler
		 * 
		 * If the handler does not exist in the list, nothing happens. Handlers
		 * are added by calling the subscribe method.
		 * 
		 * @access public
		 * @param function handler The fn to remove from the handler list
		 * @return CG.Event
		 */
		unsubscribe: function(handler) {
			var index = this.checkHandler(handler);
			if(index !== -1) {
				this._handlers.splice(index, 1);
			}

			return this;
		},
		
		/**
		 * Trigger the event
		 * 
		 * Handlers are called in the order of priority.  Any handler can
		 * prevent subsequent handlers by return false.  Handlers are called
		 * with a scope determined by the subscribe method or if no scope was provided,
		 * the window object.  Handlers are passed the event object and the context param
		 * as arguments. 
		 * 
		 * Handlers are called sequentially and "asynchronously" (using setTimeout
		 * with a negligible value) in order not to lock up the browser.  Any handler
		 * can return false to prevent other handlers further down in the chain from
		 * responding to the event.
		 * 
		 * @access public
		 * @param object context Data that's passed along to each handler
		 * @return CG.Event
		 */
		fire: function(context) {
			var that = this;

			if(!this._firing) {
				this._firing = true;

				var queue = this._handlers.slice(), results = [], context = context || {};

				(function firefn() {
					if(queue.length) {
						var h = queue.shift(), result = h.f.call(h.s, that, context) || true;
						results.push(result);

						if(result === false) {
							queue = [];
						}

						setTimeout(firefn, 10);
					} else {
						that._firing = false;
					}
				})();
			} else {
				setTimeout(function() {
					that.fire(context);
				}, 10);
			}

			return this;
		},

		/**
		 * Check to see if the handler has been added.
		 * 
		 * Returns the position of the handler in the list 
		 * (if found) or -1 (if not found).
		 * 
		 * @access private
		 * @param function handler The fn to check
		 * @return int
		 */
		checkHandler: function(handler) {
			var ind;

			$.each(this._handlers, function(index, h) {
				if(h.f && h.f === handler) {
					ind = index;
				}
			});

			return ind === undefined ? -1 : ind;
		}
	};
	
	
	if(CG.events) {
		/**
		 * Define the startup event for the CG object
		 * 
		 * SPECIAL CASE: Hate not being able to define this in
		 * cg.js, but CG.Event hasn't been defined yet...
		 */
		CG.events.startup = new CG.Event();	
	}
})(jQuery);