/**
 *  jQuery UI Carousel plugin
 *
 *  @author Zach Waugh <zwaugh@gmail.com>
 *  @version 1.2.1
 *  @require ui.core.js (jQuery UI Core)
 * 
 *  Pages are 0 indexed - i.e. page one = page 0
 *  Last updated: 6/12/09
 */

(function($) {

$.widget("ui.carousel", {

	_init: 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).size();
		
			// Figure out how many pages are needed
			// Based on total / x, where x = items per page
			options.pages = Math.ceil(total / options.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); });
	},
	
	/**
	 * 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.options.controlsParent.prepend(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;
		}
		
		// 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.duration, 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.duration, 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: '+=' + options.height + 'px'}, {duration: options.duration, 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: '-=' + options.height + 'px'}, {duration: options.duration, 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;
		
		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;
		}

		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;
	}
});

$.extend($.ui.carousel, {
	version: "1.7.1",
	getter: 'pageOfItem currentPage',
	defaults: {
		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',
		duration: 500,
		padding: 0,
		paginated: false,
		perpage: false,
		controls: false,
		controlsParent: null,
		easing: 'swing',
		horizontal: true
	}
});

})(jQuery);
