(function($){
	/**
	 * @projectDescription       Setups up an slideshow module container based upon data from a JSON web service.
	 * 
	 * @param (Object) options - Object of key/value pairs to override the plugin defaults
	 * @return (Object)          Returns the original jQuery collection
	 */
	$.fn.userSlideshow = function (options) {
		var primaryArguments = arguments;
		
		return this.each(function(index){
			// Setup reference to the container object for future calls
			var $ssObject = $(this);
			
			function fn_getOpts () {
				return $ssObject.data('userSlideshow');
			}
			function fn_saveOpts () {
				$ssObject.data('userSlideshow', opts);
			}
			
			var opts = fn_getOpts();
			
			
			/**
			 * Intializes the slideshow, retrieving slide data from a JSON feed and creating DOM elements
			 */
			function fn_initializeSlideshow () {
				// If a URL isn't defined, then build it based on the id of the container element
				if (!opts.url) {
					opts.slideshowId = $ssObject.attr('id').replace(/[a-z]+_/i, '');
					opts.url = '/slideShowJSON' + opts.slideshowId + '.json?jsonpcallback=?';
				}
				
				$.getJSON(opts.url, {}, function(data){
					if (typeof data == 'object') {
						opts.data = data;
						
						var $container = $ssObject.find('.' + opts.slideContainer);
						$container.css('z-index', 2 * (data.length + 1));
						var containerWidth = $container.width();
						var containerHeight = $container.height();
						
						if (opts.useHighlight) {
							// Setup highlight element, placing it behind all other containers for the time being
							$(document.createElement('div'))
								.addClass('highlight')
								.css({
									'position' : 'absolute',
									'z-index'  : '0',
									'width'    : $container.width() + 'px',
									'height'   : $container.height() + 'px'
								}).hide()
								.appendTo($container);
						}
						
						$.each(data, function(index){
							var xOffset = Math.floor((containerWidth - this.thumbnailWidth) / 2);
							var yOffset = Math.floor((containerHeight - this.thumbnailHeight) / 2);
							
							// Setup slideshow control links
							var $navLink = $(document.createElement('a'));
							$navLink.attr('href', '#slide_' + this.id);
							if ( opts.useImageNav ) {
								$navLink.html( '<img src="' + this.ittiePath + '" width="' + this.ittieWidth + '" height="' + this.ittieHeight + '" />' );
							} else {
								$navLink.text(index + 1);
							}
							$ssObject.find('.toolbar .numbers .placeHolder').append($navLink);
							
							// Setup slideshow images and position them inside of the container
							var $img = $(document.createElement('img'));
							$img
								.attr({
									'src'    : this.thumbnailPath,
									'width'  : this.thumbnailWidth,
									'height' : this.thumbnailHeight
								});
							
							var $imgLink = $(document.createElement('a'));
							$imgLink
								.attr({
									'id'    : 'slide_' + this.id,
									'href'  : this.fullsizePath,
									'title' : (
										this.title.length && this.caption.length ?
											this.title + ' :: ' + this.caption :
											this.title.length ?
												this.title :
												this.caption.length ?
													this.caption :
													''
									),
									'rel'   : (this.hasFullsize !== '0' ? opts.uId : '')
								}).css({
									'display'     : 'block',
									'position'    : 'absolute',
									'z-index'     : (data.length - index),
									'margin-left' : xOffset + 'px',
									'margin-top'  : yOffset + 'px'
								}).append($img)
								.hide();
							
							if (this.hasFullsize !== '0') {
								$imgLink.addClass('hasFullsize');
							}
							
							$container.append($imgLink);
							
							// Track whether or not any of the slides have details information as it will be used
							// to determine whether or not to show the details panel for the entire slideshow.
							if (!opts.hasDetails && (this.title.length > 0 || this.caption.length > 0)) {
								opts.hasDetails = true;
							}
						});
						// show the first page of nav links
						fn_switchPage( 1 );
						
						// Add fancybox effect to all images that have fullsize images. (if available)
						if (opts.hasFancybox) {
							$container.find('a.hasFullsize').fancybox({
								'onComplete'  : function ( ) { $("#fancy_outer").bind('contextmenu', function(){ return false; }); },
								'onClosed'    : function ( ) { $("#fancy_outer").unbind('contextmenu'); }
							});
						}
						
						// Setup a preload event for the first image so that we wait until the image is ready before we attempt to show it.
						var preloader = new Image();
						preloader.onload = function () {
							this.onload = null;
							fn_showControls();
						};
						preloader.src = data[0].thumbnailPath;
					}
				});
			}
			/**
			 * Destroys slideshow, removing event handlers and stored data
			 */
			function fn_destroySlideshow () {
				clearTimeout(opts.timeout);
				$ssObject.find("*").unbind('click').stop(true);
				$ssObject.removeData('userSlideshow');
			}
			
			/*
			 * Shows the controls for the slideshow
			 */
			function fn_showControls () {
				// If we don't have details, change the label text and remove the expand/shrink button
				// for details.
				if (!opts.hasDetails) {
					$ssObject.find('.detailsPanel .control')
						.find('label').html('&nbsp;')
						.end()
						.find('a:not(.fullsize)').remove();
				}
				var $curActiveDetails = $ssObject.find('.detailsPanel .ss_details .inner');
				$ssObject.find('.detailsPanel').slideDown('slow');
				
				$ssObject.queue(function(){
					// Process these two in the primary queue because the .click() event of the ".numbers a" objects won't be
					// setup until after the first fadeOut animation has completed.
					$(".loadingText", this).fadeOut('slow', function(){
						fn_assignPrimaryEvents();
						$ssObject.find(".toolbar .numbers .placeHolder").fadeIn('slow');
						$ssObject.dequeue();
					});
				}).queue(function(){
					// Add in belatedPNG fix on the newly created images since they won't be found in the document block setup
					if (opts.hasBelatedPng) {
						$ssObject.find('.png_bg, img').each(function(){
							DD_belatedPNG.fixPng(this);
						});
					}
					$ssObject.dequeue();
				}).queue(function(){
					// Begin the queue based auto play if specified, otherwise treat it as if the user clicked on the first image's link
					if (opts.useAutoPlay) {
						event_togglePlay.call(null, null);
					} else {
						$ssObject.find('.toolbar .numbers a:first').click();
					}
					$ssObject.dequeue();
				});
			}
			/**
			 * Assigns various click handlers for slideshow controls
			 */
			function fn_assignPrimaryEvents () {
				// If we're using fancybox, just pause the slideshow.  otherwise, pass into the event that
				// will look for thickbox or pop a standalone window
				$ssObject.find('.' + opts.slideContainer + ' a.hasFullsize')
					.click(
						( opts.hasFancybox ? fn_pauseSlideshow : event_openFullsize )
					);
				
				$ssObject.find('.detailsPanel .minMax, .detailsPanel .minMaxLabel')
					.click(event_toggleDetails)
					.addClass('isMined');
				$ssObject.find('.detailsPanel .fullsize')
					.click(event_openFullsize);
				
				$ssObject.find('.toolbar .numbers a')
					.click(event_manualSpecificSwitch);
				$ssObject.find('.toolbar .playPause')
					.click(event_togglePlay)
					.addClass('isPaused');
				$ssObject.find('.toolbar .previous')
					.click(event_manualDirectionSwitch)
					.addClass('activePrevious');
				$ssObject.find('.toolbar .next')
					.click(event_manualDirectionSwitch)
					.addClass('activeNext');
				$ssObject.find('.toolbar .previousPage')
					.click(event_manualPageSwitch)
					.addClass('activePreviousPage');
				$ssObject.find('.toolbar .nextPage')
					.click(event_manualPageSwitch)
					.addClass('activeNextPage');
			}
			
			
			/**
			 * Sets up the "auto switch" feature by setting a JS timeout
			 */
			function fn_queueAutoSwitch () {
				opts.timeout = setTimeout(function(){fn_switchSlide();}, opts.pause);
			}
			
			/**
			 * Set play state
			 */
			function fn_playSlideshow () {
				$ssObject.queue(function(){
					$ssObject.find('.toolbar .playPause').addClass('isPlaying').removeClass('isPaused');
					opts.isPlaying = true;
					$(this).dequeue();
					fn_switchSlide();
				});
			}
			/**
			 * Set pause state
			 */
			function fn_pauseSlideshow () {
				$ssObject.queue(function(){
					clearTimeout(opts.timeout);
					opts.isPlaying = false;
					
					$ssObject.find('.toolbar .playPause').addClass('isPaused').removeClass('isPlaying');
					$(this).dequeue();
				});
			}

			/**
			 * Show a set of nav links based on page index
			 *
			 * @param (int) targetPage - page we're switching to
			 */
			function fn_switchPage( targetPage ) {
				var $navLinks = $ssObject.find('.placeHolder a'),
				    start     = (targetPage - 1) * opts.navLinksPerPage,
				    end       = start + opts.navLinksPerPage;
				$navLinks.hide();
				$navLinks.slice( start, end ).show();
			}

			/**
			 * Switch to a given slide
			 * @param (Object) $slide   - (optional) jQuery object wrapper for the slide to be displayed
			 * @param (bool)   isManual - (optional) Bool to indicate whether or not the slide change was a manual click vs. an auto-switch
			 */
			function fn_switchSlide ($slide, isManual) {
				var $curActiveSlide  = $ssObject.find('.' + opts.slideContainer + ' a.active');
				var $curActiveDetails = $ssObject.find('.detailsPanel .ss_details .inner').css('z-index','1');
				
				// Create a new details object so we can do a smooth fade between the old and new blocks
				var $newDetails = $curActiveDetails.clone();
				$newDetails.removeAttr("style").css('z-index', '2').hide().insertAfter($curActiveDetails);
				
				// If a slide isn't defined, then this is an auto-switch, attempt to determine the next slide
				if ($slide === undefined || $slide === null) {
					// If no active slide, then this is an initial play
					if ($curActiveSlide.length === 0) {
						$slide = $ssObject.find('.' + opts.slideContainer + ' a:first');
					} else {
						$slide = $curActiveSlide.next('a');
						// Flip back to the beginning of the slideshow
						if ($slide.length === 0) {
							$slide = $curActiveSlide.prevAll('a:last');
						}
					}
				}
				
				if (opts.effect == 'fade' && $slide.length !== 0) {
					var slideshowImageId    = $slide.attr('id').replace(/\#slide_/, '');
					var slideIndex          = $slide.prevAll('a').length;
					var slideData           = opts.data[slideIndex];
					if ($curActiveSlide.length !== 0) {
						var curActiveSlideIndex = $curActiveSlide.prevAll('a').length,
						    curActivePage       = Math.ceil( ( curActiveSlideIndex + 1 ) / opts.navLinksPerPage ),
						    targetPage          = Math.ceil( ( slideIndex + 1 ) / opts.navLinksPerPage );
						// compare pages to see if we need to trigger a page switch
						if ( curActivePage != targetPage ) {
							fn_switchPage( targetPage );
						}
						
						if (opts.useHighlight) {
							$ssObject.find('.' + opts.slideContainer + ' .highlight').fadeIn(opts.speed);
						}
						$ssObject.queue(function(){
							// dequeue immediately if we're not using a highlight so the two animations transition into one another smoothly
							// otherwise, dequeue after the first image has finished animating
							if (!opts.useHighlight) {
								$ssObject.dequeue();
								$curActiveSlide
									.add($ssObject.find('a[href="#' + $curActiveSlide.attr('id') + '"]'))
									.removeClass("active");
							}
							
							// Transition the details panel with the slide change if it's visible
							if (opts.areDetailsVisible) {
								$curActiveDetails.fadeOut(opts.speed, function(){
									$(this).remove();
								});
							} else {
								$curActiveDetails.remove();
							}
							
							$curActiveSlide.fadeOut(opts.speed, function(){
								if (opts.useHighlight) {
									$ssObject.dequeue();
									$curActiveSlide
										.add($ssObject.find('a[href="#' + $curActiveSlide.attr('id') + '"]'))
										.removeClass("active");
								}
							});
						});
					} else {
						// This will only occur on the inialization of the slideshow, simply remove the current element.
						$curActiveDetails.remove();
					}
					$ssObject.queue(function(){
						if (slideData.hasFullsize !== '0') {
							$ssObject.find(".detailsPanel .fullsize").fadeIn(opts.speed);
						} else {
							$ssObject.find(".detailsPanel .fullsize").fadeOut(opts.speed);
						}
						
						if (slideData.title.length === 0 && slideData.caption.length === 0) {
							$newDetails.find('h5').text('');
							$newDetails.find('div').text('No details for this slide.');
						} else {
							$newDetails.find('h5').text(slideData.title);
							$newDetails.find('div').html(slideData.caption.replace(/\r?\n/g, '<br/>'));
						}
						
						if (opts.areDetailsVisible) {
							$newDetails.fadeIn(opts.speed);
						}
						
						$slide
							.add($ssObject.find('a[href="#' + $slide.attr('id') + '"]'))
							.addClass("active");
						if (opts.useHighlight) {
							$ssObject.find('.' + opts.slideContainer + ' .highlight').fadeOut(opts.speed);
						}
						$slide.fadeIn(opts.speed, function(){
							// If the slide switch isn't a manual click, queue up the next slide switch
							if (isManual === undefined || isManual === null) {
								fn_queueAutoSwitch();
							}
							$ssObject.dequeue();
						});
					});
				}
			}
			
			
			/**
			 * Switches between the play/pause states of the slideshow
			 * @param (Object) e - Original event object
			 */
			function event_togglePlay (e) {
				if (opts.isPlaying) {
					fn_pauseSlideshow();
				} else {
					fn_playSlideshow();
				}
				
				// Only trigger the blur if this event call is a true event.  We call it programatically in
				// fn_initializeSlideshow
				if (e !== null) {
					$(this).blur();
					return false;
				}
			}
			/**
			 * Switches between the visible/hidden states of the details panel
			 * @param (Object) e - Original event object
			 */
			function event_toggleDetails (e) {
				$ssObject.queue(function(){
					var $details = $ssObject.find('.ss_details');
					var $control = $details.closest('.detailsPanel').find(".control");
					if ($details.is(':not(:animated)')) {
						if (opts.areDetailsVisible) {
							$control.find("label span").text("Show");
							$control.find(".minMax").addClass("isMined").removeClass("isMaxed");
							$details.find('.inner').slideUp(opts.speed);
							$details.slideUp(opts.speed, function(){
								opts.areDetailsVisible = false;
								
								$ssObject.dequeue();
							});
						} else {
							$control.find("label span").text("Hide");
							$control.find(".minMax").addClass("isMaxed").removeClass("isMined");
							$details.find('.inner').slideDown(opts.speed);
							$details.slideDown(opts.speed, function(){
								opts.areDetailsVisible = true;
								
								$ssObject.dequeue();
							});
						}
					} else {
						$(this).dequeue();
					}
				});
				$(this).blur();
				return false;
			}
			/**
			 * Opens up the fullsize version of an image, using Thickbox if available.
			 * @param (Object) e - Original event object
			 */
			function event_openFullsize (e) {
				$ssObject.queue(function(){
					var i = 0;
					var $activeSlide = $ssObject.find('.' + opts.slideContainer + ' a.active');
					if ($activeSlide.hasClass('hasFullsize')) {
						fn_pauseSlideshow();
						
						if (opts.hasFancybox) {
							// If we have fancybox, just click on the active slide and let fancybox handle the rest
							$activeSlide.click();
						} else if (opts.hasThickbox) {
							// Invoke thickbox manually because tb_init can't be called properly on the dynamic
							// content.
							tb_show($activeSlide.attr('title'), $activeSlide.attr('href'), $activeSlide.attr('rel'));
						} else {
							// If thickbox and fancybox aren't available, just pop a new window for the image.
							var slideshowImageId = parseInt($activeSlide.attr('id').replace(/slide_/, ''), 10);
							var slideData = null;
							for (i = 0; i < opts.data.length; i++) {
								if (opts.data[i].id == slideshowImageId) {
									slideData = opts.data[i];
									break;
								}
							}
							window.open(
								slideData.fullsizePath,
								"userSlideshowFullsize",
								"location=0,status=0,scrollbars=0,width=" + slideData.fullsizeWidth + ",height=" + slideData.fullsizeHeight
							);
						}
					}
					$ssObject.dequeue();
				});
				$(this).blur();
				e.preventDefault();
				return false;
			}
			
			/**
			 * Process a manual switch to a specific slide, triggered by clicking on a slide number
			 * @param (Object) e - Original event object
			 */
			function event_manualSpecificSwitch (e) {
				if (opts.isPlaying) {
					fn_pauseSlideshow();
				}
				var $target = $(e.target);
				if ( $target.is('img') ) {
					$target = $target.closest('a');
				}
				var $slide  = $( $target.attr('href') );
				if (!$slide.hasClass('active')) {
					fn_switchSlide($slide, true);
				}
				$(this).blur();
				return false;
			}
			/**
			 * Process a manual switch in a given direction, triggered by clicking on the previous/next buttons
			 * @param (Object) e - Original event object
			 */
			function event_manualDirectionSwitch (e) {
				$ssObject.queue(function(){
					if (opts.isPlaying) {
						fn_pauseSlideshow();
					}
					
					var $slide;
					if ($(e.target).hasClass('previous')) {
						$slide = $ssObject.find('.' + opts.slideContainer + ' .active').prev('a');
					} else {
						$slide = $ssObject.find('.' + opts.slideContainer + ' .active').next('a');
					}
					
					if ($slide.length !== 0 && !$slide.hasClass('active')) {
						fn_switchSlide($slide, true);
					}
					$(this).dequeue();
				});
				
				$(this).blur();
				return false;
			}
			/**
			 * Process a manual page switch in a given direction, triggered by clicking on the page previous/next buttons
			 * @param (Object) e - Original event object
			 */
			function event_manualPageSwitch (e) {
				$ssObject.queue(function(){
					if (opts.isPlaying) {
						fn_pauseSlideshow();
					}
					var $activeSlide     = $ssObject.find('.' + opts.slideContainer + ' .active'),
					    $targetSlide     = null,
					    activeSlideIndex = $activeSlide.prevAll('a').length,
					    targetSlideIndex = 0,
					    totalSlides      = $activeSlide.siblings('a').andSelf().length,
					    currentPage      = Math.ceil( ( activeSlideIndex + 1 ) / opts.navLinksPerPage ),
					    totalPages       = Math.ceil( totalSlides / opts.navLinksPerPage );

					if ( $(e.target).hasClass('nextPage') ) {
						if ( currentPage < totalPages ) {
							// go to the first slide of the next page
							targetSlideIndex = currentPage * opts.navLinksPerPage;
						} else {
							// go to last slide of the page if no more pages forward
							targetSlideIndex = totalSlides - 1;
						}
					} else {
						if ( currentPage > 1 ) {
							// go to the last slide of the previous page
							targetSlideIndex = ( currentPage - 1 ) * opts.navLinksPerPage - 1;
						} else {
							// go to the first slide of the page if no more pages previous
							targetSlideIndex = 0;
						}
					}
					$targetSlide = $ssObject.find('.' + opts.slideContainer + ' a')
						.eq( targetSlideIndex );
					if ( $targetSlide.get(0) !== $activeSlide.get(0) ) {
						fn_switchSlide( $targetSlide, true );
					}
					$(this).dequeue();
				});
				
				$(this).blur();
				return false;
			}
			
			
			if ( typeof opts !== 'object' || opts === null || opts.length <= 0 ) {
				// The additional items we're extending onto opts here cannot be overridden as they're
				// state related tracking variables or other internals
				opts = $.extend(true, {}, $.fn.userSlideshow.defaults, options, {
					timeout           : null,  // Timeout to be used for auto-switching when playing
					data              : null,  // JSON return data
					isPlaying         : false, // Bool whether or not the slideshow is currently playing
					hasDetails        : false, // Bool whether or not any of the slides have details data (title or caption)
					areDetailsVisible : false, // Bool whether or not the details panel is currently visible
					hasFancybox       : false, // Bool whether or not fancybox is available
					hasThickbox       : false, // Bool whether or not thickbox is available
					hasBelatedPng     : false  // Bool whether or not DD_belatedPNG is available
				});
				
				// Setup preferences based upon what functionality is available
				if ($.isFunction($.fn.fancybox)) {
					opts.hasFancybox = true;
				} else if (typeof tb_show === 'function') {
					opts.hasThickbox = true;
				}
				if (typeof DD_belatedPNG === 'object') {
					opts.hasBelatedPng = true;
				}
				
				// Add the index onto the uId to allow each target slideshow to have it's own unique thickbox group
				opts.uId = opts.uId + '_' + index;
				
				fn_initializeSlideshow( );
			} else {
				if (primaryArguments.length == 1 && primaryArguments[0] == 'pause') {
					// pause here
					fn_pauseSlideshow( );
				} else if (primaryArguments.length == 1 && primaryArguments[0] == 'play') {
					// play here
					fn_playSlideshow( );
				} else if (primaryArguments.length == 1 && primaryArguments[0] == 'destroy') {
					// destroy here
					fn_destroySlideshow( );
				}
			}
			
			fn_saveOpts();
			
		});
	};
	
	$.fn.userSlideshow.defaults = {
		uId             : new Date().getTime(), // Unique to use when grouping thickbox elements
		slideshowId     : null,
		url             : null,                 // Server URL to retrieve JSON data from
		loadingImage    : null,                 // Loading image to place over slideshow during initialization
		effect          : 'fade',               // Transition effect
		speed           : 'slow',               // Speed of transitions
		pause           : 3000,                 // Pause time between slide transitions
		useImageNav     : true,                 // Use 'ittie' images as links instead of using numbers
		navLinksPerPage : 5,                    // Number of navigation links to put on each "page"
		slideContainer  : 'slides',             // Class of container that holds all slide images
		useHighlight    : false,                // Bool whether or not to use the 'highlight' effect during slide transitions
		useAutoPlay     : true,                 // Bool whether or not the slideshow should play automatically on load
		fancyBoxOpts    : null
	};
})(jQuery);

