﻿// determines where the page change originates from
var PageChangeSource = {
	Up : 0,	Item : 1,	Down : 2,	Slideshow : 3}

// determines the location of the carousel item
var CarouselItemLocation = {
	Top : 0,	Middle : 1,	Bottom : 2}

$.fn.infiniteCarousel = function(options) {
	var TIMER_LABEL = 'HeroCarousel';

	var settings = {
		// events
		onPageChanged: null,
		onPageChanging: null,
		// slideshow settings
		autoSlideshow: true,
		slideshowInterval: 5000,
		totalRepitions: 2,
		// fading intervals
		backgroundFadeInterval: 1000,
		textFadeOutInterval: 1000,
		textFadeInInterval: 500,
		// selectors
		carouselSelector: 'div#heroCarousel',
		textSelector: 'div#heroText'
	}

	$.extend(settings, options);

	function repeat(str, num) {
		return new Array(num + 1).join(str);
	}

	// triggers the passed event, if a valid function
	function triggerEvent(event, params) {
		if (event != null && typeof (event) == 'function')
			event(params);
	}

	// updates the hero image and text
	function updateHero(id) {
		// update hero text
		$('div.activeText', settings.textSelector).fadeOut(settings.textFadeOutInterval, function() {
			$('div#heroText div#heroText_' + id).fadeIn(settings.textFadeInInterval).addClass('activeText');
		}).removeClass('activeText');

		// update hero background
		var backgroundCssClass = 'heroBackground_' + id;
		$('div#heroFader').addClass(backgroundCssClass).css('display', 'none').fadeIn(settings.backgroundFadeInterval, function() {
			$('div#hero').attr('class', backgroundCssClass);
			$(this).removeClass(backgroundCssClass).css('display', 'none');
		});
	}

	return this.each(function() {
		var $wrapper = $(settings.carouselSelector + ' > div.carouselListWrapper', this).css('overflow', 'hidden'),
			$slider = $wrapper.find('> ul'),
			$items = $slider.find('> li'),
			$single = $items.filter(':first'),
			singleHeight = $single.outerHeight(),
			visible = 1,
			numberOfItems = $items.length;

		var currentPage = 1,
			pages = Math.ceil($items.length / visible);

		// --------------------------------------------------
		// 1. Pad so that 'visible' number will always be seen, otherwise create empty items
		if (($items.length % visible) != 0) {
			$slider.append(repeat('<li class="empty" />', visible - ($items.length % visible)));
			$items = $slider.find('> li');
		}

		// --------------------------------------------------
		// 2. Top and tail the list with 'visible' number of items, top has the last section, and tail has the first
		$items.filter(':first').before($items.slice(-2).clone().addClass('cloned'));
		$items.filter(':last').after($items.slice(0, numberOfItems).clone().addClass('cloned'));
		$items = $slider.find('> li'); // reselect

		// --------------------------------------------------
		// 3. Set the left position to the first 'real' item
		$wrapper.scrollTop(singleHeight * 2);

		// sets all active carousel item elements
		function setAllActiveCarouselItems() {
			var id = $('li.activeCarouselItem').attr('id');
			$('li#' + id).addClass('activeCarouselItem');
		}

		// updates the active carousel item, depending on the page change orientation
		function updateActiveCarouselItem(index, onlyLast) {
			var id = $($items[index]).attr('id');

			$items.removeClass('activeCarouselItem');
			if (onlyLast) {
				$('li#' + id).filter(':last').addClass('activeCarouselItem');
			} else {
				$('li#' + id).addClass('activeCarouselItem');
			}

			updateHero(id);
		}

		// deteremines the location of the click item (top, middle, or bottom)
		function getClickedItemLocation(elem) {
			var index = $items.index(elem) - (currentPage + 1);
			return index;
		}

		// --------------------------------------------------
		// 4. paging function
		function gotoPage(page, pageChangeSource, forcedDir, itemLocation) {
			var dir = (forcedDir ? forcedDir : page < currentPage ? -1 : 1),
				n = Math.abs(currentPage - page);

			var up = singleHeight * dir * visible * n;
			if (itemLocation == CarouselItemLocation.Top && page < 0) {
				up -= singleHeight;
			}

			// if the page is the last item, change the page offset
			var offset = (page > numberOfItems && forcedDir ? 1 : 3);
			//console.log('Going to page: ' + page);

			$wrapper.filter(':not(:animated)').animate({
				scrollTop: '+=' + up
			}, (itemLocation == CarouselItemLocation.Top ? 1500 : 1000), function() {
				setAllActiveCarouselItems();

				if (page < 0) {
					$wrapper.scrollTop(singleHeight * pages);
					page = pages - 1;
				} else if (page == 0) {
					$wrapper.scrollTop(singleHeight * visible * (pages + 1));
					page = pages;
				} else if (page > pages) {
					$wrapper.scrollTop(singleHeight * 2);
					page = 1;
				}

				// set the current page and trigger the page changed event
				currentPage = page;
				triggerEvent(settings.onPageChanged, { page: currentPage, elem: $items[page + offset] })
			});

			// trigger the page changing event and update the carousel and hero
			triggerEvent(settings.onPageChanging, { page: currentPage, elem: $items[page + offset] });
			updateActiveCarouselItem(page + offset, (pageChangeSource == PageChangeSource.Down ? true : false));

			return false;
		}

		// --------------------------------------------------
		// 5. Bind to the forward, back, and item buttons
		$('a.up', this).click(function() {
			stopTimer();
			return gotoPage(currentPage - 1, PageChangeSource.Up);
		});

		$('a.down', this).click(function() {
			stopTimer();
			return gotoPage(currentPage + 1, PageChangeSource.Down);
		});

		$items.children('a').bind('click', function() {
			stopTimer();
			var itemLocation = getClickedItemLocation($(this).parent());

			switch (itemLocation) {
				case CarouselItemLocation.Top:
					gotoPage(currentPage - 2, PageChangeSource.Item, -1, CarouselItemLocation.Top);
					break;
				case CarouselItemLocation.Middle:
					gotoPage(currentPage - 1, PageChangeSource.Item, -1, CarouselItemLocation.Middle);
					break;
				case CarouselItemLocation.Bottom:
					return;
			}

			return false;
		});

		// --------------------------------------------------
		// 6. Handle slideshow settings
		var numberOfRepitions = 0;

		// pauses the timer, used for mouseout
		function pauseTimer() {
			$(document).stopTime(TIMER_LABEL);
		}

		// starts the timer, initiation and mouseover
		function startTimer() {
			if (settings.autoSlideshow) {
				$(document).everyTime(settings.slideshowInterval, TIMER_LABEL, updateSlideshow);
			}
		}

		// stops the timer completely
		function stopTimer() {
			settings.autoSlideshow = false;
			$(document).stopTime(TIMER_LABEL);
			$($wrapper).unbind('mouseover', pauseTimer).unbind('mouseout', startTimer);
		}

		// updates the slideshow page, repitions, and continuation
		function updateSlideshow() {
			// if the current page is 2, we're back at the start
			if (currentPage == 2) numberOfRepitions++;

			// check we're still supposed to carry on
			if (numberOfRepitions >= settings.totalRepitions) stopTimer();

			gotoPage(currentPage - 1, PageChangeSource.Slideshow);
		}

		// bind pausing of the timer, and then start the timer
		$($wrapper).bind('mouseover', pauseTimer).bind('mouseout', startTimer);
		startTimer();
	});
};
