/**
 * An effect queueing plugin for jQuery.
 * Author: Erik Beeson <MyFirstName.MyLastName@ThatGoogleMailService.com>
 * URL: http://erikandcolleen.com/erik/jquery/fxQueue/
 *
 * More of an exercise for the author than anything else.
 *
 * License: Creative Commons Attribution-Share Alike 3.0
 *          <http://creativecommons.org/licenses/by-sa/3.0/>
 *
 * functions:
 *  $.fxQueue.start(...)       Create and start a new series of effects.
 *                               Accepts options objects. Returns a unique
 *                               id (number) for this queue.
 *  $.fxQueue.stop(id)         Stop (pause) the specified queue.
 *  $.fxQueue.add(id, ...)     Add additional effect(s) to the specified queue.
 *                               Must call restart(id) if this queue was
 *                               already stopped.
 *  $.fxQueue.restart(id)      Resume the specified queue, either because it
 *                               was manually stopped, or because more items
 *                               were added via add(...).
 *  $.fxQueue.setDefaults(map) Set default options.
 *
 * start and add accept either an array of options maps, or a variable
 *   number of parameters of options maps.
 * For example:
 *   var queueId = $.fxQueue.start({...}, {...}, {...}, ...);
 * Is the same as:
 *   var queueId = $.fxQueue.start([{...}, {...}, {...}, ...]);
 *
 * The options map is as follows:
 *   selector (or s):  Any valid jQuery selector. Defaults to the selector
 *                       used for the last effect in this queue.
 *                       Required for at least the first effect.
 *   effect (or e):    String. The name of the function to call. Defaults
 *                       to 'animate'. Any function that takes a callback
 *                       as its last parameter is valid. e.g. 'show', 'hide',
 *                       'Puff', 'Shake' (from interface plugin).
 *   params (or p):    Array. Optional parameters to pass to effect function.
 *   callback (or c):  Function. An optional function that will get called
 *                       when this effect has completed. Takes no parameters.
 *                       current context is the element that was animated, or
 *                       only the first element if multiple elements were
 *                       animated and callbackOnce is true.
 *   callbackOnce:     Boolean. If true, the callback function will only get
 *                       called one time, even if the effect was applied to
 *                       multiple elements. If false, the callback function
 *                       will be executed once for each element selected, which
 *                       is the default behavior for jQuery's animate function.
 *                       Defaults to false.
 * 
 * Examples:
 *   Without fxQueue:
 *     $('.foo').animate({width: 100}, 'slow', function() {
 *       $('.bar').animate({height: 250}, 1500, function() {
 *         $('#status').append('Done!'); // This will get called
 *                                       // $('.foo').size()*$('.bar').size() times
 *       });
 *     });
 *   With fxQueue:
 *     $.fxQueue.start(
 *        {selector: '.foo', params: [{width: 100}, 'slow']},
 *        {
 *           selector: '.bar', params: [{height: 250}, 1500],
 *           callback: function() {
 *             $('#status').append('Done!'); // Only called once
 *           }
 *        }
 *     );
 *
 */
(function($) {
	var fixArray = function(args) {
		if(args[0].constructor == Array) {
			return $.makeArray(args[0]);
		} else {
			return $.makeArray(args);
		}
	};
	var defaults = {effect: 'animate', params: [], callbackOnce: false};
	var Q = function(args) {
		var q = fixArray(args);
		var _this = this;
		var done = [];
		var lastSelector;
		this.running = true;
		this.add = function() {
			var arr = fixArray(arguments);
			for(var i = 0; i < arr.length; i++) {
				q.push(arr[i]);
			}
		};
		this.next = function() {
			if(q.length > 0 && _this.running) {
				var o = q.shift();
				lastSelector = o.selector || o.s || defaults.selector || defaults.s || lastSelector;
				var effect = o.effect || o.e || defaults.effect || defaults.e;
				var params = (o.params || o.p || defaults.params || defaults.p).slice();
				var callback = o.callback || o.c || defaults.callback || defaults.c;
				var callbackOnce = o.callbackOnce || defaults.callbackOnce;
				if(params.constructor != Array) {
					params = [params];
				}
				var $jq = $(lastSelector);
				if($jq.size() > 0) {
					var idx = done.length;
					done[idx] = false;
					params.push(function() {
						if(!done[idx]) {
							done[idx] = true;
							if(callback) {
								if(callbackOnce) {
									callback.apply($jq.get(0));
								} else {
									$jq.each(function() {
										callback.apply(this);
									});
								}
							}
							_this.next();
						}
					});
					eval('$jq.' + effect + '.apply($jq, params)');
				} else {
					_this.next();
				}
			} else {
				_this.running = false;
			}
		};
		setTimeout(this.next, 10);
	};
	var queue = [];
	$.fxQueue = {
		start: function() {
			if(arguments.length > 0) {
				var id = queue.length;
				queue[id] = new Q(arguments);
				return id;
			} else {
				return -1;
			}
		},
		stop: function(id) {
			if(queue[id]) {
				queue[id].running = false;
			}
		},
		add: function() {
			if(arguments.length > 1) {
				var arr = $.makeArray(arguments);
				var id = arr.shift();
				if(queue[id]) {
					queue[id].add(arr);
				} else {
					queue[id] = new Q(arr);
				}
			}
		},
		restart: function(id) {
			if(queue[id] && !queue[id].running) {
				queue[id].running = true;
				setTimeout(queue[id].next, 10);
			}
		},
		setDefaults: function(o) {
			defaults = $.extend(defaults, o);
		}
	};
})(jQuery);
