// TODO: - current_page should be an index

/**
 * Pageable: generic behaviour for things with pagination
 * @author Josh Johnston josh@xhtmlized.com
 * @namespace
 */
var Pageable = function(){
	/*global jQuery */

	var self = /** @scope Pageable */{
		/**
		 * Create an instance
		 * @param {hash} settings
		 */
		create: function(settings) {
			var instance = settings;

			// set default callbacks
			instance.callbacks.createTween = instance.callbacks.createTween || self.createTween;
			instance.callbacks.createPageMap = instance.callbacks.createPageMap || self.createPageMap;

			// set the current page to default
			instance.current_page = instance.config.default_page;

			// group items into pages
			instance.page_map = instance.callbacks.createPageMap(instance);

			// more than 1 page? setup behaviour
			if (instance.page_map.length > 1) {
				self.setup(instance);
			}

			return instance;
		},

		/**
		 * Setup pageable behaviour
		 * @param {hash} instance
		 */
		setup: function(instance) {
			// position items
			self.positionItems(instance);

			// make the holder big enough to fit all pages
			var viewportWidth = instance.config.viewport_width;
			var totalWidth = instance.page_map.length * viewportWidth;
			instance.itemholderElm.css({width: totalWidth+'px'});

			// enter state: "has_pages"
			instance.baseElm.addClass('has_pages');

			// show only items of the current page
			self.showSinglePage(instance, instance.current_page);

			// initialize controls
			self.updateControls(instance);

			// setup click behaviour
			instance.prevElm.click(function(event) { self.handleNavClick(event, instance); return false; });
			instance.nextElm.click(function(event) { self.handleNavClick(event, instance); return false; });
		},

		/**
		 * Create a map of items to pages
		 * @param {hash} instance
		 * @return {array}
		 */
		createPageMap: function(instance) {
			var viewportWidth = instance.config.viewport_width;
			var spaceBetweenItems = instance.config.space_between_items;
			var items = instance.itemholderElm.find('li');
			var pageWidth = 0;
			var pageMap = [[]];
			var pageCounter = 0;
			var width;
			for (var i=0,len=items.length; i<len; i++) {
				// calculate the item's width and spacing
				width = items.eq(i).width() + spaceBetweenItems;

				// will this item fit on the current page?
				if (pageWidth + width <= viewportWidth) {
					// keep track of page width
					pageWidth += width;
				}
				// start a new page
				else {
					pageWidth = width;
					pageCounter ++;
					pageMap[pageCounter] = [];
				}

				// add item to the page map
				pageMap[pageCounter].push(i);
			}
			return pageMap;
		},

		/**
		 * Position items in a horizontal row
		 * @param {hash} instance
		 */
		positionItems: function(instance) {
			var items = instance.itemholderElm.find('li');
			var viewportWidth = instance.config.viewport_width;
			var spaceBetweenItems = instance.config.space_between_items;
			var pageMap = instance.page_map;

			var pageCounter, itemCounter;
			var numPages = pageMap.length;
			var numItems;
			var itemLeft;
			var currentLeft = 0;
			var i=0;
			var itemElm;
			for (pageCounter=0; pageCounter<numPages; pageCounter++) {
				for (itemCounter=0, numItems = pageMap[pageCounter].length; itemCounter<numItems; itemCounter++) {
					itemElm = items.eq(i);

					// first item in the page
					if (itemCounter == 0) {
						// position the item at the start of the new page
						currentLeft = viewportWidth * pageCounter;
					}

					// position the item
					itemElm.css({left: currentLeft+'px'});

					// move to the next position
					currentLeft += itemElm.width() + spaceBetweenItems;

					// update the item index
					i++;
				}
			}
		},

		/**
		 * Show the items of a single page
		 * @param {hash} instance
		 * @param {int} pageNum
		 */
		showSinglePage: function(instance, pageNum) {
			var pageIndex = pageNum-1;
			var numItems = instance.page_map[pageIndex].length;
			var startIndex = instance.page_map[pageIndex][0];
			var endIndex = instance.page_map[pageIndex][numItems-1];
			self.showItemsInRange(instance, startIndex, endIndex);
		},

		/**
		 * Show items within a certain range
		 * @param {hash} instance
		 * @param {index} startIndex
		 * @param {index} endIndex
		 */
		showItemsInRange: function(instance, startIndex, endIndex) {
			var holderElm = instance.itemholderElm;
			var isAnimating = instance.baseElm.hasClass('animating');
			holderElm.find('li').each(function(index, elm) {
										  elm = jQuery(elm);
										  if (index >= startIndex && index <= endIndex) {
											  elm.css({visibility: 'visible'});
										  }
										  // if animation is in progress, don't hide anything
										  else if (!isAnimating) {
											  elm.css({visibility: 'hidden'});
										  }
									  });
		},

		/**
		 * Set the current page
		 * @param {hash} instance
		 * @param {int} page
		 */
		setCurrentPage: function(instance, page) {
			self.animatePageChange(instance, instance.current_page, page);
			instance.current_page = page;
			self.updateControls(instance);
		},

		/**
		 * Update the pagination controls
		 * @param {hash} instance
		 */
		updateControls: function(instance) {
			// start by enabling prev/next buttons
			instance.prevElm.removeClass('disabled');
			instance.nextElm.removeClass('disabled');

			// {current_page} == 1?  Disable the "previous" arrow
			if (instance.current_page == 1) {
				instance.prevElm.addClass('disabled');
			}

			// {current_page} == {num_pages}?  Disable the "next" arrow
			if (instance.current_page == instance.page_map.length) {
				instance.nextElm.addClass('disabled');
			}
		},

		/**
		 * Handle a click on a navigation button
		 * @param {DOMevent} event
		 * @param {hash} instance
		 */
		handleNavClick: function(event, instance) {
			var elm = jQuery(event.srcElement || event.target);

			if (!elm.is('a')) {
				elm = elm.parents('a:eq(0)');
				if (!elm.is('a')) {
					return true;
				}
			}

			if (elm.hasClass('disabled')) {
				return;
			}
			else if (elm.hasClass('prev') || elm.attr('href').match(/\#previous$/)) {
				self.setCurrentPage(instance, instance.current_page-1);
			}
			else if (elm.hasClass('next') || elm.attr('href').match(/\#next/)) {
				self.setCurrentPage(instance, instance.current_page+1);
			}
		},

		/**
		 * Get the index of the first item in the page
		 * @param {hash} instance
		 * @param {int} pageNum
		 */
		getFirstItemIndex: function(instance, pageNum) {
			return instance.page_map[pageNum-1][0];
		},

		/**
		 * Get the index of the last item in the page
		 * @param {hash} instance
		 * @param {int} pageNum
		 */
		getLastItemIndex: function(instance, pageNum) {
			var numItems = instance.page_map[pageNum-1].length;
			return instance.page_map[pageNum-1][numItems-1];
		},

		/**
		 * Animate the change in pages
		 * @param {hash} instance
		 * @param {int} currentPage Current page number, starting at one
		 * @param {int} newPage New page number, starting at one
		 */
		animatePageChange: function(instance, currentPage, newPage) {
			// stop any current tween
			if (instance.tweenOut) {
				instance.tweenOut.stop();
			}

			// show only items of the current page and the new page
			var minPage = currentPage < newPage ? currentPage : newPage;
			var maxPage = currentPage > newPage ? currentPage : newPage;
			var startIndex = self.getFirstItemIndex(instance, minPage);
			var endIndex = self.getLastItemIndex(instance, maxPage);
			self.showItemsInRange(instance, startIndex, endIndex);

			// slide the holder
			var holderElm = instance.itemholderElm;
			var currentLeft = 1*holderElm.css('left').replace('px', '');
			var newLeft =  -instance.config.viewport_width * (newPage-1);

			// enter state "animating"
			instance.baseElm.addClass('animating');

			holderElm.animate({left: newLeft+'px'},
							   {easing: 'easeInOutSine',
								duration: instance.config.tween_duration * 1000,
								complete: function(){
									// exit state "animating"
									instance.baseElm.removeClass('animating');

									// ensure the holder is at the right position
									holderElm.css({left: newLeft+'px'});

									// show only items from the current page
									self.showSinglePage(instance, newPage);
								}});
		}
	};

	return self;
}();

