// Slideshow "class"
// Author: Hilmar Nooitgedagt 11-2005

/*
// Usage: Include this file as a js script file in the page
// And at the end of the page, insert this code
// image is the image object used to "project" the slideshow
var slideShow = new SlideShow("slideShow"); 

slideShow.BaseUrl = 'http://www.ahostname.com';

slideShow.AddImage('/images/one.gif');
slideShow.AddImage('/images/two.gif');
slideShow.AddImage('/images/three.gif');
slideShow.AddImage('/images/four.gif');

slideShow.SetMainScreen(document.getElementById("slideshowimage")); 
slideShow.AddThumbScreen(document.getElementById("slide1"), false);
slideShow.AddThumbScreen(document.getElementById("slide2"), true);
slideShow.AddThumbScreen(document.getElementById("slide3"), false);

slideShow.Delay = 5000;
slideShow.ScrollThumbs = false;
slideShow.SetTransitionType("fade");
slideShow.Start();

*/

function SlideShow(instance)
{
	//
	// External (user) properties	
	//
	this.Delay = 5000;                       // sets the rotationdelay in milliseconds (5000 = 5 secs)
	this.ScrollThumbs = false;               // 'false' = display, but don't move the thumbnails
	                                         // 'true' = display the current image on the default thumbnail
	this.BaseUrl = '';                       // BaseUrl will be prepended to url when adding an image with AddImage()
	
	//
	// Internal properties and methods
	//
	this.Instance = instance;                // Name of the instance variable

	// Images
	this.Slides = new Array();               // List of html img objects , used for preloading slides
	this.current = 0;                        // Index of currently showing image
	this.screenBuffers = new Array();        // List of buffers used for loading images and transitions
	this.currentScreen = 0;                  // Index of the active "screen buffer"
	this.thumbScreens = new Array();         // List of html img objects on the page, used for showing thumbs
	this.defaultThumbScreen = 0;             // Index of thumbnail that is used as the current image thumb
	this.delayTimerID = null;                // Timer ID returned from setTimeout(). Used for stopping the timer
	this.isRunning = false;                  // Indicates if the slideshow is currently running

	this.transitionType = null;              // Type of transition used for switching images
	                                         // "fade" = images will be faded out/in when switching
	                                         // "dissolve" = images will blend into eachother
	                                         // "none" or null = images will switch instantly
	this.nextTransitionType = null;          // the next type of transition to use

	// Fading
	this.faderState = null;                  // fading state machine
	this.faderAmount = 10;                   // amount of opacity to change per fader step
	this.delayFadeIn = 50;                   // time (msecs) between fade-in steps
	this.delayFadeOut = 50;                  // time (msecs) between fade-out steps
}

// Method: Add an image to the slideshow
SlideShow.prototype.AddImage = function(url)
{
	var img = new Image();
	img.src = this.BaseUrl + url;
	
	this.Slides.push(img);
}

// Method: Set the 'projection screen' placeholder
SlideShow.prototype.SetMainScreen = function(screenimage)
{
	// Create new elements
	var mainlayer = document.createElement('div');
	var layerA = document.createElement('div');
	var layerB = document.createElement('div');
	var screenA = screenimage.cloneNode(true); 
	var screenB = screenimage.cloneNode(true); 
	
	// Make sure we are notified of any events that happen to the images
	screenA.onload = screenB.onload = this.ScreenBufferLoad;
	screenA.onerror = screenB.onerror = this.ScreenBufferError;
	screenA.onabort = screenB.onabort = this.ScreenBufferAbort;
	
	// Add a reference to this object to the images (used in the event handlers)
	screenA.slideShow = screenB.slideShow = this;
	
	// Copy styles
	layerA.style.position = layerB.style.position = 'absolute';
	mainlayer.style.width = 
		layerA.style.width = 
		layerB.style.width = screenimage.width;
	mainlayer.style.height = 
		layerA.style.height = 
		layerB.style.height = screenimage.height;
	mainlayer.style.zIndex = 
		layerA.style.zIndex = screenimage.style.zIndex;
	layerB.style.zIndex = -1;
	layerA.title = 'layerA';
	layerB.title = 'layerB';
	
	// Build structure	
	layerA.appendChild(screenA);
	layerB.appendChild(screenB);
	mainlayer.appendChild(layerA);
	mainlayer.appendChild(layerB);

	// Put it all into place
	var parent = screenimage.parentNode;
	parent.replaceChild(mainlayer, screenimage);
	
	// Set a reference to the buffers
	this.screenBuffers.push(mainlayer.firstChild);
	this.screenBuffers.push(mainlayer.lastChild);
	this.currentScreen = 0;
}

// Method: Add a thumbnail placeholder (image tag)
SlideShow.prototype.AddThumbScreen = function(imgelement,isDefault)
{
	this.thumbScreens.push(imgelement);
	if (isDefault)
	{
		this.defaultThumbScreen = this.thumbScreens.length-1;
	}
}

// Method: Starts rotating the show
SlideShow.prototype.Start = function()
{
	if (this.delayTimerID == null && this.screenBuffers.length > 0)
	{
		this.UpdateThumbnails();
		this.UpdateScreen();   // This will kick off the rotation sequence
		this.isRunning = true; // This will keep the rotation sequence running
	}
}

// Method: Stops rotating the show
SlideShow.prototype.Stop = function()
{
	if (this.delayTimerID != null)
	{
		clearTimeout(this.delayTimerID);  
		this.delayTimerID = null;
		this.isRunning = false;
	}
}

// Method: updates all images on screen
SlideShow.prototype.UpdateAll = function()
{
	if(this.ScrollThumbs)
	{
		this.UpdateThumbnails();
	}
	this.UpdateScreen();
}

// Method: updates the main image on screen
SlideShow.prototype.UpdateScreen = function()
{
	// Update the backbuffer
	var backbuffer = this.screenBuffers[this.BackBufferIndex()];
	backbuffer.firstChild.src = this.Slides[this.current].src;
}

// Method: updates the thumbnails on screen
SlideShow.prototype.UpdateThumbnails = function()
{
	// Update filmstrip
	for (var i=0; i<Math.min(this.thumbScreens.length,this.Slides.length); i++)
	{
		var imgIndex = (i-this.defaultThumbScreen) % this.Slides.length;
		this.thumbScreens[i].src = this.Slides[imgIndex].src;
		this.thumbScreens[i].imageId = imgIndex;
		this.thumbScreens[i].slideShow = this;
		this.thumbScreens[i].onclick = associateObjWithEvent(this,"ClickThumb");
		//this.thumbScreens[i].onclick = this.theInstance + ".Stop(); " + this.theInstance + ".Show(" + imgIndex + ");";
	}
	for (var i=this.Slides.length; i<this.thumbScreens.length; i++)
	{
		this.thumbScreens[i].style.visibility = "hidden";
	}
}

// Event: onClick handler for thumbnails
SlideShow.prototype.ClickThumb = function(event,element)
{
	this.Show(element.imageId);
	this.Stop();
}

// Helper function for hooking a function to an event
function associateObjWithEvent(obj, methodName)
{
    /* The returned inner function is intended to act as an event
       handler for a DOM element:-
    */
    return (function(e){
        /* The event object that will have been parsed as the - e -
           parameter on DOM standard browsers is normalised to the IE
           event object if it has not been passed as an argument to the
           event handling inner function:-
        */
        e = e||window.event;
        /* The event handler calls a method of the object - obj - with
           the name held in the string - methodName - passing the now
           normalised event object and a reference to the element to
           which the event handler has been assigned using the - this -
           (which works because the inner function is executed as a
           method of that element because it has been assigned as an
           event handler):-
        */
        return obj[methodName](e, this);
    });
}

// Method: Shows the image with the specified index
SlideShow.prototype.Show = function(index) 
{
	this.current = index;
	this.UpdateAll();
}

// Method: Shows the first image in the sequence
SlideShow.prototype.ShowFirst = function()
{
	this.Show(this.FirstIndex());
}

// Method: Shows the next image in the sequence (wraps around)
SlideShow.prototype.ShowNext = function() 
{
	this.Show(this.NextIndex(this.current));
}

// Method: Shows the previous image in the sequence (wraps around)
SlideShow.prototype.ShowPrevious = function() 
{
	this.Show(this.PrevIndex(this.current));
}

// Method: Shows the last image in the sequence
SlideShow.prototype.ShowLast = function() 
{
	this.Show(this.LastIndex());
}

// Method: workhorse for rotating the images
SlideShow.prototype.Rotate = function() 
{
	// Stop the timer that called this function
	this.StopTimer();
	
	// If a transition is not currently running, we move to the next image
	if (this.transitionState == null)
	{
		// Only do something if there are more than one image loaded
		if (this.Slides.length>1)
		{
			this.transitionType = this.nextTransitionType;
			this.ShowNext();
		}
	}
	else
	{
		this.DoTransition();
	}
}

// Method: set the type of the next transition
SlideShow.prototype.SetTransitionType = function(type)
{
	// First make sure it is a recognized transition type
	switch(type)
	{
		case "fade":
		case "fading":
		case "none":
		case "dissolve":
			this.nextTransitionType = type;
			if (this.transitionState == null)
			{
				this.transitionType = type;
			}
	}
}

// Method: starts a new transition sequence
SlideShow.prototype.DoTransition = function()
{
	switch(this.transitionType)
	{
		case "fade":      // Fade out to empty screen - fade in next image
		case "fading":    // alias for "fade"
			switch (this.transitionState)
			{
				case "fadein":
					this.FadeIn();
					break;
				case "fadeout_complete":
					this.FlipBuffers();
					this.FadeIn();
					break;;
				case "fadein_complete":
					this.transitionState = null;
					if (this.isRunning)
					{
						this.StartTimer(this.Delay);
					}
					break;
				case "fadeout":
					this.FadeOut();
					break;
				default:
					this.setOpacity(this.screenBuffers[this.BackBufferIndex()],0);
					this.FadeOut();
					break;
			}
			break;
		case "dissolve":  // Fade out to next image
			switch (this.transitionState)
			{
				case "fadeout_complete":
					this.FlipBuffers();
					this.transitionState = null;
					if (this.isRunning)
					{
						this.StartTimer(this.Delay);
					}
					break;
				case "fadeout":
					this.FadeOut();
					break;
				default:
					this.setOpacity(this.screenBuffers[this.BackBufferIndex()],100);
					this.FadeOut();
					break;
			}
			break;
		case "none":
		default:
			this.FlipBuffers();
			this.StartTimer(this.Delay);
			break;
	}
}

// Method: start a fade in for the current image
SlideShow.prototype.FadeIn = function()
{
	this.transitionState = "fadein";

	var opacity = this.getOpacity(this.screenBuffers[this.FrontBufferIndex()]) + this.faderAmount;
	if (opacity >= 100)
	{
		opacity = 100;
		this.transitionState = "fadein_complete";
	}
	this.setOpacity(this.screenBuffers[this.FrontBufferIndex()],opacity);
	this.StartTimer(this.delayFadeIn);
}

// Method: start a fade out for the current image
SlideShow.prototype.FadeOut = function()
{
	this.transitionState = "fadeout";
	
	var opacity = this.getOpacity(this.screenBuffers[this.FrontBufferIndex()]) - this.faderAmount;
	if (opacity <= 0)
	{
		opacity = 0;
		this.transitionState = "fadeout_complete";
	}
	this.setOpacity(this.screenBuffers[this.FrontBufferIndex()],opacity);
	this.StartTimer(this.delayFadeOut);
}

// Method: get the opacity of the image (0 < opacity < 100)
SlideShow.prototype.getOpacity = function(div)
{	
	if (div.filters)
	{
		if (div.filters[0])
		{
			return div.filters[0].opacity;
		}
		else return 100;
	}
	
	return div.style.opacity*100;
}

// Method: set the opacity of the image (0 < opacity < 100)
SlideShow.prototype.setOpacity = function(div,opacity)
{
	if (div.filters)
	{
		div.style.filter = 'alpha(opacity = ' + opacity + ');';
		return;
	}

	// To prevent ugly glitches we have to limit the opacity to the set <0.0 - 1.0> exclusive
	div.style.opacity = Math.max(.0001,Math.min(.9999,opacity/100.0));
}

// Method: flip the front and backbuffer
SlideShow.prototype.FlipBuffers = function()
{
	// Set the backbuffer as the new current screen
	this.currentScreen = this.BackBufferIndex();
	
	// Switch the z-index on the two buffers
	var frontZindex = this.screenBuffers[this.FrontBufferIndex()].style.zIndex;
	var backZindex = this.screenBuffers[this.BackBufferIndex()].style.zIndex;
	this.screenBuffers[this.FrontBufferIndex()].style.zIndex = backZindex;
	this.screenBuffers[this.BackBufferIndex()].style.zIndex = frontZindex;
}

// Method: get index of the backbuffer
SlideShow.prototype.BackBufferIndex = function()
{
	return (this.currentScreen + 1) % 2;
}

// Method: get reference to the frontbuffer
SlideShow.prototype.FrontBufferIndex = function()
{
	return this.currentScreen;
}

// Method: select first image index
SlideShow.prototype.FirstIndex = function()
{
	return 0;
}

// Method: select previous image index (wraps around)
SlideShow.prototype.PrevIndex = function(index) 
{
	if (index > 0) 
	{
		return index-1;
	}
	else return this.LastIndex();
}

// Method: select next image index (wraps around)
SlideShow.prototype.NextIndex = function(index) 
{
	if ((index+1) < this.Slides.length) 
	{
		return index+1;
	}
	else return this.FirstIndex();
}

// Method: select last image index
SlideShow.prototype.LastIndex = function()
{
	return this.Slides.length - 1;
}

// Method: starts the timer that calls Rotate()
SlideShow.prototype.StartTimer = function(delay)
{
	if (this.delayTimerID == null)
	{
		this.delayTimerID = window.setTimeout(this.Instance + ".Rotate()", delay);
	}
}

// Method: stops the timer that calls Rotate()
SlideShow.prototype.StopTimer = function()
{
	if (this.delayTimerID != null)
	{
		clearTimeout(this.delayTimerID);
		this.delayTimerID = null;
	}
}

//// EVENT HANDLERS /////////////////////////////////////////////////////
// Event handler: called when the backbuffer image has completed loading
SlideShow.prototype.ScreenBufferLoad = function(e)
{
	this.slideShow.DoTransition();
}

// Event handler: called when the backbuffer image encountered an error
SlideShow.prototype.ScreenBufferError = function(e)
{
	this.slideShow.ShowEventInfo(e);
}

// Event handler: called when the user aborted loading the image
SlideShow.prototype.ScreenBufferAbort = function(e)
{
	this.slideShow.ShowEventInfo(e);
}

SlideShow.prototype.ShowEventInfo = function(e)
{
	// X-Browser (MS quirks)
	var e = (e != null) ? e : window.event;
	var tg = (e.target != null) ? e.target : e.srcElement
	
	// Only show this debug info when running on localhost
	if (document.domain=='localhost')
	{
		window.status = "Type: " + e.type + ". Target = " + tg.name + " " + e.detail;
	}
}

