/**
 *  jQuery UI Carousel plugin
 *
 *  @author Zach Waugh <zwaugh@gmail.com>
 *  @require jquery-ui-1.8.1.widget.min.js (jQuery UI Widget Factory)
 * 
 *  Pages are 0 indexed - i.e. page one = page 0
 *
 *	Last updated 7/9/10 - Updated for jQuery UI 1.8
 */

(function($)
{
	$.widget("ui.carousel",
	{
		options: {
			next: 'a.next',
			previous: 'a.previous',
			page: 'a.page',
			current_page: '.current_page',
			total_pages: '.total_pages',
			item: 'li',
			items: '.carousel_items',
			container: '.carousel_container',
			animationSpeed: 250,
			padding: 0,
			paginated: false,
			perpage: false,
			controls: false,
			controlsParent: null,
			easing: 'swing',
			horizontal: true
		},
	
		_create: function()
		{
			var self = this, options = this.options;
			this._isAnimating = false;
			this.element.addClass('ui-carousel');
		
			if (options.horizontal)
			{
				this._initHorizontal();
			}
			else
			{
				this._initVertical();
			}
		},
	
		/**
		 * Setup and configure horizontal carousel
		 */
		_initHorizontal: function()
		{	
			var options = this.options;
			var carousel = this.element;
			var self = this;
		
			options.width = this._calculateWidth();
		
			// check if using in paginated mode
			if (options.paginated)
			{			
				if (!options.perpage)
				{
					itemsize = carousel.find(options.items).children(options.item).innerWidth();
					options.perpage = Math.round((options.width - options.padding) / itemsize);
				}
		
				total = carousel.find(options.items).children(options.item).length;
			
				// Figure out how many pages are needed
				// Based on total / x, where x = items per page
				options.pages = (options.perpage > 0) ? Math.ceil(total / options.perpage) : 1;
				carousel.find(options.total_pages).html(options.pages);
			
				// Add controls if more than one page and controls == true
				if (options.controls && options.pages > 1)
				{
					this._buildControls();
				}
			
				carousel.find(options.page).click(function(event) { return self._gotoPage(event); });
			}
		
			// Bind events
			carousel.find(options.previous).click(function(event) { return self._previousPage(event); });
			carousel.find(options.next).click(function(event) { return self._nextPage(event); });
		},
	
		/**
		 * Setup and configure vertical carousel
		 */
		_initVertical: function()
		{
			var self = this;
			var options = this.options;
			var carousel = this.element;
		
			options.height = this._calculateHeight();
	
			// Get number of items per "page"
			if (options.paginated)
			{
				var perpage;
			
				if (!options.perpage)
				{
					itemsize = carousel.find(options.items).children(options.item).innerHeight();
					perpage = Math.round((options.height - options.padding) / itemsize);
				}
				else
				{
					perpage = options.perpage;
				}

				total = carousel.find(options.items).children(options.item).size();

				// Figure out how many pages are needed
				// Based on total / x, where x = items per page
			  options.pages = Math.ceil(total / perpage);
				carousel.find(options.total_pages).html(options.pages);
			
				// Add controls if more than one page and controls == true
				if (options.controls && options.pages > 1)
				{
					this._buildControls();
				}
			
				carousel.find(options.page).click(function(event) { return self._gotoPage(event); });
			}
		
			// Bind Events
			carousel.find(options.previous).click(function(event) { return self._previousPage(event); });
			carousel.find(options.next).click(function(event) { return self._nextPage(event); });
		},
	
		/**
		 * buildControls: dynamically build html for controls
		 * @params carousel (jQuery Object), options (Object)
		 */
		_buildControls: function ()
		{
			var controls = '<div class="controls">\n<ul>\n<li><a href="" class="previous disabled"> Previous </a></li>';
		
			for (var i = 0; i < this.options.pages; i++)
			{
				if (i === 0)
				{
					controls += '<li><a href="" class="page selected">' + (i + 1) + '</a></li>\n';
				}
				else
				{
					controls += '<li><a href="" class="page">' + (i + 1) + '</a></li>\n';
				}
			}
		
			controls += '<li><a href="" class="next"> Next </a></li>\n</ul>\n</div><br class="clear" />';
		
			// Add controls node to DOM
			if (this.options.controlsParent) {
				this.element.find(this.options.controlsParent).append(controls);
			}
			else {
				this.element.prepend(controls);
			}
		},
	
		/**
		 * previousPage: go to previous page
		 * @params event (jQuery Event Object)
		 */
		_previousPage: function (event)
		{
			event.preventDefault();
			var carousel = this.element;
			var options = this.options;
		
			// Prevent clicking while animating
			if (this._isAnimating)
			{
				return false;
			}
		
			if (options.horizontal && carousel.find(options.items).css('left') != '0px')
			{
				this._slideLeft();
			}
		
			if (!options.horizontal && carousel.find(options.items).css('top') != '0px')
			{
				this._slideUp();
			}

			return false;
		},
	
		/**
		 * nextPage: go to next page
		 * @params event (jQuery Event Object)
		 */
		_nextPage: function (event)
		{
			event.preventDefault();
			var carousel = this.element;
		
			// Prevent clicking while animating
			if (this._isAnimating || $(event.target).hasClass('ui-state-disabled'))
			{
				return false;
			}
		
			if (this.options.horizontal)
			{ 
				this._slideRight();
			}
			else
			{
				this._slideDown();
			}
		
			return false;
		},
	
		/**
		 * gotoPage: handles clicking a particular page
		 * @params event (jQuery Event Object)
		 */
		_gotoPage: function (event)
		{
			// Prevent clicking while animating
			if (this._isAnimating || $(event.target).hasClass('ui-state-disabled'))
			{
				return false;
			}
	
			if (!$(event.target).hasClass('selected'))
			{
				this.slideTo(this.element.find(this.options.page).index(event.target));
			}

			return false;
		},
	
		/**
		 * Slide to a particular page in the current carousel
		 * @params page (int) - zero indexed
		 */
		slideTo: function (page)
		{
			var carousel = this.element;
			var options = this.options;
			var current_page = this.currentPage();

			if (page == current_page)
			{
				return;
			}
		
			console.log('page:', page);
			
			// Make sure page isn't out of bounds
			if (page >= options.pages)
			{
				page = options.pages;
			}
				
			if (page < current_page)
			{
				if (options.horizontal)
				{
					this._slideLeft((current_page - page) * options.width);
				}
				else
				{
					this._slideUp((current_page - page) * options.height);
				}
			}
			else
			{
				if (options.horizontal)
				{
					this._slideRight((page - current_page) * options.width);
				}
				else
				{
					this._slideDown((page - current_page) * options.height);
				}
			}
		},
	
		/**
		 * slideLeft: slide left horizontally a given distance
		 * @param distance (float)
		 */
		_slideLeft: function (distance)
		{
			this._isAnimating = true;
			var self = this;
			var carousel = this.element;
			var options = this.options;
			var width = options.width;
		
			if (distance === undefined)
			{
				distance = width;
			}
		
			carousel.find(options.items).animate({left: '+=' + distance + 'px'}, {duration: options.animationSpeed, easing: options.easing, complete: function(){
				self._isAnimating = false;
			
				// Fire callback
				self._trigger('change', null, {currentPage: self.currentPage() });
			
				carousel.find(options.next).removeClass('ui-state-disabled');
			
				if (options.paginated)
				{
				  self._selectPage();	
				}
			
				if (carousel.find(options.items).css('left') == '0px')
				{
					carousel.find(options.previous).addClass('ui-state-disabled');
				}
			}});
		},

		/**
		 * slideRight: slide right horizontally a given distance
		 * @param distance (float)
		 */
		_slideRight: function (distance)
		{
			this._isAnimating = true;
			var self = this;
			var options = this.options;
			var width = options.width;
			var carousel = this.element;
		
			if (distance === undefined)
			{
				distance = width;
			}
		
			carousel.find(options.items).animate({left: '-=' + distance + 'px'}, {duration: options.animationSpeed, easing: options.easing, complete: function(){
				self._isAnimating = false;
			
				// Fire callback
				self._trigger('change', null, {currentPage: self.currentPage() });
			
				carousel.find(options.previous).removeClass('ui-state-disabled');
			
				if (options.paginated)
				{
					self._selectPage();
				}
			
				// Figure out if last item is outside the bounds of the current window
				// if not, make next disabled
				var position = carousel.find(options.items).find(options.item + ':last').position();
				var left = parseFloat(carousel.find(options.items).css('left')) * -1;
				
				if (position.left < (left + width))
				{
					carousel.find(options.next).addClass('ui-state-disabled');
				}
			}});
		},
	
		/**
		 * slideUp: slide up vertically a given distance
		 * @param distance (float)
		 */
		_slideUp: function (distance)
		{
			this._isAnimating = true;
			var self = this;
			var carousel = this.element;
			var options = this.options;
		
			carousel.find(options.items).animate({top: '+=' + distance + 'px'}, {duration: options.animationSpeed, easing: options.easing, complete: function(){
				self._isAnimating = false;
			
				// Fire callback
				self._trigger('change', null, null);
			
				carousel.find(options.next).removeClass('ui-state-disabled');
			
				if (options.paginated)
				{
					self._selectPage();
				}
			
				if (carousel.find(options.items).css('top') == '0px')
				{
					carousel.find(options.previous).addClass('ui-state-disabled');
				}
			}});
		},

		/**
		 * Slide up vertically a given distance
		 * @param distance (float)
		 */
		_slideDown: function (distance)
		{
			this._isAnimating = true;
			var self = this;
			var carousel = this.element;
			var options = this.options;
		
			carousel.find(options.items).animate({top: '-=' + distance + 'px'}, {duration: options.animationSpeed, easing: options.easing, complete: function()
			{
				self._isAnimating = false;
			
				// Fire callback
				self._trigger('change', null, null);
			
				carousel.find(options.previous).removeClass('ui-state-disabled');
			
				if (options.paginated)
				{	
					self._selectPage();
				}
			
				var position = carousel.find(options.items).find(options.item + ':last').position();
				var top = parseFloat(carousel.find(options.items).css('top')) * -1;
			
				if (position.top < (top + options.height))
				{
					carousel.find(options.next).addClass('ui-state-disabled');
				}
			}});
		},

		/**
		 * Handles highlighting which page is selected
		 */
		_selectPage: function ()
		{
			var page = this.currentPage();
			var carousel = this.element;
			var options = this.options;
		
			carousel.find(options.page).removeClass('selected');
			carousel.find(options.page).eq(page).addClass('selected');
			carousel.find(options.current_page).html(page + 1);
		},
 
		/**
		 * Figure out which page we're currently on
		 */
		currentPage: function ()
		{
			var carousel = this.element;
			var options = this.options;
		
			if(options.horizontal)
			{
				var left = carousel.find(options.items).css('left');
				var page = 0;
				var width = options.width;
		
				if (left != 'auto')
				{
					var position = Math.abs(left.substr(0, left.length - 2));
					page = position / width;
				}	
			}
			else
			{
				var top = carousel.find(options.items).css('top');
				var page = 0;
				var height = options.height;
				
				if (top != 'auto')
				{
					var position = Math.abs(top.substr(0, top.length - 2));
					page = position / height;
				}
			}
		
			return page;
		},
	
		/**
		 * Returns which page a given index is on
		 * @param {int} index
		 */
		pageOfItem: function(index)
		{
			return Math.ceil(index / this.options.perpage);
		},
	
		/**
		 * Calculate width of one "page" of the carousel
		 */
		_calculateWidth: function ()
		{
			var carousel = this.element;
			var options = this.options;
		
			var width = carousel.find(options.container).css('width');

			// Width not set in CSS in IE6/IE7
			if (width == 'auto')
			{
				width = carousel.find(options.container).innerWidth() + options.padding;
			}
			else
			{
				width = parseFloat(width) + options.padding;
			}
		
			return width;
		},
	
		/**
		 * Calculate height of one "page" of the carousel
		 */
		_calculateHeight: function ()
		{
			var carousel = this.element;
			var options = this.options;
		
			// Calculate height of displayed area
			var height = carousel.find(options.container).css('height');
	
			// Width not set in CSS in IE6/IE7
			if (height == 'auto')
			{
				height = carousel.find(options.container).innerHeight() + options.padding;
			}
			else
			{
				height = parseFloat(height) + options.padding;
			}
		
			return height;
		}
	});

})(jQuery);
