﻿

var PCarousel2 = function(configParams, callback) {
	//default settings for the carousel
	var config = {
		speed: 0, //animation speed in ms
		direction: 'horizontal', //direction of animation, 'horizontal' or 'vertical' are valid options
		listItem: null, //DOM obj of the <UL> where we work with
		nextButton: null, //DOM obj of the next-button
		prevButton: null, //DOM obj of the prev-button
		continues: false, //determines whether to continue after last element in an loop
		loopType: 'finite', //sets the type of loop 'finite' to rwd/ffd to end or start, or 'infinite' to have a fluent experience
		offset: 0, //offset of the current selected item in pixels
		autoLoopInterval: 0, //timer for automated scroll, 0 for none
		callback: function() { },

		bind: function(params, callback) {
			if (params) {
				for (param in params) {
					eval("config." + param + " = params." + param);
				}
			}
			if (typeof (callback) == "function") {
				config.callback = callback;
			}
			//config binding done
			config.init = true;
		},
		init: false, //determines if the binding of config is done
		dummy: null //to not forget the appending of the extra , after the last variable
	};
	//bind the configuration
	config.bind(configParams, callback);

	//private methods and variables for object
	var priv = {
		curIndex: 0,
		$listItems: null,
		stepSize: 0,
		lock: !config.init,
		queue: Array(),
		maxItemsInView: 0,
		timer: null,

		//test whether we have everything required to get going
		hasListItem: function() {
			if (!config.listItem) {
				Log.Debug("PCarousel2: No valid listItem has been bound, please specify", 2);
				return false;
			}
			return true;
		},

		moveTo: function(newIndex) {
			try {
				//if locked, do nothing
				if (priv.lock) {
					Log.Debug("PCarousel: Not moving, locked");
					return;
				}

				//if the newIndex is not set, try to get the last item from queue
				var fromQueue = false;
				if (typeof (newIndex) == "undefined") {
					if (priv.queue.length > 0) {
						fromQueue = true;
						newIndex = priv.queue[priv.queue.length - 1];
						priv.queue.length = new Array();
					}
					else {
						Log.Debug("PCarousel: Not moving, nothing in queue");
						return;
					}
				}

				Log.Debug("PCarousel2: moveTo(), newindex: " + newIndex, 0);
				var doStep = false;
				var isLastStep = priv.isLastStep();
				var isFirstStep = priv.isFirstStep()

				//correct for continues loops
				newIndex = priv.continuesCorrections(newIndex, isLastStep, isFirstStep);

				//test if we can actually move
				if (config.continues || ((newIndex > priv.curIndex && !isLastStep) || (newIndex < priv.curIndex && !isFirstStep))) {
					//now lock
					priv.lock = true;
					doStep = true;

					var newItemOffset = 0;

					//determine action to take on animation
					var animationAction = "";
					if (config.direction == 'vertical') {
						newItemOffset = Math.min(priv.$listItems[newIndex].offsetTop, $(config.listItem).height() - $(config.listItem).parent().height());

						animationAction = { "top": -1 * (newItemOffset + config.offset) + "px" };
					}
					else {
						newItemOffset = Math.min(priv.$listItems[newIndex].offsetLeft, $(config.listItem).width() - $(config.listItem).parent().width());
						animationAction = { "left": -1 * (newItemOffset + config.offset) + "px" };
					}

					//remove class from old index
					$(priv.$listItems[priv.curIndex]).removeClass("selected");
					$(priv.$listItems[newIndex]).addClass("selected");

					//do the animation, and set the "selected" class
					$(config.listItem).animate(animationAction, config.speed,
		                function() {
		                	//unlock
		                	priv.lock = false;
		                	//set the nextPrevButtons
		                	priv.testNextPrevButtons();
		                	//correction for first and last items when using "To()" function in infinite loop
		                	if (fromQueue && config.offset != 0 && config.continues && config.loopType == 'infinite') {
		                		priv.continuesCorrections(null, priv.isLastStep(), priv.isFirstStep());
		                	}
		                	//make the callback
		                	config.callback();
		                	//see if we need to move into the queue object
		                	if (priv.queue.length > 0) {
		                		priv.moveTo();
		                	}
		                }
		           );
				}

				//set the curIndex
				if (doStep) {
					priv.curIndex = newIndex;
				}
				//if we havent moved but we are not at the last index, set the index
				else if ((newIndex > priv.curIndex && newIndex < priv.$listItems.length && isLastStep) || (newIndex < priv.curIndex && newIndex >= 0 && isFirstStep)) {
					//remove class from old index
					$(priv.$listItems[priv.curIndex]).removeClass("selected");
					$(priv.$listItems[newIndex]).addClass("selected");
					priv.curIndex = newIndex;
					//make the callback
					config.callback();
				}
			}
			catch (err) {
				//priv.curIndex = 0;
				priv.lock = false;
				Log.Debug("PCarousel2: Not moving due to error moving to index: " + newIndex, 2);
				Log.Debug(err, 2);
			}
		},

		testNextPrevButtons: function() {
			if (!config.continues) {
				if (config.prevButton && priv.isFirstStep()) {
					$(config.prevButton).addClass("inactive");
				}
				else if (config.prevButton) {
					$(config.prevButton).removeClass("inactive");
				}

				if (config.nextButton && priv.isLastStep()) {
					$(config.nextButton).addClass("inactive");
				}
				else if (config.nextButton) {
					$(config.nextButton).removeClass("inactive");
				}
			}
		},

		//is last step
		isLastStep: function() {
			//test whether this is the last step by checking the heights
			if (config.direction == 'vertical') {
				var parentHeight = $(config.listItem).parent().height();
				Log.Debug("PCarousel2: parentheight: " + parentHeight + ", itemheight: " + $(config.listItem).height() + ", itemtop: " + parseInt($(config.listItem).css("top")));
				//if the <UL> has less height then its parent, including newly set css top attribute, we are done stepping               
				if (($(config.listItem).height() + parseInt($(config.listItem).css("top"))) <= priv.stepSize * priv.maxItemsInView + config.offset) { //if loop for continues
					//if(($(config.listItem).height() + parseInt($(config.listItem).css("top"))) <= parentHeight){ // original if
					return true;
				}
			}
			else {
				var parentWidth = $(config.listItem).parent().width();
				Log.Debug("PCarousel2: parentwidth: " + parentWidth + ", stepsize: " + priv.stepSize + ", maxItemsInView: " + priv.maxItemsInView + ", itemleft: " + parseInt($(config.listItem).css("left")));
				//if the <UL> has less width then its parent, including newly set css left attribute, we are done stepping               
				if (($(config.listItem).width() + parseInt($(config.listItem).css("left")) - priv.stepSize) <= priv.stepSize * priv.maxItemsInView + config.offset) {
					return true;
				}
			}
			return false;
		},

		//is first step
		isFirstStep: function() {
			//test whether this is the last step by checking the heights
			if (config.direction == 'vertical') {
				//if the css property top is set to 0, presume first step
				if (parseInt($(config.listItem).css("top")) >= -1 * config.offset) {
					return true;
				}
			}
			else {
				//if the css property left is set to 0, presume first step
				if (parseInt($(config.listItem).css("left")) + priv.stepSize == -1 * config.offset) {
					return true;
				}
			}
			return false;
		},

		getFirstStep: function() {
			if (config.loopType == 'finite') {
				return 0;
			}
			else if (false) {
				//get the last item from the <UL> and compare it against the current array
				var firstItem = $("li:first", config.listItem).get(0);
				for (var i = 0; i < priv.$listItems.length; i++) {
					if (firstItem == priv.$listItems[i]) {
						return i - priv.maxItemsInView;
					}
				}
			}
		},

		getLastStep: function() {
			if (config.loopType == 'finite') {
				return priv.$listItems.length - Math.floor($(config.listItem).parent().height() / priv.stepSize);
			}
			else {
				//get the last item from the <UL> and compare it against the current array
				var lastItem = $("li:last", config.listItem).get(0);
				for (var i = 0; i < priv.$listItems.length; i++) {
					if (lastItem == priv.$listItems[i]) {
						return i;
					}
				}
			}
		},

		continuesCorrections: function(newIndex, isLastStep, isFirstStep) {
			//no correction is needed if we are not in a loop
			if (!config.continues) {
				return newIndex;
			}
			//continues finite, and last/first step set new index
			if ((config.continues && config.loopType == 'finite') && (newIndex > priv.curIndex && isLastStep)) {
				newIndex = priv.getFirstStep();
			}
			else if ((config.continues && config.loopType == 'finite') && (newIndex < priv.curIndex && isFirstStep)) {
				newIndex = priv.getLastStep();
			}

			//corrections for infinte loop
			if ((config.continues && config.loopType == 'infinite') && (isLastStep)) {
				//get the first item from the <UL> and place it after the last
				var firstItem = $("li:first", config.listItem);

				//first place the <UL> so the animation will be fluent
				if (config.direction == 'vertical') {
					var top = parseInt($(config.listItem).css("top"));
					if (isNaN(top)) {
						top = 0;
					}
					$(config.listItem).css("top", (top + priv.stepSize) + "px");
				}
				else {
					var left = parseInt($(config.listItem).css("left"));
					if (isNaN(left)) {
						left = 0;
					}
					$(config.listItem).css("left", (left + priv.stepSize) + "px");
				}
				$("li:last", config.listItem).after(firstItem);

			}
			else if ((config.continues && config.loopType == 'infinite') && (isFirstStep)) {
				//get the last item from the <UL> and place it after the first
				var lastItem = $("li:last", config.listItem);

				//first place the <UL> so the animation will be fluent
				if (config.direction == 'vertical') {
					var top = parseInt($(config.listItem).css("top"));
					if (isNaN(top)) {
						top = 0;
					}
					$(config.listItem).css("top", (top - priv.stepSize) + "px");
				}
				else {
					var left = parseInt($(config.listItem).css("left"));
					if (isNaN(left)) {
						left = 0;
					}
					$(config.listItem).css("left", (left - priv.stepSize) + "px");
				}
				$("li:first", config.listItem).before(lastItem);
			}

			//correct for infinite looptype
			if (config.continues && config.loopType == 'infinite') {
				if (newIndex >= priv.$listItems.length) {
					newIndex = 0;
				}
				else if (newIndex < 0) {
					newIndex = priv.$listItems.length - 1;
				}
			}

			return newIndex;
		},

		startAutoLoopInterval: function() {
			if (config.autoLoopInterval > 0 && priv.timer == null) {
				priv.timer = window.setInterval(priv.next, config.autoLoopInterval);
			}
		},

		stopAutoLoopInterval: function() {
			try {
				if (priv.timer != null) {
					window.clearInterval(priv.timer);
					priv.timer = null;
				}
			}
			catch (err) {
				priv.timer = null;
			}
		},


		init: function() {
			if (!config.listItem) {
				return;
			}
			//set $listItems
			if (!priv.$listItems) {
				priv.$listItems = $("li", config.listItem);
			}

			//start autoLoop
			if (config.autoLoopInterval > 0) {
				//make sure we stop the automated scroll when we hover over the navigationObject
				$(config.listItem).hover(
				    function(evt) {
				    	priv.stopAutoLoopInterval();
				    },
				    function(evt) {
				    	priv.startAutoLoopInterval();
				    }
			    );
				priv.startAutoLoopInterval();
			}

			//presumed all items are equally sized
			priv.stepSize = (config.direction == 'vertical' ? priv.$listItems.height() : priv.$listItems.width());

			//correct the container width for vertical scrolls
			if (config.direction != 'vertical') {
				$(config.listItem).width(priv.$listItems.length * priv.stepSize);
			}

			//attach some info
			priv.$listItems.each(
			    function(index) {
			    	$(this).attr("rel", index);
			    }
			);

			//set max number of items in view
			if (config.direction == 'vertical') {
				priv.maxItemsInView = Math.ceil($(config.listItem).parent().height() / priv.stepSize);
			}
			else {
				priv.maxItemsInView = Math.ceil($(config.listItem).parent().width() / priv.stepSize);
			}

			//if we dont have enough items to actually do a continues loop, set to false
			var sizeTest = false;
			if (config.direction == 'vertical') {
				sizeTest = ($(config.listItem).height() <= $(config.listItem).parent().height());
			}
			else {
				sizeTest = $(config.listItem).width() <= $(config.listItem).parent().width()
			}

			if (config.continues && sizeTest) {
				//config.continues = false;

				//disable the next and prev buttons
				if (config.nextButton) {
					$(config.nextButton).addClass("inactive");
				}
				if (config.prevButton) {
					$(config.prevButton).addClass("inactive");
				}
			}
			else {
				//bind the next and prev buttons
				if (config.nextButton) {
					$(config.nextButton).bind("click",
			            function(evt) {
			            	priv.next();
			            }
			        );
				}
				if (config.prevButton) {
					$(config.prevButton).bind("click",
			            function(evt) {
			            	priv.prev();
			            }
			        );
				}
				//correct loop for first item
				priv.continuesCorrections(priv.curIndex - 1, false, true);
				//go to the first item
				priv.to(priv.curIndex);
				priv.testNextPrevButtons();
			}			
		},

		next: function(tmpParams, tmpCallback) {
			Log.Debug("PCarousel2: Next(), index: " + (priv.curIndex + 1), 0);
			priv.moveTo(parseInt(priv.curIndex) + 1);
		},
		prev: function(tmpParams, tmpCallback) {
			Log.Debug("PCarousel2: Prev(), index: " + (priv.curIndex - 1), 0);
			priv.moveTo(parseInt(priv.curIndex) - 1);
		},
		to: function(index, tmpParams, tmpCallback) {
			Log.Debug("PCarousel2: To(" + index + ")");
			priv.queue.push(parseInt(index));
			priv.moveTo();
		},
		dummy: null //to not forget the appending of the extra , after the last variable
	};
	priv.init();

	//public methods for the object
	return {
		/**
		* Moves the carousel one forward
		**/
		Next: function(tmpParams, tmpCallback) {
			priv.next(tmpParams, tmpCallback);
		},

		/**
		* Moves the carousel one back
		**/
		Prev: function(tmpParams, tmpCallback) {
			priv.prev(tmpParams, tmpCallback);
		},

		/**
		* Moves the carousel to the specified index
		**/
		To: function(index, tmpParams, tmpCallback) {
			priv.to(index, tmpParams, tmpCallback);
		},

		/**
		* Moves the carousel to the specified item
		**/
		ToItem: function() {
			//TODO
		},

		/**
		* Returns the index of the currently selected item
		**/
		GetCurrentIndex: function() {
			return priv.curIndex;
		},

		/**
		* Returns the currently selected item
		**/
		GetCurrentItem: function() {
			return priv.$listItems[priv.curIndex];
		},

		GetItemByIndex: function(index) {
			return priv.$listItems[index];
		},

		GetNumberOfItemsInView: function() {
			return priv.maxItemsInView;
		},

		GetItemsInView: function() {
			var items = $(priv.$listItems[priv.curIndex]).nextAll("li:lt(3)").andSelf().get();
			items.unshift(items.pop());
			return items;
		},

		IsLocked: function() {
			return priv.lock;
		},

		TestConfig: function(configParam) {
			var value = eval("config." + configParam);
			return value;
		},
		TestCallback: function() {
			config.callback();
		},

		dummy: null //to not forget the appending of the extra , after the last variable
	}
};
