/**
 * 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;

                       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) {
                                  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
                                   this.$imageContainer.empty();
                                   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;
                                   this.$captionContainer.empty();

                                   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);

