/**
 *	An image fader.
 *	Depends on Prototype.js and Effects.js (Scriptaculous)
 *
 *  @author Brook Elgie
 */

var Fader = Class.create({

	/**
	 *	Initializer for the class
	 *
	 *	rotatingImages				An array of img paths to rotate through
	 *	containerID						DOM id of the container div that holds the img
	 *  fadeOutDuration       Number of seconds for the image to fade out.
	 *  fadeInDuration        Number of seconds for the next image to fade in.
	 *  displayDuration       Number of seconds for the image to display before fading out.
	 *  crossfade             If true, the next image will fade in over the top of the current image. fadeOutDuration will be ignored.
	 */
	initialize: function(rotatingImages, containerID, options) {

		// init some properties
		this.rotatingImageObjs = rotatingImages;
		this.containerElement = $(containerID);
		this.preloader = new Preloader();
		this.newTag = null;
		this.retryDuration = 1.0;
		
    var img = Fader._getImageElement(this.containerElement);
		
		this.imageHeight = img.getHeight();
		this.imageWidth = img.getWidth();	

    this.options = Object.extend({
    	fadeInDuration: 1.0,
    	fadeOutDuration: 1.0,
    	displayDuration: 2.0,
    	crossfade: false,
    	pause: false
    }, options || {});

		if(this.options.crossfade) {
      this.containerElement.setStyle({height:this.imageHeight+'px', width:this.imageWidth+'px'});
      this.containerElement.makePositioned();
      this.containerElement.firstDescendant().setStyle({position:'absolute', top:0, left:0});
	  }

		// Preload images to rotate.
		var preloadPaths = [];
		for(var i=0;i<rotatingImages.length;i++){
			preloadPaths.push(rotatingImages[i].imgPath);
		}
		this.preloader.addToQueue(preloadPaths);
		this.preloader.startLoading();

		// Set up the rotater to rotate every seconds.
		if(!this.options.pause) this.pe = new PeriodicalExecuter(this.next.bind(this), this.options.displayDuration);
	},

	/**
	 *	Rotates to the next image in rotatingImageObjs array.
	 */
	next: function(pe) {
		// Stop the PeriodicalExecuter if there is one.
		if(this.pe != null) this.pe.stop();

		// Get the next image path
		var nextRotateImage = this.rotatingImageObjs.shift();
		this.rotatingImageObjs.push(nextRotateImage);

    if(!this.preloader.hasPathLoaded(nextRotateImage.imgPath)) {
      this.rotatingImageObjs.unshift(this.rotatingImageObjs.pop());
      this.pe = new PeriodicalExecuter(this.next.bind(this), this.retryDuration);
      return false;
    }
		
		
		var imgSkip = false;
		if(nextRotateImage.imgOptions) {
		  imgSkip = nextRotateImage.imgOptions.skipNav;
		}
		if(pe != null) imgSkip = false;
		
    if(!imgSkip)
      this.fadeToImage(nextRotateImage);
    else this.next();
	},
	
	/**
	 * Rotate to the last image in the rotatingImageObjs array.
	 */
	prev: function(pe) {
		// Stop the PeriodicalExecuter.
		if(this.pe != null) this.pe.stop();
		// Get the image path
		this.rotatingImageObjs.unshift(this.rotatingImageObjs.pop());
		var nextRotateImage = this.rotatingImageObjs.pop();
		this.rotatingImageObjs.push(nextRotateImage);

    if(!this.preloader.hasPathLoaded(nextRotateImage.imgPath)) {
      this.rotatingImageObjs.push(this.rotatingImageObjs.shift());
      this.pe = new PeriodicalExecuter(this.prev.bind(this), this.retryDuration);
      return false;
    }
		
		var imgSkip = false;
		if(nextRotateImage.imgOptions) {
		  imgSkip = nextRotateImage.imgOptions.skipNav;
		}
		if(pe != null) imgSkip = false;
		
    if(!imgSkip)
      this.fadeToImage(nextRotateImage);
    else this.prev();  
	},
	
	fadeToImage: function(nextRotateImage) {
    var img = Fader._getImageElement(this.containerElement);

		// Create a new image tag with the next image path.
		this.newTag = Element.extend(document.createElement('img'));
		this.newTag.writeAttribute('src', nextRotateImage.imgPath);
		this.newTag.writeAttribute('alt', nextRotateImage.imgAlt);
		this.newTag.writeAttribute('width', this.imageWidth);
		this.newTag.writeAttribute('height', this.imageHeight);
		Element.setOpacity(this.newTag, 0);

		// Create a new a tag for the image to sit in.
		var aTag;
		if(nextRotateImage.linkUrl != null) {
			aTag = Element.extend(document.createElement('a'));
			aTag.writeAttribute('href', nextRotateImage.linkUrl);
			this.newTag = aTag.update(this.newTag);
		}
		
    if(this.options.crossfade) {
      this.newTag.setStyle({position:'absolute', top:0, left:0});
      var newImg = (this.newTag.tagName != 'IMG') ? Fader._getImageElement(this.newTag) : this.newTag;
      this.containerElement.insert(this.newTag);
      new Effect.Opacity(newImg, {duration: this.options.fadeInDuration, from:0.0, to:1.0, afterFinish:this.onCrossfadeFinish.bind(this) });      
    } else {
		  new Effect.Opacity(img, { duration: this.options.fadeOutDuration, from: 1.0, to: 0.0, afterFinish:this.onFadeOutFinish.bind(this) });
    }	  
	},

	/**
	 *	Handler for the afterFinish event on the opacity fade out.
	 */
	onFadeOutFinish: function(obj) {
		this.containerElement.childElements().first().replace(this.newTag);

    var img = Fader._getImageElement(this.containerElement);
  
    new Effect.Opacity(img, {duration: this.options.fadeInDuration, from:0.0, to:1.0, afterFinish:this.onFadeInFinish.bind(this) });
	},
	
	/**
	 * Handler for the afterFinish event on the opacity fade in.
	 */
	onFadeInFinish: function(obj) {
    // Set up the rotater to rotate every seconds.
    var options = this.rotatingImageObjs.last().imgOptions || this.options;
    if(!this.options.pause) this.pe = new PeriodicalExecuter(this.next.bind(this), options.displayDuration);	  
	},
	
	/**
	 * Handler for the afterFinish event on the crossfade in.
	 */
	onCrossfadeFinish: function(obj) {
	  this.containerElement.childElements().first().remove();
		var options = this.rotatingImageObjs.last().imgOptions || this.options;
	  if(!this.options.pause) this.pe = new PeriodicalExecuter(this.next.bind(this), options.displayDuration);	  
	}
	

});

/**
 *  Finds and returns the image element in the passed container.
 */
Fader._getImageElement = function(containerElement) {
  var img = containerElement.select('img')[0];
  return img;
};
