/**
 * Copyright (c) 2006, Bill W. Scott
 * All rights reserved.
 *
 * This work is licensed under the Creative Commons Attribution 2.5 License. To view a copy 
 * of this license, visit http://creativecommons.org/licenses/by/2.5/ or send a letter to 
 * Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.
 *
 * This work was created by Bill Scott (billwscott.com, looksgoodworkswell.com).
 * 
 * The only attribution I require is to keep this notice of copyright & license 
 * in this original source file.
 *
 * Version 0.4.0 - 01.29.2007
 *
 */
YAHOO.namespace("extension");

/**
* @class 
* The carousel class manages a content list (a set of LI elements within an UL list)  that can be displayed horizontally or vertically. The content can be scrolled back and forth  with or without animation. The content can reference static HTML content or the list items can  be created dynamically on-the-fly (with or without Ajax). The navigation and event handling  can be externalized from the class.
* @param {object|string} carouselElementID The element ID (id name or id object) of the DIV that will become a carousel
* @param {object} carouselCfg The configuration object literal containing the configuration that should be set for this module. See configuration documentation for more details.
* @constructor
*/
YAHOO.extension.Carousel = function(carouselElementID, carouselCfg) {
 		this.init(carouselElementID, carouselCfg);
	};

YAHOO.extension.Carousel.prototype = {


	/**
	 * Constant denoting that the carousel size is unbounded (no limits set on scrolling)
	 * @type number
	 */
	UNBOUNDED_SIZE: 1000000,
	
	/**
	 * Initializes the carousel object and all of its local members.
     * @param {object|string} carouselElementID The element ID (id name or id object) 
     * of the DIV that will become a carousel
     * @param {object} carouselCfg The configuration object literal containing the 
     * configuration that should be set for this module. See configuration documentation for more details.
	 */
	init: function(carouselElementID, carouselCfg) {

		var oThis = this;

		// CSS style classes
		var carouselListClass = "carousel-list";
		var carouselClipRegionClass = "carousel-clip-region";
		var carouselNextClass = "carousel-next";
		var carouselPrevClass = "carousel-prev";

 		this.carouselElemID = carouselElementID;
 		this.carouselElem = YAHOO.util.Dom.get(carouselElementID);

 		this.prevEnabled = true;
 		this.nextEnabled = true;
 		
 		// Create the config object
 		this.cfg = new YAHOO.util.Config(this);

		/**
		 * orientation property. 
		 * Either "horizontal" or "vertical". Changes carousel from a 
		 * left/right style carousel to a up/down style carousel.
		 */
		this.cfg.addProperty("orientation", { 
				value:"horizontal", 
				handler: function(type, args, carouselElem) {
					oThis.orientation = args[0];
					oThis.reload();
				},
				validator: function(orientation) {
				    if(typeof orientation == "string") {
				        return ("horizontal,vertical".indexOf(orientation.toLowerCase()) != +1);
				    } else {
						return false;
					}
				}
		} );		

		/**
		 * size property. 
		 * The upper hand for scrolling in the 'next' set of content. 
		 * Set to a large value by default (this means unlimited scrolling.) 
		 */
		this.cfg.addProperty("size", { 
				value:this.UNBOUNDED_SIZE,
				handler: function(type, args, carouselElem) {
					oThis.size = args[0];
					oThis.reload();
				},
				validator: oThis.cfg.checkNumber
		} );

		/**
		 * numVisible property. 
		 * The number of items that will be visible.
		 */
		this.cfg.addProperty("numVisible", { 
				value:3,
				handler: function(type, args, carouselElem) {
					oThis.numVisible = args[0];
					oThis.load();
				},
				validator: oThis.cfg.checkNumber
		} );

		/**
		 * firstVisible property. 
		 * Sets which item should be the first visible item in the carousel. Use to set which item will
		 * display as the first element when the carousel is first displayed. After the carousel is created,
		 * you can manipulate which item is the first visible by using the moveTo() or scrollTo() convenience
		 * methods.
		 */
		this.cfg.addProperty("firstVisible", { 
				value:1,
				handler: function(type, args, carouselElem) {
					oThis.moveTo(args[0]);
				},
				validator: oThis.cfg.checkNumber
		} );

		/**
		 * scrollInc property. 
		 * The number of items to scroll by. Think of this as the page increment.
		 */
		this.cfg.addProperty("scrollInc", { 
				value:3,
				handler: function(type, args, carouselElem) {
					oThis.scrollInc = args[0];
				},
				validator: oThis.cfg.checkNumber
		} );
		
		/**
		 * animationSpeed property. 
		 * The time (in seconds) it takes to complete the scroll animation. 
		 * If set to 0, animated transitions are turned off and the new page of content is 
		 * moved immdediately into place.
		 */
		this.cfg.addProperty("animationSpeed", { 
				value:0.25,
				handler: function(type, args, carouselElem) {
					oThis.animationSpeed = args[0];
				},
				validator: oThis.cfg.checkNumber
		} );

		/**
		 * animationMethod property. 
		 * The <a href="http://developer.yahoo.com/yui/docs/animation/YAHOO.util.Easing.html">YAHOO.util.Easing</a> 
		 * method.
		 */
		this.cfg.addProperty("animationMethod", { 
				value:  YAHOO.util.Easing.easeOut,
				handler: function(type, args, carouselElem) {
					oThis.animationMethod = args[0];
				}
		} );
		
		/**
		 * animationCompleteHandler property. 
		 * JavaScript function that is called when the Carousel finishes animation 
		 * after a next or previous nagivation. 
		 * Only invoked if animationSpeed > 0. 
		 * Two parameters are passed: type (set to 'onAnimationComplete') and 
		 * args array (args[0] = direction [either: 'next' or 'previous']).
		 */
		this.cfg.addProperty("animationCompleteHandler", { 
				value:null,
				handler: function(type, args, carouselElem) {
					if(oThis.animationCompleteEvt) {
						oThis.animationCompleteEvt.unsubscribe(oThis.animationCompleteHandler, oThis);
					}
					oThis.animationCompleteHandler = args[0];
					if(oThis._isValidObj(oThis.animationCompleteHandler)) {
						oThis.animationCompleteEvt = new YAHOO.util.CustomEvent("onAnimationComplete", oThis);
						oThis.animationCompleteEvt.subscribe(oThis.animationCompleteHandler, oThis);
					}
				}
		} );
		
		/**
		 * autoPlay property. 
		 * Specifies how many milliseconds to periodically auto scroll the content. 
		 * If set to 0 (default) then autoPlay is turned off. 
		 * If the user interacts by clicking left or right navigation, autoPlay is turned off. 
		 * You can restart autoPlay by calling the <em>startAutoPlay()</em>. 
		 * If you externally control navigation (with your own event handlers) 
		 * then you may want to turn off the autoPlay by calling<em>stopAutoPlay()</em>
		 */
		this.cfg.addProperty("autoPlay", { 
				value:0,
				handler: function(type, args, carouselElem) {
					oThis.autoPlay = args[0];
					if(oThis.autoPlay > 0)
						oThis.startAutoPlay();
					else
						oThis.stopAutoPlay();
				}
		} );
		
		/**
		 * wrap property. 
		 * Specifies whether to wrap when at the end of scrolled content. When the end is reached,
		 * the carousel will scroll backwards to the item 1 (the animationSpeed parameter is used to 
		 * determine how quickly it should animate back to the start.)
		 * Ignored if the <em>size</em> attribute is not explicitly set 
		 * (i.e., value equals YAHOO.extension.Carousel.UNBOUNDED_SIZE)
		 */
		this.cfg.addProperty("wrap", { 
				value:false,
				handler: function(type, args, carouselElem) {
					oThis.wrap = args[0];
				},
				validator: oThis.cfg.checkBoolean
		} );
		
		/**
		 * navMargin property. 
		 * The margin space for the navigation controls. This is only useful for horizontal carousels 
		 * in which you have embedded navigation controls. 
		 * The <em>navMargin</em> allocates space between the left and right margins 
		 * (each navMargin wide) giving space for the navigation controls.
		 */
		this.cfg.addProperty("navMargin", { 
				value:0,
				handler: function(type, args, carouselElem) {
					oThis.navMargin = args[0];
				},
				validator: oThis.cfg.checkNumber
		} );
		
		// For backward compatibility. Deprecated.
		this.cfg.addProperty("prevElementID", { 
			value: null,
			handler: function(type, args, carouselElem) {
				if(oThis.carouselPrev) {
					YAHOO.util.Event.removeListener(oThis.carouselPrev, "click", oThis._scrollPrev);
				} 
				oThis.prevElementID = args[0];
				if(oThis.prevElementID == null) {
					oThis.carouselPrev = YAHOO.util.Dom.getElementsByClassName(carouselPrevClass, 
														"div", oThis.carouselElem)[0];
				} else {
					oThis.carouselPrev = YAHOO.util.Dom.get(oThis.prevElementID);
				}
				YAHOO.util.Event.addListener(oThis.carouselPrev, "click", oThis._scrollPrev, oThis);
			}
		});
		
		/**
		 * prevElement property. 
		 * An element or elements that will provide the previous navigation control.
		 * prevElement may be a single element or an array of elements. The values may be strings denoting
		 * the ID of the element or the object itself.
		 * If supplied, then events are wired to this control to fire scroll events to move the carousel to
		 * the previous content. 
		 * You may want to provide your own interaction for controlling the carousel. If
		 * so leave this unset and provide your own event handling mechanism.
		 */
		this.cfg.addProperty("prevElement", { 
				value:null,
				handler: function(type, args, carouselElem) {
					if(oThis.carouselPrev) {
						YAHOO.util.Event.removeListener(oThis.carouselPrev, "click", oThis._scrollPrev);
					} 
					oThis.prevElementID = args[0];
					if(oThis.prevElementID == null) {
						oThis.carouselPrev = YAHOO.util.Dom.getElementsByClassName(carouselPrevClass, 
															"div", oThis.carouselElem)[0];
					} else {
						oThis.carouselPrev = YAHOO.util.Dom.get(oThis.prevElementID);
					}
					YAHOO.util.Event.addListener(oThis.carouselPrev, "click", oThis._scrollPrev, oThis);
				}
		} );
		
		// For backward compatibility. Deprecated.
		this.cfg.addProperty("nextElementID", { 
			value: null,
			handler: function(type, args, carouselElem) {
				if(oThis.carouselNext) {
					YAHOO.util.Event.removeListener(oThis.carouselNext, "click", oThis._scrollNext);
				} 
				oThis.nextElementID = args[0];
				if(oThis.nextElementID == null) {
					oThis.carouselNext = YAHOO.util.Dom.getElementsByClassName(carouselNextClass, 
														"div", oThis.carouselElem);
				} else {
					oThis.carouselNext = YAHOO.util.Dom.get(oThis.nextElementID);
				}
				if(oThis.carouselNext) {
					YAHOO.util.Event.addListener(oThis.carouselNext, "click", oThis._scrollNext, oThis);
				} 
			}
		});
		
		/**
		 * nextElement property. 
		 * An element or elements that will provide the next navigation control.
		 * nextElement may be a single element or an array of elements. The values may be strings denoting
		 * the ID of the element or the object itself.
		 * If supplied, then events are wired to this control to fire scroll events to move the carousel to
		 * the next content. 
		 * You may want to provide your own interaction for controlling the carousel. If
		 * so leave this unset and provide your own event handling mechanism.
		 */
		this.cfg.addProperty("nextElement", { 
				value:null,
				handler: function(type, args, carouselElem) {
					if(oThis.carouselNext) {
						YAHOO.util.Event.removeListener(oThis.carouselNext, "click", oThis._scrollNext);
					} 
					oThis.nextElementID = args[0];
					if(oThis.nextElementID == null) {
						oThis.carouselNext = YAHOO.util.Dom.getElementsByClassName(carouselNextClass, 
															"div", oThis.carouselElem);
					} else {
						oThis.carouselNext = YAHOO.util.Dom.get(oThis.nextElementID);
					}
					if(oThis.carouselNext) {
						YAHOO.util.Event.addListener(oThis.carouselNext, "click", oThis._scrollNext, oThis);
					} 
				}
		} );
		
		/**
		 * loadInitHandler property. 
		 * JavaScript function that is called when the Carousel needs to load 
		 * the initial set of visible items. Two parameters are passed: 
		 * type (set to 'onLoadInit') and an argument array (args[0] = start index, args[1] = last index).
		 */
		this.cfg.addProperty("loadInitHandler", { 
				value:null,
				handler: function(type, args, carouselElem) {
					if(oThis.loadInitHandlerEvt) {
						oThis.loadInitHandlerEvt.unsubscribe(oThis.loadInitHandler, oThis);
					}
					oThis.loadInitHandler = args[0];
					if(oThis.loadInitHandlerEvt) {
						oThis.loadInitHandlerEvt = new YAHOO.util.CustomEvent("onLoadInit", oThis);
						oThis.loadInitHandlerEvt.subscribe(oThis.loadInitHandler, oThis);
					}
				}
		} );
		
		/**
		 * loadNextHandler property. 
		 * JavaScript function that is called when the Carousel needs to load 
		 * the next set of items (in response to the user navigating to the next set.) 
		 * Two parameters are passed: type (set to 'onLoadNext') and 
		 * args array (args[0] = start index, args[1] = last index).
		 */
		this.cfg.addProperty("loadNextHandler", { 
				value:null,
				handler: function(type, args, carouselElem) {
					if(oThis.loadNextHandlerEvt) {
						oThis.loadNextHandlerEvt.unsubscribe(oThis.loadNextHandler, oThis);
					}
					oThis.loadNextHandler = args[0];
					if(oThis.loadNextHandlerEvt) {
						oThis.loadNextHandlerEvt = new YAHOO.util.CustomEvent("onLoadNext", oThis);
						oThis.loadNextHandlerEvt.subscribe(oThis.loadNextHandler, oThis);
					}
				}
		} );
				
		/**
		 * loadPrevHandler property. 
		 * JavaScript function that is called when the Carousel needs to load 
		 * the previous set of items (in response to the user navigating to the previous set.) 
		 * Two parameters are passed: type (set to 'onLoadPrev') and args array 
		 * (args[0] = start index, args[1] = last index).
		 */
		this.cfg.addProperty("loadPrevHandler", { 
				value:null,
				handler: function(type, args, carouselElem) {
					if(oThis.loadPrevHandlerEvt) {
						oThis.loadPrevHandlerEvt.unsubscribe(oThis.loadPrevHandler, oThis);
					}
					oThis.loadPrevHandler = args[0];
					if(oThis.loadPrevHandlerEvt) {
						oThis.loadPrevHandlerEvt = new YAHOO.util.CustomEvent("onLoadPrev", oThis);
						oThis.loadPrevHandlerEvt.subscribe(oThis.loadPrevHandler, oThis);
					}
				}
		} );
		
		/**
		 * prevButtonStateHandler property. 
		 * JavaScript function that is called when the enabled state of the 
		 * 'previous' control is changing. The responsibility of 
		 * this method is to enable or disable the 'previous' control. 
		 * Two parameters are passed to this method: <em>type</em> 
		 * (which is set to "onPrevButtonStateChange") and <em>args</em>, 
		 * an array that contains two values. 
		 * The parameter args[0] is a flag denoting whether the 'previous' control 
		 * is being enabled or disabled. The parameter args[1] is the element object 
		 * derived from the <em>prevElement</em> parameter.
		 * If you do not supply a prevElement then you will need to track
		 * the elements that you would want to enable/disable while handling the state change.
		 */
		this.cfg.addProperty("prevButtonStateHandler", { 
				value:null,
				handler: function(type, args, carouselElem) {
					if(oThis.prevButtonStateHandler) {
						oThis.prevButtonStateHandlerEvt.unsubscribe(oThis.prevButtonStateHandler, oThis);
					}
					oThis.prevButtonStateHandler = args[0];
					if(oThis.prevButtonStateHandler) {
						oThis.prevButtonStateHandlerEvt = new YAHOO.util.CustomEvent("onPrevButtonStateChange", oThis);
						oThis.prevButtonStateHandlerEvt.subscribe(oThis.prevButtonStateHandler, oThis);
					}
				}
		} );
		
		/**
		 * nextButtonStateHandler property. 
		 * JavaScript function that is called when the enabled state of the 
		 * 'next' control is changing. The responsibility of 
		 * this method is to enable or disable the 'next' control. 
		 * Two parameters are passed to this method: <em>type</em> 
		 * (which is set to "onNextButtonStateChange") and <em>args</em>, 
		 * an array that contains two values. 
		 * The parameter args[0] is a flag denoting whether the 'next' control 
		 * is being enabled or disabled. The parameter args[1] is the element object 
		 * derived from the <em>nextElement</em> parameter.
		 * If you do not supply a nextElement then you will need to track
		 * the elements that you would want to enable/disable while handling the state change.
		 */
		this.cfg.addProperty("nextButtonStateHandler", { 
				value:null,
				handler: function(type, args, carouselElem) {
					if(oThis.nextButtonStateHandler) {
						oThis.nextButtonStateHandlerEvt.unsubscribe(oThis.nextButtonStateHandler, oThis);
					}
					oThis.nextButtonStateHandler = args[0];
					if(oThis.nextButtonStateHandler) {
						oThis.nextButtonStateHandlerEvt = new YAHOO.util.CustomEvent("onNextButtonStateChange", oThis);
	


