/*
imageEngine.js - Image Engine Version 4.0.2
===========================================
Released: 23 December 2009
Copyright (c) 2009, Richard Smith
http://www.drawingbusiness.com/imageengine.php 
All rights reserved.

Image Engine 4.0 is free software, available under the terms of a BSD-style open source license.

Redistribution and use, with or without modification, are permitted provided that the following
conditions are met:

	1) Redistributions of source code must retain the above copyright notice, this list of
	   conditions and the following disclaimer.
	
	2) All installations of this software must display a prominent link to the software project
	   page: http://www.drawingbusiness.com/imageengine.php

Disclaimer: This software is provided by the copyright holders and contributors "as is" and any
express or implied warranties, including, but not limited to, the implied warranties of
merchantability and fitness for a particular purpose are disclaimed. In no event shall the
copyright owner or contributors be liable for any direct, indirect, incidental, special,
exemplary, or consequential damages (including, but not limited to, procurement of substitute
goods or services; loss of use, data, or profits; or business interruption) however caused and
on any theory of liability, whether in contract, strict liability, or tort (including negligence
or otherwise) arising in any way out of the use of this software, even if advised of the
possibility of such damage.	

Description: Image Engine is a lightweight browser slideshow, ideal for displaying artwork and
photographs. It is easy to configure, and simple to use. For instructions on setting it up on
your own site, please visit the project page:

http://www.drawingbusiness.com/imageengine.php

Use: If you find Image Engine useful, I would appreciate it if you could send me a message to 
let me know where you are using it:

Changelog:

Version 4.0.2
-------------

30/12/2009	Introduced improved handling of missing or incomplete HTML elements.
						
			Made a change to the method used to capture the slide description. Instead of trying
			to step through the child nodes of the <dd> element, the entire innerHTML element is
			captured. This enables the use of anchor tags and other formatting within the image
			descriptions.
			
			Added support for unordered lists - <ul>, as well as definition lists - <dl>. Using
			unordered lists means that there will be no image description; only the image title
			and the slide number will be displayed.
			
			If no alt attribute is supplied for the slide thumbnails, then an attempt will be
			made to parse the filename from the image path.

Version 4.0.1
-------------

29/12/2009	Changed the way the image src attribute is assigned, to avoid excessive pre-caching on
			pages that have a lot of slides. The maxCache setting determines how many images are
			pre-cached. Once number is reached, images are only loaded on demand.
			

http://www.drawingbusiness.com/contact.php
*/

//	Define some global variables:
var maxCache=5;					//	The maximum number of full size images to cache.
var listType;					//	Set depending on the type of list used for the slide-tray element.
var slideSize;					//	This will be set by the size of the first slide in the list.
var slideSpacing=16;			//	The space between each of the slides.
var currentImage;				//	An index value for the currently displayed image.
var slides=new Array();			//	Used to store object pointers for the thumbnail anchor tags.
var slideOrigin=0;				//	The origin of the first slide.
var slideHolderSize;			//	The width of the slide tray.
var slidesTotalSize;			//	The width of all the slides arranged in a row.
var orientation;				//	Set depending on whether the slide tray is horizontal or vertical.
var scrollTimerID;				//	An event ID to control the thumbnail scrolling.
var scrollInterval=1;			//	Controls the scroll speed interval (milliseconds).
var slideArrowLeft, slideArrowRight, imgNextArrow, imgPreviousArrow, imageContainer;	//	Shortcuts to commonly used document objects.
window.onload=initialiseEngine;

function initialiseEngine()
{
	//	Add event handlers to selected parts of the document, to add interactive
	//	components to the page. First make sure that only a modern browser will
	//	attempt this:
	if (!id("image-engine-wrapper")) return false;
	
	//	Populate some global variables.
	orientation='left';
	try
	{
		listType=id('slide-tray').tagName.toLowerCase();
		if (listType!="dl" && listType!="ul") throw new Error("Only a definition list <dl>, or unordered list <ul> can be used for the slide thumbnails.");

		slideHolderSize=parseInt(id('slide-tray').offsetWidth);
		if (parseInt(id('slide-tray').offsetHeight)>slideHolderSize)
		{
			slideHolderSize=parseInt(id('slide-tray').offsetHeight);
			orientation='top';
		}
		if (!slideHolderSize) throw new Error("Couldn't get the size of the slide-tray object.");
		
		//	Create some shortcuts to DOM elements that are referred to frequently.
		slideArrowLeft=id('tc-scroll-left');
		slideArrowRight=id('tc-scroll-right');
		imgNextArrow=id('ic-right-button');
		imgPreviousArrow=id('ic-left-button');
		imageContainer=id('image-container');

		imageContainer.setVisibility=setObjectVisibility;
		//	Set event handlers for the next/previous buttons.
		id('image-engine-wrapper').onmouseover=function () { imgPreviousArrow.setVisibility(); imgNextArrow.setVisibility() }
		id('image-engine-wrapper').onmouseout=function () { imgPreviousArrow.setVisibility('hidden'); imgNextArrow.setVisibility('hidden') }
	}
	catch (e)
	{
		//	There are required objects missing from the DOM tree, so report an error and stop executing.
		return reportErrors("There are required objects missing from the HTML document.\n\n"+e);
	}
	//	Set the method for showing/hiding the arrows.
	slideArrowLeft.setVisibility=setObjectVisibility;
	slideArrowRight.setVisibility=setObjectVisibility;
	imgPreviousArrow.setVisibility=setObjectVisibility;
	imgNextArrow.setVisibility=setObjectVisibility;
	//	Set event handlers for the slide scroll buttons.
	slideArrowLeft.onmousedown=function () { scrollInterval=100; scrollSlides(8) }
	slideArrowRight.onmousedown=function () { scrollInterval=100; scrollSlides(-8) }
	slideArrowLeft.onmouseup=function () { clearInterval(scrollTimerID) }
	slideArrowRight.onmouseup=function () { clearInterval(scrollTimerID) }
	//	Set event handlers for the next/previous buttons.
	imgPreviousArrow.onclick=function () { switchImage(-1) }
	imgNextArrow.onclick=function () { switchImage(1) }
	//	Initialise the stored visibility settings for the next/previous arrows:
	imgPreviousArrow.tb_visibility='hidden';
	imgNextArrow.tb_visibility='hidden';

	//	Walk the DOM to read the slide data.
	var slideCount=0;
	var slideTray=id('slide-tray').childNodes;
	
	//	This isn't a very sophisticated way to populate the slide array. It relies on well formed HTML,
	//	with all the required tags in place. It's pretty dumb, and not set up to deal with errors or omissions yet.
	for (i=0; i<slideTray.length; i++)
	{
		//	To walk the DOM tree, look for nodeType==1 (an element), then match the tagName.
		//	Element: 1
		//	Attribute: 2
		//	Text: 3
		var theTagName=(slideTray[i].nodeType==1) ? slideTray[i].tagName.toLowerCase() : false;
		if (theTagName=="dt" || theTagName=="li")
		{
			var slideNodes=slideTray[i].childNodes;
			for (var a=0; a<slideNodes.length; a++)
			{
				//	Look through the child nodes of the dt element to find an anchor tag.
				if (slideNodes[a].nodeType==1 && slideNodes[a].tagName.toLowerCase()=="a")
				{
					//	Retrieve the padding-right setting from the anchor tag to set the
					//	slide spacing (if it's specified).
					if (parseInt(slideNodes[a].style.paddingRight)) slideSpacing=parseInt(slideNodes[a].style.paddingRight);
					if (parseInt(slideNodes[a].style.paddingBottom)) slideSpacing=parseInt(slideNodes[a].style.paddingBottom);
					//	Search through the child elements of the anchor tag to find the <img> tag.
					imgTag=findChildElement(slideNodes[a], "img");
					if (slideSize==null)
					{
						if (imgTag.width) slideSize=imgTag.width;
						if (orientation=='top' && imgTag.height) slideSize=imgTag.height;
					}
					//	Store the reference to the current anchor tag object in an array.
					slides[slideCount]=slideNodes[a];
					//	Add some properties to the array element so they are easy to retrieve.
					slides[slideCount].makeCurrent=showImage;
					slides[slideCount].index=slideCount;
					//	Set the origin for the slides.
					slides[slideCount].setPosition=setSlideOrigin;
					slides[slideCount].setPosition();
					//	We need to add an "onclick" event handler to the anchor tag.
					slideNodes[a].onclick=showImage;
					// Create a new object to store the full sized image and associated data.
					slideNodes[a].slidedata=new Image();
					//	Pre-cache the image, provided that the preset maximum hasn't been reached.
					if (slideCount<=maxCache) slideNodes[a].slidedata.src=slideNodes[a].href;
					//	To the image path so that un-cached images can be loaded up when requested.
					slideNodes[a].slidedata.pathToImg=slideNodes[a].href;
					slideNodes[a].slidedata.description=null;
					//	alert("Alt tag: "+imgTag.alt.toString());
					if (imgTag.alt.toString()!="")
					{
						slideNodes[a].slidedata.imgTitle=imgTag.alt;
					} else
					{
						//	No alt text has been set for the thumbnail image, so use the last part of the
						//	filename as the image title.
						var filename=/([^\/]+\.\w+)$/;
						try
						{
							slideNodes[a].slidedata.imgTitle=slideNodes[a].href.match(filename)[0];
						}
						catch (ex)
						{
							slideNodes[a].slidedata.imgTitle=null;
						}
					}
					slideNodes[a].slidedata.index=slideCount;
					slideNodes[a].slidedata.show=setImageSize;	//	Set a method for showing the main image.
					slideCount++;
					break;	//	We break here to preserve the value of 'a' for use in the next condition.
				}
			}
		}
		//	Now look for the corresponding <dd> tag if the slide-tray element is a <dt> list.
		if (listType=="dl" && slideTray[i].nodeType==1 && slideTray[i].tagName.toLowerCase()=="dd")
		{
			//	Try to grab the HTML contents of the <dd> element.
			try
			{
				slideNodes[a].slidedata.description=trim(slideTray[i].innerHTML);
			}
			catch (ex)
			{
				slideNodes[a].slidedata.description="No description is provided for this image.";
			}
		}
	}
	//	Some rudimentary error checking.
	if (!slideSize) return reportErrors("Couldn't get the pixel dimensions of any of the slide images.");
	if (slideCount==0) return reportErrors("No qualifying anchor tags where located withing the document.\n\nCheck the validity of the HTML; qualifying anchors must be within <dt> tags in a well formed definition list (<dl>)");
	
	//	Set the total width of all the slides, used for calculating the maximum scroll position.
	slidesTotalSize=parseInt(((slideSize+slideSpacing)*slideCount)-slideSpacing);
	
	//	If all the slides fit within the containing object, then we need to hide the left
	//	and right buttons.
	var slideArrowVisibility='visible';
	if (slidesTotalSize<slideHolderSize) slideArrowVisibility='hidden';
	
	//	Now we need to show the first image in the sequence, and reveal the slide holder and right arrow (if visible).
	//	return false;
	slideArrowRight.setVisibility(slideArrowVisibility);
	id('slide-tray').style.visibility='visible';
	slides[0].makeCurrent();
}
function showImage()
{
	slideinfo=this.slidedata;
	//	Create the HTML for the image tag.
	var imageWidth, imageHeight;
	imageContainer.style.visibility='hidden';
	imageContainer.innerHTML="<img src=\"" + slideinfo.pathToImg + "\" />";
	//	Set the index for the current image, so we know what to do if the next/previous buttons are used.
	currentImage=slideinfo.index;

	//	Set the description text for the image.
	var imgDescription;
	if (slideinfo.imgTitle) imgDescription="<h4>"+slideinfo.imgTitle+"</h4>";
	if (slideinfo.description) imgDescription+=("<p>"+slideinfo.description+"</p>");
	if (id('ic-description')) id('ic-description').innerHTML=imgDescription+"<p id='iess-counter'>"+(currentImage+1)+"/"+slides.length+"</p>";
		
	if (!slideinfo.width || slideinfo.width==0)
	{
		if (!slideinfo.src) slideinfo.src=slideinfo.pathToImg;
		slideinfo.onload=setImageSize;
	} else {
		slideinfo.show();
	}
	//	Scroll the thumbnail for the current image into view.
	var unitSize=slideSize+slideSpacing;
	var origin=(currentImage*unitSize)+slideOrigin;
	var offset=slideHolderSize-unitSize;
	var increment=4;
	var originTarget;
	if (origin<0) originTarget=slideOrigin-origin;
	if (origin>offset)
	{
		originTarget=slideOrigin-(origin-offset);
		increment=-4;
	}
	if (originTarget!=null) scrollSlides(increment,originTarget);
	//	Now hide the next/previous buttons if we are at the end of the sequence.
	var setLeftArrow="visible";
	var setRightArrow="visible";
	if (currentImage==0) setLeftArrow="hidden";
	if (currentImage==slides.length-1) setRightArrow="hidden";
	imgPreviousArrow.tb_visibility=setLeftArrow;
	imgNextArrow.tb_visibility=setRightArrow;
	return false;
}
function setImageSize()
{
	//	Before setting the image dimensions, check that the image that triggered
	//	this function is still the current image. Otherwise the wrong dimensions
	//	could be applied to the currently selected image.
	if (this.index!=currentImage) return false;
	//	Set the dimensions of the image-container element, and of the img element within it.
	imageContainer.childNodes[0].width=this.width;
	imageContainer.childNodes[0].height=this.height;
	//	Set the height for the whole image engine wrapper element.
	if (orientation=='left') id('image-engine-wrapper').style.height=this.height+"px";
	//	Set dimensions of the image container element.
	imageContainer.style.width=this.width+"px";
	imageContainer.style.height=this.height+"px";
	imageContainer.style.left=Math.round((parseInt(id('image-engine-wrapper').offsetWidth)-this.width)/2)+"px";
	if (orientation=='top') imageContainer.style.top=Math.round((parseInt(id('image-engine-wrapper').offsetHeight)-this.height)/2)+"px";
	//	Make the image container visible.
	imageContainer.setVisibility('visible');
}
function switchImage(direction)
{
	//	Change the current image.
	currentImage=currentImage+direction;
	if (currentImage<0) currentImage=0;
	if (currentImage==slides.length) currentImage=slides.length-1;
	slides[currentImage].makeCurrent();
}
function scrollSlides(increment)
{
	//	Move the slides when the left or right arrows are clicked, or if the next/previous image
	//	buttons are used. If an optional second argument is passed to the function, this is treated
	//	as a target to scroll to.
	var originTarget;
	if (arguments.length>1) originTarget=arguments[1];
	
	var setLeftArrow="visible";
	var setRightArrow="visible";
	if (slidesTotalSize+slideOrigin+increment<slideHolderSize)
	{
		increment=0;
		setRightArrow="hidden";
	}
	if (slideOrigin+increment>0)
	{
		increment=0;
		setLeftArrow="hidden";
	};
	slideArrowLeft.setVisibility(setLeftArrow);
	slideArrowRight.setVisibility(setRightArrow);
	if (!increment) return false;

	slideOrigin=slideOrigin+increment;
	//	It's possible that if the combined width of the slides and spacing isn't divisible
	//	by the scroll increment, then the slides could just keep scrolling, so we need to
	//	test for this.
	moveAllSlides();
	if (originTarget!=null && slideOrigin==originTarget) return false;

	//	The arguments passed to the setTimeout function depend on whether this function
	//	is being called with the optional originTarget.
	var passArgs=increment.toString();
	if (originTarget==null)
	{
		//	Set the next interval, and increase the speed by decreasing the timeout.
		if (scrollInterval>1) scrollInterval=Math.round(scrollInterval*0.8);
	} else
	{
		passArgs+=(","+originTarget);
		scrollInterval=1;
	}
	scrollTimerID=window.setTimeout("scrollSlides("+passArgs+")", scrollInterval);
	
	return false;
}
function moveAllSlides()
{
	//	Loop through each slide object and trigger their positioning method.
	for(i=0;i<slides.length;i++) { slides[i].setPosition() }			
}
function setSlideOrigin() { this.style[orientation]=parseInt(this.index*(slideSize+slideSpacing)+slideOrigin)+"px" }
function setObjectVisibility(a)
{
	//	Set the visibility of the passed object. If no value is passed, use the stored setting.
	if (!a) a=this.tb_visibility;
	this.style.visibility=a;
}
function findChildElement(element, tagToFind)
{
	//	Look through the child nodes of the passed object, and look for the specified tag.
	if (!element.childNodes) return false;
	for(var a=0; a<element.childNodes.length; a++)
	{
		if (element.childNodes[a].nodeType==1 && element.childNodes[a].tagName.toLowerCase()==tagToFind) return element.childNodes[a];
	}
	return false;
}
function trim(stringToTrim) { return stringToTrim.replace(/^\s+|\s+$/g,"") }
function id(x)
{
	if (typeof x=="string") return document.getElementById(x);
	return x;
}
function reportErrors(msg)
{
	//	Eventually the error reporting will be expanded to be a bit more elegant.
	//	For now it just shows an alert with a brief message.
	alert("IMAGE ENGINE ERROR:\n\n"+msg);
	return false;
}
