/*
 * jQuery Galleriffic plugin
 *
 * Copyright (c) 2008 Trent Foley (http://trentacular.com)
 * Licensed under the MIT License:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Thanks to Taku Sano (Mikage Sawatari), whose history plugin I adapted to work with Galleriffic
 * Modified by Ghismo (ghismo.com) to disable the location rewrite 
 */
;(function($) {

  // Write noscript style
  document.write("<style type='text/css'>.noscript{display:none}</style>");

  var ver = 'galleriffic-1.0';
  var galleryOffset = 0;
  var galleries = [];
  var allImages = [];	
  var historyCurrentHash;
  var historyBackStack;
  var historyForwardStack;
  var isFirst = false;
  var dontCheck = false;
  var isInitialized = false;

  function getHashFromString(hash) {
    if (!hash) return -1;
    hash = hash.replace(/^.*#/, '');
    if (isNaN(hash)) return -1;
    return (+hash);
  }

  function getHash() {
    var hash = location.hash;
    return getHashFromString(hash);
  }

  function registerGallery(gallery) {
    galleries.push(gallery);

    // update the global offset value
    galleryOffset += gallery.data.length;
  }

  function getGallery(hash) {
    for (i = 0; i < galleries.length; i++) {
      var gallery = galleries[i];
      if (hash < (gallery.data.length+gallery.offset))
	return gallery;
    }
    return 0;
  }
  
  function getIndex(gallery, hash) {
    return hash-gallery.offset;
  }
  
  function clickHandler(e, gallery, link) {
    gallery.pause();

    if (!gallery.settings.enableHistory) {
      var hash = getHashFromString(link.href);
      if (hash >= 0) {
	var index = getIndex(gallery, hash);
	if (index >= 0)
	  gallery.goto(index);
      }
      e.preventDefault();
    }
  }

  function historyCallback() {
    // Using present location.hash always (seems to work, unlike the hash argument passed to this callback)
    var hash = getHash();
    if (hash < 0) return;

    var gallery = getGallery(hash);
    if (!gallery) return;
    
    var index = hash-gallery.offset;
    gallery.goto(index);
  }
  
  function historyInit() {
    if (isInitialized) return;
    isInitialized = true; 

    var current_hash = location.hash; //(enableHistory) ? location.hash : currentIndexHash; // Ghismo

    historyCurrentHash = current_hash;
    if ($.browser.msie) {
      // To stop the callback firing twice during initilization if no hash present
      if (historyCurrentHash == '') {
	historyCurrentHash = '#';
      }
    } else if ($.browser.safari) {
      // etablish back/forward stacks
      historyBackStack = [];
      historyBackStack.length = history.length;
      historyForwardStack = [];
      isFirst = true;
    }

    setInterval(function() { historyCheck(); }, 100);
  }
  
  function historyAddHistory(hash) {
    // This makes the looping function do something
    historyBackStack.push(hash);
    historyForwardStack.length = 0; // clear forwardStack (true click occured)
    isFirst = true;
  }
  
  function historyCheck() {
    if ($.browser.safari) {
      if (!dontCheck) {
	var historyDelta = history.length - historyBackStack.length;
	
	if (historyDelta) { // back or forward button has been pushed
	  isFirst = false;
	  if (historyDelta < 0) { // back button has been pushed
	    // move items to forward stack
	    for (var i = 0; i < Math.abs(historyDelta); i++) historyForwardStack.unshift(historyBackStack.pop());
	  } else { // forward button has been pushed
	    // move items to back stack
	    for (var i = 0; i < historyDelta; i++) historyBackStack.push(historyForwardStack.shift());
	  }
	  var cachedHash = historyBackStack[historyBackStack.length - 1];
	  if (cachedHash != undefined) {
	    historyCurrentHash = location.hash; // (enableHistory) ? location.hash : currentIndexHash; // Ghismo
	    historyCallback();
	  }
	} else if (historyBackStack[historyBackStack.length - 1] == undefined && !isFirst) {
	  historyCallback();
	  isFirst = true;
	}
      }
    } else {
      // otherwise, check for location.hash
      var current_hash = location.hash; // (enableHistory) ? location.hash : currentIndexHash; // Ghismo
      if(current_hash != historyCurrentHash) {
	historyCurrentHash = current_hash;
	historyCallback();
      }
    }
  }

  var defaults = {
    delay:                  3000,
    numThumbs:              20,
    preloadAhead:           40, // Set to -1 to preload all images
    enableTopPager:         false,
    enableBottomPager:      true,
    imageContainerSel:      '',
    captionContainerSel:    '',
    controlsContainerSel:   '',
    loadingContainerSel:    '',
    renderSSControls:       true,
    renderNavControls:      true,
    playLinkText:           'Play',
    pauseLinkText:          'Pause',
    prevLinkText:           'Previous',
    nextLinkText:           'Next',
    nextPageLinkText:       'Next &rsaquo;',
    prevPageLinkText:       '&lsaquo; Prev',
    enableHistory:          false,
    autoStart:              false,
    onChange:               undefined, // accepts a delegate like such: function(prevIndex, nextIndex) { ... }
    onTransitionOut:        undefined, // accepts a delegate like such: function(callback) { ... }
    onTransitionIn:         undefined, // accepts a delegate like such: function() { ... }
    onPageTransitionOut:    undefined, // accepts a delegate like such: function(callback) { ... }
    onPageTransitionIn:     undefined  // accepts a delegate like such: function() { ... }
  };

  $.fn.galleriffic = function(thumbsContainerSel, settings) {
    //  Extend Gallery Object
    $.extend(this, {
      ver: function() {
	return ver;
      },

      initializeThumbs: function() {
	this.data = [];
	var gallery = this;
	
	this.$thumbsContainer.find('ul.thumbs > li').each(function(i) {
	  var $li = $(this);
	  var $aThumb = $li.find('a.thumb');
	  var hash = gallery.offset+i;

	  gallery.data.push({
	    title:$aThumb.attr('title'),
	    slideUrl:$aThumb.attr('href'),
	    caption:$li.find('.caption').remove(),
	    hash:hash
	  });

	  // Setup history
	  $aThumb.attr('rel', 'history');
	  $aThumb.attr('href', '#'+hash);
	  $aThumb.click(function(e) {
	    clickHandler(e, gallery, this);
	  });
	});
	return this;
      },

      isPreloadComplete: false,

      preloadInit: function() {
	if (this.settings.preloadAhead == 0) return this;
	
	this.preloadStartIndex = this.currentIndex;
	var nextIndex = this.getNextIndex(this.preloadStartIndex);
	return this.preloadRecursive(this.preloadStartIndex, nextIndex);
      },
      
      preloadRelocate: function(index) {
	// By changing this startIndex, the current preload script will restart
	this.preloadStartIndex = index;
	return this;
      },

      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.settings.preloadAhead >= 0 && preloadCount > this.settings.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;
      },
      
      preloadNext: function(startIndex, currentIndex) {
	var nextIndex = this.getNextIndex(currentIndex);
	if (nextIndex == startIndex) {
	  this.isPreloadComplete = true;
	} else {
	  // Use set timeout to free up thread
	  var gallery = this;
	  setTimeout(function() { gallery.preloadRecursive(startIndex, nextIndex); }, 100);
	}
	return this;
      },

      getNextIndex: function(index) {
	var nextIndex = index+1;
	if (nextIndex >= this.data.length)
	  nextIndex = 0;
	return nextIndex;
      },
      
      getPrevIndex: function(index) {
	var prevIndex = index-1;
	if (prevIndex < 0)
	  prevIndex = this.data.length-1;
	return prevIndex;
      },

      pause: function() {
	if (this.interval)
	  this.toggleSlideshow();
	
	return this;
      },

      play: function() {
	if (!this.interval)
	  this.toggleSlideshow();
	
	return this;
      },

      toggleSlideshow: function() {
	if (this.interval) {
	  clearInterval(this.interval);
	  this.interval = 0;
	  
	  if (this.$controlsContainer) {
	    this.$controlsContainer
	    .find('div.ss-controls a').removeClass().addClass('play')
	    .attr('title', this.settings.playLinkText)
	    .attr('href', '#play')
	    .html(this.settings.playLinkText);
	  }
	} else {
	  this.ssAdvance();

	  var gallery = this;
	  this.interval = setInterval(function() {
	    gallery.ssAdvance();
	  }, this.settings.delay);
	  
	  if (this.$controlsContainer) {
	    this.$controlsContainer
	    .find('div.ss-controls a').removeClass().addClass('pause')
	    .attr('title', this.settings.pauseLinkText)
	    .attr('href', '#pause')
	    .html(this.settings.pauseLinkText);
	  }
	}

	return this;
      },

      ssAdvance: function() {
	var nextIndex = this.getNextIndex(this.currentIndex);
	var nextHash = this.data[nextIndex].hash;

	// Seems to be working on both FF and Safari
	if (this.settings.enableHistory)
	  location.href = '#'+nextHash;
	else
	  this.goto(nextIndex);

	// IE we need to explicity call goto
	//if ($.browser.msie) {
	//	this.goto(nextIndex);
	//}

	return this;
      },

      goto: function(index) {
	if (index < 0) index = 0;
	else if (index >= this.data.length) index = this.data.length-1;
	
	if (this.settings.onChange)
	  this.settings.onChange(this.currentIndex, index);
	
	this.currentIndex = index;
	this.preloadRelocate(index);
	return this.refresh();
      },
      
      refresh: function() {
	var imageData = this.data[this.currentIndex];
	if (!imageData)
	  return this;
	
	// Flag we are transitioning
	var isTransitioning = true;

	var gallery = this;

	var transitionOutCallback = function() {
	  // Flag that the transition has completed
	  isTransitioning = false;

	  // Update Controls
	  if (gallery.$controlsContainer) {
	    gallery.$controlsContainer
	    .find('div.nav-controls a.prev').attr('href', '#'+gallery.data[gallery.getPrevIndex(gallery.currentIndex)].hash).end()
	    .find('div.nav-controls a.next').attr('href', '#'+gallery.data[gallery.getNextIndex(gallery.currentIndex)].hash);
	  }

	  var imageData = gallery.data[gallery.currentIndex];

	  // Replace Caption
	  if (gallery.$captionContainer) {
	    gallery.$captionContainer.empty().append(imageData.caption);
	  }

	  if (imageData.image) {
	    gallery.buildImage(imageData.image);
	  } else {
	    // Show loading container
	    if (gallery.$loadingContainer) {
	      gallery.$loadingContainer.show();
	    }
	  }
	}

	if (this.settings.onTransitionOut) {
	  this.settings.onTransitionOut(transitionOutCallback);
	} else {
	  this.$transitionContainers.hide();
	  transitionOutCallback();
	}

	if (!imageData.image) {
	  var image = new Image();
	  
	  // Wire up mainImage onload event
	  image.onload = function() {
	    imageData.image = this;

	    if (!isTransitioning) {
	      gallery.buildImage(imageData.image);
	    }
	  };

	  // 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();
      },
      
      buildImage: function(image) {
	if (this.$imageContainer) {
	  this.$imageContainer.empty();

	  var gallery = this;
	  var nextIndex = this.getNextIndex(this.currentIndex);

	  // Hide the loading conatiner
	  if (this.$loadingContainer) {
	    this.$loadingContainer.hide();
	  }

	  // Setup image
	  this.$imageContainer
	  .append('<span class="image-wrapper"><a class="advance-link" rel="history" href="#'+this.data[nextIndex].hash+'" title="'+image.alt+'"></a></span>')
	  .find('a')
	  .append(image)
	  .click(function(e) {
	    clickHandler(e, gallery, this);
	  });
	}

	if (this.settings.onTransitionIn)
	  this.settings.onTransitionIn();
	else
	  this.$transitionContainers.show();

	return this;
      },

      syncThumbs: function() {
	if (this.$thumbsContainer) {
	  var page = Math.floor(this.currentIndex / this.settings.numThumbs);
	  if (page != this.currentPage) {
	    this.currentPage = page;
	    this.updateThumbs();
	  }

	  // Remove existing selected class and add selected class to new thumb
	  var $thumbs = this.$thumbsContainer.find('ul.thumbs').children();
	  $thumbs.filter('.selected').removeClass('selected');
	  $thumbs.eq(this.currentIndex).addClass('selected');
	}

	return this;
      },

      updateThumbs: function() {
	var gallery = this;
	var transitionOutCallback = function() {
	  gallery.rebuildThumbs();

	  // Transition In the thumbsContainer
	  if (gallery.settings.onPageTransitionIn)
	    gallery.settings.onPageTransitionIn();
	  else
	    gallery.$thumbsContainer.show();
	};

	// Transition Out the thumbsContainer
	if (this.settings.onPageTransitionOut) {
	  this.settings.onPageTransitionOut(transitionOutCallback);
	} else {
	  this.$thumbsContainer.hide();
	  transitionOutCallback();
	}

	return this;
      },

      rebuildThumbs: function() {
	// Initialize currentPage to first page
	if (this.currentPage < 0)
	  this.currentPage = 0;
	
	var needsPagination = this.data.length > this.settings.numThumbs;

	// Rebuild top pager
	var $topPager = this.$thumbsContainer.find('div.top');
	if ($topPager.length == 0)
	  $topPager = this.$thumbsContainer.prepend('<div class="top pagination"></div>').find('div.top');

	if (needsPagination && this.settings.enableTopPager) {
	  $topPager.empty();
	  this.buildPager($topPager);
	}

	// Rebuild bottom pager
	if (needsPagination && this.settings.enableBottomPager) {
	  var $bottomPager = this.$thumbsContainer.find('div.bottom');
	  if ($bottomPager.length == 0)
	    $bottomPager = this.$thumbsContainer.append('<div class="bottom pagination"></div>').find('div.bottom');
	  else
	    $bottomPager.empty();

	  this.buildPager($bottomPager);
	}

	var startIndex = this.currentPage*this.settings.numThumbs;
	var stopIndex = startIndex+this.settings.numThumbs-1;
	if (stopIndex >= this.data.length)
	  stopIndex = this.data.length-1;

	// Show/Hide thumbs
	var $thumbsUl = this.$thumbsContainer.find('ul.thumbs');
	$thumbsUl.find('li').each(function(i) {
	  var $li = $(this);
	  if (i >= startIndex && i <= stopIndex) {
	    $li.show();
	  } else {
	    $li.hide();
	  }
	});

	// Remove the noscript class from the thumbs container ul
	$thumbsUl.removeClass('noscript');
	
	return this;
      },

      buildPager: function(pager) {
	var gallery = this;
	var startIndex = this.currentPage*this.settings.numThumbs;
	
	// Prev Page Link
	if (this.currentPage > 0) {
	  var prevPage = startIndex - this.settings.numThumbs;
	  pager.append('<a rel="history" href="#'+this.data[prevPage].hash+'" title="'+this.settings.prevPageLinkText+'">'+this.settings.prevPageLinkText+'</a>');
	}

	// Page Index Links
	for (i=this.currentPage-3; i<=this.currentPage+3; i++) {
	  var pageNum = i+1;
	  
	  if (i == this.currentPage)
	    pager.append('<span class="current">'+pageNum+'</span>');
	  else if (i>=0 && i<this.numPages) {
	    var imageIndex = i*this.settings.numThumbs;
	    pager.append('<a rel="history" href="#'+this.data[imageIndex].hash+'" title="'+pageNum+'">'+pageNum+'</a>');
	  }
	}

	// Next Page Link
	var nextPage = startIndex+this.settings.numThumbs;
	if (nextPage < this.data.length) {
	  pager.append('<a rel="history" href="#'+this.data[nextPage].hash+'" title="'+this.settings.nextPageLinkText+'">'+this.settings.nextPageLinkText+'</a>');
	}

	pager.find('a').click(function(e) {
	  clickHandler(e, gallery, this);
	});

	return this;
      }
    });

    // Now initialize the gallery
    this.settings = $.extend({}, defaults, settings);
    //enableHistory = this.settings.enableHistory; // Ghismo

    if (this.interval)
      clearInterval(this.interval);

    this.interval = 0;
    
    if (this.settings.imageContainerSel) this.$imageContainer = $(this.settings.imageContainerSel);
    if (this.settings.captionContainerSel) this.$captionContainer = $(this.settings.captionContainerSel);
    if (this.settings.loadingContainerSel) this.$loadingContainer = $(this.settings.loadingContainerSel);

    // Setup the jQuery object holding each container that will be transitioned
    this.$transitionContainers = $([]);
    if (this.$imageContainer)
      this.$transitionContainers = this.$transitionContainers.add(this.$imageContainer);
    if (this.$captionContainer)
      this.$transitionContainers = this.$transitionContainers.add(this.$captionContainer);
    
    // Set the hash index offset for this gallery
    this.offset = galleryOffset;

    this.$thumbsContainer = $(thumbsContainerSel);
    this.initializeThumbs();

    // Add this gallery to the global galleries array
    registerGallery(this);

    this.numPages = Math.ceil(this.data.length/this.settings.numThumbs);
    this.currentPage = -1;
    this.currentIndex = 0;
    var gallery = this;

    // Hide the loadingContainer
    if (this.$loadingContainer)
      this.$loadingContainer.hide();

    // Setup controls
    if (this.settings.controlsContainerSel) {
      this.$controlsContainer = $(this.settings.controlsContainerSel).empty();
      
      if (this.settings.renderSSControls) {
	if (this.settings.autoStart) {
	  this.$controlsContainer
	  .append('<div class="ss-controls"><a href="#pause" class="pause" title="'+this.settings.pauseLinkText+'">'+this.settings.pauseLinkText+'</a></div>');
	} else {
	  this.$controlsContainer
	  .append('<div class="ss-controls"><a href="#play" class="play" title="'+this.settings.playLinkText+'">'+this.settings.playLinkText+'</a></div>');
	}

	this.$controlsContainer.find('div.ss-controls a')
	.click(function(e) {
	  gallery.toggleSlideshow();
	  e.preventDefault();
	  return false;
	});
      }
      
      if (this.settings.renderNavControls) {
	var $navControls = this.$controlsContainer
	.append('<div class="nav-controls"><a class="prev" rel="history" title="'+this.settings.prevLinkText+'">'+this.settings.prevLinkText+'</a><a class="next" rel="history" title="'+this.settings.nextLinkText+'">'+this.settings.nextLinkText+'</a></div>')
	.find('div.nav-controls a')
	.click(function(e) {
	  clickHandler(e, gallery, this);
	});
      }
    }

    // Initialize history only once when the first gallery on the page is initialized
    historyInit();
    
    // Build image
    var hash = getHash();
    var hashGallery = (hash >= 0) ? getGallery(hash) : 0;
    var gotoIndex = (hashGallery && this == hashGallery) ? (hash-this.offset) : 0;
    this.goto(gotoIndex);

    if (this.settings.autoStart) {
      
      setTimeout(function() { gallery.play(); }, this.settings.delay);
    }

    // Kickoff Image Preloader after 1 second
    setTimeout(function() { gallery.preloadInit(); }, 1000);

    return this;
  };
})(jQuery);

