/*
	Name: KBurnalizer
	Author: Rui Pereira
	URI: http://iRui.ac/cool-stuff/kburnalizer

	This work is licensed under a Creative Commons License
	http://creativecommons.org/licenses/by/2.5/
*/


/*
	Version 1.0 - 2006/04/09
	Version 1.1 - 2006/05/25 
	Version 2.0 - 2007/01/13
	Version 2.1 - 2007/01/28
		Changes:
			- Supports hiding of all display visuals on the constructor (progress bars, iRui logo, and so on...)
			- The background color has been replaced by viewPortBackground, which now:
				* No background (transparent): empty string (this is the new default)
				* Color: provide color code (e.g. #fff)
				* Background image: provide an URL
			- New parameter: delayStart. Specifies a delay period before the Slide Show starts. 
			- Bug fix: kbImage.src only gets assigned once per slide (instead of every frame). This solves memory leaks and improves performance.
	Version 2.2 - 2007/03/28
		Changes:
			- Supports a width of 100% by using the value 0
	Version 2.3 - 2007/05/01
		Changes:
			- Checks if slideShows is already defined from another JS file, to allow multiple file loads
	Version 3.0 - 2007/06/07
		Changes:
			- Fixed bug related to src=null becoming URL of page
			- Code cleanups

*/



/*****
	Class KBurnalizer
	This is the main slideshow class.
	In order for the OO design to work, it is *extremely* important that the kbID parameter 
	matches the "name" of the variable that holds an instance of this objects
	If you don't respect this rule, this doesn't work - simple as that :)	
******/
function KBurnalizer(kbID, viewPortID, displayText /*optional*/, displayVisuals /*optional*/, viewPortWidth /*optional*/, viewPortHeight /*optional*/, viewPortBackground /*optional - color or URL*/, preload /*optional*/, delayStart /*optional*/, duration /*optional*/, transition /*optional*/,  fps /*optional*/) {

	// The unique identifier of this object (the variable name)
	this.kbID = kbID;

	// The ID of the "div" that will be replaced by the slide show
	this.viewPortID = viewPortID;
	
	// The specified dimensions of the view port
	// This can be optional
	this.viewPortWidth = 0;
	this.viewPortHeight = 0;
	if(viewPortWidth) {
		this.viewPortWidth = viewPortWidth;
	}
	if(viewPortHeight) {
		this.viewPortHeight = viewPortHeight;
	}

	// Set the backgroung color
	if(!viewPortBackground) {
		this.viewPortBackground = "";
	}
	else {
		this.viewPortBackground = viewPortBackground;
	} 

	// Set the preload flag
	// If true, all images are loaded before the slideshow starts (can be a while if there a lot of images...)
	// If false (default), as soon as one is available, the slideshow starts 
	this.preload = false;
	if(preload == true) {
			this.preload = true;
	}
	
	// Set the start delay
	if(!delayStart) {
		this.delayStart = 0;
	}
	else {
		this.delayStart = delayStart;
	} 
	
	// Set the framerate
	if(!fps) {
		this.fps = 25;
	}
	else {
		this.fps = fps;
	} 
	
	// Set the duration
	if(!duration) {
		// Default is 5 seg
		this.duration = 5000; 
	}
	else {
		this.duration = duration;
	} 
	
	// Set the transition
	if(!transition) {
		// Default is 2 seg
		this.transition = 2000;
	}
	else {
		this.transition = transition;
	} 
	
	// Set the display text flag
	this.displayText = false;
	if(displayText == true) {
			this.displayText = true;
	}
	
	// Set the display visuals flag (loadbar, logo, etc...)
	this.displayVisuals = true;
	if(displayVisuals == false) {
			this.displayVisuals = false;
	}
		
	this.delay = 1000 / this.fps;	

	// Shutdown flag
	this.shutdownFlag = false;

	// Slideshow started flag
	this.started = false;


	// Slides	    	
	this.slides = new Array();	
	this.slideIndex;
	this.loadedSlides = 0;
	
	
	// Active objects
	this.kbViewport; 
	this.kbImage = new Array(2);
	this.kbTextArea;
	this.kbText;
	this.kbLoadBar;
	this.kbiRui;
	
	// Swap support objects	
	this.swap = 0;
	this.activeSlide = new Array(2);
	this.activeFocusArea = new Array(2);;
	
	
	// Register the init of the slideshow on the onload event of the page
	slideShows.push(this);
	
	////////////////////////////////////////////
	// Init function of this object
	this.init = function() {	
		
		// Get the specified viewport				
		this.kbViewport = document.getElementById(this.viewPortID);	
				
		// Sets the dimensions, if needed
		if( this.viewPortWidth > 0) {
			this.kbViewport.style.width = this.viewPortWidth + "px";
		}
		else {
			this.kbViewport.style.width = "100%";
		}			
		if(this.viewPortHeight > 0) {
			this.kbViewport.style.height = this.viewPortHeight + "px";
		}

		// Set the background
		// none (by default), a color or a URL to a image
		// 
		if( viewPortBackground != "" ) {
			if( viewPortBackground.substr(0,1) == "#" ) {
				this.kbViewport.style.backgroundColor = this.viewPortBackground;
			}
			else {
				this.kbViewport.style.background = "url('" + viewPortBackground + "')";
			}
		}


		// Creates the image objects and places them under the viewport						
		// There are two to support the swapping process
		this.kbImage[0] = document.createElement('img');
		this.kbImage[0].className = 'kb-image';
		this.kbImage[0].style.zIndex = 10;
		this.kbViewport.appendChild(this.kbImage[0]);

		this.kbImage[1] = document.createElement('img');
		this.kbImage[1].className = 'kb-image';
		this.kbImage[1].style.zIndex = -10;
		this.kbViewport.appendChild(this.kbImage[1]);
		
		
		// Text area
		this.kbTextArea = document.createElement('div');
		this.kbTextArea.className = 'kb-text-area';
		this.kbTextArea.style.zIndex = 50;
		if(!this.displayText) {
			this.kbTextArea.style.visibility="hidden";
		}
		this.kbViewport.appendChild(this.kbTextArea);					
		
		
		// Text
		this.kbText = document.createElement('div');
		this.kbText.className = 'kb-text';
		this.kbText.style.zIndex = 60;
		this.kbViewport.appendChild(this.kbText);		
		
		// Load bar
		this.kbLoadBar = document.createElement('div');
		this.kbLoadBar.className = 'kb-load-bar';
		this.kbLoadBar.style.zIndex = 50;
		if(this.displayVisuals == true) {
			this.kbViewport.appendChild(this.kbLoadBar);	
		}
		
		// Image counter text
		this.kbCounter = document.createElement('div');
		this.kbCounter.className = 'kb-counter-text';
		this.kbCounter.style.zIndex = 100;
		this.kbCounter.innerHTML = "";
		this.kbViewport.appendChild(this.kbCounter);			
				
		// iRui text
		this.kbiRui = document.createElement('div');
		this.kbiRui.className = 'kb-irui-text';
		this.kbiRui.style.zIndex = 100;
		this.kbiRui.innerHTML = "&nbsp;<a href='http://iRui.ac/cool-stuff/kburnalizer' title='Visit the homepage of KBurnalizer at iRui.ac'>KB</a>&nbsp;";
		if(this.displayVisuals == true) {
			this.kbViewport.appendChild(this.kbiRui);
		}
		
		// Triggers the loading of all images. From this point on everything happens in parallel... event handlers take care of rest
		if(this.slides.length>0) {
			for(var i=0; i<this.slides.length; i++) {
				this.slides[i].load();
			}				
		}
		// In case no images were supplied in the first place
		else {
			this.startSlideShow();
		}
	}
	
	// Resizes the slideshow - takes immediate effect
	this.setViewPortDimensions = function(newViewPortWidth, newViewPortHeight) {
			this.viewPortWidth = newViewPortWidth;
			this.viewPortHeight = newViewPortHeight;
			this.kbViewport.style.width = this.viewPortWidth + "px";
			this.kbViewport.style.height = this.viewPortHeight + "px";
	}
		
	// Event handler for a failed image load
	this.onerrorHandler = function() {	
		this.loadStatus = false;
		this.kbObj.commonHandler();
	}
	
	// Event handler for a sucessfull image load
	this.onloadHandler = function() {
		this.loadStatus = true;
		this.kbObj.commonHandler();
	}
	
	
	this.commonHandler = function() {		
		// One more has been loaded
		this.loadedSlides++;
		
		// Shows the progress bar
		this.kbLoadBar.style.width = Math.round((this.loadedSlides * 100.0) / this.slides.length) + "%";
		
		
		
		// There are two distinct ways to handle the loading of images: preloading or immediate
		if(!this.preload) {
			
			///////////////////////////////////////////////////////////////////////////////////
			// At least one image must be sucessfuly loaded in order to start the slideshow
			// If the slideshow has not started
			if(!this.started) {
				
				// Check if a at least one image was successful
				// This event could have been from a failed image
				var i=0;				
				while(i<this.slides.length) {			
					if(!this.slides[i].image.loadStatus) {				
						i++;
					}
					// A good one was found
					else {				
						this.started=true;
						this.startSlideShow();
						// Stop checking...
						break;
					}
				}				
			}
			
			// If this was the last one
			if(this.loadedSlides == this.slides.length) {
				// Clear the progress bar
				this.kbLoadBar.style.display = "none";
				// If everything went COMPLETELY WRONG...
				if(!this.started) this.kbText.innerHTML = "No Images Sucessfuly Loaded";
			}
			///////////////////////////////////////////////////////////////////////////////////
			
		}
		else {
			
			///////////////////////////////////////////////////////////////////////////////////
			// If the last one was loaded
			if(this.loadedSlides == this.slides.length) {
				this.kbText.innerHTML = "";
				this.kbLoadBar.style.display = "none";
				
				var i=0;				
				while(i<this.slides.length) {			
					if(!this.slides[i].image.loadStatus) {				
						i++;
					}
					// A good one was found
					else {				
						this.started=true;
						this.startSlideShow();
						// Stop checking...
						return;
					}
					this.kbText.innerHTML = "No Images Sucessfuly Loaded";
				}
			}
			///////////////////////////////////////////////////////////////////////////////////
			
		}		
	}
	
	
	// Actually starts the slideshow. Should only be called after one image has been loaded
	this.startSlideShow = function() {
		
		if(this.slides.length>0) {
				this.slideIndex = 0;
				this.next();
		}
		// Just in case a slideshow is started without any slides being loaded
		else {
			this.kbLoadBar.style.display = "none";
			this.kbText.innerHTML = "No Images";
		}
	}

	////////////////////////////////////////////
	// Renders a frame
	this.renderFrame = function(swap, opacity) {
		    		
		// Get viewport size    		
		var viewPortWidth = this.kbViewport.clientWidth;
		var viewPortHeight = this.kbViewport.clientHeight;
		
		// Get image original dimensions
		var originalImageWidth = this.activeSlide[swap].originalImageWidth;
		var originalImageHeight = this.activeSlide[swap].originalImageHeight;
				
		// Dimensions of the selected area    		
		var newWidth =this.activeFocusArea[swap].bottomX - this.activeFocusArea[swap].topX;
		var newHeight = this.activeFocusArea[swap].bottomY - this.activeFocusArea[swap].topY;
		
		// Zoom level
		var zoomX = viewPortWidth * 1.0 / newWidth;
		var zoomY = viewPortHeight * 1.0 / newHeight;

		// Resize image
		this.kbImage[swap].style.width = Math.round((zoomX * originalImageWidth)) + "px";
		this.kbImage[swap].style.height = Math.round((zoomY * originalImageHeight)) + "px";

		// And displace to put selected area on top left    		
		this.kbImage[swap].style.left = -Math.round((this.activeFocusArea[swap].topX * zoomX)) + "px";
		this.kbImage[swap].style.top = -Math.round((this.activeFocusArea[swap].topY * zoomY)) + "px";   


		// Control the opacity. These three settings should cover most browsers
		this.kbImage[swap].style.MozOpacity = opacity;
		this.kbImage[swap].style.filter = 'Alpha(opacity=' + (opacity * 100) + ')';
		this.kbImage[swap].style.opacity = opacity;
		
		
		// Only set the image after all the resizing has been done, to avoid artifacts
		// And only do it one time! 
		if(this.kbImage[swap].src != this.activeSlide[swap].image.src) {
			this.kbImage[swap].src = this.activeSlide[swap].image.src;	
			this.kbImage[swap].title = this.activeSlide[swap].description;			
			// Make sure the new slide is visible
			this.kbImage[swap].style.visibility = 'visible';
		}
		
	}


	////////////////////////////////////////////////////////////
	// Calculate the settings for the next frame of the slide
	this.calculateNextFrame = function(
		swap, 
		stepsToGo, inStepsToGo, outStepsToGo, opacity, 
		opacityInc, topXInc, topYInc, bottomXInc, bottomYInc
	) {
		

		
		// If global shutdown is active, stop processing
		if(this.shutdownFlag) {
			return;
		}
		
		// If the slide was forced to stop, do some cleanup
		if(this.activeSlide[swap].isStopping()) {
			this.kbImage[swap].style.visibility = 'hidden';
			this.kbImage[swap].style.zIndex = -10;
			this.activeSlide[swap].stopRunning();
			return;
		}
		
		
		// Render the current frame
		this.renderFrame(swap, opacity);
		
		// If there are frames left for this slide
		if(stepsToGo>0) {
			
			// New focus area values
			this.activeFocusArea[swap].topX += topXInc;
			this.activeFocusArea[swap].topY += topYInc;
			this.activeFocusArea[swap].bottomX += bottomXInc;
			this.activeFocusArea[swap].bottomY += bottomYInc;    			
			
			
			// Fade in block
			if(inStepsToGo > 0) {
				opacity += opacityInc;
				
				// Don't let opacity reach 1! 
				// Browsers flicker when changing from "Alpha" mode to standard solid display
				if(opacity >= .99) {
					opacity = .99;
				}
				inStepsToGo--;
			}

			// Fade out block
			if(outStepsToGo < 0) {
				opacity -= opacityInc;				

				if(opacity < 0.01) { 
						opacity = 0.01;
				}
			}

			// The next slide begins when the fade out of the current slide starts
			if(outStepsToGo == 0) {
				// Move this image back
				this.kbImage[swap].style.zIndex = -10;
				// Start next slide
				this.next();				
			}
			
			// Reduce steps
			outStepsToGo--;										
			stepsToGo--;

			
			// Calculate the next frame after the delay time
			setTimeout(this.kbID + ".calculateNextFrame(" + 
				swap + "," + 
				stepsToGo + "," + 
				inStepsToGo + "," + 
				outStepsToGo + "," + 
				opacity + "," + 
				opacityInc + "," + 
				topXInc + "," + 
				topYInc + "," + 
				bottomXInc + "," + 
				bottomYInc + ")", 
				
				this.delay);
		}
		else {
			// Hide the image when not in use. Speeds up the browser rendering a little bit
			this.kbImage[swap].style.visibility = 'hidden';
			this.activeSlide[swap].stopRunning();
			// No more frames to display in this slide. Die away in peace...
		}
	}



	////////////////////////////////////////////////////////////
	// Starts a new slide
	this.startSlide = function(swap, duration, transition, startFocusArea, endFocusArea) {    		    		
		
		
		// Calculate the opposite corners displacements
		var topDisplacementX = endFocusArea.topX - startFocusArea.topX;
		var topDisplacementY = endFocusArea.topY - startFocusArea.topY;
		var bottomDisplacementX = endFocusArea.bottomX - startFocusArea.bottomX;
		var bottomDisplacementY = endFocusArea.bottomY - startFocusArea.bottomY;
		
		// How many frames will be needed at the current framerate
		var durationSteps = Math.round(duration/this.delay);
				
		// How many frames of fade in (half of the transition time)
		var transitionStepsIn = Math.round(((transition / 2) / this.delay));
		// Fade out lasts as long as fade in. transitionStepsOut holds how many frames need to be
		// rendered before the fade out starts
		var transitionStepsOut = durationSteps - transitionStepsIn - 1;
		
		// Opacity increments must correspond to how many frames the fade effect lasts
		var opacityInc = 1 / transitionStepsIn;
		
		// Opposite corners will move in a linear progression between the start and the end point
		var topXInc = topDisplacementX / durationSteps;
		var topYInc = topDisplacementY / durationSteps;
		var bottomXInc = bottomDisplacementX / durationSteps;
		var bottomYInc = bottomDisplacementY / durationSteps;
		
		// Set the working focus area. This will be updated during the rendering process
		this.activeFocusArea[swap] = startFocusArea.getCopy();		
		
		// Show the image description
		this.displayDescription();
		    
		// Start the first frame		
		setTimeout(this.kbID + ".calculateNextFrame(" + 
			swap + "," + 
			durationSteps + "," + 
			transitionStepsIn + "," + 
			transitionStepsOut + ", 0.01 ," + 
			opacityInc + "," + 
			topXInc + "," + 
			topYInc + "," + 
			bottomXInc + "," + 
			bottomYInc + ")", 			
			this.delay);    		    		    		
	}


	////////////////////////////////////////////////////////////
	// Next on the slide show
	this.next = function() {	
		
			// Make an infinite loop	
			if(this.slideIndex == this.slides.length) {
					this.slideIndex=0;					
			}
			if(this.slideIndex < 0 ) {
					this.slideIndex=this.slides.length-1;
			}
			
			// This will cycle through the array, skipping any "holes", that is, images that haven't been loaded yet
			// This allows the slideshow to run even if all images have not been loaded
			while(!this.slides[this.slideIndex].image.loadStatus) {
				this.slideIndex++;
				// Due to the infinite loop	
				if(this.slideIndex == this.slides.length) {
					this.slideIndex=0;
				}
			}
									
			// Activate the next slide
			this.activeSlide[this.swap] = this.slides[this.slideIndex];
			this.activeSlide[this.swap].startRunning();
			
			this.kbImage[this.swap].src = null;
			this.kbImage[this.swap].style.zIndex = 10;

			
			// Start the slide
			this.startSlide(this.swap, this.duration, this.transition, this.activeSlide[this.swap].getStartFocusArea(this.kbViewport), this.activeSlide[this.swap].getEndFocusArea(this.kbViewport));

			// Prepare for the next one
			this.slideIndex++;					
			this.swap = Math.abs(this.swap-1);
	}


	////////////////////////////////////////////////////////////
	// Start the KBurnalizer slideshow
	this.start = function() {
		setTimeout(this.kbID + ".init()", this.delayStart);    		    		    		
	}
	
	////////////////////////////////////////////////////////////
	// Add a new slide to the show
	this.addSlide = function(src,description,width,height,startFocusArea,endFocusArea) {
		var slide = new KBSlide(this,src,description,width,height,startFocusArea, endFocusArea);
		this.slides.push(slide);
	}	
	

	////////////////////////////////////////////////////////////
	// Change the slide duration dinamically	
	// This takes effect on the next slide
	this.setSlideDuration = function(duration) {
		// The duration can't be lower than the transition time
		if(duration>=this.transition) {
			this.duration = duration;
		}
		return this.duration;

	}
	
	
	////////////////////////////////////////////////////////////
	// Change the transition time dinamically	
	// This takes effect on the next slide
	this.setTransitionTime = function(transition) {
		// The transition can't be higher than the slide duration time
		if(transition<=this.duration) {
			this.transition = transition;
		}
		return this.transition;
		
	}


	////////////////////////////////////////////////////////////
	// Change the frame rate
	// This takes effect immediately
	// Be very carefull with this! It might make everything slower if you increase too much
	this.setFPS = function(fps) {
			this.fps = fps;
			this.delay = 1000 / this.fps;	
	}
	
	////////////////////////////////////////////////////////////
	// Prints the text of the slide description
	this.displayDescription = function() {		
		if(this.displayText) {
			this.kbText.innerHTML = this.activeSlide[this.swap].description;
			this.kbCounter.innerHTML = "&nbsp;<a href='javascript:" + this.kbID + ".skip(true)'>&laquo;</a>&nbsp;<a>[" + padNumber(this.slideIndex+1,this.slides.length) + "/" + this.slides.length + "]</a>&nbsp;<a href='javascript:" + this.kbID + ".skip(false)'>&raquo;</a><a>&nbsp;";
		}
	}
	
	////////////////////////////////////////////////////////////
	// Hides/shows the description area
	// Takes effect immediately
	this.toggleDisplayDescription = function() {		
		this.displayText = !this.displayText;
		if(!this.displayText) {
			this.kbTextArea.style.visibility = "hidden";
			this.kbText.style.visibility = "hidden";
			this.kbCounter.style.visibility = "hidden";
		}
		else {
			this.kbTextArea.style.visibility = "visible";
			this.kbText.style.visibility = "visible";
			this.kbCounter.style.visibility = "visible";
		}
	}
	
	////////////////////////////////////////////////////////////
	// Set the shutdown flag to true
	// This stops the slideshow immediately
	this.shutdown = function() {
		this.shutdownFlag = true;
	}
	
	////////////////////////////////////////////////////////////
	// Skips one slide forward or backward
	// Signals active slides to stop
	// Uses waitForForcedStop to check periodically if they have stopped
	this.skip = function(previous) {
		if(this.activeSlide[0]!=null) {
			this.activeSlide[0].stopSignal();
		} 
		if(this.activeSlide[1]!=null) {
			this.activeSlide[1].stopSignal();
		} 
		if(previous) this.slideIndex -= 2;
		this.waitForForcedStop();
	}
	
	
	////////////////////////////////////////////////////////////
	// Waits until slides have stopped
	this.waitForForcedStop = function() {
		if(
			((this.activeSlide[0]==null) || (this.activeSlide[0].isStopped()))
			&&
			((this.activeSlide[1]==null) || (this.activeSlide[1].isStopped()))
		) {
			this.next();
		}
		else {
			setTimeout(this.kbID + ".waitForForcedStop()", 10);
		};
	}
}


/******************************************************************************************************
 ******************************************************************************************************/


/*****
	Class KBSlide
	This is a slide in a KBurnalizer slideshow. Every photo has 
	to be wrapped in one of these	
******/
function KBSlide(kbObj,src,description,width,height,startFocusArea, endFocusArea) {

	// The slideshow object that contains this slideshow
	// This is needed for callbacks
	this.kbObj = kbObj;
	
  // Image URL
  this.src = src;

  // Photo description
  this.description = description;

	// Image width
  this.originalImageWidth = width;
  this.originalImageHeight = height;  
  
  // Focus area to start
  this.startFocusArea = startFocusArea;
  
  // Focus area to finish
  this.endFocusArea = endFocusArea;

  // Image object
  this.image = document.createElement("img");
  
  // Images should also know to which slide show they belong, for callbacks
  this.image.kbObj = kbObj;
  this.image.onerror = kbObj.onerrorHandler; // This might not work in FF if the given URL is invalid (but it should!).
  this.image.onload = kbObj.onloadHandler;  
  this.image.loadStatus = false;  
   
	// Current state of the slide
	this.runningStatus = STATUS_STOPPED;
  
  // This method loads the image for the slide  
  this.load = function() {  	
  	this.image.src = this.src;
  }  

	// Sets the slide with the "runnning" status
	this.startRunning = function() {
		this.runningStatus = STATUS_RUNNING;
	}
	
	// Signals the slide that it should stop running
	this.stopSignal = function() {
		if(this.runningStatus == STATUS_RUNNING)
			this.runningStatus=STATUS_STOPPING;
	}

	// Set's the signal has stopped
	this.stopRunning = function() {
		this.runningStatus = STATUS_STOPPED;
	}
	
	this.isStopped = function() {
		if(this.runningStatus == STATUS_STOPPED) {
			return true;
		}
		else {
			return false;
		}
	}
	
	this.isStopping = function() {
		if(this.runningStatus == STATUS_STOPPING) {
			return true;
		}
		else {
			return false;
		}
	}

  
  // If the area was not specified, choose a random one
  this.getStartFocusArea = function(kbViewport) {

  	if(!this.startFocusArea) {
			this.randomizeArea(kbViewport);			
		}
		return this.startFocusArea;
  }
  
  // If the area was not specified, choose a random one
  this.getEndFocusArea = function(kbViewport) {
  	if(!this.endFocusArea) {
			this.randomizeArea(kbViewport);
		}
	  return this.endFocusArea;
  }
  
  // Randomizes the start and end focus area
	// To be used when these areas are not provided
  this.randomizeArea = function(kbViewport) {
  	
  	var vpWidth = kbViewport.clientWidth;
  	var vpHeight = kbViewport.clientHeight;
  	var imgWidth = this.originalImageWidth;
  	var imgHeight = this.originalImageHeight;

  	// We will start by calculating the largest possible rectangle that fits the image
		// and is proportional to the viewport
		var largestWidth = vpWidth;
		var largestHeight = vpHeight;
		
		// What factor we need to multiply to make it as wide as the image?
		var scaleFactor = imgWidth * 1.0 / largestWidth;
		
		// First we try to make the rectangle as wide as the image
		largestWidth = scaleFactor * largestWidth; // Which is of course the same as largestWidth = imgWidth, but just to make it clear
		largestHeight = scaleFactor * largestHeight; // Multiply the height by the same amount

		// If the height for any reason is too large, scale everything down
		if(largestHeight > imgHeight) {
		       scaleFactor = imgHeight * 1.0 / largestHeight;
		       largestWidth = scaleFactor * largestWidth;
		       largestHeight = scaleFactor * largestHeight;
		}

		// Ok, now we not everything fits		
		
		// Randomize the start size and end size
		// between 10% and 30% of the rectangle size
		var startRandomScale = 1 - ((Math.random() * 20 + 10) / 100);
		var endRandomScale = 1 - ((Math.random() * 20 + 10) / 100);
		
		var startWidth = Math.floor(largestWidth * startRandomScale);
		var startHeight = Math.floor(largestHeight * startRandomScale);
		
		var endWidth = Math.floor(largestWidth * endRandomScale);
		var endHeight = Math.floor(largestHeight * endRandomScale);
				
		var maximumStartX = imgWidth - startWidth;
		var maximumStartY = imgHeight - startHeight;
		
		var maximumEndX = imgWidth - endWidth;
		var maximumEndY = imgHeight - endHeight;
		
		var startX = Math.floor( Math.random() * maximumStartX);
		var startY = Math.floor( Math.random() * maximumStartY);
		
		var endX = Math.floor( Math.random() * maximumEndX);
		var endY = Math.floor( Math.random() * maximumEndY);

		this.startFocusArea = new FocusArea(startX,startY,startX+startWidth,startY+startHeight);
		this.endFocusArea = new FocusArea(endX,endY,endX+endWidth,endY+endHeight);
	}
}



/******************************************************************************************************
 ******************************************************************************************************/


/*****
	Class FocusArea
	FocusArea is an auxiliary class that represents a rectangular area in a photo		
******/
function FocusArea(topx, topY, bottomX, bottomY) {
		this.topX = topx;
		this.topY = topY;
		this.bottomX = bottomX;
		this.bottomY = bottomY;
		
		this.getCopy = function() {
				return new FocusArea(this.topX, this.topY, this.bottomX, this.bottomY);
		}		
}


/******************************************************************************************************
 ******************************************************************************************************/
// Utils
 
function padNumber(number, reference) {
	number=number + "";
	reference=reference + "";
 	while(number.length<reference.length) {
 		number = "0" + number;
	}
 	return number;
}

// Constants (kind of...)
var STATUS_STOPPED = 0;
var STATUS_RUNNING = 1;
var STATUS_STOPPING = 2;

/******************************************************************************************************
 ******************************************************************************************************/


// Event Listener
// by Scott Andrew - http://scottandrew.com
// edited by Mark Wubben, <useCapture> is now set to false

function addEvent(obj, evType, fn){
	if(obj.addEventListener){
		obj.addEventListener(evType, fn, false); 
		return true;
	} else if (obj.attachEvent){
		var r = obj.attachEvent('on'+evType, fn);
		return r;
	} else {
		return false;
	}
}	

// Hold all created slideshow objects - only per file can be active
if(!slideShows) { 
	var slideShows = new Array();

	// Start them all...
	function startSlideShows() { 
		for(var i=0; i<slideShows.length; i++) {		
			slideShows[i].start();
		}
	} 
	
	// ...on load event
	addEvent(window, "load", startSlideShows);
}