function FeatureSwapper(featureElem, manager1, manager2) {

	var self = this;

	self.featureElem = featureElem;

	self.manager1 = manager1;
	self.manager2 = manager2;
	self.currManager = manager1;

	self.swapElem = document.createElement("span");
	self.swapElem.setAttribute("id", "feature_swapper");

	self.featureElem.appendChild(self.swapElem);

	/* Register the class methods. */

	self.setSwapperContent = setSwapperContent;
	self.swap = swap;

	self.setSwapperContent(manager2);

	self.swapElem.onclick = function() { self.swap(); }
}

function setSwapperContent(manager) {

	var swapIcon;
	var swapText;

	while(this.swapElem.firstChild)
		this.swapElem.removeChild(this.swapElem.firstChild);

	swapIcon = new Image();
	swapIcon.src = manager.icon;
	swapIcon.alt = "[icon] " + manager.type;

	swapText = document.createTextNode("View as " + manager.type);

	this.swapElem.appendChild(swapIcon);
	this.swapElem.appendChild(swapText);
}

function swap() {

	var manager;

	if (this.currManager == this.manager1) {

		this.manager1.hide();
		manager = this.manager2;
	}

	else {

		this.manager2.hide();
		manager = this.manager1;
	}

	manager.display();

	this.setSwapperContent(this.currManager);
	this.currManager = manager;
}

function SculptureListManager(sculptureListElem) {

	this.sculptureListElem = sculptureListElem;
	this.type = "sculpture list";
	this.icon = "style/icons/sculpture_list_icon.png";

	/* Register the class methods. */

	this.display = displaySculptureList;
	this.hide = hideSculptureList;
}

function displaySculptureList() {

	this.sculptureListElem.style.display = "block";
}

function hideSculptureList() {

	this.sculptureListElem.style.display = "none";
}

/*******************************************************************************
** SlideshowManager class: This class connects a slideshow, its controller, and
** a loading widget together and mediates communication between these objects.
*******************************************************************************/

/*
** Constructor\4:
**
**	Arguments:		- The parent or host HTML element of the slideshow.
**					- An array of slide objects.
**					- The dimension of the slideshow area (width = height).
**					- The width of the slideshow controller HTML element.
**
**	Description:	Initialises the SlideshowManager object, creating the
**					slideshow, controller, and progress bar children, and
**					beginning the slideshow.
*/	

function SlideshowManager(parentElem, slides, slideshowSize, controlWidth) {

	this.parentElem = parentElem;
	this.type = "slideshow";
	this.icon = "style/icons/slideshow_icon.png";

	this.slideshowElem = document.createElement("div");
	this.slideshowElem.setAttribute("id", "slideshow");

	this.controlsElem = document.createElement("div");
	this.controlsElem.setAttribute("id", "slideshow_controls");

	this.imageElem = new Image();

	this.progressBar = null;
	this.slideshow = null;
	this.slideshowControl = null;

	/* Register the class methods. */

	this.display = displaySlideshow;
	this.hide = hideSlideshow;

	this.notifySlideLoaded = notifySlideLoaded;
	this.notifyProgressComplete = notifyProgressComplete;

	/* Add in the slideshow elements and instantiate the various slideshow
	** objects, if appropriate. */

	this.slideshowElem.appendChild(this.imageElem);
	this.parentElem.appendChild(this.slideshowElem);

	if (slides == null) {

		var noImage = this.imageElem;

		noImage.src = "images/slideshow_no_images.png";
		noImage.alt = "No images available";
		noImage.style.marginTop = ((slideshowSize -
									(noImage.height + 1)) / 2) + "px";
	}

	else if (slides.length == 1) {

		this.slideshow = new Slideshow(this, slides, 0, this.imageElem,
									   slideshowSize, slideshowSize);
	}

	else {

		this.slideshowElem.appendChild(this.controlsElem);

		this.slideshow = new Slideshow(this, slides, 5000, this.imageElem,
									   slideshowSize, slideshowSize);
		this.progressBar = new ProgressBar(this.controlsElem, this,
										   controlWidth - 2, slides.length,
										   "Loading...");
		this.slideshowControl = new SlideshowControl(this.controlsElem,
													 this.slideshow);

		/* Begin the slideshow. */

		this.slideshow.begin();
	}
}

/*
** notifySlideLoaded\0:
**
**	Description:	When called, this method tells the loading widget that a
**					slide has been loaded.
*/

function notifySlideLoaded() {

	this.progressBar.increment();
}

/*
** notifyProgressComplete\0:
**
**	Description:	When called, this method disposes of the slideshow widget
**					and replaces it with the controller object. The manager
**					then tells the slideshow to play.
*/

function notifyProgressComplete() {

	this.progressBar.dispose();
	this.progressBar = null;

	this.slideshowControl.display();
	this.slideshow.play();
}

/*
** displaySlideshow\0:
**
**	Description:	Display the slideshow.
*/

function displaySlideshow() {

	this.slideshowElem.style.display = "block";
}

/*
** hideSlideshow\0:
**
**	Description:	Hide the slideshow.
*/

function hideSlideshow() {

	this.slideshowElem.style.display = "none";
}

/*******************************************************************************
** Slide class: This very simple class simply packages the properties needed by
** each slide into an object.
*******************************************************************************/

/*
** Constructor\5:
**
**	Description:	Self-explanatory.
*/

function Slide(name, width, height, imageURL, linkURL) {

	this.name = name;
	this.width = width;
	this.height = height;
	this.imageURL = imageURL;
	this.linkURL = linkURL;
}

/*******************************************************************************
** Slideshow class: Creates a slideshow object, where a slideshow is defined as
** a series of images, shown one after the other, with a (user-defined) pause
** inbetween.
*******************************************************************************/

/*
** Constructor\6:
**
**	Arguments:		- The slideshow's manager object.
**					- An array of slide objects.
**					- The length of time between slide transitions (ms).
**					- The <img> element that hosts each slide.
**					- The slideshow container's width and height.
**
**	Description:	Initialises the Slideshow object and displays the first
**					slide.
*/

function Slideshow(manager, slides, pauseLength, imageElem, width, height) {

	/*
	** Member variables and constants:
	**
	**	manager:		the slideshow manager object for this slideshow.
	**	slides:			array of slide objects.
	**	images:			array of Image objects in slideshow.
	**	imageElem:		<img> element that will "house" the slideshow.
	**	width:			width of the slideshow container in pixels.
	**	height:			height of the slideshow container in pixels.
	**	opacity:		how opaque (and thus visible) the current slide is.
	**	pauseLength:	time in milliseconds between image changes.
	**	currIndex:		index of the image currently being displayed.
	**	interval:		reference to the slide change interval object.
	**	tInterval:		reference to the transition interval object.
	**	tMode:			current transition mode.
	**	tModes:			associative array of possible transition modes. 
	*/

	this.manager = manager;

	this.slides = slides;
	this.images = new Array();
	this.imageElem = imageElem;

	this.width = width;
	this.height = height;

	this.opacity = 100;

	this.pauseLength = pauseLength;
	this.currIndex = 0;

	this.interval = null;

	this.tInterval = null;
	this.tMode = null;

	this.tModes = new Object();
	this.tModes['FADE_OUT'] = 1;
	this.tModes['FADE_IN'] = 2;

	/* Member methods. */

	this.begin = begin;

	this.changeSlide = changeSlide;
	this.transition = transition;	

	this.setSlide = setSlide;
	this.setSlideOpacity = setSlideOpacity;

	this.back = back;
	this.next = next;

	this.pause = pause;
	this.play = play;

	/* Set the image element to display the first slide. */

	this.setSlide();
}

/*
** begin\0:
**
**	Description:	Initialise the slideshow's array of images. As each image
**					downloads, notify the slideshow manager (the manager will
**					typically display a loading widget of some kind).
*/

function begin() {

	var self = this;
	var currImage;

	for (var i = 0; i < self.slides.length; i++) {

		currImage = new Image();
		currImage.src = self.slides[i].imageURL;

		if (currImage.complete)
			self.manager.notifySlideLoaded();

		else
			currImage.onload = function() { self.manager.notifySlideLoaded(); }

		self.slides[i].image = currImage;
	}
}

/*
** setSlide\0:
**
**	Description:	Sets the visible slide to that at index currIndex in the
**					slides[] array.
*/

function setSlide() {

	var i = this.currIndex;
	var slide = this.slides[i];

	this.imageElem.width = slide.width;
	this.imageElem.height = slide.height;

	this.imageElem.style.width = slide.width + "px";
	this.imageElem.style.height = slide.height + "px";
	this.imageElem.style.cursor = "pointer";
	this.imageElem.style.marginTop = ((this.height - (slide.height + 1)) / 2)
									 + "px";

	this.imageElem.src = slide.imageURL;
	this.imageElem.title = slide.name;
	this.imageElem.alt = "Slide " + (i+1) + " of " + this.slides.length + ": "
						 + slide.name;

	this.imageElem.onclick = function () { window.location.href = slide.linkURL; }
}

/*
** changeSlide\1:
**
**	Arguments:		- An optional flag indicating whether the slide should be
**					  replaced with its predecessor or successor.
**
**	Description:	Changes the displayed image to either the following or
**					preceding image in the slides array, offloading the opacity
**					fade-out/fade-in to transition().
*/

function changeSlide(direction) {

	var self = this;
	var fadeDelay = 25;

	/* If no direction was supplied, choose 'f' (forward) as the default. */

	direction = direction || 'f';

	if (direction == 'f') {

		if (self.currIndex == (self.slides.length - 1))
			self.currIndex = 0;

		else
			self.currIndex += 1;
	}

	else {

		if (self.currIndex == 0)
			self.currIndex = self.slides.length - 1;

		else
			self.currIndex -= 1;
	}

	self.tMode = self.tModes['FADE_OUT'];
	self.tInterval = setInterval(function() { self.transition() }, fadeDelay);
}

/*
** transition\0:
**
**	Description:	Performs the slide fade-out/fade-in effect. This method
**					should be called at a regular interval via changeSlide().
**					Its behaviour depends on the value of tMode.
*/

function transition() {

	var numOpacityLevels = 10;
	var opacityDelta = 100 / numOpacityLevels;

	switch(this.tMode) {

		/* Fade out the old slide. */
		case this.tModes['FADE_OUT']:

			if (this.opacity > 0) {

				this.opacity -= opacityDelta;
				this.setSlideOpacity();
			}

			else {

				this.setSlide();
				this.tMode = this.tModes['FADE_IN'];
			}

			break;

		/* Fade in the new slide. */
		case this.tModes['FADE_IN']:

			if (this.opacity < 100) {

				this.opacity += opacityDelta;
				this.setSlideOpacity();
			}

			else {

				clearInterval(this.tInterval);
				this.tMode = null;
			}

			break;

		/* Default case (only activated in error). */
		default:

			clearInterval(this.tInterval);
			this.tMode = null;
			break;
	}

}

/*
** setSlideOpacity\0:
**
**	Description:	Sets the current slide's opacity to that specified by the
**					value of the 'opacity' class member.
*/

function setSlideOpacity() {

	var opacity;

	opacity = (this.opacity == 100) ? 99.999 : this.opacity;
	opacity = (this.opacity == 0) ? 0.001 : this.opacity;
  
	/* IE/Win */
	this.imageElem.style.filter = "alpha(opacity:" + opacity + ")";
  
	/* Safari < 1.2, Konqueror */
	this.imageElem.style.KHTMLOpacity = opacity / 100;
  
	/* Older Mozilla and Firefox */
  	this.imageElem.style.MozOpacity = opacity / 100;
  
	/* Safari > 1.2, newer Firefox and Mozilla (CSS3) */
  	this.imageElem.style.opacity = opacity / 100;
}

/*
** play\0:
**
**	Description:	Plays the slideshow. More specifically, this method creates
**					an interval object that executes the changeSlide() method
**					every pauseLength seconds.
*/

function play() {

	var self = this;

	this.interval = setInterval(function() { self.changeSlide('f') },
								this.pauseLength);
}

/*
** pause\0:
**
**	Description:	Pauses the slideshow by destroying an existing interval
**					object.
*/

function pause() {

	clearInterval(this.interval);
}

/*
** next\0:
**
**	Description:	Displays the next slide immediately. Also pauses the
**					slideshow.
*/

function next() {

	this.pause();
	this.changeSlide('f');
}

/*
** back\0:
**
**	Description:	Displays the previous slide immediately. Also pauses the
**					slideshow.
*/

function back() {

	this.pause();
	this.changeSlide('b');
}


/*******************************************************************************
** SlideshowControl class: This class creates a control element for a slideshow,
** with buttons that activate the play(), pause(), back() and next() methods. A
** SlideshowManager can optionally be used to integrate this control with a
** loading widget.
*******************************************************************************/

/*
** Constructor\2:
**
**	Arguments:		- The parent or host HTML element of the slideshow control.
**					- The slideshow object to be controlled.
**
**	Description:	Initialises the SlideshowControl object, creating the back,
**					next, pause, and play buttons and their event handlers.
*/	

function SlideshowControl(parentElem, slideshow) {

	var self = this;

	self.parentElem = parentElem;
	self.slideshow = slideshow;

	/*
	** Create the four slideshow control buttons:
	**
	**	backButton:		loads the previous slide,
	**	nextButton:		loads the next slide,
	**	pauseButton:	pauses the slideshow, and
	**	playButton:		plays the slideshow.
	*/

	self.backButton = document.createElement("div");
	self.backButton.setAttribute("id", "back_button");
	self.backButton.setAttribute("title", "View previous slide");
	self.backButton.setAttribute("class", "slideshow_control");
	self.backButton.setAttribute("className", "slideshow_control");

	self.nextButton = document.createElement("div");
	self.nextButton.setAttribute("id", "next_button");
	self.nextButton.setAttribute("title", "View next slide");
	self.nextButton.setAttribute("class", "slideshow_control");
	self.nextButton.setAttribute("className", "slideshow_control");

	self.pauseButton = document.createElement("div");
	self.pauseButton.setAttribute("id", "pause_button");
	self.pauseButton.setAttribute("title", "Pause slideshow");
	self.pauseButton.setAttribute("class", "slideshow_control");
	self.pauseButton.setAttribute("className", "slideshow_control");

	self.playButton = document.createElement("div");
	self.playButton.setAttribute("id", "play_button");
	self.playButton.setAttribute("title", "Play slideshow");
	self.playButton.setAttribute("class", "slideshow_control");
	self.playButton.setAttribute("className", "slideshow_control");

	/* Attach event handlers to each button. */

	self.backButton.onmouseover = function() { this.setAttribute("id", "back_button_pressed"); }
	self.backButton.onmouseout = function() { this.setAttribute("id", "back_button"); }
	self.backButton.onclick = function() {

		self.pauseButton.style.display = "none";
		self.playButton.style.display = "block";

		slideshow.back();
	}

	self.nextButton.onmouseover = function() { this.setAttribute("id", "next_button_pressed"); }
	self.nextButton.onmouseout = function() { this.setAttribute("id", "next_button"); }
	self.nextButton.onclick = function() {

		self.pauseButton.style.display = "none";
		self.playButton.style.display = "block";

		slideshow.next();
	}

	self.pauseButton.onmouseover = function() { this.setAttribute("id", "pause_button_pressed"); }
	self.pauseButton.onmouseout = function() { this.setAttribute("id", "pause_button"); }
	self.pauseButton.onclick = function() {

		self.pauseButton.style.display = "none";
		self.playButton.style.display = "block";

		slideshow.pause();
	}

	self.playButton.onmouseover = function() { this.setAttribute("id", "play_button_pressed"); }
	self.playButton.onmouseout = function() { this.setAttribute("id", "play_button"); }
	self.playButton.onclick = function() {

		self.playButton.style.display = "none";
		self.pauseButton.style.display = "block";

		slideshow.play();
	}

	/* Register the class methods. */

	this.display = display;
}

/*
** display:\0:
**
**	Description:	Adds the control buttons as children of the parent element,
**					thus displaying them.
*/

function display() {

	this.parentElem.appendChild(this.backButton);
	this.parentElem.appendChild(this.nextButton);
	this.parentElem.appendChild(this.playButton);
	this.parentElem.appendChild(this.pauseButton);
}

/*******************************************************************************
** ProgressBar class: displays a progress bar.
*******************************************************************************/

/*
** Constructor\2:
**
**	Arguments:		- The parent or host HTML element of the progress bar.
**					- The manager object that coordinates the actual loading
**					  of objects and the progress bar display.
**					- The maximum width of the progress bar.
**					- The number of loading items the progress bar represents.
**					- Text to display while loading (ideally, this should be
**					  shorter than the maximum width of the progress bar).
**
**	Description:	Initialises the ProgressBar object.
*/

function ProgressBar(parentElem, manager, maxWidth, numItems, loadingText) {

	this.parentElem = parentElem;
	this.manager = manager;

	this.maxWidth = maxWidth;
	this.currWidth = 0;

	this.totalNumItems = numItems;
	this.loadedItems = 0;
	this.incrementWidth = maxWidth / numItems;

	/* Create the div elements that form the background and foreground of the
	** progress bar, and assign them the relevant class names. */

	this.backgroundElem = document.createElement("div");
	this.backgroundElem.setAttribute("class", "progress_bar_background");
	this.backgroundElem.setAttribute("className", "progress_bar_background");

	this.foregroundElem = document.createElement("div");
	this.foregroundElem.setAttribute("class", "progress_bar_foreground");
	this.foregroundElem.setAttribute("className", "progress_bar_foreground");

	this.loadingText = document.createTextNode(loadingText);

	/* Add the divs and text node to the supplied parent element. */

	this.backgroundElem.appendChild(this.foregroundElem);
	this.parentElem.appendChild(this.loadingText);
	this.parentElem.appendChild(this.backgroundElem);

	/* Register the class methods. */

	this.increment = increment;
	this.dispose = dispose;
}

/*
** increment\0:
**
**	Description:	Called when one of the tracked items has loaded. If all
**					tracked items have loaded, notify the manager of that fact.
*/

function increment() {

	this.loadedItems++;

	if (this.loadedItems < this.totalNumItems) {

		this.currWidth += this.incrementWidth;
		this.foregroundElem.style.width = this.currWidth + "px";
	}

	else {

		this.foregroundElem.style.width = this.maxWidth + "px";
		this.manager.notifyProgressComplete();
	}
}

/*
** dispose\0:
**
**	Description:	Called when all tracked items have loaded. The HTML
**					elements that constitute the progress bar are removed from
**					the host page's DOM tree.
*/

function dispose() {

	this.parentElem.removeChild(this.backgroundElem);
	this.parentElem.removeChild(this.loadingText);
}

