// JavaScript Document

// Default code to grab all ul elements with class "basic-carousel", and convert them
$(document).ready(function () {
	$("ul.carousel").carousel({
		pagination: "top"
	});
});

/*********************************
	
	Parameter Name			Default		Type		Description
	-------------------------------------------------------------------------------------
	size:					4			int			The number of items to be shown at one time
	liWidth:				"100px"		String		The width of each element (not including borders, padding, or margin) Be sure to include 'px' at the end.
	liHeight: 				"100px"		String		The height of each element (not including borders, padding, or margin) Be sure to include 'px' at the end.
	liMargin:				"0px"		String		The margin of each element. Be sure to include 'px' at the end.
	liPadding:				"0px"		String		The padding of each element. Be sure to include 'px' at the end.
	liBorderWidth:			"0px"		String		The border width of each element. Be sure to include 'px' at the end.
	vertical: 				false		boolean		true if this is a to be a vertical carousel.
	pagination:				false		boolean		Leave undefined if no pagination, otherwise enter, "top" or "bottom" for horizontal carousel, or "left" or "right" for vertical
	clickablePagination:	true		boolean		Set to false to disable pagination click function
	animSpeed:				600			int			The animation time (in milliseconds) for switching sets
	paginationHeight:		15			int			The height (if vertical: width) of the pagination area
	buttonWidth:			20			int			The width (if vertical: height) of the next and previous buttons
	overlapNav:				false		boolean		Set to true if the next and previous buttons are on the inside or outside of the carousel
	-- Only definable via the plugin's params:
	itemCurrentClass		null		String		Class name that will be added when an item is clicked. If another item has this class, it will be removed.
	changeSet				null		function	fires after the set is done animating change (prev, next, and active pagination buttons will cause this)
											changeSet(setNum) where setNum is the new set number.
	clickFunction			null		function	Fires when an item is clicked. It passes back the jQuery pointer of the item and information about where the item is in the carousel.
										clickFunction(jObj,info)
										jObj is a jquery object, so all of jquery's methods come with it: .html(), .hide(), .load(), etc.
											Example: jObj.addClass("clicked")
											This will add the class "clicked" whene the item is clicked.
										info has the following parameters
											eq			The number of visible elements before this one
											set			The current set
											subEq		The number of elements before this one in it's own set
											origEq		The number of elements (including hidden elements) before this one. (If no items are hidden, it will equal eq)
											origSet		The set this elements started in (If no items are hidden, it will equal set)
											Example: location.href="?item=" + info.eq;
**/

(function($) {
$.fn.carousel = function (params)
{
	params = params || {}; // Ensures that params is not undefined.
	$(this).each(function () {
		
		// Parameters: first checks inline attribute, then the parameters of the plugin, then resolves to default
		
		//  size: the number of items to be shown at one time
		var size = parseInt($(this).attr("size") || params.size || 4);
		//  liWidth: The width of each element (not including borders, padding, or margin) Be sure to include 'px' at the end.
		var liWidth = $(this).attr("liWidth") || params.itemWidth || "100px";
		//  liHeight: The height of each element (not including borders, padding, or margin) Be sure to include 'px' at the end.
		var liHeight = $(this).attr("liHeight") || params.itemHeight || "100px";
		//  liMargin: The margin of each element. Be sure to include 'px' at the end.
		var liMargin = $(this).attr("liMargin") || params.itemMargin || "0px";
		//  liPadding: The padding of each element. Be sure to include 'px' at the end.
		var liPadding = $(this).attr("liPadding") || params.itemPadding || "0px";

		//  liBorderWidth: The border width of each element. Be sure to include 'px' at the end.
		var liBorderWidth = $(this).attr("liBorderWidth") || params.itemBorderWidth || "0px";
		//  vertical: true if this is a vertical carousel. boolean
		var vertical = ($(this).attr("vertical") == "true") || params.vertical==true || false;
		//  pagination: Leave undefined if no pagination, otherwise enter, "top" or "bottom" for horizontal carousel, or "left" or "right" for vertical
		var pagination = $(this).attr("pagination") || params.pagination || false;
			pagination = ((":top:left:right:bottom:").indexOf((":"+pagination+":").toLowerCase()) > -1) ? pagination.toLowerCase() : false;
		// clickablePagination: boolean of weather or not clicking on pagination will do anything
		var clickablePagination = ($(this).attr("clickablePagination") == "false") || (params.clickablePagination==false); clickablePagination = !clickablePagination;
		// animSpeed: The animation time (in milliseconds) for switching sets
		var animSpeed = parseInt($(this).attr("animSpeed") || params.animSpeed || 600);
		//  paginationHeight: the height (if vertical: width) of the pagination area
		var paginationHeight = parseInt($(this).attr("paginationHeight") || params.paginationHeight || 15);
		//  buttonWidth: the width (if vertical: height) of the next and previous buttons
		var buttonWidth = parseInt($(this).attr("buttonWidth") || params.buttonWidth || 20);
		//  overlapNav: boolean that decides if the next and previous buttons are on the inside or outside of the carousel
		var overlapNav = ($(this).attr("overlapNav") == "true") || params.overlapNav==true || false;
		// Class name that will be added when an item is clicked. If another item has this class, it will be removed.
		var itemCurrentClass = $(this).attr("itemCurrentClass") || params.itemCurrentClass || "";
		
		// Adds wrappers and nav buttons
		$(this).wrap("<div class=\"carousel-outer-wrapper\"></div>");
		$(this).after("<div class=\"right-arrow\"></div>");
		$(this).after("<div class=\"left-arrow\"></div>");
		$(this).wrap("<div class=\"carousel-inner-wrapper\"></div>");
		
		// Setting up pointers (to be used from here on in the code)
		var shell		= $(this).parent().parent();
		var wrapper		= $(this).parent();
		var prevArr		= wrapper.next(".left-arrow");
		var ulli		= $(this);
		var ullis		= ulli.children("li");
		var nextArr		= wrapper.next().next(".right-arrow");
		
		// Setting up pagination
		
		// The number of slides or "sets" in the carousel
		var sets = (size != 0) ? Math.ceil(ulli.children("li:visible").length / size) : 0;
		paginationHtml = " ";
		for (var i=0; i<sets; i++)
			paginationHtml += "<span>&bull;</span> "; // This is the html for each pagination item. <span> is needed for later binding
		paginationHtml = "<div class=\"carousel-pagination\">" + paginationHtml + "</div>";
		
		// Adds paggination to correct place, and offsets the navigation to keep it aligned with the carousel
		switch (pagination)
		{
			case false:		break;
			case "top":		wrapper.before(paginationHtml);
							prevArr.css("top",paginationHeight+"px");
							nextArr.css("top",paginationHeight+"px");
							break;
			case "bottom":	wrapper.after(paginationHtml);
							prevArr.css("top","-"+paginationHeight+"px");
							nextArr.css("top","-"+paginationHeight+"px");
							break;
			case "left":	wrapper.before(paginationHtml);
							prevArr.css("left",(paginationHeight / 2)+"px");
							nextArr.css("left",(paginationHeight / 2)+"px");
							break;
			case "right":	wrapper.after(paginationHtml);
							prevArr.css("left","-"+(paginationHeight / 2)+"px");
							nextArr.css("left","-"+(paginationHeight / 2)+"px");
							break;
		}
		
		var paginator = {};
		if (pagination != false)
		{
			paginator = wrapper.siblings(".carousel-pagination"); // Creates pagination object pointer.
			paginator.children("span:eq(0)").addClass("current"); // Adds the class 'current' to first pagination item
			paginator.addClass(pagination); // Adds the class "top", "bottom", "left", or "right" to the pagination div
		}
		
		ulli.addClass("carousel");
		
		// Calculates the actual width and height of an the carousel items, including padding, borders, and margin
		var realLiWidth = parseInt(liWidth) + (2 * parseInt(liMargin)) + (2 * parseInt(liPadding)) + (2 * parseInt(liBorderWidth));
		var realLiHeight = parseInt(liHeight) + (2 * parseInt(liMargin)) + (2 * parseInt(liPadding)) + (2 * parseInt(liBorderWidth));
		
		// Fine tuning the positioning of elements, based on its orientation and other preferences
		if (vertical)
		{
			prevArr.css("height",buttonWidth+"px");
			nextArr.css("height",buttonWidth+"px");
			shell.addClass("vertical");
			wrapper.addClass("vertical");
			wrapper.css({"width":realLiWidth+"px","height":(realLiHeight*size)+"px","top":(((overlapNav) ? 0 : buttonWidth)+5)+"px"});
			ulli.css({"width":realLiWidth+"px","top":"0px"});
			shell.css({"height":((size * realLiHeight) + (((overlapNav) ? 0 : buttonWidth) * 2) + 4) + "px","width":(realLiWidth + 4)+"px"});
			if (pagination)
			{
				paginator.addClass("vertical");
				paginator.css("top",((0 + shell.height() / 2) - ((sets * paginationHeight) / 2)) + "px");
				//shell.css("padding-"+pagination,"15px");
			}
		} else {
			prevArr.css("width",buttonWidth+"px");
			nextArr.css("width",buttonWidth+"px");
			wrapper.css({
				"height":(realLiHeight)+"px",
				"margin-left":((overlapNav) ? 0 : buttonWidth)+"px",
				"margin-right":((overlapNav) ? 0 : buttonWidth)+"px"
			});
			ulli.css({"width":(ullis.length * realLiWidth)+"px","left":"0px"});
			shell.css({"width":((size * realLiWidth) + (((overlapNav) ? 0 : buttonWidth) * 2) + 4) + "px"});
			if (pagination)
			{
				//shell.css("padding-"+pagination,"15px");
				paginator.css("left",((5 + shell.width() / 2) - ((sets * paginationHeight) / 2)) + "px");
			}
		}
		
		// setNum is the current viewed set. It is checked later to ensure the user never runs past the carousel boundaries.
		ulli.attr("setNum","0");
		// overwrites the css of each item, based on the parameters given at the top of this script
		ullis.css({
			"margin":liMargin,
			"padding":liPadding,
			"border-width":liBorderWidth,
			"float":"left",
			"display":"inline",
			"width":liWidth,
			"height":liHeight
		});
		
		// binds the next button
		nextArr.click(function () {
			set = parseInt(ulli.attr("setNum"));
			// Checks to make sure the carousel is not already on the last set
			if (set < ((size != 0) ? Math.ceil(ulli.children("li:visible").length / size) : 0)-1)
			{
				ulli.attr("setNum",++set); // sets the setNum attribute to the new value. ++set (instead of set++) will add 1 to itself and THEN return the value.
				if (vertical)
				{
					ulli.animate({"top":"-=" + (realLiHeight*size) + "px"}, animSpeed, function () {
						// if there is pagination, set the correct "current" class
						if (pagination)
							paginator.children("span:eq(" + set + ")").addClass("current").siblings().removeClass("current");
						// if there is changeSet is defined, run it
						if (params.changeSet != undefined)
							params.changeSet(set);
					});
				} else {
					ulli.animate({"left":"-=" + (realLiWidth*size) + "px"}, animSpeed, function () {
						// if there is pagination, set the correct "current" class
						if (pagination)
							paginator.children("span:eq(" + set + ")").addClass("current").siblings().removeClass("current");
						// if there is changeSet is defined, run it
						if (params.changeSet != undefined)
							params.changeSet(set);
					});
				}
			}
		});
		
		// binds prev buttons
		prevArr.click(function () {
			set = parseInt(ulli.attr("setNum"));
			if (set>0)
			{
				ulli.attr("setNum",--set);
				if (vertical)
				{
					ulli.animate({"top":"+=" + (realLiHeight*size) + "px"}, animSpeed, function () {
						// if there is pagination, set the correct "current" class
						if (pagination)
							paginator.children("span:eq(" + set + ")").addClass("current").siblings().removeClass("current");
						// if there is changeSet is defined, run it
						if (params.changeSet != undefined)
							params.changeSet(set);
					});
				} else {
					ulli.animate({"left":"+=" + (realLiWidth*size) + "px"}, animSpeed, function () {
						// if there is pagination, set the correct "current" class
						if (pagination)
							paginator.children("span:eq(" + set + ")").addClass("current").siblings().removeClass("current");
						// if there is changeSet is defined, run it
						if (params.changeSet != undefined)
							params.changeSet(set);
					});
				}
			}
		});
		
		// binds pagination click function
		if (pagination && clickablePagination)
		{
			paginator.children("span").click(function () {
				var eq = $(this).prevAll().length;
				$(this).addClass("current").siblings().removeClass("current");
				ulli.attr("setNum",eq);
				if (vertical)
				{
					ulli.animate({"top":(-eq * size * realLiHeight)+"px"}, animSpeed, function () {
						if (params.changeSet != undefined)
							params.changeSet(eq);
					});
				} else {
					ulli.animate({"left":(-eq * size * realLiWidth)+"px"}, animSpeed, function () {
						if (params.changeSet != undefined)
							params.changeSet(eq);
					});
				}
			});
		}
		
		// If there is a class to be added to an item (and removed from siblings) when clicked, this binds that action
		if (params.itemCurrentClass != undefined)
		{
			ullis.click(function () {
				$(this).addClass(params.itemCurrentClass).siblings("."+params.itemCurrentClass).removeClass(params.itemCurrentClass);
			});
		}
		
		// If there is a click function assigned to each item, this binds that function.
		// It passes back the jQuery pointer of the item and information about where the item is in the carousel
		if (params.clickFunction != undefined)
		{
			ullis.click(function () {
				var eq = $(this).prevAll().length;
				var visEq = $(this).prevAll(":visible").length;
				var set = Math.floor(eq / size);
				var visSet = Math.floor(visEq / size);
				var subEq = visEq % size;
				var info = {eq:visEq, set:visSet, subEq:subEq, origEq:eq, origSet:set};
				params.clickFunction($(this),info);
			});
		}
	});
}

})(jQuery);