/**
 * JQuery Plugin to create a simple coverflow.
 */
(function($) {
	
	var CF_ROOT_CSS_CLASS = '_jsCoverflowRoot';
	
	var CF_CONFIG_KEY = 'coverflow.config';
	
	var CF_ITEM_ORIG = 'coverflow.itemOrig';
	
	var CF_CURR_INDEX = 'coverflow.index';
	
	var CF_ORIG_COUNT = 'coverflow.origCount';
	
	/**
	 * Class to encapsulate the coverflow
	 */
	var Coverflow = function(elem,config) {
		this.elem = elem;
		this.config = config;
	};
	
	Coverflow.prototype.setup = function() {
		var self = this;
		self.trigger('onPrepare',self);
		// Initialize the Object
		$(self.elem).data(CF_CURR_INDEX,self.config.initialIndex);
		// Initialize the DOM
		$(self.elem).css('overflow','hidden');
		$(self.elem).css('position','relative');
		if (self.config.isInfinite) {
			$(self.elem).data(CF_ORIG_COUNT,self.getItems().length);
			if (self.config.leftItemCount == 0) {
				//TODO: Fix special case
				var initialItems = self.getItems().length;
				var multiplier = 2;
				if (initialItems == 1) { multiplier = 4; }
				if (initialItems == 2) { multiplier = 3; }
				self.config.initialIndex = self.config.initialIndex+(initialItems*multiplier);
				for (var i = 0; i < multiplier; i++) {
					this.getItems().clone().appendTo($('ul',self.elem));
				}
				if (initialItems < self.config.rightItemCount+1) {
					self.config.rightItemCount = initialItems-1;	
					$(self.elem).data(CF_CONFIG_KEY,self.config);		
				}
				
			} else {
				self.config.initialIndex = self.config.initialIndex+((self.getItems().length)*2)-1;
				for (var i = 0; i < 4; i++) {
					this.getItems().clone().appendTo($('ul',self.elem));
				}				
			}
			$(self.elem).data(CF_CURR_INDEX,self.config.initialIndex);
		}
		$(self.getItems()).each(function(i,e) {self.setupItem(e,i);});
		// Initialize the offsets
		var offsets = {};
		$(self.getItems()).each(function(i,e) {
			offsets[i] = i - self.config.initialIndex;
		});
		$(this.elem).data('offsets',offsets);
		self.render(offsets);
		self.trigger('onInit',self);
		self.trigger('onItemSelected',self.config.initialIndex,self);
		//this.positionAt(0,0.9);
		//this.positionAt(1,1);
		//this.positionAt(2,2.9);
		//this.positionAt(3,3);
	};
	
	Coverflow.prototype.setupItem = function(domElem,index) {
		var self = this;
		$(domElem).css({
			'position': 'absolute',
			'left': '0px',
			'top': '0px',
			'list-style': 'none'
		});
		$(domElem).data(CF_ITEM_ORIG,self.getDomItemStyle(domElem));
		$(domElem).hide();
		$(domElem).click(function(e) {
			self.itemClicked(index);
			return false;
		});
	};
	
	Coverflow.prototype.render = function(offsets) {
		var self = this;
		$.each(offsets,function(i,o) {
			if ((o > 0 && o > self.config.rightItemCount) ||
				(o < 0 && Math.abs(o) > self.config.leftItemCount)) {
				// If the item offset(o) is outside the display range
				$($(self.getItems()).get(i)).hide();
				return;
			}
			self.positionAt(i,o);
		});
	};
	
	/**
	 * Click handler for covers
	 */
	Coverflow.prototype.itemClicked = function(i) {
		var self = this;
		var currIndex = $(self).data(CF_CURR_INDEX);
		var offsets = $(self.elem).data('offsets');
		if (i < 0 || i >= self.getItems().length) {
			// If i is out of range, abortd
			return false;
		}
		if (offsets[i] > 0) {
			self.trigger('onItemDeselected',self.getCurrentIndex(),self);
			this.moveLeft(offsets[i],200,function() {
				// On complete update current index
				self.trigger('onItemSelected',self.getCurrentIndex(),self);
				self.setAnimating(false);
			});
		}
		if (offsets[i] < 0) {
			self.trigger('onItemDeselected',self.getCurrentIndex(),self);
			this.moveRight(Math.abs(offsets[i]),200,function() {
				// On complete update current index
				self.trigger('onItemSelected',self.getCurrentIndex(),self);
				self.setAnimating(false);
			});
		}	
		if (offsets[i] == 0) {
			//alert("no offset klick here"+this);
			for (prop in this) {
				erg+=" "+prop;
			}
			alert(erg);
		}
		return false;
	};
	
	Coverflow.prototype.getItem = function(index) {
		return $(this.getItems()).get(index);
	};
	
	Coverflow.prototype.positionAt = function(itemIndex,offset) {
		var overlayRatio = 0.4; // All items will be stacked to 40%
		var scaleFactor = 0.8; // All items will be 80% of the size after

		/**
		 * Declare general variables
		 */
		var item = $(this.getItems()).get(itemIndex);
		var absOffset = Math.abs(offset);
		var origStyle = $(item).data(CF_ITEM_ORIG);		
		var center = {
			left: this.getWidth()/2,
			top: this.getHeight()/2
		};

		/**
		 * Calculate the z-index. We will simply
		 * multiply the index so we can use 
		 * floating point offsets up to 1/100.
		 */
		var zIndex = 32000 - (absOffset*100);

		/**
		 * Calculate the size of the final item
		 */		
		var finalScale = Math.pow(scaleFactor,absOffset);
		var finalWidth = origStyle.width*finalScale;
		var finalHeight = origStyle.height*finalScale;		
		
		/**
		 * Calculate the offset from the center
		 */
		var fullSteps = Math.floor(absOffset);
		var partSteps = absOffset%1.0;
		var leftOffset = (-1*origStyle.width/2);
		// If the offset is not the center
		if (absOffset > 0) {
			// Calculate the position after the full
			// steps. The decimals will be added later.
			// Example: If the offset is 3.4 this loop
			// will calculate the offset of position 3
			// NOTE: There might be a really nice formula
			for (var i = 0; i < fullSteps; i++) {
				var scale = Math.pow(scaleFactor,i);
				var width = origStyle.width*scale;
				var height =origStyle.height*scale;	
				leftOffset += width;
				if (i > 0) {
					leftOffset = leftOffset - (width*overlayRatio);
				}					
			}
			
			// Calculation of the parts
			var fullScale = Math.pow(scaleFactor,absOffset);
			var fullWidth = origStyle.width*fullScale;
			var fullHeight =origStyle.height*fullScale;	
			if (partSteps > 0 && fullSteps >= 1) {
				// This is the calculation for all offsets >= 1
				var prevScale = Math.pow(scaleFactor,Math.floor(absOffset));
				var nextScale = Math.pow(scaleFactor,Math.ceil(absOffset));
				var currScale = fullScale + ((prevScale - nextScale) * partSteps);
				
				var prevWidth = origStyle.width*prevScale;
				var nextWidth = origStyle.width*nextScale;
				var currWidth = origStyle.width*currScale;
				
				var prevOffset = leftOffset; // overlay is already applied
				var nextOffset = leftOffset + (prevWidth - (prevWidth*overlayRatio));
				
				leftOffset = leftOffset + ((nextOffset-prevOffset) * partSteps);
			} else if (partSteps > 0 && fullSteps == 0) {
				// This is the calculation for all offsets between 0 and 1
				var prevScale = Math.pow(scaleFactor,Math.floor(absOffset));
				var nextScale = Math.pow(scaleFactor,Math.ceil(absOffset));
				var currScale = fullScale + ((prevScale - nextScale) * partSteps);
				
				var currWidth = origStyle.width*fullScale;
				var prevOffset = leftOffset;
				var nextOffset = leftOffset + (origStyle.width - (currWidth*overlayRatio));
				
				leftOffset = ((nextOffset-prevOffset) * partSteps);
			}
			if (offset > 0) {
				// If the cover is aligned to the right
				leftOffset = leftOffset - (finalWidth*overlayRatio);
			} else if (offset < 0) {
				// If the cover is aligned to the left
				leftOffset = (leftOffset * -1) - (origStyle.width/2);
			}
		} 
		var left = center.left + leftOffset;
		
		/**
		 * Calculate the offset from the top.
		 */
		var top = center.top - (finalHeight*0.5);

		var style = {
				'left': left,
				'top': top,
				'width': finalWidth,
				'height': finalHeight,
				'zindex': zIndex
		}
		if (this.config.onCalculate != undefined) {
			style = this.config.onCalculate(offset,style);
		}
		
		/**
		 * Apply the calculated style
		 */
		$(item).css('left',style.left);
		$(item).css('top',style.top);
		$(this.config.imgSelector,item).css('width',style.width);
		$(this.config.imgSelector,item).css('height',style.height);
		$(item).css('z-index',style.zindex);
		$(item).show();
	};
	
	/**
	 * Move the coverflow one step to the left.
	 */
	Coverflow.prototype.stepLeft = function(time,onComplete,isPartOfMove) {
		if (this.getCurrentIndex() >= this.getItems().length-1) {
			return false;
		}
		if (this.isAnimating() && isPartOfMove != true) {
			return false;
		}
		this.setAnimating(true);		
		var self = this;
		self.trigger('onItemDeselected',self.getCurrentIndex(),self);
		this.step('left', time, function() {
			self.trigger('onItemSelected',self.getCurrentIndex(),self);
			if (isPartOfMove != true) {
				self.setAnimating(false);
			}
			if (onComplete != undefined) { onComplete(); }
		});
		return true;
	};
	
	/**
	 * Move the coverflow one step to the left.
	 */
	Coverflow.prototype.stepRight = function(time,onComplete,isPartOfMove) {
		if (this.getCurrentIndex() <= 0) {
			return false;
		}
		if (this.isAnimating() && isPartOfMove != true) {
			return false;
		}
		this.setAnimating(true);
		var self = this;
		self.trigger('onItemDeselected',self.getCurrentIndex(),self);		
		this.step('right', time,  function() {
			self.trigger('onItemSelected',self.getCurrentIndex(),self);
			if (isPartOfMove != true) {
				self.setAnimating(false);
			}
			if (onComplete != undefined) { onComplete(); }
		});
		return true;
	};	
	
	/**
	 * Moves the coverflow n steps to the left
	 */	
	Coverflow.prototype.moveLeft = function(steps,time,onComplete) {
		this.move('stepLeft',steps, time, onComplete);
		return true;
	};
	
	/**
	 * Moves the coverflow n steps to the right
	 */
	Coverflow.prototype.moveRight = function(steps,time,onComplete) {
		this.move('stepRight',steps, time, onComplete);
		return true;
	};	
	
	/**
	 * Generic step function. The direction can be either
	 * 'left' or 'right'.
	 */
	Coverflow.prototype.step = function(dir,time,onComplete) {
		var self = this;
		var numDir = (dir == 'left') ? -1 : 1; // Numeric value for direction
		var currStep = 0;
		var animationStepSize = 0.1;
		var animationStepTime = time * animationStepSize;
		var offsets = $(self.elem).data('offsets');
		var origOffsets = $.extend({}, offsets);
		var animFunc = function() {
			currStep += animationStepSize;
			if (currStep < 1-animationStepSize) {
				$.each(offsets,function(i,e) {
					offsets[i] = e + (animationStepSize*numDir);
				});
				self.render(offsets);
				setTimeout(arguments.callee,animationStepTime);
				return;
			}
			//console.log($.extend({},offsets));
			// last step:
			$.each(offsets,function(i,e) {
				offsets[i] = origOffsets[i]+(1*numDir);
				if (origOffsets[i] == 0) {
					//offsets[i] =$(self.getItems()).size()-1;
				}
			});
			
			$(self.elem).data(CF_CURR_INDEX,self.getCurrentIndex()+(numDir*-1));
			if (self.config.isInfinite) {
				var indexAfterStep = self.getCurrentIndex();
				var origItemCount = $(self.elem).data(CF_ORIG_COUNT);
				if (indexAfterStep%origItemCount == 0) {
					$(self.elem).data(CF_CURR_INDEX,self.config.initialIndex);
					offsets = {};
					$(self.getItems()).each(function(i,e) {
						offsets[i] = i - self.config.initialIndex;
					});
					//console.log($.extend(offsets,{}));
					//console.log(self.getCurrentIndex());
				}
			}
			
			$(self.elem).data('offsets',offsets);
			self.render(offsets);
			if (onComplete != undefined) { onComplete(); }
		};
		self.trigger('onAnimationStart',self);
		setTimeout(animFunc,animationStepTime);
	};	
	
	/**
	 * Generic move function
	 */
	Coverflow.prototype.move = function(stepFunction,steps,time,onComplete) {
		var self = this;
		var curr = 0;
		// If we have to move 3 steps in time t we simply
		// call the step function 3 times with t/3 duration each.
		self[stepFunction](time/steps, function() {
			curr++;
			if (curr < steps) {
				// Recursion for next step
				self[stepFunction](time/steps,arguments.callee,true);
				return true;
			}
			if (onComplete != undefined) { onComplete(); }
			return true;
		},true);
		return true;
	};	
	
	Coverflow.prototype.trigger = function(callbackName,param1,param2) {
		if (typeof this.config[callbackName] == 'function') {
			this.config[callbackName](param1,param2);
		}
		return true;
	};
	
	Coverflow.prototype.getDomItemStyle = function(domElem) {
		var style = {
			width: $(domElem).width(),
			height: $(domElem).height(),
			left: $(domElem).css('left'),
			right: $(domElem).css('right')
		};	
		return style;
	};

	Coverflow.prototype.getWidth = function() {
		return $(this.elem).width();
	};
	
	Coverflow.prototype.getHeight = function() {
		return $(this.elem).height();
	};
		
	Coverflow.prototype.setAnimating = function(param) {
		 $(this.elem).data('isAnimating',param);
	};
	
	Coverflow.prototype.isAnimating = function() {
		return $(this.elem).data('isAnimating') == true;
	};
	
	Coverflow.prototype.getCurrentIndex = function() {
		return $(this.elem).data(CF_CURR_INDEX);
	};
	
	Coverflow.prototype.getOffsets = function() {
		return $(this.elem).data('offsets');
	};
	
	Coverflow.prototype.setOffsets = function(offsets) {
		$(this.elem).data('offsets',offsets);
		return true;
	};
	
	Coverflow.prototype.getItems = function() {
		return $('.items li',this.elem);
	};
	
	/**
	 * Method get the enclosing coverflow object from
	 * a DOM element.
	 */
	var getEnclosingCoverflow = function(domElem) {
		var rootElem = $(domElem).closest(CF_ROOT_CSS_CLASS);
		return new Coverflow(rootElem,$(rootElem).data(CF_CONFIG_KEY));
	};
	
	/**
	 * JQuery Plugin to create coverflow.
	 */
	$.fn.coverflow = function(settings) {
		// (left + right + 1) must be <= itemCount
		var config = {
			// General
			leftItemCount: 0,
			rightItemCount: 3,
			initialIndex: 0,
			isInfinite: false,
			// Animation

			// Callbacks	
			onAnimationStart: undefined,
			onItemSelected: undefined,
			onInit: undefined,
			onItemDeselected: undefined,
			onPrepare: undefined,
			onCalculate: undefined,
			// CSS
			imgSelector: 'img',
			zero: 0 // This is just here so we can always use the ","
		};
		if (settings) $.extend(config, settings);
		this.each(function() {
			$(this).addClass(CF_ROOT_CSS_CLASS);
			$(this).data(CF_CONFIG_KEY,config);		
			var coverflow = new Coverflow(this,config);
			coverflow.setup();
		});
	};
	
})(jQuery);
