/*
 * jQuery kAnim animation Plugin 
 *
 * Copyright (c) 2009 Keith Philpott
 * Licensed under the MIT license.
 *
 */
(function($){
	$.fn.kAnim = function(animParams,duration,order,callback,overlap){

		//if already animated, commit suicide, but still provide jquery object
		if( $(this).is(":animated") ){
			return $(this);
		}

		var 
			opts = {
				"duration": duration ? duration : 1000 ,
				"order":order ? order : "normal", //normal, reverse, or random
				"callback": callback ? callback : function(){},
				"overlap": overlap < 1 && overlap >=0 ? 1-overlap : .5 //(1 - .5)
			},

			obj = this,
			n = obj.size(),
			overlapFreq = ( opts.duration / n ) * opts.overlap ,
			//trying to get the actual total duration as close as possible to opts.duration	
			overlapDuration = opts.duration - ( (n - 1) * overlapFreq ),
			
			//determine order
			arOrder = (function(){
				var arRet = [];
				if(opts.order==="random"){
					var tn; // testnumber
					//can't use indexOf because of IE
					function checkIndex(ar,val){
						for(var i=0; i<ar.length; i++){
				   		if(ar[i]==val){
				    			return i;
				   		}
				  		}
						return -1;
					}
					while (arRet.length < n){
						tn = Math.floor( Math.random() * n);
						//can't use indexOf because of IE
						if(checkIndex(arRet,tn) === -1){
							arRet.push(tn);
						}
					}
					
					return arRet;
				}
				else{
					for(var ind = 0;ind < n; ind++){
						arRet.push(ind);
					}
					if( opts.order === "reverse"){
						arRet.reverse();
					}
				}
				return arRet;
			})(),
			
			//can be set in presets below
			presetCallback = null, 
			//if string is entered instead of obj in anim parameters, it looks here
			presets = {
				
				"appear" : function(){
					//any prep css
					obj.css( {"opacity":"0","display":"block"} );				
					animParams = {"opacity":1};					
					return true;
				},
				
				"disappear": function(){
					var 
						alphas  = $.map(obj,function(item){return $(item).css("opacity")});
					
					//from initial arguments
					presetCallback = function(){
						obj.each(function(i){
							//restore opacities
							$(this).hide().css( { "opacity":alphas[i] } );
						});
						return true;
					};
					
					//from initial arguments
					animParams = {"opacity":0};
					return true;
				},

				
				"horizontalAppear": function(){
					presets["hSlideIn"](true);
					return true;
				},

				"horizontalSlideIn": function(){
					presets["hSlideIn"](false);
					return true;
				},
				
				"hSlideIn": function(fade){
					var 
						highW = 0,
						orig = {
							oFlow: [],
							maxW: []
						},
						cssParams = {};

					obj.each(function(i){ 
						highW = Math.max( $(this).width(), highW );
						orig.maxW.push( $(this).css("maxWidth") );
						orig.oFlow.push( $(this).css("overflow") );
					});

					presetCallback = function(){
						obj.each(function(i){
							//opera retarded
							var mx = orig.maxW[i];
							if(mx == "-1px"){
								mx = "none";
							}
							$(this).css({"maxWidth":mx,"overflow":orig.oFlow[i] });
						});
						return true;
					}

					cssParams = {
						"display":"block",
						"maxWidth":0,
						"overflow":"hidden"			
					}
					
					//from initial arguments
					animParams = {
						"maxWidth":highW
					}
					//from this function's arguments
					if(fade){
						cssParams["opacity"] = 0;
						animParams["opacity"] = 1;
					}
					obj.css( cssParams );
					return true;
				},


				"verticalAppear": function(){
					presets["vSlideIn"](true);
					return true;
				},

				"verticalSlideIn": function(){
					presets["vSlideIn"](false);
					return true;
				},
				
				"vSlideIn": function(fade){
					var 
						highH = 0,
						orig = {
							oFlow: [],
							maxH: [],
							display:[]
						},
						cssParams={};

					obj.each(function(i){ 
						//get Stats
						highH = Math.max( $(this).height(), highH ); 
						orig.oFlow.push( $(this).css("overflow") );
						orig.maxH.push( $(this).css("maxHeight") );
					});

					presetCallback = function(){
						obj.each(function(i){
							//opera retarded
							var mx = orig.maxH[i];
							if(mx == "-1px"){
								mx = "none";
							}
							$(this).css({"maxHeight":mx,"overflow":orig.oFlow[i] });
						});
						return true;
					}

					cssParams = {
						"display":"block",
						"maxHeight":0,
						"overflow":"hidden"		
					}
					//from initial arguments
					animParams = {
						"maxHeight":highH
					}
					//from this function's arguments
					if(fade){
						cssParams["opacity"] = 0;
						animParams["opacity"] = 1;			
					}
					obj.css( cssParams )
				},
				
				
				"horizontalSlideOut" : function(){
					presets["hSlideOut"](false);
				},

				"horizontalDisappear" : function(){
					presets["hSlideOut"](true);
				},

				"hSlideOut" : function (fade){
					var 
						highW = 0,
						orig = {
							oFlow: [],
							maxW: [],
							alpha:[]
						},
						cssParams = {};

					obj.each(function(i){ 
						highW = Math.max( $(this).width(), highW );
						orig.maxW.push( $(this).css("maxWidth") );
						orig.oFlow.push( $(this).css("overflow") );
						orig.alpha.push( $(this).css("opacity") );
					});
					//from defined in the main function arguments
					presetCallback = function(){
						obj.each(function(i){
							//opera retarded
							var mx = orig.maxW[i];
							if(mx == "-1px"){
								mx = "none";
							}
							$(this).hide().css({"maxWidth":mx,"overflow":orig.oFlow[i],"opacity":orig.alpha[i] });
						});
						return true
					}

					cssParams = {
						"maxWidth":highW,
						"overflow":"hidden"			
					}
					//from initial arguments
					animParams = {
						"maxWidth":0
					}
					//from function arguments
					if(fade){
						animParams["opacity"] = 0;
					}
					obj.css( cssParams );
					return true;
				},


				"verticalSlideOut" : function(){
					presets["vSlideOut"](false);
				},

				"verticalDisappear" : function(){
					presets["vSlideOut"](true);
				},


				"vSlideOut" : function (fade){
					var 
						highH = 0,
						orig = {
							oFlow: [],
							maxH: [],
							alpha:[]
						},
						cssParams = {};
						
					obj.each(function(i){ 
						highH = Math.max( $(this).height(), highH );
						orig.maxH.push( $(this).css("maxHeight") );
						orig.oFlow.push( $(this).css("overflow") );
						orig.alpha.push( $(this).css("opacity") );
					});
					//from defined in the main function arguments
					presetCallback = function(){
						obj.each(function(i){
							//opera retarded
							var mx = orig.maxH[i];
							if(mx == "-1px"){
								mx = "none";
							}
							$(this).hide().css({"maxHeight":mx,"overflow":orig.oFlow[i],"opacity":orig.alpha[i] });
						});
						return true;
					}

					cssParams = {
						"maxHeight":highH,
						"overflow":"hidden"			
					}
					//from initial arguments
					animParams = {
						"maxHeight":0
					}
					//from function arguments
					if(fade){
						animParams["opacity"] = 0;
					}
					
					obj.css( cssParams );
					return true;
				}
				
			},

			i = 0, // index for iteration (before func)
			j = 0; // goes at different speed (callback)
		
			
		try{
			//if it's a string, it will call the appropriate function
			presets[animParams]();
			run();
		}catch(e){
			try{
				//if not a string, assume parameters
				run();
			}catch(e){
				return obj;
			}
		}
		
		function run(){
			obj.eq( arOrder[i] ).animate(animParams, overlapDuration,"swing",function(){
				/*
					BUG - have to use setTimeout to kill the .is(":animate") == true ...
					this makes sure animation ends. JQUERY BUG SOMEWHERE..?
				*/
				setTimeout(function(){ 
					j++;
					if( j === n ){
						//obj.stop();
						presetCallback ? presetCallback() : null;//doesn't need .call() because obj used in presetCallback functions
						opts.callback.call(obj);
					}
				},0);
			});
			i++;
			if(i < n){
				setTimeout(run, overlapFreq );
			}
		}

		return obj;//returns jquery obj
	}

})(jQuery);