/**----------------------------------------------------------------------------
 *  IMS_JS_WIDGET.SlideShow
 *  (c) 2007 Third Light Ltd
 * ----------------------------------------------------------------------------
 */

/**
 * IMS_JS_WIDGET.SlideShow
 */
IMS_JS_WIDGET.SlideShow = Class.create(
    IMS_JS_WIDGET.Widget, {

    /**
     * Constructor.
     * Configuration, then does initial set-up and fires off the slideshow.
     */
     initialize: function ($super, required, options) {
       $super(required, options);	    

       /* Process required parameters. */
       this.strContainer = this.required.strContainer;
       this.arrPhotos    = this.required.arrPhotos;

       /* Process optional parameters. */
       this.nStartPicture = this.options.nStartPicture || 1;
       this.bShowToolbars = true;
       if (this.options.bShowToolbars !== undefined) this.bShowToolbars = this.options.bShowToolbars;
       this.nEffectDuration  = this.options.nEffectDuration || 0.5; /* seconds */	    
       this.nViewDuration    = this.options.nViewDuration || 3000;	 /* milliseconds */
       this.strImageCssClass = this.options.strImageCssClass || "ims_js_widget_SlideShow_image";
       this.bBrowserSecurityEnabled = this.options.bBrowserSecurityEnabled;
       /* Member variables */
       this.elContainer       = $(this.strContainer);
       if(this.elContainer.className == "ims_js_widget_SlideShow_container") {
         this.elContainer.style.height="618px";
       }
       this.nNumImages        = this.arrPhotos.length;
       this.elToolbar         = new Element("div");
       this.elSliderTrack     = new Element("div", {"class": "ims_js_widget_SlideShow_slider_track"});
       this.elSliderHandle    = new Element("div", {"class": "ims_js_widget_SlideShow_slider_handle"});
       this.elContextTrack    = new Element("div", {"class": "ims_js_widget_SlideShow_context_track"});
       this.elContextHandle   = new Element("div", {"class": "ims_js_widget_SlideShow_context_handle"});
       this.elBtnStop         = {};
       this.elContextSlider   = {};		
       this.nCurrentImage     = this.nStartPicture - 1;		
       this.nMinViewTime      = 1000;    /* milliseconds */
       this.nMaxViewTime      = 6000;    /* milliseconds */
       this.nWindowTimeoutID  = 0;
       this.bPaused           = false;
       this.bIsDragged        = false;
       this.bCanClickNextPrev = true;

       this.messages = this.options.messages || {
         PLAY:            "Play",
         NEXT:            "Next",
         PREVIOUS:        "Previous",
         PAUSE:           "Pause",
         FASTER:          "Faster",
         SLOWER:          "Slower",
         CLOSE_SLIDESHOW: "Close Slideshow", 
         COPYRIGHT:       "These pictures are copyrighted material. Please do not take copies."
       };
       /* Graphics config
       * Hash stores {button name: [imageurl, alt/title text, event handler]}
       * Order of element in arrBtnNames is the order they are laid out in the toolbar */
       this.arrBtnNames = ["btnPrevious", "btnPlay", "btnPause", "btnNext"];
       this.hashBtnDefns = $H({
         btnPlay:     ["ss_btn_play.png",     this.messages.PLAY.escapeHTML(),     "OnClickPlay"],
         btnNext:     ["ss_btn_next.png",     this.messages.NEXT.escapeHTML(),     "OnClickNext"],
         btnPrevious: ["ss_btn_previous.png", this.messages.PREVIOUS.escapeHTML(), "OnClickPrevious"],
         btnPause:    ["ss_btn_pause.png",    this.messages.PAUSE.escapeHTML(),    "OnClickPause"]
       });
       this.strImgPath = "images/";

       /* Image double-buffering to preload next image for faster display, and
       * for cross-fade effect. */
       this.imageShow        = new Element("img", {"class": this.strImageCssClass});
       this.imageBuff        = new Element("img", {"class": this.strImageCssClass});

       /* The two variabless coordinate whether or not to show the next image, which
       * happens when both are true. */
       this.bNextImageLoaded      = false;
       this.bWindowTimeoutOccured = false;

       /* Useful pre-flight checks and setup! */
       Event.observe(window, "load", this.SetupContainer.bindAsEventListener(this));
     },

     /**
     * SetTimeout
     * Called whenever the next image is to be loaded automatically, rather than
     * via the next/previous buttons.
     */
     SetTimeout: function() {
       this.bTimeoutOccured  = false;
       var myself            = this;
       this.nWindowTimeoutID = window.setTimeout(
         function() {
           myself.ShowNextImage(true); 
         },
         myself.nViewDuration
       );
     },

     /**
     * SetupSlider
     * Sets the parameters for the speed slider.
     * Called from window.onload via this.AttachEvents
     */
     SetupSlider: function() {		
       var myself = this;
       new Control.Slider(
         this.elSliderHandle,
         this.elSliderTrack, {
           range:       $R(this.nMinViewTime, this.nMaxViewTime),
           /* reverse the value so the slider is intuitive (faster to the right) */
           onSlide:     function(nValue) {myself.SetViewDuration(myself.nMaxViewTime - nValue + myself.nMinViewTime)},
           sliderValue: myself.nMaxViewTime - this.nViewDuration + myself.nMinViewTime
         }
       );
     },

	/**
	 * SetupContext
	 * Sets the parameters for the context slider.
	 * Called from window.onload via this.AttachEvents
	 */
	SetupContext: function() {	
	    	    
	    /* Establish the handle widths. */
	    var nWidth = Math.floor(this.elContextTrack.getWidth() / this.nNumImages);
		this.elContextHandle.setStyle({
			width: nWidth + "px"		
		});	
		
		/* Create the Slider control. */
		var myself               = this;
		var objRangePhotoIndexes = $R(0, this.nNumImages - 1)
		var arrValues            = $A(objRangePhotoIndexes); 
  		this.elContextSlider = new Control.Slider(
  			this.elContextHandle,
  			this.elContextTrack, {
  				range:       objRangePhotoIndexes,
        		onChange:    function(nValue) {myself.GotoImage(nValue)},
        		onSlide:     function(nValue) {myself.bIsDragged = true},
        		sliderValue: 0,
        		increment:   nWidth,
        		values:      arrValues
        	}
        );
        
        /* Set the value - the slideshow might not be starting with the first image. */
        this.SetContextPosition(this.nCurrentImage);
	},
	
	/**
	 * SetupContainer
	 * Create the HTML elements for the SlideShow and lay them out into the
	 * container. This is called from the constructor.
	 */
	SetupContainer: function() {

		/* Double-buffered image structure */
		this.imageBuff.hide();
		this.imageShow.hide();
		
		/* Create the toolbar and button holder. */
		if (this.bShowToolbars) {
    		this.elToolbar.className = "ims_js_widget_SlideShow_toolbar";
		}
		
    	var elImgCont = new Element("div", {"class": "ims_js_widget_SlideShow_toolbar_button_container"});		
		
    	if (this.bShowToolbars) {    	   
    		/* Replace the button definitions with actual elements. */
    		for (var i = 0; i < this.arrBtnNames.length; i++) {
    			var strName    = this.arrBtnNames[i]
    			var arrBtnDefn = this.hashBtnDefns.get(strName);
    			var elImg      = new Element("img", {
    				"src":   this.strImgPath + arrBtnDefn[0],
    				"alt":   arrBtnDefn[1],
    				"title": arrBtnDefn[1],
    				"class": "ims_js_widget_SlideShow_toolbar_button"
    			});				
    			/* Dynamically attach onclick event handlers; the handler function name is
    			 * stored in arrBtnDefns[2]. */
    			var myself = this;
    			strCode = "var fnTemp = function() {myself." + arrBtnDefn[2] + "()}";
    			eval(strCode);
    			Event.observe(elImg, "click", fnTemp);    			
    			/* Insert the button into the container. */
    			elImgCont.insert({bottom: elImg});
    			this.hashBtnDefns.set(strName, elImg);
    		  }
    				
    		  this.hashBtnDefns.get("btnPlay").hide();
    		  this.elToolbar.insert({bottom: elImgCont});

    		/* Create the speed slider */
    		var elSliderCont = new Element("div", {"class": "ims_js_widget_SlideShow_slider_container"});
    		this.elSliderTrack.insert({bottom: this.elSliderHandle});
    		elSliderCont.insert({bottom: "<span style='float: left'>"+this.messages.SLOWER.escapeHTML()+"</span>"});
    		elSliderCont.insert({bottom: this.elSliderTrack});
    		elSliderCont.insert({bottom: "<span style='float: left'>"+this.messages.FASTER.escapeHTML()+"</span>"});
    		this.elToolbar.insert({bottom: elSliderCont});
		
    		/* Create the context slider */
    		var elContextCont = new Element("div", {"class": "ims_js_widget_SlideShow_context_container"});	
    		this.elContextTrack.insert({bottom: this.elContextHandle});	
    		elContextCont.insert({bottom: this.elContextTrack});

    		/* Create the stop slide shopw button. */
    		this.elBtnStop        = new Element("input", {
    		     "class": "ims_js_widget_SlideShow_generic_button",
    		     "type":  "button",
    		     "value": this.messages.CLOSE_SLIDESHOW
    		});
		}
				
		/* Add the toolbar, the buffered images, the context slider and the
		 * stop button to the container. */
		if (this.bShowToolbars == true) {
		    this.elContainer.insert({bottom: this.elToolbar});
		    this.elContainer.insert({bottom: elContextCont});
		}  
		this.elContainer.insert({bottom: this.imageShow});
		this.elContainer.insert({bottom: this.imageBuff});
		if (this.bShowToolbars == true) {
		    this.elContainer.insert({after: this.elBtnStop});
		}
		
		this.AttachEvents();
		this.SetupSlider();
		this.SetupContext();
        this.SetTimeout();
		this.LoadFirstImage();
	},  
	
	/**
	 * LoadFirstImage
	 * Utility method used from the constructor to load the very first image only.
	 */
	LoadFirstImage: function() {
		this.imageShow.src = this.arrPhotos[this.nCurrentImage].url;		
	},

	/**
	 * AttachEvents
	 * Create most of the events needed to manage the SlideShow. Note: button events
	 * are created in SetupContainer()
	 * This is called from the constructor.
	 */
	AttachEvents: function () {
		Event.observe(this.imageBuff, "load", this.NextImageLoaded.bindAsEventListener(this));
		Event.observe(this.imageShow, "load", this.SwapBuffersHelper.bindAsEventListener(this));
		
		if(this.bBrowserSecurityEnabled) {
			Event.observe(this.imageShow, "contextmenu", function(e) {
			    alert(this.messages.COPYRIGHT);
			    Event.stop(e);
			  }.bindAsEventListener(this)
			);
		}
		
		if (this.bShowToolbars) {
		    Event.observe(this.elBtnStop, "click", this.StopSlideShow.bindAsEventListener(this));
		}
	},

	/**
	 * NextImageLoaded and ShowNextImage
	 * These coordinate when to display the image using the member variables
	 * bNextImageLoaded and bTimeoutOccured. NextImageLoaded is called when
	 * the buffer image is available. ShowNextImage is called when the display
	 * interval completes via window.setTimeout.
	 */
	NextImageLoaded: function() {
	    /* Resize for Safari; doesn't hurt in other browsers. */
	    this.imageBuff.setStyle({
	       width:  "auto",
	       height: "auto"
	    });
	    
	    /* Position the buffer. */
	    this.PositionImage(this.imageBuff);
        	    	    
		/**
		 * If the timeout has already occured, cross-fade in the new image.
		 */
		if (this.bTimeoutOccured == true) {
			this.bTimeoutOccured = false;
			this.CrossFade();
		}
		/* Mark the image as loaded and wait for the timeout */
		else {
			this.bNextImageLoaded = true;
		}
	},
	
	ShowNextImage: function() {
		if (this.bNextImageLoaded == true) {
    		/* The new image has already loaded, so cross-fade it in */
			this.CrossFade();
		} else {
    		/* Mark the timeout as having occured and wait for the image to load */
			this.bTimeoutOccured = true;
		}
	},
	
	/**
	 * CrossFade
	 * Uses a parallel effect to bring the buffer into view and the current
	 * image out of view. Once this effect completes, the next image is
	 * prepared and the timeout is reset.
	 */
	CrossFade: function() {
		var elOut = this.imageShow;
		var elIn = this.imageBuff;
		var myself = this;
		new Effect.Parallel([
				new Effect.Fade  (elOut, {sync: true}),
				new Effect.Appear(elIn,  {sync: true})
			], {
			    beforeStart: function() {
            		myself.nCurrentImage++;
            		if (myself.nCurrentImage >= myself.nNumImages) myself.nCurrentImage = 0;
            		myself.SetContextPosition(myself.nCurrentImage);
			    },
				duration:    this.nEffectDuration,
				afterFinish: function() {
					myself.SwapBuffers();
                    if (!myself.bPaused) myself.SetTimeout();
				}
			}
		);
	},

	/**
	 * SwapBuffersHelper
	 * Called by the imageShow.load event when the buffers are swapped 
	 * in SwapBuffers. It makes the swap a bit smoother for IE6. This 
	 * also triggers LoadNextImage - important to trigger this here (when
	 * we know we have imageShow displaying the same as imageBuff) as 
	 * this then enforces a series operation rather than parallel - it is
	 * possible for LoadNextImage to work faster than SwapBuffers when 
	 * pictures are cached and thus cause glitchyness.
	 */
	SwapBuffersHelper: function() {
	    this.PositionImage(this.imageShow);	
	    this.imageShow.show();
		this.imageBuff.hide();
		this.LoadNextImage(true);
	},
	
	/**
	 * SwapBuffers
	 * After the CrossFade, the buffer is now in front, so swap it with imageShow
	 * in anticipation of loading the next image.
	 */
	SwapBuffers: function(bForwards) {		
	    this.imageShow.src = this.imageBuff.src;	
	    this.bCanClickNextPrev = true;
	},

	/**
	 * LoadNextImage
	 * Begins loading the next image into the buffer.
	 */
	LoadNextImage: function(bForwards) {
		this.bNextImageLoaded = false;
		if (bForwards == true) {
			/* If at the end, loop. */
			if ((this.nCurrentImage + 1) >= this.nNumImages) var nNext = 0;
			else var nNext = this.nCurrentImage + 1;
		} else {
		    /* If at start of loop */
		    if (this.nCurrentImage == 0) var nNext = (this.nNumImages - 1);
		    else var nNext = this.nCurrentImage - 1;
		}
		this.imageBuff.src = this.arrPhotos[nNext].url;
	},

    /**
     * GotoImage
     * Loads any image into the buffer and displays it.
     */
    GotoImage: function(nIndex) {
    	if (this.bIsDragged == true) {
    		this.bIsDragged = false;
			window.clearTimeout(this.nWindowTimeoutID);
			this.bTimeoutOccured = true;
			this.nCurrentImage = nIndex - 1;
			this.LoadNextImage(true);
		}
    },
    
    /**
     * PositionImage
     * Centers either the buffer or the main image in the container.
     */
    PositionImage: function(img) {
	    var nLeft = (this.elContainer.getWidth() - img.getWidth()) / 2;
	    var nTop  = (this.elContainer.getHeight() - img.getHeight()) / 2;	            
	    img.setStyle({
	        position:"absolute",
	        left: nLeft + "px",
	        top:  nTop + "px"
	    });
	    this.elContainer.makePositioned();
    },        

	/**
	 * Click event handler for play button.
	 */
	OnClickPlay: function() {
		this.hashBtnDefns.get("btnPause").show();
		this.hashBtnDefns.get("btnPlay").hide();
		this.bPaused = false;
		/**
		 * The following two lines determine action upon pressing play.
		 * ShowNextImage() will cause the pending image to be displayed 
		 * immediately. SetTimeout() will initiate a time delay before
		 * resuming. Current preference is the former - if you press play
		 * following a pause it suggests you want to see the next image
		 * sharpish.
		 */
		this.ShowNextImage();
		//this.SetTimeout();
	},

	/**
	 * Click event handler for pause button.
	 */
	OnClickPause: function() {
		this.hashBtnDefns.get("btnPlay").show();
		this.hashBtnDefns.get("btnPause").hide();
		this.bPaused = true;
		window.clearTimeout(this.nWindowTimeoutID);
	},

	/**
	 * Click event handler for previous button.
	 */
	OnClickPrevious: function() {
		if (this.bCanClickNextPrev) {
		    this.bCanClickNextPrev = false; 
    	    window.clearTimeout(this.nWindowTimeoutID);
    		this.LoadNextImage(false);
    		/**
    		 * Fudge the current image reference so that when we CrossFade
    		 * and advance this reference we move to the right place
    		 */
    		if (this.nCurrentImage > 0) {
        		this.nCurrentImage = this.nCurrentImage - 2;
    		} else {
        		this.nCurrentImage = this.nNumImages - 2;
    		}		
    		this.ShowNextImage();
		}
	},

	/**
	 * Click event handler for next button.
	 */
	OnClickNext: function() {
	    if (this.bCanClickNextPrev) {
		    this.bCanClickNextPrev = false;
	        window.clearTimeout(this.nWindowTimeoutID);		
		    this.ShowNextImage();
	    }
	},

	/**
	 * Utility setter method.
	 */
	SetViewDuration: function(nValue) {
		this.nViewDuration = nValue;
	},
	
	/**
	 * Utility setter method for the postion of the context scrollbar.
	 */
	SetContextPosition: function(nValue) {
		this.elContextSlider.setValue(nValue);
	},
	
	/**
	 * StopSlideShow
	 * Returns the user to viewpicture.tlx for the current slideshow picture.
	 */
    StopSlideShow: function() {
        document.location = this.arrPhotos[this.nCurrentImage].previewurl;
    }
});
