/*
 * Class Slideshow
 * Accepts two image tags, positioned one on top of the other,
 * and creates a rotating slideshow using an array of images.
 *
 * Requires Prototype.
 *
 * @param options - object hash of:
 *      interval:       Float  - the length of time, in seconds, each slide is to remain on screen
 *      duration:       Float  - the length of the transition between slides, in seconds
 *      target1:        String - the ID of the first image tag, on the "top" (highest z-index)
 *      target2:        String - the ID of the second image tag, on the "bottom" (lowest z-index)
 *      images:         Array  - the array containing the images
 *
 * Useage:
 *      Place the image tags in the markup. I recommend placing a default image in the first img,
 *      and a display:none on the second img. This will prevent the empty img tags from appearing on the page.
 *      The script will call a show() on the second img to remove the display:none      
 *
 *          <img id="slide1" src="/path/to/optional/default/image.jpg" alt="optional alt" title="optional title" style="position:absolute;z-index:900"/>
 *          <img id="slide2" src="" alt="" title="" style="position:absolute;z-index:900;display:none;"/>
 *
 *      (Style attributes do not need to be inline)
 *
 *      Define a javascript array of the images, with the src, alt, and title properties,
 *      either inline on the page, or in an external script
 *
 *          var mySlides = new Array();
 *              mySlides[i] = new Array();
 *              mySlides[i]['src'] = '/path/to/image';
 *              mySlides[i]['alt'] = 'Alt Text';
 *              mySlides[i]['title'] = 'Title Text';
 *      
 *      Initialize the slideshow object
 *
 *          mySlideshow = new slideshow({interval: 10, duration: 1.5, target1: 'slide1', target2: 'slide2', images: 'mySlides'})
 */

var slideshow = Class.create();
slideshow.prototype = {
    initialize: function(options)
    {
        this.interval        = options['interval'];
        this.duration        = options['duration'];
        this.target1         = options['target1'];
        this.target2         = options['target2'];
        this.slides          = options['images'];
        
        this.images       = new Array();
        this.ready        = false;
        this.count        = this.slides.length;
        this.visibleSlide = $(this.target1);
        this.hiddenSlide  = $(this.target2);
        this.currentImage = 0;
        this.started      = false;
        this.setup();
    },
    
    dropImageFromSlideshow: function()
    {
        // If for some reason the image will not load (invalid URL, etc), drop it from the slideshow
        i = $A(arguments)[1];
        this.images.splice(i, 1);
    },
    
    setup: function()
    {
        for(i = 0; i < this.count; i++)
        {    
            // Loop through the provided images array and create a new Image object for each
            this.images[i]        = new Image();
            this.images[i].src    = this.slides[i]['src'];
            this.images[i].title  = this.slides[i]['title'];
            this.images[i].alt    = this.slides[i]['alt'];
            this.images[i].loaded = false;
            this.images[i].id     = i;
            // After each image has loaded, call checkImageLoadedState()
            Event.observe(this.images[i], 'load', this.checkImageLoadedState.bindAsEventListener(this, i));
            // If the image returned an error, call dropImageFromSlideshow()
            Event.observe(this.images[i], 'error', this.dropImageFromSlideshow.bindAsEventListener(this, i));
        };

    },
    
    checkImageLoadedState: function()
    {
        if(!this.started)
        {
            i = $A(arguments)[1];
            this.images[i].loaded = true;
            this.image_count      = this.images.length;
            // Loop through the array of Image objects

            // If the image at the current iteration has not loaded,
            // set the image ready flag to false,
            // and wait for the next call to this.checkImageLoadedState()
            // (i.e. when the next image has loaded, check them all again.)
            // We have to do this because we cannot guarantee that the
            // images will load sequentialy, and thus we cannot simply
            // check that the last image in the array is loaded.
            
            // If the image has loaded, set the image ready flag to true,
            // and move on to the next image
            ii = 0;              
            do
            {            
                this.ready = false;
                if(this.images[ii].loaded || this.images[ii].complete) //.complete for IE7, which won't fire an onload if the image src defined for the new Image() has already been loaded on the page
                    this.ready = true;
                
                ii++;
            }
            while(this.ready && ii < this.image_count);
            
            // If, after the loop is completed, the image ready flag is still true
            // (i.e. all the images are fully loaded), start the slideshow
            if(this.ready)
               this.start();
       }
    },
    
    changeSlides: function()
    {
        // Make the bottom image visible again, fade out the top image, and call this.updateVisible when done fading
        this.hiddenSlide.show();
        new Effect.Fade(
            this.visibleSlide,
            {
                duration: this.duration,
                afterFinish: this.updateVisible.bind(this)
            }
        );
    },
    
    updateVisible: function()
    {
        // Swap the image depths, and change the variable assignment so this.visibleSlide always
        // refers to the image on the top.
        oldSlide = this.visibleSlide;
        oldZ     = this.visibleSlide.getStyle('z-index');
        this.hiddenSlide.setStyle({zIndex: oldZ});
        this.visibleSlide.setStyle({zIndex: oldZ - 1});
        this.visibleSlide = this.hiddenSlide;
        this.hiddenSlide  = oldSlide;
        this.currentImage++;
        if(this.currentImage >= this.image_count)
            this.currentImage = 0;
        
        // Change the src, alt, and title attributes of the currently hidden image to those of the
        // next image in the cycle
        this.hiddenSlide.src   = this.images[this.currentImage].src;
        this.hiddenSlide.alt   = this.images[this.currentImage].alt;
        this.hiddenSlide.title = this.images[this.currentImage].title;
        
    },
    
    start: function()
    {
        // Load the image data into the second img, and call this.changeSlides() every [this.interval] seconds.
        this.hiddenSlide.src   = this.images[1].src;
        this.hiddenSlide.alt   = this.images[1].alt;
        this.hiddenSlide.title = this.images[1].title;
        this.started           = true;
        new PeriodicalExecuter(this.changeSlides.bind(this), this.interval);
    }
}