/**
 * jQuery Galleriffic plugin
 * 
 * Copyright (c) 2008 Trent Foley (http://trentacular.com) Licensed under the
 * MIT License: http://www.opensource.org/licenses/mit-license.php
 * 
 * Much thanks to primary contributer Ponticlaro (http://www.ponticlaro.com)
 */
;
(function($) {
	// Globally keep track of all images by their unique hash. Each item is an
	// image data object.
	var allImages = {};
	var imageCounter = 0;

	// Galleriffic static class
	$.galleriffic = {
		version : '2.0.1',

		// Strips invalid characters and any leading # characters
		normalizeHash : function(hash) {
			return hash.replace(/^.*#/, '').replace(/\?.*$/, '');
		},

		getImage : function(hash) {
			if (!hash)
				return undefined;

			hash = $.galleriffic.normalizeHash(hash);
			return allImages[hash];
		},

		// Global function that looks up an image by its hash and displays the
		// image.
		// Returns false when an image is not found for the specified hash.
		// @param {String} hash This is the unique hash value assigned to an
		// image.
		gotoImage : function(hash) {
			var imageData = $.galleriffic.getImage(hash);
			if (!imageData)
				return false;

			var gallery = imageData.gallery;
			gallery.gotoImage(imageData);

			return true;
		},

		// Removes an image from its respective gallery by its hash.
		// Returns false when an image is not found for the specified hash or
		// the
		// specified owner gallery does match the located images gallery.
		// @param {String} hash This is the unique hash value assigned to an
		// image.
		// @param {Object} ownerGallery (Optional) When supplied, the located
		// images
		// gallery is verified to be the same as the specified owning gallery
		// before
		// performing the remove operation.
		removeImageByHash : function(hash, ownerGallery) {
			var imageData = $.galleriffic.getImage(hash);
			if (!imageData)
				return false;

			var gallery = imageData.gallery;
			if (ownerGallery && ownerGallery != gallery)
				return false;

			return gallery.removeImageByIndex(imageData.index);
		}
	};

	var defaults = {
		delay : 3000,
		numThumbs : 20,
		preloadAhead : 40, // Set to -1 to preload all images
		enableTopPager : false,
		enableBottomPager : true,
		maxPagesToShow : 7,
		imageContainerSel : '',
		captionContainerSel : '',
		controlsContainerSel : '',
		loadingContainerSel : '',
		renderSSControls : true,
		renderNavControls : true,
		playLinkText : 'Play',
		pauseLinkText : 'Pause',
		prevLinkText : 'Previous',
		nextLinkText : 'Next',
		nextPageLinkText : 'Next &rsaquo;',
		prevPageLinkText : '&lsaquo; Prev',
		enableHistory : false,
		enableKeyboardNavigation : true,
		autoStart : false,
		syncTransitions : false,
		defaultTransitionDuration : 1000,
		onSlideChange : undefined, // accepts a delegate like such:
		// function(prevIndex, nextIndex) { ... }
		onTransitionOut : undefined, // accepts a delegate like such:
		// function(slide, caption, isSync,
		// callback) { ... }
		onTransitionIn : undefined, // accepts a delegate like such:
		// function(slide, caption, isSync) { ... }
		onPageTransitionOut : undefined, // accepts a delegate like such:
		// function(callback) { ... }
		onPageTransitionIn : undefined, // accepts a delegate like such:
		// function() { ... }
		onImageAdded : undefined, // accepts a delegate like such:
		// function(imageData, $li) { ... }
		onImageRemoved : undefined
		// accepts a delegate like such: function(imageData, $li) { ... }
	};

	// Primary Galleriffic initialization function that should be called on the
	// thumbnail container.
	$.fn.galleriffic = function(settings) {
		// Extend Gallery Object
		$.extend(this, {
			// Returns the version of the script
			version : $.galleriffic.version,

			// Current state of the slideshow
			isSlideshowRunning : false,
			slideshowTimeout : undefined,

			// This function is attached to the click event of generated
			// hyperlinks within the gallery
			clickHandler : function(e, link) {
				this.pause();

				if (!this.enableHistory) {
					// The href attribute holds the unique hash for an image
					var hash = $.galleriffic.normalizeHash($(link).attr('href'));
					$.galleriffic.gotoImage(hash);
					e.preventDefault();
				}
			},

			// Appends an image to the end of the set of images. Argument
			// listItem can be either a jQuery DOM element or arbitrary html.
			// @param listItem Either a jQuery object or a string of html of the
			// list item that is to be added to the gallery.
			appendImage : function(listItem) {
				this.addImage(listItem, false, false);
				return this;
			},

			// Inserts an image into the set of images. Argument listItem can be
			// either a jQuery DOM element or arbitrary html.
			// @param listItem Either a jQuery object or a string of html of the
			// list item that is to be added to the gallery.
			// @param {Integer} position The index within the gallery where the
			// item shouold be added.
			insertImage : function(listItem, position) {
				this.addImage(listItem, false, true, position);
				return this;
			},

			// Adds an image to the gallery and optionally inserts/appends it to
			// the DOM (thumbExists)
			// @param listItem Either a jQuery object or a string of html of the
			// list item that is to be added to the gallery.
			// @param {Boolean} thumbExists Specifies whether the thumbnail
			// already exists in the DOM or if it needs to be added.
			// @param {Boolean} insert Specifies whether the the image is
			// appended to the end or inserted into the gallery.
			// @param {Integer} position The index within the gallery where the
			// item shouold be added.
			addImage : function(listItem, thumbExists, insert, position) {
				var $li = (typeof listItem === "string") ? $(listItem) : listItem;
				var $aThumb = $li.find('a.thumb');
				var slideUrl = $aThumb.attr('href');
				var title = $aThumb.attr('title');
				var $caption = $li.find('.caption').remove();
				var hash = $aThumb.attr('name');

				// Increment the image counter
				imageCounter++;

				// Autogenerate a hash value if none is present or if it is a
				// duplicate
				if (!hash || allImages['' + hash]) {
					hash = imageCounter;
				}

				// Set position to end when not specified
				if (!insert)
					position = this.data.length;

				var imageData = {
					title : title,
					slideUrl : slideUrl,
					caption : $caption,
					hash : hash,
					gallery : this,
					index : position
				};

				// Add the imageData to this gallery's array of images
				if (insert) {
					this.data.splice(position, 0, imageData);

					// Reset index value on all imageData objects
					this.updateIndices(position);
				} else {
					this.data.push(imageData);
				}

				var gallery = this;

				// Add the element to the DOM
				if (!thumbExists) {
					// Update thumbs passing in addition post transition out
					// handler
					this.updateThumbs(function() {
								var $thumbsUl = gallery.find('ul.thumbs');
								if (insert)
									$thumbsUl.children(':eq(' + position + ')').before($li);
								else
									$thumbsUl.append($li);

								if (gallery.onImageAdded)
									gallery.onImageAdded(imageData, $li);
							});
				}

				// Register the image globally
				allImages['' + hash] = imageData;

				// Setup attributes and click handler
				/*
				 * $aThumb.attr('rel', 'history') .attr('href', '#'+hash)
				 * .removeAttr('name') .click(function(e) {
				 * gallery.clickHandler(e, this); });
				 */
				return this;
			},

			// Removes an image from the gallery based on its index.
			// Returns false when the index is out of range.
			removeImageByIndex : function(index) {
				if (index < 0 || index >= this.data.length)
					return false;

				var imageData = this.data[index];
				if (!imageData)
					return false;

				this.removeImage(imageData);

				return true;
			},

			// Convenience method that simply calls the global removeImageByHash
			// method.
			removeImageByHash : function(hash) {
				return $.galleriffic.removeImageByHash(hash, this);
			},

			// Removes an image from the gallery.
			removeImage : function(imageData) {
				var index = imageData.index;

				// Remove the image from the gallery data array
				this.data.splice(index, 1);

				// Remove the global registration
				delete allImages['' + imageData.hash];

				// Remove the image's list item from the DOM
				this.updateThumbs(function() {
							var $li = gallery.find('ul.thumbs').children(':eq(' + index + ')').remove();

							if (gallery.onImageRemoved)
								gallery.onImageRemoved(imageData, $li);
						});

				// Update each image objects index value
				this.updateIndices(index);

				return this;
			},

			// Updates the index values of the each of the images in the gallery
			// after the specified index
			updateIndices : function(startIndex) {
				for (i = startIndex; i < this.data.length; i++) {
					this.data[i].index = i;
				}

				return this;
			},

			// Scraped the thumbnail container for thumbs and adds each to the
			// gallery
			initializeThumbs : function() {
				this.data = [];
				var gallery = this;

				this.find('ul.thumbs > li').each(function(i) {
							gallery.addImage($(this), true, false);
						});

				return this;
			},

			isPreloadComplete : false,

			// Initalizes the image preloader
			preloadInit : function() {
				if (this.preloadAhead == 0)
					return this;
				if (!this.currentImage)
					return false;
				this.preloadStartIndex = this.currentImage.index;
				var nextIndex = this.getNextIndex(this.preloadStartIndex);
				return this.preloadRecursive(this.preloadStartIndex, nextIndex);
			},

			// Changes the location in the gallery the preloader should work
			// @param {Integer} index The index of the image where the preloader
			// should restart at.
			preloadRelocate : function(index) {
				// By changing this startIndex, the current preload script will
				// restart
				this.preloadStartIndex = index;
				return this;
			},

			// Recursive function that performs the image preloading
			// @param {Integer} startIndex The index of the first image the
			// current preloader started on.
			// @param {Integer} currentIndex The index of the current image to
			// preload.
			preloadRecursive : function(startIndex, currentIndex) {
				// Check if startIndex has been relocated
				if (startIndex != this.preloadStartIndex) {
					var nextIndex = this.getNextIndex(this.preloadStartIndex);
					return this.preloadRecursive(this.preloadStartIndex, nextIndex);
				}

				var gallery = this;

				// Now check for preloadAhead count
				var preloadCount = currentIndex - startIndex;
				if (preloadCount < 0)
					preloadCount = this.data.length - 1 - startIndex + currentIndex;
				if (this.preloadAhead >= 0 && preloadCount > this.preloadAhead) {
					// Do this in order to keep checking for relocated start
					// index
					setTimeout(function() {
								gallery.preloadRecursive(startIndex, currentIndex);
							}, 500);
					return this;
				}

				var imageData = this.data[currentIndex];
				if (!imageData)
					return this;

				// If already loaded, continue
				if (imageData.image)
					return this.preloadNext(startIndex, currentIndex);

				// Preload the image
				var image = new Image();

				image.onload = function() {
					imageData.image = this;
					gallery.preloadNext(startIndex, currentIndex);
				};

				image.alt = imageData.title;
				image.src = imageData.slideUrl;

				return this;
			},

			// Called by preloadRecursive in order to preload the next image
			// after the previous has loaded.
			// @param {Integer} startIndex The index of the first image the
			// current preloader started on.
			// @param {Integer} currentIndex The index of the current image to
			// preload.
			preloadNext : function(startIndex, currentIndex) {
				var nextIndex = this.getNextIndex(currentIndex);
				if (nextIndex == startIndex) {
					this.isPreloadComplete = true;
				} else {
					// Use setTimeout to free up thread
					var gallery = this;
					setTimeout(function() {
								gallery.preloadRecursive(startIndex, nextIndex);
							}, 100);
				}

				return this;
			},

			// Safe way to get the next image index relative to the current
			// image.
			// If the current image is the last, returns 0
			getNextIndex : function(index) {
				var nextIndex = index + 1;
				if (nextIndex >= this.data.length)
					nextIndex = 0;
				return nextIndex;
			},

			// Safe way to get the previous image index relative to the current
			// image.
			// If the current image is the first, return the index of the last
			// image in the gallery.
			getPrevIndex : function(index) {
				var prevIndex = index - 1;
				if (prevIndex < 0)
					prevIndex = this.data.length - 1;
				return prevIndex;
			},

			// Pauses the slideshow
			pause : function() {
				this.isSlideshowRunning = false;
				if (this.slideshowTimeout) {
					clearTimeout(this.slideshowTimeout);
					this.slideshowTimeout = undefined;
				}

				if (this.$controlsContainer) {
					this.$controlsContainer.find('div.ss-controls a').removeClass().addClass('play').attr('title', this.playLinkText).attr('href',
							'#play').html(this.playLinkText);
				}

				return this;
			},

			// Plays the slideshow
			play : function() {
				this.isSlideshowRunning = true;

				if (this.$controlsContainer) {
					this.$controlsContainer.find('div.ss-controls a').removeClass().addClass('pause').attr('title', this.pauseLinkText).attr('href',
							'#pause').html(this.pauseLinkText);
				}

				if (!this.slideshowTimeout) {
					var gallery = this;
					this.slideshowTimeout = setTimeout(function() {
								gallery.ssAdvance();
							}, this.delay);
				}

				return this;
			},

			// Toggles the state of the slideshow (playing/paused)
			toggleSlideshow : function() {
				if (this.isSlideshowRunning)
					this.pause();
				else
					this.play();

				return this;
			},

			// Advances the slideshow to the next image and delegates navigation
			// to the
			// history plugin when history is enabled
			// enableHistory is true
			ssAdvance : function() {
				if (this.isSlideshowRunning)
					this.next(true);

				return this;
			},

			// Advances the gallery to the next image.
			// @param {Boolean} dontPause Specifies whether to pause the
			// slideshow.
			// @param {Boolean} bypassHistory Specifies whether to delegate
			// navigation to the history plugin when history is enabled.
			next : function(dontPause, bypassHistory) {
				this.gotoIndex(this.getNextIndex(this.currentImage.index), dontPause, bypassHistory);
				return this;
			},

			// Navigates to the previous image in the gallery.
			// @param {Boolean} dontPause Specifies whether to pause the
			// slideshow.
			// @param {Boolean} bypassHistory Specifies whether to delegate
			// navigation to the history plugin when history is enabled.
			previous : function(dontPause, bypassHistory) {
				this.gotoIndex(this.getPrevIndex(this.currentImage.index), dontPause, bypassHistory);
				return this;
			},

			// Navigates to the next page in the gallery.
			// @param {Boolean} dontPause Specifies whether to pause the
			// slideshow.
			// @param {Boolean} bypassHistory Specifies whether to delegate
			// navigation to the history plugin when history is enabled.
			nextPage : function(dontPause, bypassHistory) {
				var page = this.getCurrentPage();
				var lastPage = this.getNumPages() - 1;
				if (page < lastPage) {
					var startIndex = page * this.numThumbs;
					var nextPage = startIndex + this.numThumbs;
					this.gotoIndex(nextPage, dontPause, bypassHistory);
				}

				return this;
			},

			// Navigates to the previous page in the gallery.
			// @param {Boolean} dontPause Specifies whether to pause the
			// slideshow.
			// @param {Boolean} bypassHistory Specifies whether to delegate
			// navigation to the history plugin when history is enabled.
			previousPage : function(dontPause, bypassHistory) {
				var page = this.getCurrentPage();
				if (page > 0) {
					var startIndex = page * this.numThumbs;
					var prevPage = startIndex - this.numThumbs;
					this.gotoIndex(prevPage, dontPause, bypassHistory);
				}

				return this;
			},

			// Navigates to the image at the specified index in the gallery
			// @param {Integer} index The index of the image in the gallery to
			// display.
			// @param {Boolean} dontPause Specifies whether to pause the
			// slideshow.
			// @param {Boolean} bypassHistory Specifies whether to delegate
			// navigation to the history plugin when history is enabled.
			gotoIndex : function(index, dontPause, bypassHistory) {
				if (!dontPause)
					this.pause();

				if (index < 0)
					index = 0;
				else if (index >= this.data.length)
					index = this.data.length - 1;

				var imageData = this.data[index];

				if (!bypassHistory && this.enableHistory)
					$.historyLoad(String(imageData.hash)); // At the moment,
				// historyLoad only
				// accepts string
				// arguments
				else
					this.gotoImage(imageData);

				return this;
			},

			// This function is garaunteed to be called anytime a gallery slide
			// changes.
			// @param {Object} imageData An object holding the image metadata of
			// the image to navigate to.
			gotoImage : function(imageData) {
				if (!imageData)
					return false;
				var index = imageData.index;

				if (this.onSlideChange)
					this.onSlideChange(this.currentImage.index, index);

				this.currentImage = imageData;
				this.preloadRelocate(index);

				this.refresh();

				return this;
			},

			// Returns the default transition duration value. The value is
			// halved when not
			// performing a synchronized transition.
			// @param {Boolean} isSync Specifies whether the transitions are
			// synchronized.
			getDefaultTransitionDuration : function(isSync) {
				if (isSync)
					return this.defaultTransitionDuration;
				return this.defaultTransitionDuration / 2;
			},

			// Rebuilds the slideshow image and controls and performs
			// transitions
			refresh : function() {
				var imageData = this.currentImage;
				if (!imageData)
					return this;

				var index = imageData.index;

				// Update Controls
				if (this.$controlsContainer) {
					this.$controlsContainer.find('div.nav-controls a.prev').attr('href', '#' + this.data[this.getPrevIndex(index)].hash).end()
							.find('div.nav-controls a.next').attr('href', '#' + this.data[this.getNextIndex(index)].hash);
				}

				var previousSlide = this.$imageContainer.find('span.current').addClass('previous').removeClass('current');
				var previousCaption = 0;

				if (this.$captionContainer) {
					previousCaption = this.$captionContainer.find('span.current').addClass('previous').removeClass('current');
				}

				// Perform transitions simultaneously if syncTransitions is true
				// and the next image is already preloaded
				var isSync = this.syncTransitions && imageData.image;

				// Flag we are transitioning
				var isTransitioning = true;
				var gallery = this;

				var transitionOutCallback = function() {
					// Flag that the transition has completed
					isTransitioning = false;

					// Remove the old slide
					previousSlide.remove();

					// Remove old caption
					if (previousCaption)
						previousCaption.remove();

					if (!isSync) {
						if (imageData.image && imageData.hash == gallery.data[gallery.currentImage.index].hash) {
							gallery.buildImage(imageData, isSync);
						} else {
							// Show loading container
							if (gallery.$loadingContainer) {
								gallery.$loadingContainer.show();
							}
						}
					}
				};

				if (previousSlide.length == 0) {
					// For the first slide, the previous slide will be empty, so
					// we will call the callback immediately
					transitionOutCallback();
				} else {
					if (this.onTransitionOut) {
						this.onTransitionOut(previousSlide, previousCaption, isSync, transitionOutCallback);
					} else {
						previousSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0, transitionOutCallback);
						if (previousCaption)
							previousCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0);
					}
				}

				// Go ahead and begin transitioning in of next image
				if (isSync)
					this.buildImage(imageData, isSync);

				if (!imageData.image) {
					var image = new Image();

					// Wire up mainImage onload event
					image.onload = function() {
						imageData.image = this;

						// Only build image if the out transition has completed
						// and we are still on the same image hash
						if (!isTransitioning && imageData.hash == gallery.data[gallery.currentImage.index].hash) {
							gallery.buildImage(imageData, isSync);
						}
					};

					// set alt and src
					image.alt = imageData.title;
					image.src = imageData.slideUrl;
				}

				// This causes the preloader (if still running) to relocate out
				// from the currentIndex
				this.relocatePreload = true;

				return this.syncThumbs();
			},

			// Called by the refresh method after the previous image has been
			// transitioned out or at the same time
			// as the out transition when performing a synchronous transition.
			// @param {Object} imageData An object holding the image metadata of
			// the image to build.
			// @param {Boolean} isSync Specifies whether the transitions are
			// synchronized.
			buildImage : function(imageData, isSync) {
				var gallery = this;
				var nextIndex = this.getNextIndex(imageData.index);

				// Construct new hidden span for the image
				var newSlide = this.$imageContainer.append('<span class="image-wrapper current"><a class="advance-link" rel="history" href="#'
						+ this.data[nextIndex].hash + '" title="' + imageData.title + '">&nbsp;</a></span>').find('span.current').css('opacity', '0');

				newSlide.find('a').append(imageData.image).click(function(e) {
							gallery.clickHandler(e, this);
						});

				var newCaption = 0;
				if (this.$captionContainer) {
					// Construct new hidden caption for the image
					newCaption = this.$captionContainer.append('<span class="image-caption current"></span>').find('span.current')
							.css('opacity', '0').append(imageData.caption);
				}

				// Hide the loading conatiner
				if (this.$loadingContainer) {
					this.$loadingContainer.hide();
				}

				// Transition in the new image
				if (this.onTransitionIn) {
					this.onTransitionIn(newSlide, newCaption, isSync);
				} else {
					newSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0);
					if (newCaption)
						newCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0);
				}

				if (this.isSlideshowRunning) {
					if (this.slideshowTimeout)
						clearTimeout(this.slideshowTimeout);

					this.slideshowTimeout = setTimeout(function() {
								gallery.ssAdvance();
							}, this.delay);
				}

				return this;
			},

			// Returns the current page index that should be shown for the
			// currentImage
			getCurrentPage : function() {
				return Math.floor(this.currentImage.index / this.numThumbs);
			},

			// Applies the selected class to the current image's corresponding
			// thumbnail.
			// Also checks if the current page has changed and updates the
			// displayed page of thumbnails if necessary.
			syncThumbs : function() {
				var page = this.getCurrentPage();
				if (page != this.displayedPage)
					this.updateThumbs();

				// Remove existing selected class and add selected class to new
				// thumb
				var $thumbs = this.find('ul.thumbs').children();
				$thumbs.filter('.selected').removeClass('selected');
				$thumbs.eq(this.currentImage.index).addClass('selected');

				return this;
			},

			// Performs transitions on the thumbnails container and updates the
			// set of
			// thumbnails that are to be displayed and the navigation controls.
			// @param {Delegate} postTransitionOutHandler An optional delegate
			// that is called after
			// the thumbnails container has transitioned out and before the
			// thumbnails are rebuilt.
			updateThumbs : function(postTransitionOutHandler) {
				var gallery = this;
				var transitionOutCallback = function() {
					// Call the Post-transition Out Handler
					if (postTransitionOutHandler)
						postTransitionOutHandler();

					gallery.rebuildThumbs();

					// Transition In the thumbsContainer
					if (gallery.onPageTransitionIn)
						gallery.onPageTransitionIn();
					else
						gallery.show();
				};

				// Transition Out the thumbsContainer
				if (this.onPageTransitionOut) {
					this.onPageTransitionOut(transitionOutCallback);
				} else {
					this.hide();
					transitionOutCallback();
				}

				return this;
			},

			// Updates the set of thumbnails that are to be displayed and the
			// navigation controls.
			rebuildThumbs : function() {
				var needsPagination = this.data.length > this.numThumbs;

				// Rebuild top pager
				if (this.enableTopPager) {
					var $topPager = this.find('div.top');
					if ($topPager.length == 0)
						$topPager = this.prepend('<div class="top pagination"></div>').find('div.top');
					else
						$topPager.empty();

					if (needsPagination)
						this.buildPager($topPager);
				}

				// Rebuild bottom pager
				if (this.enableBottomPager) {
					var $bottomPager = this.find('div.bottom');
					if ($bottomPager.length == 0)
						$bottomPager = this.append('<div class="bottom pagination"></div>').find('div.bottom');
					else
						$bottomPager.empty();

					if (needsPagination)
						this.buildPager($bottomPager);
				}

				var page = this.getCurrentPage();
				var startIndex = page * this.numThumbs;
				var stopIndex = startIndex + this.numThumbs - 1;
				if (stopIndex >= this.data.length)
					stopIndex = this.data.length - 1;

				// Show/Hide thumbs
				var $thumbsUl = this.find('ul.thumbs');
				$thumbsUl.find('li').each(function(i) {
							var $li = $(this);
							if (i >= startIndex && i <= stopIndex) {
								$li.show();
							} else {
								$li.hide();
							}
						});

				this.displayedPage = page;

				// Remove the noscript class from the thumbs container ul
				$thumbsUl.removeClass('noscript');

				return this;
			},

			// Returns the total number of pages required to display all the
			// thumbnails.
			getNumPages : function() {
				return Math.ceil(this.data.length / this.numThumbs);
			},

			// Rebuilds the pager control in the specified matched element.
			// @param {jQuery} pager A jQuery element set matching the
			// particular pager to be rebuilt.
			buildPager : function(pager) {
				var gallery = this;
				var numPages = this.getNumPages();
				var page = this.getCurrentPage();
				var startIndex = page * this.numThumbs;
				var pagesRemaining = this.maxPagesToShow - 1;

				var pageNum = page - Math.floor((this.maxPagesToShow - 1) / 2) + 1;
				if (pageNum > 0) {
					var remainingPageCount = numPages - pageNum;
					if (remainingPageCount < pagesRemaining) {
						pageNum = pageNum - (pagesRemaining - remainingPageCount);
					}
				}

				if (pageNum < 0) {
					pageNum = 0;
				}

				// Prev Page Link
				if (page > 0) {
					var prevPage = startIndex - this.numThumbs;
					pager.append('<a rel="history" href="#' + this.data[prevPage].hash + '" title="' + this.prevPageLinkText + '">'
							+ this.prevPageLinkText + '</a>');
				}

				// Create First Page link if needed
				if (pageNum > 0) {
					this.buildPageLink(pager, 0, numPages);
					if (pageNum > 1)
						pager.append('<span class="ellipsis">&hellip;</span>');

					pagesRemaining--;
				}

				// Page Index Links
				while (pagesRemaining > 0) {
					this.buildPageLink(pager, pageNum, numPages);
					pagesRemaining--;
					pageNum++;
				}

				// Create Last Page link if needed
				if (pageNum < numPages) {
					var lastPageNum = numPages - 1;
					if (pageNum < lastPageNum)
						pager.append('<span class="ellipsis">&hellip;</span>');

					this.buildPageLink(pager, lastPageNum, numPages);
				}

				// Next Page Link
				var nextPage = startIndex + this.numThumbs;
				if (nextPage < this.data.length) {
					pager.append('<a rel="history" href="#' + this.data[nextPage].hash + '" title="' + this.nextPageLinkText + '">'
							+ this.nextPageLinkText + '</a>');
				}

				pager.find('a').click(function(e) {
							gallery.clickHandler(e, this);
						});

				return this;
			},

			// Builds a single page link within a pager. This function is called
			// by buildPager
			// @param {jQuery} pager A jQuery element set matching the
			// particular pager to be rebuilt.
			// @param {Integer} pageNum The page number of the page link to
			// build.
			// @param {Integer} numPages The total number of pages required to
			// display all thumbnails.
			buildPageLink : function(pager, pageNum, numPages) {
				var pageLabel = pageNum + 1;
				var currentPage = this.getCurrentPage();
				if (pageNum == currentPage)
					pager.append('<span class="current">' + pageLabel + '</span>');
				else if (pageNum < numPages) {
					var imageIndex = pageNum * this.numThumbs;
					pager.append('<a rel="history" href="#' + this.data[imageIndex].hash + '" title="' + pageLabel + '">' + pageLabel + '</a>');
				}

				return this;
			}
		});

		// Now initialize the gallery
		$.extend(this, defaults, settings);

		// Verify the history plugin is available
		if (this.enableHistory && !$.historyInit)
			this.enableHistory = false;

		// Select containers
		if (this.imageContainerSel)
			this.$imageContainer = $(this.imageContainerSel);
		if (this.captionContainerSel)
			this.$captionContainer = $(this.captionContainerSel);
		if (this.loadingContainerSel)
			this.$loadingContainer = $(this.loadingContainerSel);

		// Initialize the thumbails
		this.initializeThumbs();

		if (this.maxPagesToShow < 3)
			this.maxPagesToShow = 3;

		this.displayedPage = -1;
		this.currentImage = this.data[0];
		var gallery = this;

		// Hide the loadingContainer
		if (this.$loadingContainer)
			this.$loadingContainer.hide();

		// Setup controls
		if (this.controlsContainerSel) {
			this.$controlsContainer = $(this.controlsContainerSel).empty();

			if (this.renderSSControls) {
				if (this.autoStart) {
					this.$controlsContainer.append('<div class="ss-controls"><a href="#pause" class="pause" title="' + this.pauseLinkText + '">'
							+ this.pauseLinkText + '</a></div>');
				} else {
					this.$controlsContainer.append('<div class="ss-controls"><a href="#play" class="play" title="' + this.playLinkText + '">'
							+ this.playLinkText + '</a></div>');
				}

				this.$controlsContainer.find('div.ss-controls a').click(function(e) {
							gallery.toggleSlideshow();
							e.preventDefault();
							return false;
						});
			}

			if (this.renderNavControls) {
				this.$controlsContainer.append('<div class="nav-controls"><a class="prev" rel="history" title="' + this.prevLinkText + '">'
						+ this.prevLinkText + '</a><a class="next" rel="history" title="' + this.nextLinkText + '">' + this.nextLinkText
						+ '</a></div>').find('div.nav-controls a').click(function(e) {
							gallery.clickHandler(e, this);
						});
			}
		}

		var initFirstImage = !this.enableHistory || !location.hash;
		if (this.enableHistory && location.hash) {
			var hash = $.galleriffic.normalizeHash(location.hash);
			var imageData = allImages[hash];
			if (!imageData)
				initFirstImage = true;
		}

		// Setup gallery to show the first image
		if (initFirstImage)
			this.gotoIndex(0, false, true);

		// Setup Keyboard Navigation
		if (this.enableKeyboardNavigation) {
			$(document).keydown(function(e) {
						var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0;
						switch (key) {
							case 32 : // space
								gallery.next();
								e.preventDefault();
								break;
							case 33 : // Page Up
								gallery.previousPage();
								e.preventDefault();
								break;
							case 34 : // Page Down
								gallery.nextPage();
								e.preventDefault();
								break;
							case 35 : // End
								gallery.gotoIndex(gallery.data.length - 1);
								e.preventDefault();
								break;
							case 36 : // Home
								gallery.gotoIndex(0);
								e.preventDefault();
								break;
							case 37 : // left arrow
								gallery.previous();
								e.preventDefault();
								break;
							case 39 : // right arrow
								gallery.next();
								e.preventDefault();
								break;
						}
					});
		}

		// Auto start the slideshow
		if (this.autoStart)
			this.play();

		// Kickoff Image Preloader after 1 second
		setTimeout(function() {
					gallery.preloadInit();
				}, 1000);

		return this;
	};
})(jQuery);

