/**
 * 
 * Designed and implemented by getpepper.com (http://www.getpepper.com/)
 * Copyright getpepper 2008
 * 
 * jquery.peppershow takes a structured set of images and descriptions and
 * presents them in a slideshow format. The structure used should take the
 * following form:
 *   <ol id="my-slideshow">
 *   
 *     <li>
 *     	 <img src="url-to-image-1">
 *       <p>description of image-1</p>
 *     </li>
 *     
 *     ...
 *     
 *     <li>
 *     	 <img src="url-to-image-n">
 *       <p>description of image-n</p>
 *     </li>
 *     
 *   </ol>
 *   
 * Caller Settings
 * =============
 * 
 * The following table lists caller-configurable attributes with accompanying
 * descriptions.
 * 
 * Attribute     Description
 * ---------     -----------
 * 
 *  width         Width of the div container that holds the slideshow. Normally,
 *                this will take the width of the widest image in the slideshow.
 *                Defaults to the width of the widest slideshow image.
 *               
 *  height        Height of the div container that holds the slideshow. Normally,
 *                this will take the height of the highest image in the
 *                slideshow. Defaults to the height of the tallest slideshow
 *                image).
 *               
 * previousLabel  The label used to allow the user to navigate to the previous
 *                image in the slideshow. Defaults to '&lt; prev' ('< pre').
 * 
 * nextLabel      The label used to allow the user to navigate to the next image
 *                in the slideshow. Defaults to 'next &gt;' ('next >').
 *                
 * loadingMessage Message displayed while the slideshow images are downloaded.
 *                Defaults to the message 'downloading slideshow...'.
 *                
 * loadingImage   URL to the image that is displayed while the slideshow images
 *                are downloaded (make this small so that it can be downloaded
 *                quickly and is therefore readily available, server- and
 *                network-permitting!). The default behaviour is to show no
 *                image.
 *
 */


(function($) {
	
	$.slideshow = {
			version: '1.2'
	};

	var settings;

	/**
	 * Slide data structure used to hold all slideshow slides.
	 */
	function Slide(listItem) {
		this.uri = null;
		this.image = null;
		this.desc = null;
		
		var self = this;
		
		function parse(listItem) {
			var slide = listItem.children();
			$(slide).each(function() {
				var current = $(this);
				if (current.is('img')) {
					self.uri = current.attr('src');
				} else {
					self.desc = $.trim(current.html());
				}
			});
		};
		
		parse(listItem);
		
		return this;
	};
	
	/**
	 * Display a loading message and optionally an accompanying image.
	 */
	function showLoading() {
		// Create the "loading" notification elements.
		settings.loadingContainer = $(
			'<div class="loading-container">' +
				'<p>' + settings.loadingMessage + '</p>' +
			'</div>'
		);
		if (settings.loadingImage) {
			loadingImage = new Image();
			$(loadingImage).load(function() {
				settings.loadingContainer.prepend(loadingImage);
			})
			.attr('src', settings.loadingImage);
		}
		settings.callerList.before(settings.loadingContainer);
	};
	
	/**
	 * Hide/remove the loading message.
	 */
	function removeLoading() {
		settings.loadingContainer.remove();
	};
	
	/**
	 * Hide the (ordered or unordered) list provided by the caller and iterate
	 * over each of the list items, creating a new Slide for each.
	 */
	function processImageList() {
		settings.callerList.hide();
		settings.callerList.find('li').each(function() {
			slide = new Slide($(this));
			settings.slides.push(slide);
		});
		settings.downloadsCount = settings.slides.length;
	};
	
	/**
	 * Process all the images in the slideshow in preparation for display.
	 */
	function processImages() {
		$(settings.slides).each(function(n) {
			this.image = new Image();
			var image = this.image;
			$(image).load(function() {
				sizeUpImage(image);
				settings.downloadsCount--;
				if (!settings.downloadsCount) {
					settings.callerList.trigger('imageDownloadComplete');
				}
			})
			.error(function() {
				// should do something, but nothing too instrusive for the user!
			})
			.attr('src', this.uri);
		});
	};

	/**
	 * get the max width and height of an image and use it to set the slide
	 * viewer.
	 */
	function sizeUpImage(image) {
		if (!settings.callerWidth) {
			settings.width = Math.max(settings.width, image.width);
		}
		if (!settings.callerHeight) {
			settings.height = Math.max(settings.height, image.height);
		}
	};
	
	/**
	 * Create the elements used to present the slideshow images, their
	 * descriptions and gallery.
	 */
	function createLayout() {
		// Create the slideshow components.
		settings.container = $('<div class="slideshow-container"/>');
		settings.slideViewer = $('<div class="slide-viewport"/>');
		settings.currentSlide = $('<img/>');
		settings.description = $('<p class="slide-description"/>');
		// Construct the components.
		settings.container.prepend(
				'<div class="slideshow-gallery">' +
					'<a href="#" class="slideshow-back">' +
						settings.previousLabel +
					'</a>' +
					'<a href="#" class="slideshow-next">' +
						settings.nextLabel +
					'</a>' +
					'<ol/>' +
				'</div>'
		);
		settings.slideViewer.prepend(settings.currentSlide);
		settings.container.prepend(settings.description);
		settings.container.prepend(settings.slideViewer);
		
		var gallery = settings.container.find('.slideshow-gallery ol');
		$(settings.slides).each(function(n) {
			gallery.append('<li><a href="#">' + (n + 1) + '</a></li>');
		});
		settings.back = settings.container.find('.slideshow-back');
		settings.next = settings.container.find('.slideshow-next');
		// set an index on each of the gallery items
		settings.galleryItems =
			settings.container.find('.slideshow-gallery li a');
		settings.galleryItems.each(function(n) {
			this.index = n;
		});
		// Initially set the container to hidden so that sizes can be taken on
		// the description element.
		settings.container.css('visibility', 'hidden');
		settings.callerList.before(settings.container);
	};

	/**
	 * Install the click handlers for the slideshow navigation elements.
	 */
	function setClickHandlers() {
		settings.galleryItems.click(function(event) {
			if (this.index != settings.currentIndex) {
				showImage(this.index);
			}
			event.preventDefault();
		});
		
		settings.back.click(function(event) {
			if (settings.currentIndex > 0) {
				showImage(settings.currentIndex - 1);
			}
			event.preventDefault();
		});

		settings.next.click(function(event) {
			if (settings.currentIndex < (settings.galleryItems.length - 1)) {
				showImage(settings.currentIndex + 1);
			}
			event.preventDefault();
		});
	};
	
	/**
	 * Show the slide and description with index 'index' and update the gallery.
	 */
	function showImage(index) {
		settings.currentSlide.fadeOut(function() {
			// Remove the old slide from view.
			settings.currentSlide.remove();
			// Add in the new slide.
			settings.slideViewer.prepend(settings.slides[index].image);
			settings.currentSlide = settings.slideViewer.find('img').css('visibility', 'hidden');
			settings.currentSlide.css({
				'margin-left':
					Math.floor(
						(settings.width - settings.slides[index].image.width) / 2),
				'margin-top':
					Math.floor(
						(settings.height - settings.slides[index].image.height) / 2)
			});
			settings.currentSlide.css('visibility', 'visible').hide();
			settings.currentSlide.fadeIn();
		});
		
		updateNavigation(index);
	};
	
	/**
	 * Set the display and state of the slideshow's navigation elements.
	 */
	function updateNavigation(index) {
		settings.description.html(settings.slides[index].desc);
		settings.container.find('.slideshow-gallery li.current').
			removeClass('current');
		$(settings.galleryItems[index]).parent().addClass('current');
		settings.currentIndex = index;
	};
	
	/**
	 * Slideshow dimensions are set when the all images are complete
	 * (downloaded), although this could be done at any time if the caller has
	 * set both the width AND height. The height and width of the slide viewport
	 * (the div element that wraps the currently shown image) includes any
	 * padding, border and margin that may have been set on image within the
	 * slide viewport.
	 */
	function setSlideshowSize() {
		// Calculate total height. including padding, border and margin.
		var totalHeight = settings.height;
		var widthProperties = [	'paddingTop',
								'paddingBottom',
								'borderTopWidth',
								'borderBottomWidth',
								'marginTop',
								'marginBottom'];
		for (index in widthProperties) {
			totalHeight += (parseInt($.curCSS(settings.currentSlide[0],
											widthProperties[index], 
											true)) || 0);
		}
		// Calculate total width, including padding, border and margin.
		var totalWidth = settings.width;
		var heightProperties = ['paddingLeft',
								'paddingRight',
								'borderLeftWidth',
								'borderRightWidth',
								'marginLeft',
								'marginRight'];
		var index = 0;
		for (index in heightProperties) {
			var p = heightProperties[index];
			totalWidth += (parseInt($.curCSS(settings.currentSlide[0], 
											heightProperties[index],
											true)) || 0);
		}
		// Apply the width and height to the viewport elements.
		settings.slideViewer.css({ width: totalWidth, height: totalHeight });
		settings.description.css('width', totalWidth);
		settings.container.find('.slideshow-gallery').css('width', totalWidth);
		// Get and set the max height of the description element. 
		var maxHeight = 0;
		$(settings.slides).each(function(n) {
			maxHeight = Math.max(maxHeight,
					settings.description.html(this.desc).height());
		});
		settings.description.height(maxHeight);
	};
	
	/**
	 * Start running the slideshow.
	 */
	function beginSlideshow() {
		if (settings.downloadsCount) {
			settings.callerList.bind('imageDownloadComplete', function() {
				removeLoading();
				setSlideshowSize();
				settings.container.css('visibility', 'visible');
				showImage(settings.currentIndex);
			});
		} else {
			removeLoading();
			setSlideshowSize();
			settings.container.css('visibility', 'visible');
			showImage(settings.currentIndex);
		}
	};
	
	/**
	 * The plug-in function
	 */
	$.fn.peppershow = function(callerSettings) {
		settings = $.extend({
			callerWidth:	0,
			callerHeight:	0,
			previousLabel:	'&lt; prev',
			nextLabel:		'next &gt;',
			loadingMessage:	'downloading slideshow...',
			loadingImage:	null
		}, callerSettings || {});
		settings.callerList		= this;
		settings.width			= settings.callerWidth;
		settings.height			= settings.callerHeight;
		settings.slides			= new Array();
		settings.container		= null;
		settings.loadingContainer = null;
		settings.slideViewer	= null;
		settings.currentSlide	= null;
		settings.description	= null;
		settings.back			= null;
		settings.next			= null;
		settings.galleryItems	= null;
		settings.downloadsCount	= 0;
		settings.currentIndex	= 0;

		showLoading();
		processImageList();
		processImages();
		createLayout();
		setClickHandlers();
		beginSlideshow();
		
		return this;
	};
	
}) (jQuery);
