/**
 * @author Jesse Hemminger
 */

 /**
 * Randomly replaces the images in the <ul> element specified by the elementId parameter
 * with images from the imageData array parameter. The randomly selected image is faded out
 * and the new image is faded in. The options object allows you to optionally specify a number of
 * blank spaces to leave in the list, the speed of the transitions, and
 * the lenght of the pause between transitions. As well you can specify an optional
 * second imageData array and how many imgaes from this array should be present in the list.
 * This is useful to create a random mixture of two types of images in a fixed proportion to
 * one another. For example if you want to have mostly thumbnail images of photos, but with
 * a few text images always mixed in. The photos might be samples of work and the text images
 * might be words describing your firm, your competences, or testemonials. If you use this optional
 * second imageData array you must specify a class name for the elements from this second array.
 * This is required in order to check if the randomly selected image to be replaced is from
 * this second array so that the program knows that it needs to place a new image from the
 * second array in the list.
 *
 * @param	elementId	string	The DOM id of the list of images, normally a <ul> element
 * @param	imageData	array	An array of objects with the image data. The objects in the
 * 								array need to have an imageURL attribute defined and can
 * 								optionally have the linkURL, caption and altText attributes defined.
 * 								{
 * 									imageURL: string, 
 * 									linkURL: string, 
 * 									altText: string, 
 * 									titleText: string, 
 * 									caption: string
 * 								}
 * @param	options		object	This paramiter is option. Its Structure is as follows:
 * 								{
 * 									autoPlay:				boolean,//if true it will automatically play the slideshow once the images have preloaded and the initialPause has passed
 * 									loopNewImage:			int,	// n = -1 : infinite repeat; n = 0 : play once and stop on last image(s); n > 0 : the number of times to loop though the complete list of images in imageData array.
 * 									randomNewImage:			boolean,//determines if the slideshow plays in random order or in the order that they appear in the imageData array
 * 									loopReplaced:			int,	// n = -1 : infinite repeat; n = 0 : play once and stop on last image(s); n > 0 : the number of times to loop though replacing the complete HTML list of images on screen.
 * 									randomReplaced:			boolean,//determines if the new images appear in a random position on screen in the list or if the element are replaced in the order that they appear in the HTML
 * 									captionClass:			string,	//if specified and if the imageData also specifies a caption, 
 * 																	//then a sibling element to the img element with the captionClass class 
 * 																	//will be search and its inner HTML replaced with the imageData caption value
 * 									imageListLength: 		int,	//not implemented. Would be used to build the HTML list from scratch.
 * 									numBlankSpaces: 		int,	//number of blank <li> elements to leave in the list
 * 									blankSpaceClass: 		string,	//not implemented. class name for the <li> elements containing no image
 * 									initialPause:	 		int,	//the milliseconds to pause before the slide show initially starts
 * 									loopPause:	 			int,	//the milliseconds to pause between the loops
 * 									htmlListPause:		 	int,	//the milliseconds to pause between each time the complete onscreen list has been replace and it start over again (note: every item in the onscreen list could be replaced once before the every image has been displayed once)
 * 									transitionSpeed: 		int,	//the milliseconds
 * 									interval: 		int,	//the length of time between transtions, for example the time from the start on one transition out and the start of the next transition out
 * 									additionalImageData: 	array,	//an array of objects with the image data. See imageData param above.
 * 									additionalImageClass: 	str,	//class name for the <li> elements containing images from this additional image data array
 * 									numAddlImgDisplayed: 	int,		//not yet implemented. Could be implemented if this function were to build the list from 
 * 																	//scratch instead of just updating an existing list in the DOM
 * 									normalImageClass:		str
 * 								}
 */

var AnimatedImageList = new Class({
	options: {
		autoPlay: true,
		loopNewImage: -1,
		randomNewImage: false,
		loopReplaced: -1,
		randomReplaced: false,
		captionClass: "",
		imageListLength: 9,
		numBlankSpaces: 3,
		blankSpaceClass: "blankSpace",
		initialPause: 500,
		loopPause: 500,
		htmlListPause: 500,
		transitionSpeed: 500,
		interval: 1000,
		additionalImageData: null,
		additionalImageClass: "additionalImage",
		numAddlImgDisplayed: 2,
		normalImageClass: "normalImage"
	},
	initialize: function(elementId, imageData, options) {
		this.elementId = elementId;
		this.imageData = imageData;
		this.setOptions(options);
		
		//reference to the timer that will trigger the next image, i.e. what makes the auto playing of the slideshow
		//could be used if an API for starting and stopping the slideshow is implemented
		this.timer;
		//keep track if the slideshow should be auto advancing
		this.isPlaying = this.options.autoPlay;
		//
		this.initializationTime = $time();
		
		//window.addEvent('domready', this.domReadyHandler.bind(this));
		window.addEvent('load', this.onLoadHandler.bind(this));
	},
	initializeImageData: function() {
		//I think I need to rethink this whole concept, maybe keep track of indexes and not storing references to the whole image data object into different arrays.
		//or maybe really use the DOM as the model and add a class to each of the list elements to label them as
		//normal image, additional image, or blank. Then we can do a simple search with a filter to select a random blank item
		//and attach the not displayed images to the DOM but make them not display
		//or instead of image data arrays we make arrays of the dom elements.
		
		//Okay, I decided that the DOM is our model.
		// so instead of these arrays I will simple attach my preloaded images to the DOM and make them ...? what am I doing?
		//list of currently displayed images
		this.currentlyDisplayedImageData = [];
		//list of not displayed images
		this.notDisplayedImageData = this.imageData.copy();
		//images that have already been displayed in this round
		this.alreadyDisplayedImageData = [];
			
		//we need to find out which images are currently on display so that we don't end up with the same image on screen twice.
			//compare the imageData.imageURL's with the src param of the <img> tags currently in the DOM
			//we also need to set up a structure for keeping track of what is currently on display. or do we
			//isn't there a helper function to check if an element exists in the DOM?
			var countImages = 0;
			var countMatches = 0;
		this.listElement.getChildren().each(function(listItemElement) {
			var imageElement = $E('img', listItemElement);
			//if it isn't a blank list item check if the currently displayed image belongs to one of our lists
			if (imageElement) {
				countImages++;
				this.imageData.each(function(item,index){
					if (item.imageURL == imageElement.getProperty('src')) {
						countMatches++;
						this.currentlyDisplayedImageData.include(item);
						this.notDisplayedImageData.remove(item);
						//make a reference to the corresponding imageData item for checking later
						imageElement.imageDataItem = item;
						if (imageElement.getParent().getTag() == 'a') {
							imageElement.getParent().imageDataItem = item;
						}
					}
				}, this);
			}
		}, this);
		//alert ('countImages: ' + countImages + ', countMatches: ' + countMatches + ', this.imageData.length: ' + this.imageData.length + ', this.currentlyDisplayedImageData.length: ' + this.currentlyDisplayedImageData.length + ', this.notDisplayedImageData.length: ' + this.notDisplayedImageData.length + ', this.alreadyDisplayedImageData.length: ' + this.alreadyDisplayedImageData.length);
	},
	initializeAdditionalImageData: function() {
		//I think I need to rethink this whole concept, maybe keep track of indexes and not storing references to the whole image data object into different arrays.
		//or maybe really use the DOM as the model and add a class to each of the list elements to label them as
		//normal image, additional image, or blank. Then we can do a simple search with a filter to select a random blank item
		//and attach the not displayed images to the DOM but make them not display
		//or instead of image data arrays we make arrays of the dom elements.
		
		//Okay, I decided that the DOM is our model.
		// so instead of these arrays I will simple attach my preloaded images to the DOM and make them ...? what am I doing?
			
		//list of currently displayed additional images
		this.currentlyDisplayedAdditionalImageData = [];
		//list of not displayed additional images
		this.notDisplayedAdditionalImageData = this.options.additionalImageData.copy();
		//additional images that have already been displayed in this round
		this.alreadyDisplayedAdditionalImageData = [];
			
		//we need to find out which images are currently on display so that we don't end up with the same image on screen twice.
			//compare the imageData.imageURL's with the src param of the <img> tags currently in the DOM
			//we also need to set up a structure for keeping track of what is currently on display. or do we
			//isn't there a helper function to check if an element exists in the DOM?
		this.listElement.getChildren().each(function(listItemElement) {
			var imageElement = $E('img', listItemElement);
			//if it isn't a blank list item check if the currently displayed image belongs to one of our lists
			if (imageElement) {
				this.options.additionalImageData.each(function(item, index){
					if (item.imageURL == imageElement.getProperty('src')) {
						this.currentlyDisplayedAdditionalImageData.include(item);
						this.notDisplayedAdditionalImageData.remove(item);
						//make a reference to the corresponding imageData item for checking later
						imageElement.imageDataItem = item;
						if (imageElement.getParent().getTag() == 'a') {
							imageElement.getParent().imageDataItem = item;
						}
					}
				}, this);
			}
		}, this);
		
	},
	onLoadHandler: function(e) {
		//first set things up
			//search for and store a reference to the DOM element with id = elementID
		this.listElement = $(this.elementId);
		
		this.initializeImageData();
		this.initializeAdditionalImageData();
		//I think I can assume that if the image is not passed in the imageData parameter it should not be included
		//in the slideshow. This would allow for an intro set of images that only is there at the beginning and
		//slowly gets replaced be the imageData set of images.
		
		//we need to preload the images.
			//we want to start this after the images that are currently displayed are finished loading
			//that is why this whole whole function is attached with the onload event
		var images = [];
		this.imageData.each(function(item,index){
			images.push(item.imageURL);
		});
		this.options.additionalImageData.each(function(item,index){
			images.push(item.imageURL);
		});
	    var options = {onComplete: this.preloadImagesComplete.bind(this)};
		this.preloadedImages = new Asset.images(images, options);
	},
	preloadImagesComplete: function() {
		this.imageData.each(function(item,index){
			var img = this.preloadedImages[index];
			img.addClass(this.options.normalImageClass);
			if (item.altText) {
				img.setProperty('alt', item.altText);
			}
			img.imageDataItem = item;
			if (item.linkURL) {
				var anchor = new Element('a', {href: item.linkURL});
				anchor.adopt(img);
				item.el = anchor;
			} else {
				item.el = img;
			}
			if (item.titleText) {
				//if linkURL is set the title belongs on the anchor tag, and if linkURL is set item.el will refer to the anchor tag
				//but if linkURL is not set the title belongs on the img tag, and if linkURL is not set item.el will refer to the img tag
				item.el.setProperty('title', item.titleText);
			}
			item.el.imageDataItem = item;
		}, this);
		
		//TODO: combine this function with the one above since they are essentially the same
		this.options.additionalImageData.each(function(item,index){
			var img = this.preloadedImages[this.imageData.length + index];
			img.addClass(this.options.additionalImageClass);
			if (item.altText) {
				img.setProperty('alt', item.altText);
			}
			img.imageDataItem = item;
			if (item.linkURL) {
				var anchor = new Element('a', {href: item.linkURL});
				anchor.adopt(img);
				item.el = anchor;
			} else {
				item.el = img;
			}
			if (item.titleText) {
				//if linkURL is set the title belongs on the anchor tag, and if linkURL is set item.el will refer to the anchor tag
				//but if linkURL is not set the title belongs on the img tag, and if linkURL is not set item.el will refer to the img tag
				item.el.setProperty('title', item.titleText);
			}
			item.el.imageDataItem = item;
		}, this);
		
		if (this.isPlaying) {
			//take into account the time that has already passed from image preloading
			var alreadyPaused = $time() - this.initializationTime;
			if (alreadyPaused >= this.options.initialPause) {
				//if enough time has already passed just start
				this.nextImage();
			} else {
				//otherwise subtract the time that has already passed from the initialPause
				var pause = this.options.initialPause - alreadyPaused;
				this.timer = this.nextImage.delay(pause, this);
			}
		}
	},
	nextImage: function() {
		//pick a random element from the list to replace
		var nonBlankElements = this.listElement.getChildren().filter(function(item, index){
			return $E('img', item);
		});
		var replaceListElement = nonBlankElements.getRandom();
		var imageElement = $E('img', replaceListElement);
		
		//if the random element is an additional image, set the flag so that we insert an additional image next
		//or just check it yourself in the fadeOutComplete function since we pass the element to that function
		
		// the element that will actually be replaced
		var replaceElement = imageElement;
		// if the image is inside a link (anchor tag), replace the whole link
		if (imageElement.getParent().getTag() == 'a') {
			replaceElement = imageElement.getParent();
		}
		
		var currentOpacity = replaceElement.getStyle('opacity').toFloat();
		var transitionSpeed = this.options.transitionSpeed;
		var that = this;
		var options = {};
		options.duration = transitionSpeed * currentOpacity;
		options.onComplete = this.fadeOutComplete.bind(this, replaceElement);
		fadeOut = new Fx.Style(imageElement, 'opacity', options);
		fadeOut.start(0);
	},
	fadeOutComplete: function(replaceElement) {
		//if the element we picked is blank the next list element we replace needs to be replaced blank
			//set a marker for the next transition
		
		//if the element we picked is one of the additional images, the next list element we replace needs to also be an additional image
			//set a marker for the next transtion
			
		//if the last element replaced wasn't blank (check the marker) we need to pick a random image
			//if the last element we replaced wasn't an additional image (check the marker)
				//pick the random image from the normal images
			//otherwise if the last element we replaced was an additional image (check the marker)
				//pick the random image from additional images
			//replace the random element with the random image
				//update the altText, caption and create a link if necessary
		//otherwise if the last element replaced was blank (check the marker) we don't need to pick a random image
			//just remove the image from the random element
			
		// when all of the images have been displayed once, reset the lists
		if (this.notDisplayedImageData.length == 0){
			this.notDisplayedImageData = this.alreadyDisplayedImageData.copy();
			this.alreadyDisplayedImageData = [];
		}
		// when all of the additional images have been displayed once, reset the lists
		if (this.notDisplayedAdditionalImageData.length == 0){
			this.notDisplayedAdditionalImageData = this.alreadyDisplayedAdditionalImageData.copy();
			this.alreadyDisplayedAdditionalImageData = [];
		}
		
		//if the image being removed from the screen is an additional image than new image must also be from the additional image list
		var insertImageData;
		if (this.options.additionalImageData.contains(replaceElement.imageDataItem)) {
			// pick the next additional image to be displayed and move it from not displayed list to currently displayed list
			insertImageData = this.notDisplayedAdditionalImageData.getRandom();
			this.currentlyDisplayedAdditionalImageData.include(insertImageData);
			this.notDisplayedAdditionalImageData.remove(insertImageData);
			// if the image that we are replacing on-screen belongs to our imageData set
			// then we need to move it from the currently displayed list to the already displayed list
			if ($defined(replaceElement.imageDataItem)) {
				this.currentlyDisplayedAdditionalImageData.remove(replaceElement.imageDataItem);
				this.alreadyDisplayedAdditionalImageData.include(replaceElement.imageDataItem);
			}
		} else {
			// pick the next image to be displayed and move it from not displayed list to currently displayed list
			insertImageData = this.notDisplayedImageData.getRandom();
			this.currentlyDisplayedImageData.include(insertImageData);
			this.notDisplayedImageData.remove(insertImageData);
			// if the image that we are replacing on-screen belongs to our imageData set
			// then we need to move it from the currently displayed list to the already displayed list
			if ($defined(replaceElement.imageDataItem)) {
				this.currentlyDisplayedImageData.remove(replaceElement.imageDataItem);
				this.alreadyDisplayedImageData.include(replaceElement.imageDataItem);
			}
		}
		
		var imageElement = $pick($E('img', insertImageData.el), insertImageData.el);
		imageElement.setOpacity(0);
		//pick a random blank element from the list to replace
		var blankElements = this.listElement.getChildren().filter(function(item, index){
			if ($E('img', item)) {
				return false;
			} else {
				return true;
			}
		});
		if (blankElements.length > 0) {
			var blankListElement = blankElements.getRandom();
			insertImageData.el.injectInside(blankListElement);
		} else {
			insertImageData.el.injectBefore(replaceElement);
		}
		
		// remove the old element
		replaceElement.remove();
		
		// fade our new element in
		var options = {};
		options.duration = this.options.transitionSpeed;
		options.onComplete = this.fadeInComplete.bind(this);
		fadeIn = new Fx.Style(imageElement, 'opacity', options);
		fadeIn.start(1);
		
	},
	fadeInComplete: function() {
		if (this.isPlaying) {
			this.timer = this.nextImage.delay(this.options.interval, this);
		}
	},
	play: function() {
		this.isPlaying = true;
		this.nextImage();
	},
	pause: function() {
		this.isPlaying = false;
		$clear(this.timer);
	}
});
AnimatedImageList.implement(new Options);
