/*
 * jQuery Galleriffic plugin
 *
 * Copyright (c) 2008 Trent Foley
 * 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
 */
;(function($) {

	var ver = 'galleriffic-0.5';

	function clickHandler(gallery) {
		gallery.pause();
		return false;
	}

	$.fn.galleriffic = {
		ver: function() {
			return ver;
		},
	
		defaults: {
			delay:                3000,
			numThumbs:            20,
			enableTopPager:       true,
			enableBottomPager:    true,
			imageContainerSel:    '',
			thumbsContainerSel:   '',
			controlsContainerSel: '',
			titleContainerSel:    '',
			descContainerSel:     '',
			downloadLinkSel:      '',
			renderSSControls:     true,
			renderNavControls:    true,
			playLinkText:         'Play',
			pauseLinkText:        'Pause',
			prevLinkText:         'Previous',
			nextLinkText:         'Next',
			nextPageLinkText:     'Next &rsaquo;',
			prevPageLinkText:     '&lsaquo; Prev'
		},
		
		init: function(data, settings) {
			this.settings = $.extend({}, this.defaults, settings);
		
			if (this.interval)
				clearInterval(this.interval);

			this.interval = 0;

			this.data = data;
			if (this.settings.imageContainerSel) this.$imageContainer = $(this.settings.imageContainerSel);
			if (this.settings.thumbsContainerSel) this.$thumbsContainer = $(this.settings.thumbsContainerSel);
			if (this.settings.titleContainerSel) this.$titleContainer = $(this.settings.titleContainerSel);
			if (this.settings.descContainerSel) this.$descContainer = $(this.settings.descContainerSel);
			if (this.settings.downloadLinkSel) this.$downloadLink = $(this.settings.downloadLinkSel);

			this.currentPage = -1;
			this.currentIndex = 0;

			this.numPages = Math.ceil(this.data.length/this.settings.numThumbs);

			var gallery = this;

			// Setup controls
			if (this.settings.controlsContainerSel) {
				this.$controlsContainer = $(this.settings.controlsContainerSel).empty();
				
				if (this.settings.renderSSControls) {
					var gallery = this;
					this.$controlsContainer
						.append('<div class="ss-controls"><span class="play" title="'+this.settings.playLinkText+'">'+this.settings.playLinkText+'</span></div>')
						.find('div.ss-controls span')
						.click(function() { gallery.toggleSlideshow(); });
				}
			
				if (this.settings.renderNavControls) {					
					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="Next">'+this.settings.nextLinkText+'</a></div>')
						.find('a[@rel="history"]')
						.click(function() { clickHandler(gallery); });
				}
			}

			// Initialize history.
			// The callback is called at once by present location.hash to build the initial gallery 
			this.historyInit(function(h) {
				// Using present location.hash always (seems to work, unlike the hash argument passed to this callback)
				var hash = location.hash;
				gallery.goto(hash ? hash.replace(/^.*#/, '') : 0);
			});
			
			// Kickoff Image Preloader after 1 second
			var gallery = this;
			setTimeout(function() { gallery.preloadInit(); }, 1000);
			
			return this;
		},

		isPreloadComplete: false,

		preloadInit: function() {
			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 imageData = this.data[currentIndex];

			// If already loaded, continue
			if (imageData.loaded)
				return this.preloadNext(startIndex, currentIndex); 
			
			// Preload the image
			var image = new Image();
			
			var gallery = this;
			image.onload = function() {
				imageData.loaded = this;
				gallery.preloadNext(startIndex, currentIndex);
			};

			image.alt = imageData.title;
			image.src = imageData.slide;

			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 span').removeClass().addClass('play')
						.attr('title', this.settings.playLinkText)
						.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 span').removeClass().addClass('pause')
						.attr('title', this.settings.pauseLinkText)
						.html(this.settings.pauseLinkText);
				}
			}

			return this;
		},

		ssAdvance: function() {
			var nextIndex = this.getNextIndex(this.currentIndex);
			// Seems to be working on both FF and Safari
			location.href = '#'+nextIndex;
			
			// IE we need to explicity call goto
			if ($.browser.msie) {
				this.goto(nextIndex);
			}
			
			return this;
		},

		goto: function(i) {
			var index = (+i);

			if (index < 0) index = 0;
			else if (index >= this.data.length) index = this.data.length-1;
			this.currentIndex = index;
			this.preloadRelocate(index);
			return this.refresh();
		},
		
		refresh: function() {
			if (this.$imageContainer) {
				var imageData = this.data[this.currentIndex];
				var isFading = 1;
				var gallery = this;

				// hide img
				this.$imageContainer.fadeOut('fast', function() {
					isFading = 0;

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

					// Replace Title
					if (gallery.$titleContainer) {
						gallery.$titleContainer.empty().html(imageData.title);
					}

					// Replace Description
					if (gallery.$descContainer) {
						gallery.$descContainer.empty().html(imageData.description);
					}

					// Update Download Link
					if (gallery.$downloadLink) {
						gallery.$downloadLink.attr('href', imageData.original);
					}

					if (imageData.loaded) {
						gallery.buildImage(imageData.loaded);
					}
				});
				
				if (this.onFadeOut) this.onFadeOut();

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

						if (!isFading) {
							gallery.buildImage(imageData.loaded);
						}
					};

					// set alt and src
					image.alt = imageData.title;
					image.src = imageData.slide;
				}

				// 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 imageData = this.data[this.currentIndex];

				// <img src="'+imageData.slide+'" alt="'+imageData.title+'" />
				// image.alt = imageData.title;

				// Setup image
				this.$imageContainer
					.append('<span class="image-wrapper"><a class="advance-link" rel="history" href="#'+this.getNextIndex(this.currentIndex)+'" title="'+image.alt+'"></a></span>')
					.find('a')
					.append(image)
					.click(function() { clickHandler(gallery); })
					.end()
					.fadeIn('fast');
				
				if (this.onFadeIn) this.onFadeIn();
			}

			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();
				} else {
					var selectedThumb = this.currentIndex % this.settings.numThumbs;

					// Remove existing selected class and add selected class to new thumb
					this.$thumbsContainer
						.find('ul.thumbs li.selected')
						.removeClass('selected')
						.end()
						//.find('ul.thumbs a[href="#'+this.currentIndex+'"]')
						.find('ul.thumbs li').eq(selectedThumb)
						.addClass('selected');
				}
			}

			return this;
		},

		updateThumbs: function() {
			if (this.$thumbsContainer) {
				// Initialize currentPage to first page
				if (this.currentPage < 0)
					this.currentPage = 0;
			
				var startIndex = this.currentPage*this.settings.numThumbs;
		        var stopIndex = startIndex+this.settings.numThumbs-1;
		        if (stopIndex >= this.data.length)
					stopIndex = this.data.length-1;

				var needsPagination = this.data.length > this.settings.numThumbs;

				// Clear thumbs container
				this.$thumbsContainer.empty();
			
				// Rebuild top pager
				if (needsPagination && this.settings.enableTopPager) {
					this.$thumbsContainer.append('<div class="top pagination"></div>');
					this.buildPager(this.$thumbsContainer.find('div.top'));
				}

				// Rebuild thumbs
				var $ulThumbs = this.$thumbsContainer.append('<ul class="thumbs"></ul>').find('ul.thumbs');
				for (i=startIndex; i<=stopIndex; i++) {
					var selected = '';
				
					if (i==this.currentIndex)
						selected = ' class="selected"';
					
					var imageData = this.data[i];
					$ulThumbs.append('<li'+selected+'><a rel="history" href="#'+i+'" title="'+imageData.title+'"><img src="'+imageData.thumb+'" alt="'+imageData.title+'" /></a></li>');
				}

				// Rebuild bottom pager
				if (needsPagination && this.settings.enableBottomPager) {
					this.$thumbsContainer.append('<div class="bottom pagination"></div>');
					this.buildPager(this.$thumbsContainer.find('div.bottom'));
				}

				// Add click handlers
				var gallery = this;
				this.$thumbsContainer.find('a[@rel="history"]').click(function() { clickHandler(gallery); });
			}

			return this;
		},

		buildPager: function(pager) {
			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="#'+prevPage+'" title="'+this.settings.prevPageLinkText+'">'+this.settings.prevPageLinkText+'</a>');
			}
			
			// Page Index Links
			for (i=this.currentPage-5; i<=this.currentPage+5; i++) {
				var pageNum = i+1;
				
				if (i == this.currentPage)
					pager.append('<strong>'+pageNum+'</strong>');
				else {
					var imageIndex = i*this.settings.numThumbs;
					if (i>=0 && i<this.numPages) {
						pager.append('<a rel="history" href="#'+imageIndex+'" title="'+pageNum+'">'+pageNum+'</a>');
					}
				}
			}
			
			// Next Page Link
			var nextPage = startIndex+this.settings.numThumbs;
			if (nextPage < this.data.length) {
				pager.append('<a rel="history" href="#'+nextPage+'" title="'+this.settings.nextPageLinkText+'">'+this.settings.nextPageLinkText+'</a>');
			}
			
			return this;
		},
		
		historyCurrentHash: undefined,
	
		historyCallback: undefined,
		
		historyInit: function(callback){
			this.historyCallback = callback;
			var current_hash = location.hash;
			
			this.historyCurrentHash = current_hash;
			if ($.browser.msie) {
				// To stop the callback firing twice during initilization if no hash present
				if (this.historyCurrentHash == '') {
					this.historyCurrentHash = '#';
				}
			} else if ($.browser.safari) {
				// etablish back/forward stacks
				this.historyBackStack = [];
				this.historyBackStack.length = history.length;
				this.historyForwardStack = [];
				
				this.isFirst = true;
			}
			this.historyCallback(current_hash.replace(/^#/, ''));
			
			var gallery = this;
			setInterval(function() { gallery.historyCheck(); }, 100);
			
			return this;
		},
		
		historyAddHistory: function(hash) {
			// This makes the looping function do something
			this.historyBackStack.push(hash);
			
			this.historyForwardStack.length = 0; // clear forwardStack (true click occured)
			this.isFirst = true;
		},
		
		historyCheck: function() {
			if ($.browser.safari) {
				if (!this.dontCheck) {
					var historyDelta = history.length - this.historyBackStack.length;
					
					if (historyDelta) { // back or forward button has been pushed
						this.isFirst = false;
						if (historyDelta < 0) { // back button has been pushed
							// move items to forward stack
							for (var i = 0; i < Math.abs(historyDelta); i++) this.historyForwardStack.unshift(this.historyBackStack.pop());
						} else { // forward button has been pushed
							// move items to back stack
							for (var i = 0; i < historyDelta; i++) this.historyBackStack.push(this.historyForwardStack.shift());
						}
						var cachedHash = this.historyBackStack[this.historyBackStack.length - 1];
						if (cachedHash != undefined) {
							this.historyCurrentHash = location.hash;
							this.historyCallback(cachedHash);
						}
					} else if (this.historyBackStack[this.historyBackStack.length - 1] == undefined && !this.isFirst) {
						// back button has been pushed to beginning and URL already pointed to hash (e.g. a bookmark)
						// document.URL doesn't change in Safari
						if (document.URL.indexOf('#') >= 0) {
							this.historyCallback(document.URL.split('#')[1]);
						} else {
							var current_hash = location.hash;
							this.historyCallback('');
						}
						this.isFirst = true;
					}
				}
			} else {
				// otherwise, check for location.hash
				var current_hash = location.hash;
				if(current_hash != this.historyCurrentHash) {
					this.historyCurrentHash = current_hash;
					this.historyCallback(current_hash.replace(/^#/, ''));
				}
			}
			
			return this;
		}

	};

})(jQuery);
