/*by Roman Hodko www.pivot at gmail.com*/
(function($){
	var STORAGE_NAME = 'resizableCarousel';
	
	/*public API*/
	$.resizableCarousel = {};
	
	$.resizableCarousel.show = function(jQObj, rowNumber) {
		jQObj.each(function(){
			$(this).trigger(STORAGE_NAME, [rowNumber]);
		});
	};

	/*plugin body*/
	$.fn.resizableCarousel = function (userSettings) {
		var s = $.extend({
			buttonNext:null,
			buttonPrev:null,
			showNextInterval:5000
			
		}, userSettings || {});
		
		var defaultElementCSS = {
			position:'relative', top:0, bottom:0, left:0, right:0, zIndex:2, opacity:0
		};
	
		
		return this.each(function(){
			var me = $(this);
			
			var txt = me.html();
			me.html(txt + txt + txt);
			
			var carouselItems = me.find('>*');
			var carouselIntervalHandler;

			/*places a carousel element in the tail of other elements and restores default css properties for the element*/
			var toTail = function (jQElement) {
				jQElement.remove();
				jQElement.css( defaultElementCSS );
				me.append(jQElement);
			};
			
			/*calculates the number of visible items and stores it into the passed object data storage*/
			var countVisibleItems = function() {
				var w = me.width();
				
				/*TODO протестировать, если будут установлены margin-ы*/
				var itemW = carouselItems.filter(':first').outerWidth(true);
				return Math.floor( w / itemW );
			};
			
			/*returnes wrapped set of visible carousel items*/
			var getItemsRow = function (rowNumber, startFromTail) {
				var count = countVisibleItems();
				var items = me.find('>*:lt(' + count * (rowNumber + 1) + ')');
				if (rowNumber !== 0) {
					items = items.filter(':gt(' + (count * rowNumber - 1) + ')');
				}
				return items;
			};
			
			/*swaps 2 rows with the specified indexes*/
			var swapRows = function (r1, r2) {
				var row1 = Math.min(r1, r2);
				var row2 = Math.max(r1, r2);
				var count = countVisibleItems();
				var items = getItemsRow(row1);
				/*var items1 = items.clone(true);*/
				items.remove();
				me.find('>*:eq(' + (row2 * count - 1) + ')').after(items);
			};
			
			/*a template for all animation functions*/
			var Animation = function(animation) {
				var _self = this;
				
				/*the animation itself*/
				_self.animation = animation;
				
				/*default parameters that are used by _self.start method*/
				_self.__pars = {
					nextFunc:function(){},
					triggerAtPercent:100,
					millisPerItem:1200 /*every visible item in the carousel is animated during this time*/
				}

				/*nextFunc - function to be triggered after the specified percent of animation (triggerAtPercent) is finished. Is used by show effects*/
				_self.__getUpdatePercentFunc = function(nextFunc, triggerAtPercent){
					var cou  = 0;
					var total = countVisibleItems();
					var percent = 0; 
					var notYetFired = true;
					return function(){
						cou++;
						percent = 100 * cou / total;
						if (notYetFired && triggerAtPercent <= percent) {
							notYetFired = false;
							setTimeout(nextFunc, 0);
						}
					};
				};
				
				/*call this function to begin animation*/
				_self.start = function (pars) {
					var d = $.data(me, STORAGE_NAME);
					
					/*if animation is not allowed do nothing. Doing animation isn't possible if window has been resized*/
					if (! d.isAnimationAllowed) return;
					
					var upars = $.extend(this.__pars, pars || {});
					upars.updatePercent = _self.__getUpdatePercentFunc(upars.nextFunc, upars.triggerAtPercent);
					_self.animation(upars);
				};
			};
			
			//show effects section
			var showEffects = new Array();
			
			/*'appear'*/
			/* showEffects.push( new Animation(function(pars){
				var d = $.data(me, STORAGE_NAME);
				if (! d.isAnimationAllowed) return;
				
				var items = getItemsRow(1);
				var total = items.length;
				items.css({top:-items.outerHeight(true)});
				
				items.each(function(i){
					var _me = $(this);
					setTimeout(function(){
						var d = $.data(me, STORAGE_NAME);
						if (! d.isAnimationAllowed) return;
						
						_me.animate({opacity: 1}, {duration:pars.millisPerItem, easing:'swing', complete:pars.updatePercent});
					}, 100 * (total - i));
				});
			})); */
			
			/*'teeth'*/
			showEffects.push( new Animation(function(pars){
				var d = $.data(me, STORAGE_NAME);
				if (! d.isAnimationAllowed) return;
				
				var items = getItemsRow(1);
				var total = items.length;
				var outerH = items.outerHeight(true);

				items.each(function(i){
					var _me = $(this);
					var _meTop = (i % 2 === 0) ? outerH * 2: 0;
					_me.css({top:- _meTop});
					
					var timeout = 130 * i;
					
					setTimeout(function(){
						var d = $.data(me, STORAGE_NAME);
						if (! d.isAnimationAllowed) return;
						
						_me.animate({opacity: 1, top:-outerH}, {duration:pars.millisPerItem, easing:'swing', complete:pars.updatePercent});
					}, timeout);
				});
				
			}));
			
			//hide effects section
			var hideEffects = new Array();
			
			/*'goTopAndBottom'*/
			hideEffects.push( new Animation(function(pars){
				var d = $.data(me, STORAGE_NAME);
				if (! d.isAnimationAllowed) return;
				
				var items = getItemsRow(0);
				var maxTimeout = 0;
				
				items.each(function(i){
					var _me = $(this);
					var outerH = _me.outerHeight(true);
					
					var timeout = 130 * i;
					maxTimeout = Math.max(maxTimeout, timeout);
					
					setTimeout(function(){
						var d = $.data(me, STORAGE_NAME);
						if (! d.isAnimationAllowed) return;
						
						_me.animate({opacity:0, top: (i % 2 === 0 ? 1 : -1) * outerH}, {duration:pars.millisPerItem, easing:'swing'});
					}, timeout);
				});
				
				//callback
				setTimeout(function(){
					var d = $.data(me, STORAGE_NAME);
					if (! d.isAnimationAllowed) return;
					
					pars.nextFunc();
				}, (pars.millisPerItem + maxTimeout) * pars.triggerAtPercent / 100);
			}));
			
			/*'floatLeft'*/
			/* hideEffects.push( new Animation(function(pars){
				var d = $.data(me, STORAGE_NAME);
				if (! d.isAnimationAllowed) return;
				
				var items = getItemsRow(0);
				var maxTimeout = 0;
				
				items.each(function(i){
					var _me = $(this);
					var outerH = _me.outerHeight(true);
					var w = me.width();
					
					var timeout = 50 * i;
					maxTimeout = Math.max(maxTimeout, timeout);
					
					setTimeout(function(){
						var d = $.data(me, STORAGE_NAME);
						if (! d.isAnimationAllowed) return;
						
						_me.animate({
							top: (i % 2 === 0 ? 1 : -1) * outerH,
							left: -w,
							opacity: 0
						}, {duration:pars.millisPerItem, easing:'swing'});
					}, timeout);
				});
				
				//callback
				setTimeout(function(){
					var d = $.data(me, STORAGE_NAME);
					if (! d.isAnimationAllowed) return;
					
					pars.nextFunc();
				}, (pars.millisPerItem + maxTimeout) * pars.triggerAtPercent / 100);
			})); */
			
			$(s.buttonNext).click(function(){
				me.trigger(STORAGE_NAME, 1);
			});
			
			$(s.buttonPrev).click(function(){
				me.trigger(STORAGE_NAME, -1);
			});
			
			/*change images after the specified amount of time showNextInterval has elapsed*/
			var startIntervalChange = function(){
				clearInterval(carouselIntervalHandler);
				carouselIntervalHandler = setInterval(function(){
					me.trigger(STORAGE_NAME, 1);
					
				}, s.showNextInterval);
			};
			
			me.hover(
				function(){
					clearInterval(carouselIntervalHandler);
				},
				function(){
					startIntervalChange();
				}
			);
			
			startIntervalChange();
			
			/*possible effects combinations*/
			var callEffects = function (rowNumber) {
				var d = $.data(me, STORAGE_NAME);
				
				/*prevent from starting an animation while another is in progress*/
				if (d.isAnimated) {
					return;
				}
				d.isAnimated = true;
				d.isAnimationAllowed = true; /*allow animation after window has been resized*/
				d.areRowsSwapped = false;
				$.data(me, STORAGE_NAME, d);
				
				if (rowNumber < 0) {
					prepareNextRow();
				}
				
				var r = getItemsRow(0);
				var inx = Math.floor( Math.random() * showEffects.length);
				var inx1 = Math.floor( Math.random() * hideEffects.length);
				hideEffects[ inx1 ].start({
					nextFunc:function(){
						showEffects[ inx ].start({
							nextFunc: completeAnimation,
							triggerAtPercent:100
						});
					},
					triggerAtPercent:3
				});
				
				function completeAnimation() {
					if (rowNumber < 0) {
						swapRows(0,1);
						getItemsRow(1).css(defaultElementCSS);
					} else {
						toTail(r);
					}
					
					getItemsRow(0).css(defaultElementCSS).css({opacity:1});
					
					d.isAnimated = false; /*allow animation*/
					d.areRowsSwapped = false; /*reset to default*/
					$.data(me, STORAGE_NAME, d);
					
					startIntervalChange();
				}
				
				/*insert the last row before the second one*/
				function prepareNextRow() {
					var total = carouselItems.length;
					var num = countVisibleItems();
					var lastRow = me.find('>*:gt(' + (total - num - 1) + ')');
					me.find('>*:eq(' + (num - 1) + ')').after(lastRow);
					
					/*save state of rows (already swapped)*/
					var d = $.data(me, STORAGE_NAME);
					d.areRowsSwapped = true;
					$.data(me, STORAGE_NAME, d);
				}
			};
			
			/*if window was resised and some items became invisible or visible, then stop any animation and show the row that was animated*/
			$(window).resize(function(){
				var d = $.data(me, STORAGE_NAME);
				
				/*if there is enough space left for the visible items - do nothing*/
				var num = countVisibleItems();
				if (d.itemsInRow === num) return;
				
				d.isAnimationAllowed = false; /*true will be set in callEffects()*/
				$.data(me, STORAGE_NAME, d);

				/*apply default css to all elements*/
				carouselItems.stop().css(defaultElementCSS);
				
				if (d.areRowsSwapped) {
					var r = me.find('>*').filter(function(i){
						return ((i < d.itemsInRow) || (i > d.itemsInRow * 2 - 1))? false : true;
					});
					r.remove();
					me.append(r);
				}
				
				getItemsRow(0).css({opacity:1});
				
				d.itemsInRow = num;
				d.isAnimated = false;
				d.areRowsSwapped = false;
				$.data(me, STORAGE_NAME, d);
				
				startIntervalChange();
			});
			
			
			
			
			/*bind event for manipulating the carousel*/
			me.bind(STORAGE_NAME, function(e, arr){
				var rowNum = arr || 1; /*если arr - это массив, то почему arr[0]=='undefined'???*/
				callEffects(rowNum);
			});
			
			/*
			 * TODO: найти другой путь убрать word-spacing. Убирать его надо, т.к. если к элементу применить последовательно
			 * remove() и append(), то word-spacing сам куда-то исчезает.
			 */
			toTail(carouselItems);
			
			/*init carousel style*/
			var css = {
				width:'100%',
				textAlign:'center',
				overflow:'hidden',
				position:'relative',
				zIndex:1
			};
			//css.height = carouselItems.filter(':first').find('img').outerHeight(true);
			css.height = carouselItems.find('img:first').outerHeight(true);
			me.css( css );
			
			$.data(me, STORAGE_NAME, {
				isAnimated : false,
				itemsInRow : countVisibleItems(),
				isAnimationAllowed : true, /*indicates whether an animation can be done*/
				areRowsSwapped : false /*true if the last and the second rows have been swapped when images are shown in reverse order*/
			});

			getItemsRow(0).css({opacity:1});
		});
	};

})(jQuery)
