/*
SlideShow class v0.5, 2009-2-4
copyright Hayden Porter, www.aviarts.com

Example usage
1) create instance
var myshow = SlideShow(fps,transition_duration,slide_duration)

optional: myshow.auto_start = true; // start playing after all slides downloaded, image tags created and parent element exists

2) load slides
myshow.loadSlides(array of image urls);

3) myshow.insertSlides(id of parent html element to create image tags within);

4) myshow.play(); // play the slide show

optional: myshow.onReady = function(){myshow.play()} // make sure all slides downloaded, image tags created and parent element exists
*/

// constructor
function SlideShow(slide_duration,fps,transition_duration){
	// public
	this.fps = fps; // number of frames per second
	this.transition_duration = transition_duration; // millisecond duration of transition
	this.slide_duration = slide_duration; // duration to pause after completed transition
	this.image_urls; // urls of slide images
	this.loop = false; // control looping of slide show
	
	this.auto_start = false; // automatically start from onReady;
	this.use_transition = true; // play slide show with transitions
	this.fadein_first_slide = false; // show or hide first slide with true or false, all others are hidden
	
	// private
	this.transition_duration_seconds = this.transition_duration/1000; // convert ms to seconds
	this.transition_total_frames = this.transition_duration_seconds * this.fps; // calculate total frames in duration of animation
	this.frame_duration = (1/this.fps) * 1000; // convert fps to milliseconds
	this.current_frame = 0;
	
	this.transition_type = null;  // fadeIn,crossFade,fadeOut
	
	this.frameloop; // contains id of setInterval for transition animation
	this.pausetimer; // contains id of setTiemout for pause between slides
	
	this.opacity_range = 100; // range of values
	this.opacity_change = this.opacity_range/this.transition_total_frames; // amount to change opacity for each frame
	
	this.slides_loaded = 0; // count slides loaded
	this.is_connecting = false; // set to true when call loadSlides, change to false after first slide loads
	this.is_loaded = false; // change to true after all slides loaded
	this.is_ready = false; // change to true after all slides loaded, html built, parent element exists
	this.image_objects = new Array(); // array that contains off screen image objects
	this.loading_msg = null;
	this.loading_msg_container_id = "slideshow_loading_message";
	
	this.is_built = false; // change to true after embedding image tags in parent element
	this.parent_element = null; // string representing id attribute of parent element to embed slide show within
	
	this.total_slides; // set from loadSlide
	this.current_slide = 0;
	this.next_slide = 1;
}

/**************** SLIDE SHOW PLAYBACK METHODS ********************/

// stop slide show, reset to first slide - NOT WORKING CORRECTLY
function reset(){
	if(this.is_ready == false){ return; } // do not reset if slide show is not ready
	
	this.stopTransitionLoop(); // stop transition loop
	this.stopPauseTimer(); // stop pause timer
	
	var currentslide_id = "slide" + this.getCurrentSlide();
	var nextslide_id = "slide" + this.getNextSlide();
	this.setOpacity(currentslide_id,0);
	this.setOpacity(nextslide_id,0);
	
	this.setCurrentSlide(0);
	this.setOpacity("slide0",100);
	this.is_playing = false;
	this.is_stopped = true;
	if(this.onStateChange != null){this.onStateChange();} // execute callback on each state change
}
	
// - NOT WORKING CORRECTLY
function play(){
	if(this.is_ready == false){ return; } // do not play if slide show is not ready
	
	this.setCurrentSlide(0); // always start at first slide
	if(this.use_transition == true){
		if(this.fadein_first_slide == true){ // fade in hidden starting slide
			this.startFadeInTransition();
		} else { // starting slide is visible, transition to next slide
			this.startCrossFadeTransition();
		}
	} else {
		this.showNextSlide();
	}
	this.is_playing = true;
	this.is_stopped = false;
	if(this.onStateChange != null){this.onStateChange();} // execute callback on each state change
}

/******************** TRANSITION METHODS ***************************/

// set the opacity 
function setOpacity(id,opacity) {
    var object = document.getElementById(id).style; 
    object.opacity = (opacity / 100); 
    object.MozOpacity = (opacity / 100); 
    object.KhtmlOpacity = (opacity / 100); 
    object.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=" + opacity + ")"; //msie
}

// get opacity values, values must have been set from javascript
function getOpacity(id){
	var obj = document.getElementById(id);
	if(obj.style.opacity){ return obj.style.opacity * 100; }
	else if(obj.filters) {return obj.filters.item("DXImageTransform.Microsoft.Alpha").Opacity;}
	else if(obj.style.MozOpacity){ return obj.MozOpacity * 100; }
	else if(obj.style.KhtmlOpacity){ return obj.KhtmlOpacity * 100; }
	else {return false;}
}

// called from a setInterval loop
// cross fade between two slides, transitioning to this.next_slide
// outslide prefix represents current slide fading out
// inslide prefix represents next slide fading in
function crossFadeOpacity(outslide,inslide){
	if(this.current_frame > this.transition_total_frames){ // transition complete
		this.stopTransitionLoop(); // stop setInterval loop
	} else { // execute next frame of transition		
		// get current opacities
		var outslide_opacity = this.getOpacity(outslide);
		var inslide_opacity = this.getOpacity(inslide);
		
		// calculate new opacities
		outslide_opacity -= this.opacity_change;
		inslide_opacity += this.opacity_change;
		
		// apply new opacity setting to each slide
		this.setOpacity(outslide,outslide_opacity);
		this.setOpacity(inslide,inslide_opacity);
		
		// update current frame count
		this.current_frame++;
	}
}

// fade in the current slide
function fadeInOpacity(slide){
	if(this.current_frame > this.transition_total_frames){ // transition complete
		this.stopTransitionLoop(); // stop setInterval loop
	} else { // execute next frame of transition
		var slide_opacity = this.getOpacity(slide);
		slide_opacity += this.opacity_change;
		this.setOpacity(slide,slide_opacity);
		
		// update current frame count
		this.current_frame++;
	}
}

function fadeOutOpacity(outslide){

}

/******************* SLIDE SHOW EXECUTION METHODS ***********************/

function getNextSlide(){
	if(this.isLastSlide()){
		this.next_slide = 0;
	} else {
		this.next_slide = this.current_slide + 1;
	} 
	return this.next_slide;
}

function getCurrentSlide(){
	return this.current_slide;
}

function setCurrentSlide(num){
	this.current_slide = num;
}

function showNextSlide(){
	
}

// return true if last slide or false
function isLastSlide(){
	return (this.getCurrentSlide() == this.total_slides - 1);
}

// upon reaching last slide, execute custom callback or restart
function onLastSlide(){
	// check for looping
	if(this.loop == false){  // no loop, slide show is now complete
		if(this.onSlideShowComplete != null) {this.onSlideShowComplete();} // execute custom assigned call back
		if(this.onStateChange != null){this.onStateChange();} // execute callback on each state change
		this.is_playing = false;
		this.is_stopped = true;
	} else { // looping, restart from begining
		this.pauseBeforeNextSlide(); // pause before playing next slide or transition
	}
}

// start transition where current slide fades in, no cross fade
function startFadeInTransition(){
	this.transition_type = "fadeIn";
	var obj = this; // store in reference variable to avoid scope problems when passed to setInterval
	var dur = this.frame_duration;
	var curslide_id = "slide" + this.getCurrentSlide();
	this.current_frame = 0; //reset before playing transition
	
	// assign interval to repeatedly execute fadeInOpacity at the frame rate
	this.frameloop = window.setInterval(function(){obj.fadeInOpacity(curslide_id)},dur);
}

// start transition where current slide cross fades with next slide
function startCrossFadeTransition(){
	this.transition_type = "crossFade";
	var obj = this; // store in reference variable to avoid scope problems when passed to setInterval
	var dur = this.frame_duration;
	var curslide_id = "slide" + this.getCurrentSlide();
	var nextslide_id = "slide" + this.getNextSlide();
	this.current_frame = 0; //reset before playing transition
	
	// assign interval to repeatedly execute crossFadeOpacity at the frame rate
	this.frameloop = window.setInterval(function(){obj.crossFadeOpacity(curslide_id,nextslide_id)},dur);
}

// transition is complete
function onTransitionComplete(){
	if(this.getCurrentSlide() == 0 && this.fadein_first_slide == true && this.transition_type == "fadeIn"){
		this.setCurrentSlide(0); // allow fadein, pause and cross fade before advancing to next slide
	} else {
		this.setCurrentSlide(this.getNextSlide()); // in transition to next slide, make what was the next slide now the current slide
	}
	
	if(this.isLastSlide() == true){ // reached last slide
		this.onLastSlide(); // internal callback
	} else { // not at end of slide show
		this.pauseBeforeNextSlide(); // pause before playing next slide or transition
	}
}

// stop the transition interval loop
function stopTransitionLoop(){
	// stop the loop
	var intervalId = this.frameloop;
	window.clearInterval(intervalId);
	
	// reset variables
	this.onTransitionComplete(); // internal callback for switching between transition and paused slide
	if(this.onSlideChange != null) {this.onSlideChange();} // execute custom call back
}

// stop the setTimeout timer for slide pause
function stopPauseTimer(){
	var timerId = this.pausetimer;
	window.clearTimeout(timerId);
}

// pause for slide_duration before going to next transition or slide
function pauseBeforeNextSlide(){
	var obj = this; // store in reference variable to avoid scope of this when passed to setTimeout function
	var dur = this.slide_duration;
	if(this.use_transition == true){ // use a transition
		this.pausetimer = window.setTimeout(function(){obj.startCrossFadeTransition()},dur);
	} else { // just show next slide
		this.showNextSlide();
	}
}

/*************** LOADING FUNCTIONS *********************/

// all files must be loaded, image tags inserted into parent element, and parent element must exist to be ready for playback
function isReady(){
	if(this.is_loaded == true && this.is_built == true){
		this.is_ready = true;
		this.onReady(); // internal callback
	} else {
		this.is_ready = false;
	}
	return this.is_ready;
}

// slide show ready
function onReady(){
	if(this.onStateChange != null){this.onStateChange();} // execute callback on each state change
	if(this.auto_start == true){ this.play(); } // automatically start playback as soon as ready
	if(this.onSlideShowReady != null) { this.onSlideShowReady();} // all conditions true, slide show is ready, call custom callback
}

function isConnecting(){
	return this.is_connecting;
}

// atleast one slide loaded
function isLoading(){
	return (this.slides_loaded > 0 && this.slides_loaded < this.total_slides);
}

// all slides loaded
function isLoaded(){
	return (this.slides_loaded == this.total_slides);
}

function getState(){
	if(this.is_playing == true) { return "playing"; }
	if(this.is_stopped == true) { return "stopped"; }
	if(this.is_ready == true){ return "ready"; }
	
	if(this.isLoaded() == true){ return "loaded"; }
	if(this.isLoading() == true) { return "Loaded image " + this.slides_loaded + " of " + this.total_slides; }
	if(this.is_connecting == true){ return "connecting";}
}

// called from image object onload event
function countSlidesLoaded(){
	this.slides_loaded++;
	if(this.isLoading()){ this.is_connecting = false;} // managing state change
	if(this.isLoaded()){ // all slides loaded
		this.is_loaded = true; // store state
		this.is_connecting = false; // reset after loaded or cached
		this.hideLoadingMsg();
		this.isReady(); // make sure images loaded before attempting to play
	}
	if(this.onStateChange != null){this.onStateChange();} // execute callback on each state change
}

// load slide images from urls in imageurls array
function loadSlides(imageurls){
	this.is_connecting = true; // attempting to load slides
	if(this.onStateChange != null){this.onStateChange();} // execute callback on each state change
	
	this.image_urls = imageurls;
	this.total_slides = this.image_urls.length; // store total number of slides
	
	// create off screen iamge objects for loading
	for(var i=0; i<this.total_slides; i++){
		this.image_objects[i] = new Image();
		this.image_objects[i]._parent = this; // work around for executing instance methods from image onload event
		this.image_objects[i].onload = function(){this._parent.countSlidesLoaded()}
		this.image_objects[i].src = this.image_urls[i]; // start loading the image
	}
	this.showLoadingMsg();
}

/************* CREATE IMAGE TAGS FOR SLIDE SHOW ************************/

function elementExists(e){
	if(document.getElementById(e) == null){
		return false;
	} else {
		return true;
	}
}

// add image tags to the parent element
function insertSlides(parent){
	if(this.elementExists(parent) == true){
		this.parent_element = parent;
		var slides = new Array();
		for(var i=0; i<this.total_slides; i++){
			var idattr = "slide" + i; // build id attribute
			var srcattr = slide_image_urls[i]; // build src attribute
				
			// create the image tag
			slides[i] = document.createElement("img");
			slides[i].setAttribute("id",idattr); // set the id attribute
			slides[i].setAttribute("src",srcattr); // set the src attribute
			document.getElementById(this.parent_element).appendChild(slides[i]); // append the image tag to the parent element
		
			// set the image tag style with the style object
			document.getElementById(idattr).style.zIndex = this.total_slides - i; // z-index
			document.getElementById(idattr).style.position = "absolute"; // position
		
			this.setOpacity(idattr,0); // set initial opacity, use seperate function to support multiple browsers
		}
		
		if(this.fadein_first_slide == false){this.setOpacity("slide0",100);} // show first slide
		
		if(this.loading_msg != null){
			var msg_container = document.createElement("div"); // create a div tag for the loading msg
			msg_container.setAttribute("id",this.loading_msg_container_id); // assign id
			document.getElementById(this.parent_element).appendChild(msg_container);
			
			var obj = document.getElementById(this.loading_msg_container_id);
			var table = '<table border="0" cellspacing="0" cellpadding="0" width="100%" height="100%">' +
							'<tr>' +
								'<td valign="center" align="center" id="slideshow_loading_message_container">' + 
								this.loading_msg + 
								'</td>' +
							'</tr>' +
						'</table>';
			obj.innerHTML = table;
			
			// set the tag style with the style object
			obj.style.zIndex = this.total_slides; // msg appears on top of all slides
			obj.style.position = "relative"; // position
			obj.style.visibility = "hidden"; // hide loading message
			
			if(this.isConnecting() == true || this.isLoading() == true){ 
				obj.style.visibility = "visible"; // show loading message during load
			}
		}
		
		this.is_built = true; // all html inserted
		
	} else {
		this.is_built = false; // no html inserted because parent did not exist
	}
	
	this.isReady() // check if slide show is ready, make sure html built before attempting to play
}

function showLoadingMsg(){
	if(this.elementExists(this.loading_msg_container_id) == true){ 
		document.getElementById(this.loading_msg_container_id).style.visibility = "visible"; // show loading message during load
	}
}

function hideLoadingMsg(){
	if(this.elementExists(this.loading_msg_container_id) == true){ 
		document.getElementById(this.loading_msg_container_id).style.visibility = "hidden"; // hide loading message
	}
}

/***************** PROTOTYPE METHODS *************************/

SlideShow.prototype.stopTransitionLoop = stopTransitionLoop;
SlideShow.prototype.stopPauseTimer = stopPauseTimer;
SlideShow.prototype.onTransitionComplete = onTransitionComplete;
SlideShow.prototype.onLastSlide = onLastSlide;

// call backs: onSlideShowComplete, onReady, onSlideChange, onStateChange
SlideShow.prototype.getState = getState;
SlideShow.prototype.isConnecting = isConnecting;
SlideShow.prototype.isLoading = isLoading;
SlideShow.prototype.isLoaded = isLoaded;

SlideShow.prototype.setOpacity = setOpacity;
SlideShow.prototype.getOpacity = getOpacity;
SlideShow.prototype.crossFadeOpacity = crossFadeOpacity;
SlideShow.prototype.fadeInOpacity = fadeInOpacity;
SlideShow.prototype.fadeOutOpacity = fadeOutOpacity;

SlideShow.prototype.setLoadingMsg = showLoadingMsg;
SlideShow.prototype.showLoadingMsg = showLoadingMsg;
SlideShow.prototype.hideLoadingMsg = hideLoadingMsg;

SlideShow.prototype.loadSlides = loadSlides;
SlideShow.prototype.elementExists = elementExists;
SlideShow.prototype.isReady = isReady;
SlideShow.prototype.onReady = onReady;
SlideShow.prototype.countSlidesLoaded = countSlidesLoaded;

SlideShow.prototype.insertSlides = insertSlides;

SlideShow.prototype.getNextSlide = getNextSlide;
SlideShow.prototype.getCurrentSlide = getCurrentSlide;
SlideShow.prototype.setCurrentSlide = setCurrentSlide;
SlideShow.prototype.isLastSlide = isLastSlide;

SlideShow.prototype.pauseBeforeNextSlide = pauseBeforeNextSlide;
SlideShow.prototype.startCrossFadeTransition = startCrossFadeTransition;
SlideShow.prototype.startFadeInTransition = startFadeInTransition;
SlideShow.prototype.play = play;
SlideShow.prototype.reset = reset;