/**
 * Slideshow
 * Displays a set of images, allowing user to step through one by one
 *
 * version    1.1
 * author     Fred LeBlanc
 * modified   2006-12-12
 *
 * for more information: http://labs.tyrnia.net/javascript/slideshow/
 */

var slideshow = {
	// user updatable
	slides           : [],      // array of slides, each slide
	wrap             : false,   // should last slide have first slide as being next?
	imagePath        : '',      // absolute path to large slide images
	thumbPath        : '',      // absolute path to thumb images
	frameRate        : 1000/40, // interval rate for all animations
	framePadding     : 0,       // padding around the slide image
	resizeFrame      : true,    // should the frame resize?

	// internal vars, don't touch
	currentSlide     : null,
	ssFrame          : null,
	ssImageFrame     : null,
	ssNextFrame      : null,
	ssPreviousFrame  : null,
	ssImage          : null,
	ssLoading        : null,
	ssCaptionHolder  : null,
	ssCaptionFrame   : null,
	ssCaption        : null,
	ssStatus         : null,
	browser          : null,
	busy             : false,
	loadingInBusy    : false,
	loadingOutBusy   : false,
	imageLoadBusy    : false,
	showCaptions     : false,
	debug            : false,

	// Reference -------------------------------------------------------------------------

	// returns browser (ie|opera|mozilla|safari|netscape|unknown)
	getBrowser : function() {
		var appName    = navigator.appName;
		var userAgent  = navigator.userAgent;
		
		if (this.debug == true) {
			alert("appName: " + appName + "\nuserAgent: " + userAgent);
		}

		if (appName.indexOf('Internet Explorer') > -1) {
			return (userAgent.indexOf('Opera') > -1) ? 'opera' : 'ie';
		} else if (appName.indexOf('Opera') > -1) {
			return 'opera';
		} else if (appName.indexOf('Netscape') > -1) {
			// firefox, mozilla (seamonkey), safari, camino
			return 'netscape';
		}
			
		alert("We don't recognize your browser, slideshows may not work correctly.");
		return 'unknown';
	},

	// calculates next value in sequence based on: new += (<end> - <current>) / <rate>
	calculateNextValue : function (current, end, rate) {
		var totalDistance = parseInt(end) - parseInt(current);

		if (totalDistance != 0) {
			delta = (totalDistance < 2 && totalDistance > -2) ? totalDistance : this.roundAwayFromZero(totalDistance / rate);
			return (parseInt(current, 10) + parseInt(delta, 10));
		}
	},

	// rounds <number> using ceil for positive numbers, floor for negative numbers
	roundAwayFromZero : function(number) {
		number = parseFloat(number);

		if (number != 0) {
			number = (number > 0) ? Math.ceil(number) : Math.floor(number);
		}

		return number;
	},

	// gets current opacity of <element>, or null if not set
	getOpacity : function(element) {
		var opacity;

		switch (this.browser) {
			case 'mozilla':
				opacity = element.style.MozOpacity * 100;
				break;
			case 'ie':
				var filters = element.style.filter.match(/alpha\(opacity=(.*)\)/);
				opacity = (filters == null) ? null : filters[1];
				break;
			default:
				opacity = element.style.opacity * 100;
				break;
		}

		return opacity;
	},

	// sets opacity of <element> to <opacity>, where 0 <= <opacity> <= 100
	setOpacity : function(element, opacity) {
		switch (this.browser) {
			case 'mozilla':
				element.style.MozOpacity = opacity / 100;
				break;
			case 'ie':
				element.style.filter = 'alpha(opacity=' + opacity + ')';
				break;
			default:
				element.style.opacity = opacity / 100;
				break;
		}
	},
	
	// turns on debug mode, displays pop-up
	startDebugMode : function() {
		this.debug = true;
		
		var about = new Array();
		about.push("---------------------  [debug mode]  ---------------------");
		about.push(" ");
		about.push("The Fred Slideshow, version 1.0");
		about.push("by Fred LeBlanc <web@eternalis.net>");
		
		alert(about.join("\n"));
	},

	// Slideshow -------------------------------------------------------------------------

	// sets major internal variables, optional first parameter <startingSlideId> to start directly at slide this.slides.<startingSlideId>
	initSlideshow : function() {
		this.cleanSlides();
		
		if (this.slides.length == 0) {
			alert('There are no slides in this gallery.');
			return;
		}

		if (arguments.length > 0) {
			var startingSlideId = arguments[0];
			var startingKey     = this.getKey(startingSlideId);
		}
		
		// check for debug mode
		if (window.location.search.search(/ssdebug=true/i) > -1) {
			this.startDebugMode();
		}

		// set divs
		this.ssFrame          = document.getElementById('ssFrame');
		this.ssImageFrame     = document.getElementById('ssImageFrame');
		this.ssCaptionHolder  = document.getElementById('ssCaptionHolder');
		this.ssCaptionFrame   = document.getElementById('ssCaptionFrame');
		this.ssCaption        = document.getElementById('ssCaption');
		this.ssNextFrame      = document.getElementById('ssNextFrame');
		this.ssPreviousFrame  = document.getElementById('ssPreviousFrame');
		this.ssLoading        = document.getElementById('ssLoading');
		this.ssStatus         = document.getElementById('ssStatus');
		this.browser          = this.getBrowser();

		this.ssStatus               = (this.ssStatus == undefined) ? null : this.ssStatus;
		this.ssFrame.style.padding  = this.framePadding + 'px';
		this.initCaptions();

		if (startingKey != undefined) {
			this.transitionSlide('direct', 1, startingKey);
		} else {
			this.transitionSlide('', 1);
		}
	},

	// gets this.slides array key based on this.slides.<id>
	getKey : function(id) {
		for (var i=0; this.slides.length > i; i++) {
			if (this.slides[i].id == id) {
				return i;
			}
		}
	},
	
	// removes any accidental extra slides at the end
	cleanSlides : function() {
		var i=0;
		while (i < this.slides.length) {
			if (this.slides[i] == undefined) {
				this.slides.splice(i, 1);
				continue;
			}
			
			i++;
		}
	},

	// hides current slide if this.ssImage is set
	hideCurrentImage : function() {
		if (!this.ssImage) {
			return;
		}

		this.ssImage.style.display = 'none';
		this.imageLoadBusy = true;
	},

	// shows current slide, sets opacity to 0 (for fading in)
	showCurrentImage : function() {
		this.setOpacity(this.ssImage, 0);
		this.ssImage.style.display = '';
	},

	// updates current slide in a given <direction>
	// if <direction> is 'direct': looks for optional second parameter <slideKey>
	updateCurrentSlide : function(direction) {
		var newSlide;
		var slideKey = (arguments[1] != undefined && arguments[1] != null) ? arguments[1] : null;

		if (this.currentSlide == null) {
			this.currentSlide = 0;
		}

		if (direction == 'previous') {
			this.currentSlide--;

			if (this.currentSlide < 0) {
				this.currentSlide = (this.wrap) ? this.slides.length - 1 : this.currentSlide + 1;
			}
		} else if (direction == 'next') {
			this.currentSlide++;

			if (this.currentSlide > this.slides.length - 1) {
				this.currentSlide = (this.wrap) ? 0 : this.currentSlide - 1;
			}
		} else if (direction == 'direct' && slideKey != null) {
			if (slideKey < this.slides.length) {
				this.currentSlide = slideKey;
			} else {
				alert('Cannot find the slide selected. Now showing the first slide.');
				this.currentSlide = 0;
			}
		}

		// update status (if available)
		if (this.ssStatus != null) {
			this.ssStatus.innerHTML = 'Slide ' + (this.currentSlide + 1) + ' of ' + this.slides.length;
		}

		newSlide = this.slides[this.currentSlide];

		if (!this.ssImage) {
			var newImage = document.createElement('img');

			newImage.src     = this.imagePath + newSlide.image;
			newImage.alt     = newSlide.caption;
			newImage.width   = newSlide.width;
			newImage.height  = newSlide.height;
			newImage.setAttribute('id', 'ssImage');
			newImage.style.display = 'none';

			with (this) {
				newImage.onload = function() {
					completeImageLoad();
				}
			}

			this.ssImageFrame.appendChild(newImage);
			this.ssImage = document.getElementById('ssImage');
		} else {
			this.ssImage.src     = this.imagePath + newSlide.image;
			this.ssImage.alt     = newSlide.caption;
			this.ssImage.width   = newSlide.width;
			this.ssImage.height  = newSlide.height;
			this.ssImage.setAttribute('id', 'ssImage');
		}
	},

	// turns off image loading lock
	completeImageLoad : function() {
		this.imageLoadBusy = false;
	},

	// if slideshow isn't busy, starts sequence to display next slide
	nextSlide : function() {
		if (this.busy == true) {
			return false;
		}

		this.transitionSlide('next');
	},

	// if slideshow isn't busy, starts sequence to display previous slide
	previousSlide : function () {
		if (this.busy == true) {
			return false;
		}

		this.transitionSlide('previous');
	},

	// if slideshow isn't busy, starts sequence to display slide <slide>
	goToSlide : function(slide) {
		if (this.busy == true) {
			return false;
		}

		this.transitionSlide('direct', 1, slide);
	},
	
	// if slideshow isn't busy, starts sequence to display slide by id
	goToSlideId : function(id) {
		var slide = this.getKey(id);
		this.goToSlide(slide);
	},

	// transition sequence for slide, fades out old slide and displays <direction> slide (next|previous|direct)
	// if <direction> is 'direct': looks for optional second parameter <step> set to 1 and optional third parameter <slideKey>
	transitionSlide : function(direction) {
		// optional parameters
		var step     = (arguments[1] != undefined && arguments[1] != null) ? arguments[1] : 1;
		var slideKey = (arguments[2] != undefined && arguments[2] != null) ? arguments[2] : null;

		switch (step) {
			// starts transition, fades out slide image and hides it
			case 1:
				if (this.loadingOutBusy == true) {
					with (this) {
						setTimeout(function() { transitionSlide(direction, step, slideKey); }, 200);
					}
					break;
				}
				this.busy = true;

				if (this.ssImage == null) {
					this.transitionSlide(direction, (step + 1), slideKey);
					this.transitionSlide(direction, (step + 2), slideKey);
					this.imageLoadBusy = true;
					break;
				}

				var currentOpacity;
				var endOpacity = 0;
				currentOpacity = this.getOpacity(this.ssImage);

				if (currentOpacity != endOpacity) {
					var newOpacity = this.calculateNextValue(currentOpacity, endOpacity, 1.8);
					this.setOpacity(this.ssImage, newOpacity);

					with (this) {
						setTimeout(function() { transitionSlide(direction, step, slideKey); }, frameRate);
					}
				} else {
					this.hideCurrentImage();
					this.transitionSlide(direction, (step + 1), slideKey);
					this.transitionSlide(direction, (step + 2), slideKey);
				}
				break;

			// fades loading block in
			case 2:
				var currentOpacity;
				var endOpacity = 100;

				currentOpacity = this.getOpacity(this.ssLoading);
				currentOpacity = (currentOpacity == null) ? 0 : currentOpacity;

				if (currentOpacity != endOpacity) {
					this.loadingInBusy = true;
					var newOpacity = this.calculateNextValue(currentOpacity, endOpacity, 1.8);
					this.setOpacity(this.ssLoading, newOpacity);

					with (this) {
						setTimeout(function() { transitionSlide(direction, step, slideKey); }, frameRate);
					}
				} else {
					this.loadingInBusy = false;
				}
				break;

			// updates image to new image
			// trigger: caption change
			case 3:
				this.updateCurrentSlide(direction, slideKey);
				this.transitionSlide(direction, (step + 1), slideKey);
				this.transitionCaption(direction, 1);
				break;

			// adjust frame size
			case 4:
				if (this.resizeFrame == true) {
					var currentWidth  = (this.ssFrame.style.width != '') ? parseInt(this.ssFrame.style.width, 10) : parseInt(this.ssFrame.offsetWidth, 10) - (this.framePadding * 2);
					var currentHeight = (this.ssFrame.style.height != '') ? parseInt(this.ssFrame.style.height, 10) : parseInt(this.ssFrame.offsetHeight, 10) - (this.framePadding * 2);
					var endWidth      = this.slides[this.currentSlide].width;
					var endHeight     = this.slides[this.currentSlide].height;

					if (currentWidth != endWidth || currentHeight != endHeight) {
						if (currentWidth != endWidth) {
							currentWidth = this.calculateNextValue(currentWidth, endWidth, 5);
							this.ssFrame.style.width = currentWidth + 'px';
						}
						if (currentHeight != endHeight) {
							currentHeight = this.calculateNextValue(currentHeight, endHeight, 5);
							this.ssFrame.style.height = currentHeight + 'px';
						}
						with (this) {
							setTimeout(function() { transitionSlide(direction, step, slideKey); }, frameRate);
						}
					} else {
						with (this) {
							setTimeout(function() { transitionSlide(direction, (step + 1), slideKey); }, 10);
						}
					}
				} else {
					with (this) {
						setTimeout(function() { transitionSlide(direction, (step + 1), slideKey); }, 10);
					}
				}
				break;

			// fades out loading block
			case 5:
				if (this.loadingInBusy == true || this.imageLoadBusy == true) {
					with (this) {
						setTimeout(function() { transitionSlide(direction, step, slideKey); }, 200);
					}
					break;
				}

				var currentOpacity = this.getOpacity(this.ssLoading);
				var endOpacity     = 0;

				currentOpacity      = (currentOpacity == null) ? 100 : currentOpacity;

				if (currentOpacity != endOpacity) {
					this.loadingOutBusy = true;
					var newOpacity = this.calculateNextValue(currentOpacity, endOpacity, 2);
					this.setOpacity(this.ssLoading, newOpacity);

					with (this) {
						setTimeout(function() { transitionSlide(direction, step, slideKey); }, frameRate);
					}
				} else {
					this.loadingOutBusy = false;
				}

				if (currentOpacity == 100) {
					this.transitionSlide(direction, (step + 1), slideKey);
				}
				break;

			// shows current image
			case 6:
				this.showCurrentImage();
				this.transitionSlide(direction, (step + 1), slideKey);
				break;

			// fades new slide image in
			case 7:
				var currentOpacity = this.getOpacity(this.ssImage);
				var endOpacity     = 100;

				if (currentOpacity != endOpacity) {
					var newOpacity = this.calculateNextValue(currentOpacity, endOpacity, 3);
					this.setOpacity(this.ssImage, newOpacity);

					with (this) {
						setTimeout(function() { transitionSlide(direction, step); }, frameRate);
					}
				} else {
					this.busy = false;
				}
				break;
		}
	},

	// Captions --------------------------------------------------------------------------

	// checks if captions are being used, sets major caption variables
	initCaptions : function() {
		if (this.showCaptions == false) {
			// hide caption box
			this.hideCaptionBox();
			return;
		}

		this.ssCaptionHolder.setAttribute('width', this.ssCaptionHolder.offsetWidth);
		this.ssCaptionFrame.style.left = '0px';
	},

	// when captions are not being used, shrinks box out of sight
	hideCaptionBox : function() {
		var endSize       = 0;
		var currentWidth  = this.ssCaptionHolder.offsetWidth;
		var currentHeight = this.ssCaptionHolder.offsetHeight;

		if (currentWidth != endSize && currentHeight != endSize) {
			if (currentWidth != endSize) {
				var newWidth = this.calculateNextValue(currentWidth, endSize, 3);
				this.ssCaptionHolder.style.width = newWidth + 'px';
			}
			if (currentHeight != endSize) {
				var newHeight = this.calculateNextValue(currentHeight, endSize, 3);
				this.ssCaptionHolder.style.height = newHeight + 'px';
			}

			with (this) {
				setTimeout(function() { hideCaptionBox(); }, frameRate);
			}
		}
	},

	// creates temporary box with copy of caption in it for sliding effect
	createTemporaryCaption : function() {
		if (this.showCaptions == false) { return; }
		var tempCaption      = document.createElement('div');
		var tempCaptionFrame = document.createElement('div');

		// temp caption
		tempCaption.className  = this.ssCaption.className;
		tempCaption.setAttribute('id', 'tempCaption');
		tempCaption.innerHTML  = this.ssCaption.innerHTML;

		// temp caption frame
		tempCaptionFrame.className      = this.ssCaptionFrame.className;
		tempCaptionFrame.style.width    = this.ssCaptionFrame.offsetWidth + 'px';
		tempCaptionFrame.style.position = 'absolute';
		tempCaptionFrame.style.top      = '0px';
		tempCaptionFrame.style.left     = '0px';
		tempCaptionFrame.style.zIndex   = 4;
		tempCaptionFrame.setAttribute('id', 'tempCaptionFrame');
		tempCaptionFrame.appendChild(tempCaption);

		// append caption frame to holder
		this.ssCaptionHolder.appendChild(tempCaptionFrame);
	},

	// moves main caption box to it's temporary new location for sliding effect
	moveCaptionFrame : function(direction) {
		if (this.showCaptions == false) { return; }
		var newLeft = this.ssCaptionFrame.offsetWidth + this.framePadding;

		this.ssCaptionFrame.style.left = (direction == 'previous') ? (newLeft * -1) + 'px' : newLeft + 'px';
	},

	// loads caption of current slide
	loadNewCaption : function() {
		if (this.showCaptions == false) { return; }
		this.ssCaption.innerHTML = this.slides[this.currentSlide].caption;
	},

	// transition sequence for caption, slides old caption out and new caption in
	transitionCaption : function (direction) {
		if (this.showCaptions == false) { return; }
		var step = (arguments[1] != undefined && arguments[1] != null) ? arguments[1] : 1;

		switch (step) {
			// create copy of caption frame
			case 1:
				this.createTemporaryCaption();
				this.transitionCaption(direction, step + 1);
				break;

			// move starting caption frame
			case 2:
				this.moveCaptionFrame(direction);
				this.transitionCaption(direction, step + 1);
				break;

			// update caption text
			case 3:
				this.loadNewCaption();
				this.transitionCaption(direction, step + 1);
				break;

			// slide captions
			case 4:
				var tempDiv         = document.getElementById('tempCaptionFrame');
				var currentPosition = parseInt(this.ssCaptionFrame.style.left);

				if (currentPosition != 0) {
					var newPosition     = this.calculateNextValue(currentPosition, 0, 4);
					var newTempPosition = newPosition + this.ssCaptionFrame.offsetWidth + this.framePadding;

					this.ssCaptionFrame.style.left = newPosition + 'px';

					if (direction != 'previous') {
						tempDiv.style.left = (newPosition - this.ssCaptionFrame.offsetWidth - this.framePadding) + 'px';
					} else {
						tempDiv.style.left = (newPosition + this.ssCaptionFrame.offsetWidth + this.framePadding) + 'px';
					}

					with (this) {
						setTimeout(function() { transitionCaption(direction, step); }, frameRate/2);
					}
				} else {
					this.transitionCaption(direction, step + 1);
				}
				break;

			// delete temporary caption div
			case 5:
				this.ssCaptionHolder.removeChild(document.getElementById('tempCaptionFrame'));
				break;
		}
	}
}