
CCAN.namespace("extension");

CCAN.extension.Carousel = function(carouselElementID, carouselCfg) {
 	this.init(carouselElementID, carouselCfg);
	};

CCAN.extension.Carousel.prototype = {

	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;
	
	this.getCarouselItem = this.getItem;
	
	// 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 = CCAN.util.Dom.get(carouselElementID);

 	this._prevEnabled = true;
 	this._nextEnabled = true;
 	
 	// Create the config object
 	this.cfg = new CCAN.util.Config(this);


	this.cfg.addProperty("scrollBeforeAmount", { 
	value:0, 
	handler: function(type, args, carouselElem) {
	},
	validator: oThis.cfg.checkNumber
	} );	


	this.cfg.addProperty("scrollAfterAmount", { 
	value:0, 
	handler: function(type, args, carouselElem) {
	},
	validator: oThis.cfg.checkNumber
	} );	


	this.cfg.addProperty("loadOnStart", { 
	value:true, 
	handler: function(type, args, carouselElem) {
	// no action, only affects startup
	},
	validator: oThis.cfg.checkBoolean
	} );	


	this.cfg.addProperty("orientation", { 
	value:"horizontal", 
	handler: function(type, args, carouselElem) {
	oThis.reload();
	},
	validator: function(orientation) {
	    if(typeof orientation == "string") {
	        return ("horizontal,vertical".indexOf(orientation.toLowerCase()) != -1);
	    } else {
	return false;
	}
	}
	} );	


	this.cfg.addProperty("size", { 
	value:this.UNBOUNDED_SIZE,
	handler: function(type, args, carouselElem) {
	oThis.reload();
	},
	validator: oThis.cfg.checkNumber
	} );


	this.cfg.addProperty("numVisible", { 
	value:3,
	handler: function(type, args, carouselElem) {
	oThis.reload();
	},
	validator: oThis.cfg.checkNumber
	} );


	this.cfg.addProperty("firstVisible", { 
	value:1,
	handler: function(type, args, carouselElem) {
	oThis.moveTo(args[0]);
	},
	validator: oThis.cfg.checkNumber
	} );


	this.cfg.addProperty("scrollInc", { 
	value:3,
	handler: function(type, args, carouselElem) {
	},
	validator: oThis.cfg.checkNumber
	} );
	

	this.cfg.addProperty("animationSpeed", { 
	value:0.25,
	handler: function(type, args, carouselElem) {
	oThis.animationSpeed = args[0];
	},
	validator: oThis.cfg.checkNumber
	} );


	this.cfg.addProperty("animationMethod", { 
	value:  CCAN.util.Easing.easeOut,
	handler: function(type, args, carouselElem) {
	}
	} );
	

	this.cfg.addProperty("animationCompleteHandler", { 
	value:null,
	handler: function(type, args, carouselElem) {
	if(oThis._animationCompleteEvt) {
	oThis._animationCompleteEvt.unsubscribe(oThis._currAnimationCompleteHandler, oThis);
	}
	oThis._currAnimationCompleteHandler = args[0];
	if(oThis._currAnimationCompleteHandler) {
	if(!oThis._animationCompleteEvt) {
	oThis._animationCompleteEvt = new CCAN.util.CustomEvent("onAnimationComplete", oThis);
	}
	oThis._animationCompleteEvt.subscribe(oThis._currAnimationCompleteHandler, oThis);
	}
	}
	} );
	
	this.cfg.addProperty("autoPlay", { 
	value:0,
	handler: function(type, args, carouselElem) {
	var autoPlay = args[0];
	if(autoPlay > 0)
	oThis.startAutoPlay();
	else
	oThis.stopAutoPlay();
	}
	} );
	

	this.cfg.addProperty("wrap", { 
	value:false,
	handler: function(type, args, carouselElem) {
	},
	validator: oThis.cfg.checkBoolean
	} );
	

	this.cfg.addProperty("navMargin", { 
	value:0,
	handler: function(type, args, carouselElem) {
	oThis.calculateSize();	
	},
	validator: oThis.cfg.checkNumber
	} );
	

	this.cfg.addProperty("revealAmount", { 
	value:0,
	handler: function(type, args, carouselElem) {
	oThis.reload();
	},
	validator: oThis.cfg.checkNumber
	} );
	
	// For backward compatibility. Deprecated.
	this.cfg.addProperty("prevElementID", { 
	value: null,
	handler: function(type, args, carouselElem) {
	if(oThis._carouselPrev) {
	CCAN.util.Event.removeListener(oThis._carouselPrev, "click", oThis._scrollPrev);
	} 
	oThis._prevElementID = args[0];
	if(oThis._prevElementID == null) {
	oThis._carouselPrev = CCAN.util.Dom.getElementsByClassName(carouselPrevClass, 
	"div", oThis.carouselElem)[0];
	} else {
	oThis._carouselPrev = CCAN.util.Dom.get(oThis._prevElementID);
	}
	CCAN.util.Event.addListener(oThis._carouselPrev, "click", oThis._scrollPrev, oThis);
	}
	});
	

	this.cfg.addProperty("prevElement", { 
	value:null,
	handler: function(type, args, carouselElem) {
	if(oThis._carouselPrev) {
	CCAN.util.Event.removeListener(oThis._carouselPrev, "click", oThis._scrollPrev);
	} 
	oThis._prevElementID = args[0];
	if(oThis._prevElementID == null) {
	oThis._carouselPrev = CCAN.util.Dom.getElementsByClassName(carouselPrevClass, 
	"div", oThis.carouselElem)[0];
	} else {
	oThis._carouselPrev = CCAN.util.Dom.get(oThis._prevElementID);
	}
	CCAN.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) {
	CCAN.util.Event.removeListener(oThis._carouselNext, "click", oThis._scrollNext);
	} 
	oThis._nextElementID = args[0];
	if(oThis._nextElementID == null) {
	oThis._carouselNext = CCAN.util.Dom.getElementsByClassName(carouselNextClass, 
	"div", oThis.carouselElem);
	} else {
	oThis._carouselNext = CCAN.util.Dom.get(oThis._nextElementID);
	}
	if(oThis._carouselNext) {
	CCAN.util.Event.addListener(oThis._carouselNext, "click", oThis._scrollNext, oThis);
	} 
	}
	});
	

	this.cfg.addProperty("nextElement", { 
	value:null,
	handler: function(type, args, carouselElem) {
	if(oThis._carouselNext) {
	CCAN.util.Event.removeListener(oThis._carouselNext, "click", oThis._scrollNext);
	} 
	oThis._nextElementID = args[0];
	if(oThis._nextElementID == null) {
	oThis._carouselNext = CCAN.util.Dom.getElementsByClassName(carouselNextClass, 
	"div", oThis.carouselElem);
	} else {
	oThis._carouselNext = CCAN.util.Dom.get(oThis._nextElementID);
	}
	if(oThis._carouselNext) {
	CCAN.util.Event.addListener(oThis._carouselNext, "click", oThis._scrollNext, oThis);
	} 
	}
	} );
	

	this.cfg.addProperty("loadInitHandler", { 
	value:null,
	handler: function(type, args, carouselElem) {
	if(oThis._loadInitHandlerEvt) {
	oThis._loadInitHandlerEvt.unsubscribe(oThis._currLoadInitHandler, oThis);
	}
	oThis._currLoadInitHandler = args[0];
	if(oThis._currLoadInitHandler) {
	if(!oThis._loadInitHandlerEvt) {
	oThis._loadInitHandlerEvt = new CCAN.util.CustomEvent("onLoadInit", oThis);
	}
	oThis._loadInitHandlerEvt.subscribe(oThis._currLoadInitHandler, oThis);
	}
	}
	} );
	

	this.cfg.addProperty("loadNextHandler", { 
	value:null,
	handler: function(type, args, carouselElem) {
	if(oThis._loadNextHandlerEvt) {
	oThis._loadNextHandlerEvt.unsubscribe(oThis._currLoadNextHandler, oThis);
	}
	oThis._currLoadNextHandler = args[0];
	if(oThis._currLoadNextHandler) {
	if(!oThis._loadNextHandlerEvt) {
	oThis._loadNextHandlerEvt = new CCAN.util.CustomEvent("onLoadNext", oThis);
	}
	oThis._loadNextHandlerEvt.subscribe(oThis._currLoadNextHandler, oThis);
	}
	}
	} );
	

	this.cfg.addProperty("loadPrevHandler", { 
	value:null,
	handler: function(type, args, carouselElem) {
	if(oThis._loadPrevHandlerEvt) {
	oThis._loadPrevHandlerEvt.unsubscribe(oThis._currLoadPrevHandler, oThis);
	}
	oThis._currLoadPrevHandler = args[0];
	if(oThis._currLoadPrevHandler) {
	if(!oThis._loadPrevHandlerEvt) {
	oThis._loadPrevHandlerEvt = new CCAN.util.CustomEvent("onLoadPrev", oThis);
	}
	oThis._loadPrevHandlerEvt.subscribe(oThis._currLoadPrevHandler, oThis);
	}
	}
	} );
	

	this.cfg.addProperty("prevButtonStateHandler", { 
	value:null,
	handler: function(type, args, carouselElem) {
	if(oThis._currPrevButtonStateHandler) {
	oThis._prevButtonStateHandlerEvt.unsubscribe(oThis._currPrevButtonStateHandler, oThis);
	}

	oThis._currPrevButtonStateHandler = args[0];
	
	if(oThis._currPrevButtonStateHandler) {
	if(!oThis._prevButtonStateHandlerEvt) {
	oThis._prevButtonStateHandlerEvt = new CCAN.util.CustomEvent("onPrevButtonStateChange", oThis);
	}
	oThis._prevButtonStateHandlerEvt.subscribe(oThis._currPrevButtonStateHandler, oThis);
	}
	}
	} );
	

	this.cfg.addProperty("nextButtonStateHandler", { 
	value:null,
	handler: function(type, args, carouselElem) {
	if(oThis._currNextButtonStateHandler) {
	oThis._nextButtonStateHandlerEvt.unsubscribe(oThis._currNextButtonStateHandler, oThis);
	}
	oThis._currNextButtonStateHandler = args[0];
	
	if(oThis._currNextButtonStateHandler) {
	if(!oThis._nextButtonStateHandlerEvt) {
	oThis._nextButtonStateHandlerEvt = new CCAN.util.CustomEvent("onNextButtonStateChange", oThis);
	}
	oThis._nextButtonStateHandlerEvt.subscribe(oThis._currNextButtonStateHandler, oThis);
	}
	}
	} );
	
	
 	if(carouselCfg) {
 	this.cfg.applyConfig(carouselCfg);
 	}
 	
	this._origFirstVisible = this.cfg.getProperty("firstVisible");
	
	// keep a copy of curr handler so it can be removed when a new handler is set
	this._currLoadInitHandler = this.cfg.getProperty("loadInitHandler");
	this._currLoadNextHandler = this.cfg.getProperty("loadNextHandler");
	this._currLoadPrevHandler = this.cfg.getProperty("loadPrevHandler");
	this._currPrevButtonStateHandler = this.cfg.getProperty("prevButtonStateHandler");
	this._currNextButtonStateHandler = this.cfg.getProperty("nextButtonStateHandler");
	this._currAnimationCompleteHandler = this.cfg.getProperty("animationCompleteHandler");
	
	this._nextElementID = this.cfg.getProperty("nextElementID");
	if(!this._nextElementID) 
	this._nextElementID = this.cfg.getProperty("nextElement");
	
	this._prevElementID = this.cfg.getProperty("prevElementID");
	if(!this._prevElementID) 
	this._prevElementID = this.cfg.getProperty("prevElement");

	this._autoPlayTimer = null;
	this._priorLastVisible = this._priorFirstVisible = this.cfg.getProperty("firstVisible");
	this._lastPrebuiltIdx = 0;
// this._currSize = 0;
	 	
 	// prefetch elements
 	this.carouselList = CCAN.util.Dom.getElementsByClassName(carouselListClass, 
	"ul", this.carouselElem)[0];
	
	if(this._nextElementID == null) {
	this._carouselNext = CCAN.util.Dom.getElementsByClassName(carouselNextClass, 
	"div", this.carouselElem)[0];
	} else {
	this._carouselNext = CCAN.util.Dom.get(this._nextElementID);
	}

	if(this._prevElementID == null) {
 	this._carouselPrev = CCAN.util.Dom.getElementsByClassName(carouselPrevClass, 
	"div", this.carouselElem)[0];
	} else {
	this._carouselPrev = CCAN.util.Dom.get(this._prevElementID);
	}
	
	this._clipReg = CCAN.util.Dom.getElementsByClassName(carouselClipRegionClass, 
	"div", this.carouselElem)[0];
	
	// add a style class dynamically so that the correct styles get applied for a vertical carousel
	if(this.isVertical()) {
	CCAN.util.Dom.addClass(this.carouselList, "carousel-vertical");
	}
	
	// initialize the animation objects for next/previous
 	this._scrollNextAnim = new CCAN.util.Motion(this.carouselList, this.scrollNextParams, 
   	this.cfg.getProperty("animationSpeed"), this.cfg.getProperty("animationMethod"));
 	this._scrollPrevAnim = new CCAN.util.Motion(this.carouselList, this.scrollPrevParams, 
   	this.cfg.getProperty("animationSpeed"), this.cfg.getProperty("animationMethod"));
	
	// If they supplied a nextElementID then wire an event listener for the click
	if(this._carouselNext) {
	CCAN.util.Event.addListener(this._carouselNext, "click", this._scrollNext, this);
	} 
	
	// If they supplied a prevElementID then wire an event listener for the click
	if(this._carouselPrev) {
	CCAN.util.Event.addListener(this._carouselPrev, "click", this._scrollPrev, this);
	}
	
	// Wire up the various event handlers that they might have supplied
	var loadInitHandler = this.cfg.getProperty("loadInitHandler");
	if(loadInitHandler) {
	this._loadInitHandlerEvt = new CCAN.util.CustomEvent("onLoadInit", this);
	this._loadInitHandlerEvt.subscribe(loadInitHandler, this);
	}
	var loadNextHandler = this.cfg.getProperty("loadNextHandler");
	if(loadNextHandler) {
	this._loadNextHandlerEvt = new CCAN.util.CustomEvent("onLoadNext", this);
	this._loadNextHandlerEvt.subscribe(loadNextHandler, this);
	}
	var loadPrevHandler = this.cfg.getProperty("loadPrevHandler");
	if(loadPrevHandler) {
	this._loadPrevHandlerEvt = new CCAN.util.CustomEvent("onLoadPrev", this);
	this._loadPrevHandlerEvt.subscribe(loadPrevHandler, this);
	}
	var animationCompleteHandler = this.cfg.getProperty("animationCompleteHandler");
	if(animationCompleteHandler) {
	this._animationCompleteEvt = new CCAN.util.CustomEvent("onAnimationComplete", this);
	this._animationCompleteEvt.subscribe(animationCompleteHandler, this);
	}
	var prevButtonStateHandler = this.cfg.getProperty("prevButtonStateHandler");
	if(prevButtonStateHandler) {
	this._prevButtonStateHandlerEvt = new CCAN.util.CustomEvent("onPrevButtonStateChange", 
	this);
	this._prevButtonStateHandlerEvt.subscribe(prevButtonStateHandler, this);
	}
	var nextButtonStateHandler = this.cfg.getProperty("nextButtonStateHandler");
	if(nextButtonStateHandler) {
	this._nextButtonStateHandlerEvt = new CCAN.util.CustomEvent("onNextButtonStateChange", this);
	this._nextButtonStateHandlerEvt.subscribe(nextButtonStateHandler, this);
	}
	
	// Since loading may take some time, wire up a listener to fire when at least the first
	// element actually gets loaded
	var visibleExtent = this._calculateVisibleExtent();
  	CCAN.util.Event.onAvailable(this._carouselElemID + "-item-"+
	visibleExtent.start,  this._calculateSize, this);
  	
  	// Call the initial loading sequence
	if(this.cfg.getProperty("loadOnStart"))
	this._loadInitial();	

	},
	

	clear: function() {
	// remove all items from the carousel for dynamic content
	var loadInitHandler = this.cfg.getProperty("loadInitHandler");
	if(loadInitHandler) {
	this._removeChildrenFromNode(this.carouselList);
	this._lastPrebuiltIdx = 0;
	}
	// turn off autoplay
	this.stopAutoPlay(); // should we only turn this off for dynamic during reload?
	
	this._priorLastVisible = this._priorFirstVisible = this._origFirstVisible;
	
	// is this redundant since moveTo will set this?	
	this.cfg.setProperty("firstVisible", this._origFirstVisible, true);	
	this.moveTo(this._origFirstVisible);
	},
	
	/**
	 * Clears all items from the list and calls the loadInitHandler to load new items into the list. 
	 * The carousel size is reset to the original size set during creation.
	 * @param {number}	numVisible	Optional parameter: numVisible. 
	 * If set, the carousel will resize on the reload to show numVisible items.
	 */
	reload: function(numVisible) {
	// this should be deprecated, not needed since can be set via property change
	    if(this._isValidObj(numVisible)) {
	this.cfg.setProperty("numVisible", numVisible);
	    }
	this.clear();
	
	// clear resets back to start
	var visibleExtent = this._calculateVisibleExtent();
	CCAN.util.Event.onAvailable(this._carouselElemID+"-item-"+visibleExtent.start,
	 	this._calculateSize, this);  	
	this._loadInitial();
	
	},

	load: function() {
	var visibleExtent = this._calculateVisibleExtent();
	
	CCAN.util.Event.onAvailable(this._carouselElemID + "-item-"+visibleExtent.start, 
	this._calculateSize, this);  	
	this._loadInitial();
	},
	
	addItem: function(idx, innerHTMLOrElem, itemClass) {
	
	if(idx > this.cfg.getProperty("size")) {
	return null;
	}
	
        var liElem = this.getItem(idx);

	// Need to create the li
	if(!this._isValidObj(liElem)) {
	liElem = this._createItem(idx, innerHTMLOrElem);
	this.carouselList.appendChild(liElem);
	
	} else if(this._isValidObj(liElem.placeholder)) {	
	    	var newLiElem = this._createItem(idx, innerHTMLOrElem);
	this.carouselList.replaceChild(newLiElem, liElem);
	liElem = newLiElem;
	}
	
	// if they supplied an item class add it to the element
	if(this._isValidObj(itemClass)){
	CCAN.util.Dom.addClass(liElem, itemClass);
	}
	

	if(this.isVertical())
	setTimeout( function() { liElem.style.display="block"; }, 1 );	
	return liElem;
	},


	insertBefore: function(refIdx, innerHTML) {
	// don't allow insertion beyond the size
	if(refIdx >= this.cfg.getProperty("size")) {
	return null;
	}
	
	if(refIdx < 1) {
	refIdx = 1;
	}
	
	var insertionIdx = refIdx - 1;
	
	if(insertionIdx > this._lastPrebuiltIdx) {
	this._prebuildItems(this._lastPrebuiltIdx, refIdx); // is this right?
	}
	
	var liElem = this._insertBeforeItem(refIdx, innerHTML);
	
	this._enableDisableControls();
	
	return liElem;
	},
	
	insertAfter: function(refIdx, innerHTML) {
	
	if(refIdx > this.cfg.getProperty("size")) {
	refIdx = this.cfg.getProperty("size");
	}
	
	var insertionIdx = refIdx + 1;	
	
	// if we are inserting this item past where we have prebuilt items, then
	// prebuild up to this point.
	if(insertionIdx > this._lastPrebuiltIdx) {
	this._prebuildItems(this._lastPrebuiltIdx, insertionIdx+1);
	}

	var liElem = this._insertAfterItem(refIdx, innerHTML);	

	if(insertionIdx > this.cfg.getProperty("size")) {
	this.cfg.setProperty("size", insertionIdx, true);
	}
	
	this._enableDisableControls();

	return liElem;
	},	

	scrollNext: function() {
	this._scrollNext(null, this);
	
	// we know the timer has expired.
	//if(this._autoPlayTimer) clearTimeout(this._autoPlayTimer);
	this._autoPlayTimer = null;
	if(this.cfg.getProperty("autoPlay") !== 0) {
	this._autoPlayTimer = this.startAutoPlay();
	}
	},
	

	scrollPrev: function() {
	this._scrollPrev(null, this);
	},
	
	scrollTo: function(newStart) {
	this._position(newStart, true);
	},

	moveTo: function(newStart) {
	this._position(newStart, false);
	},

	startAutoPlay: function(interval) {
	// if interval is passed as arg, then set autoPlay to this interval.
	if(this._isValidObj(interval)) {
	this.cfg.setProperty("autoPlay", interval, true);
	}
	
	// if we already are playing, then do nothing.
	if(this._autoPlayTimer !== null) {
	return this._autoPlayTimer;
	}
	
	var oThis = this;  
	var autoScroll = function() { oThis.scrollNext(); };
	this._autoPlayTimer = setTimeout( autoScroll, this.cfg.getProperty("autoPlay") );
	
	return this._autoPlayTimer;
	},


	stopAutoPlay: function() {
	if (this._autoPlayTimer !== null) {
	clearTimeout(this._autoPlayTimer);
	this._autoPlayTimer = null;
	}
	},

	isVertical: function() {
	return (this.cfg.getProperty("orientation") != "horizontal");
	},
	
	

	isItemLoaded: function(idx) {
	var liElem = this.getItem(idx);
	
	if(this._isValidObj(liElem) && !this._isValidObj(liElem.placeholder)) {
	return true;
	}
	
	return false;
	},
	

	getItem: function(idx) {
	var elemName = this._carouselElemID + "-item-" + idx;
 	var liElem = CCAN.util.Dom.get(elemName);
	return liElem;	
	},
	
	show: function() {
	CCAN.util.Dom.setStyle(this.carouselElem, "display", "block");
	this.calculateSize();
	},
	
	hide: function() {
	CCAN.util.Dom.setStyle(this.carouselElem, "display", "none");
	},

	calculateSize: function() {
 	var ulKids = this.carouselList.childNodes;
 	var li = null;
	for(var i=0; i<ulKids.length; i++) {
	
	li = ulKids[i];
	if(li.tagName == "LI" || li.tagName == "li") {
	break;
	}
	}

	var navMargin = this.cfg.getProperty("navMargin");
	var numVisible = this.cfg.getProperty("numVisible");
	var firstVisible = this.cfg.getProperty("firstVisible");
	var pl = this._getStyleVal(li, "paddingLeft");
	var pr = this._getStyleVal(li, "paddingRight");
	var ml = this._getStyleVal(li, "marginLeft");
	var mr = this._getStyleVal(li, "marginRight");
	var pt = this._getStyleVal(li, "paddingTop");
	var pb = this._getStyleVal(li, "paddingBottom");
	var mt = this._getStyleVal(li, "marginTop");
	var mb = this._getStyleVal(li, "marginBottom");

	CCAN.util.Dom.removeClass(this.carouselList, "carousel-vertical");
	CCAN.util.Dom.removeClass(this.carouselList, "carousel-horizontal");
	if(this.isVertical()) {
	var liPaddingMarginWidth = pl + pr + ml + mr;
	CCAN.util.Dom.addClass(this.carouselList, "carousel-vertical");
	var liPaddingMarginHeight = pt + pb + mt + mb;
	
	var upt = this._getStyleVal(this.carouselList, "paddingTop");
	var upb = this._getStyleVal(this.carouselList, "paddingBottom");
	var umt = this._getStyleVal(this.carouselList, "marginTop")
	var umb = this._getStyleVal(this.carouselList, "marginBottom")
	var ulPaddingHeight = upt + upb + umt + umb;

	var revealAmt = (this._isExtraRevealed()) ?
	 	(this.cfg.getProperty("revealAmount")+(liPaddingMarginHeight)/2) : 0;

	var liHeight = this._getStyleVal(li, "height", true);
	this.scrollAmountPerInc = (liHeight + liPaddingMarginHeight);
	
	var liWidth = this._getStyleVal(li, "width");
	this.carouselElem.style.width = (liWidth + liPaddingMarginWidth) + "px";	
	this._clipReg.style.height = 
	(this.scrollAmountPerInc * numVisible + revealAmt*2 + 
	ulPaddingHeight) + "px";
	this.carouselElem.style.height = 
	(this.scrollAmountPerInc * numVisible + revealAmt*2 + navMargin*2 +
	ulPaddingHeight) + "px";

	// possible that the umt+upt is needed... need to test this.
	var revealTop = (this._isExtraRevealed()) ? 
	(revealAmt - (Math.abs(mt-mb)+Math.abs(pt-pb))/2
	) : 
	0;
	CCAN.util.Dom.setStyle(this.carouselList, "position", "relative");
	CCAN.util.Dom.setStyle(this.carouselList, "top", "" + revealTop + "px");

	var currY = CCAN.util.Dom.getY(this.carouselList);	
	CCAN.util.Dom.setY(this.carouselList, currY - this.scrollAmountPerInc*(firstVisible-1));

	} else {
	CCAN.util.Dom.addClass(this.carouselList, "carousel-horizontal");

	var upl = this._getStyleVal(this.carouselList, "paddingLeft");
	var upr = this._getStyleVal(this.carouselList, "paddingRight");
	var uml = this._getStyleVal(this.carouselList, "marginLeft")
	var umr = this._getStyleVal(this.carouselList, "marginRight")
	var ulPaddingWidth = upl + upr + uml + umr;

	var liMarginWidth = ml + mr;
	var liPaddingMarginWidth = liMarginWidth + pr + pl;
	
	var revealAmt = (this._isExtraRevealed()) ?
	 	(this.cfg.getProperty("revealAmount")+(liPaddingMarginWidth)/2) : 0;
	
	var liWidth = li.offsetWidth; 
	this.scrollAmountPerInc = liWidth + liMarginWidth;
	
	this._clipReg.style.width = 
	(this.scrollAmountPerInc*numVisible + revealAmt*2) + "px";
	this.carouselElem.style.width =
	 	(this.scrollAmountPerInc*numVisible + navMargin*2 + revealAmt*2 + 
	ulPaddingWidth) + "px";
	
	var revealLeft = (this._isExtraRevealed()) ? 
	(revealAmt - (Math.abs(mr-ml)+Math.abs(pr-pl))/2 - (uml+upl)
	) : 
	0;
	CCAN.util.Dom.setStyle(this.carouselList, "position", "relative");
	CCAN.util.Dom.setStyle(this.carouselList, "left", "" + revealLeft + "px");

	var currX = CCAN.util.Dom.getX(this.carouselList);
	CCAN.util.Dom.setX(this.carouselList, currX - this.scrollAmountPerInc*(firstVisible-1));
	}
	},
	
	setProperty: function(property, value, silent) {
	this.cfg.setProperty(property, value, silent);
	},
	
	getProperty: function(property) {
	return this.cfg.getProperty(property);
	},
	
	getFirstItemRevealed: function() {
	return this._firstItemRevealed;
	},
	getLastItemRevealed: function() {
	return this._lastItemRevealed;
	},
	
	getFirstVisible: function() {
	return this.cfg.getProperty("firstVisible");
	},
	
	getLastVisible: function() {
	var firstVisible = this.cfg.getProperty("firstVisible");
	var numVisible = this.cfg.getProperty("numVisible");
	
	return firstVisible + numVisible - 1;
	},
	
	_getStyleVal : function(li, style, returnFloat) {
	var styleValStr = CCAN.util.Dom.getStyle(li, style);
	
	var styleVal = returnFloat ? parseFloat(styleValStr) : parseInt(styleValStr, 10);
	if(style=="height" && isNaN(styleVal)) {
	styleVal = li.offsetHeight;
	} else if(isNaN(styleVal)) {
	styleVal = 0;
	}
	return styleVal;
	},
	
	_calculateSize: function(me) {
	me.calculateSize();
	me.show();
	//CCAN.util.Dom.setStyle(me.carouselElem, "visibility", "visible");
	},

	// From Mike Chambers: http://weblogs.macromedia.com/mesh/archives/2006/01/removing_html_e.html
	_removeChildrenFromNode: function(node)
	{
	if(!this._isValidObj(node))
	{
      	return;
	}
   
	var len = node.childNodes.length;
   
	while (node.hasChildNodes())
	{
	node.removeChild(node.firstChild);
	}
	},
	
	_prebuildLiElem: function(idx) {
	if(idx < 1) return;
	
	
	var liElem = document.createElement("li");
	liElem.id = this._carouselElemID + "-item-" + idx;

	liElem.placeholder = true;   
	this.carouselList.appendChild(liElem);
	
	this._lastPrebuiltIdx = (idx > this._lastPrebuiltIdx) ? idx : this._lastPrebuiltIdx;
	},
	
	_createItem: function(idx, innerHTMLOrElem) {
	if(idx < 1) return;
	
	
	var liElem = document.createElement("li");
	liElem.id = this._carouselElemID + "-item-" + idx;

	if(typeof(innerHTMLOrElem) === "string") {
	liElem.innerHTML = innerHTMLOrElem;
	} else {
	liElem.appendChild(innerHTMLOrElem);
	}
	
	return liElem;
	},
	
	_insertAfterItem: function(refIdx, innerHTMLOrElem) {
	return this._insertBeforeItem(refIdx+1, innerHTMLOrElem);
	},
	
	
	_insertBeforeItem: function(refIdx, innerHTMLOrElem) {

	var refItem = this.getItem(refIdx);
	var size = this.cfg.getProperty("size");
	if(size != this.UNBOUNDED_SIZE) {
	this.cfg.setProperty("size", size + 1, true);
	}
	
	for(var i=this._lastPrebuiltIdx; i>=refIdx; i--) {
	var anItem = this.getItem(i);
	if(this._isValidObj(anItem)) {
	anItem.id = this._carouselElemID + "-item-" + (i+1);
	}
	}

	var liElem = this._createItem(refIdx, innerHTMLOrElem);
	
	var insertedItem = this.carouselList.insertBefore(liElem, refItem);
	this._lastPrebuiltIdx += 1;
	
	return liElem;
	},
	
	insertAfterEnd: function(innerHTMLOrElem) {
	return this.insertAfter(this.cfg.getProperty("size"), innerHTMLOrElem);
	},
	
	_position: function(newStart, showAnimation) {
	// do we bypass the isAnimated check?
	var currStart = this._priorFirstVisible;
	if(newStart > currStart) {
	var inc = newStart - currStart;
	this._scrollNextInc(inc, showAnimation);
	} else {
	var dec = currStart - newStart;
	this._scrollPrevInc(dec, showAnimation);
	}
	},

	_scrollPrev: function(e, carousel) {
	if(e !== null) { 
	carousel.stopAutoPlay();
	}
	if(carousel._scrollPrevAnim.isAnimated()) {
	return false;
	}
	carousel._scrollPrevInc(carousel.cfg.getProperty("scrollInc"), 
	(carousel.cfg.getProperty("animationSpeed") !== 0));
	},
	
	_scrollNext: function(e, carousel) {	
	if(e !== null) { 
	carousel.stopAutoPlay();
	}
	if(carousel._scrollNextAnim.isAnimated()) {
	return false; 
	// then just do this function. that will allow faster scroll responses.
	}

	carousel._scrollNextInc(carousel.cfg.getProperty("scrollInc"), 
	(carousel.cfg.getProperty("animationSpeed") !== 0));
	},
	
	
	_handleAnimationComplete: function(type, args, argList) {
	var carousel = argList[0];
	var direction = argList[1];
	
	carousel._animationCompleteEvt.fire(direction);

	
	},
	
	_areAllItemsLoaded: function(first, last) {
	var itemsLoaded = true;
	for(var i=first; i<=last; i++) {
	var liElem = this.getItem(i);
	
	if(!this._isValidObj(liElem)) {
	this._prebuildLiElem(i);
	itemsLoaded = false;
	} else if(this._isValidObj(liElem.placeholder)) {
	itemsLoaded = false;
	}
	}
	return itemsLoaded;
	}, 
	
	_prebuildItems: function(first, last) {
	for(var i=first; i<=last; i++) {
	var liElem = this.getItem(i);
	
	if(!this._isValidObj(liElem)) {
	this._prebuildLiElem(i);
	}
	}
	}, 
	
	_isExtraRevealed: function() {
	return (this.cfg.getProperty("revealAmount") > 0);
	},

	_scrollNextInc: function(inc, showAnimation) {
	var numVisible = this.cfg.getProperty("numVisible");
	var currStart = this._priorFirstVisible;
	var currEnd = this._priorLastVisible;
	var size = this.cfg.getProperty("size");

	var scrollExtent = this._calculateAllowableScrollExtent();
	
	if(this.cfg.getProperty("wrap") && currEnd == scrollExtent.end) {
	this.scrollTo(scrollExtent.start); // might need to check animation is on or not
	return;
	}

	// increment start by inc
	var newStart = currStart + inc;	
	var newEnd = newStart + numVisible - 1;

	// If we are past the end, adjust or wrap
	if(newEnd > scrollExtent.end) {
	newEnd = scrollExtent.end;
	newStart = newEnd - numVisible + 1;
	}

	inc = newStart - currStart;

	this.cfg.setProperty("firstVisible", newStart, true);

	if(inc > 0) {
	if(this._isValidObj(this.cfg.getProperty("loadNextHandler"))) {
	var visibleExtent = this._calculateVisibleExtent(newStart, newEnd);
	var cacheStart = (currEnd+1) < visibleExtent.start ? (currEnd+1) : visibleExtent.start;	
	var alreadyCached = this._areAllItemsLoaded(cacheStart, visibleExtent.end);
	this._loadNextHandlerEvt.fire(visibleExtent.start, visibleExtent.end, alreadyCached);
	}

	if(showAnimation) {
	 	var nextParams = { points: { by: [-this.scrollAmountPerInc*inc, 0] } };
	 	if(this.isVertical()) {
	 	nextParams = { points: { by: [0, -this.scrollAmountPerInc*inc] } };
	 	}

	 	this._scrollNextAnim = new CCAN.util.Motion(this.carouselList, 
	 	nextParams, 
   	this.cfg.getProperty("animationSpeed"), 
	this.cfg.getProperty("animationMethod"));

	if(this.cfg.getProperty("animationCompleteHandler")) {
	this._scrollNextAnim.onComplete.subscribe(this._handleAnimationComplete, [this, "next"]);
	}
	this._scrollNextAnim.animate();
	} else {
	if(this.isVertical()) {
	var currY = CCAN.util.Dom.getY(this.carouselList);

	CCAN.util.Dom.setY(this.carouselList, 
	currY - this.scrollAmountPerInc*inc);
	} else {
	var currX = CCAN.util.Dom.getX(this.carouselList);
	CCAN.util.Dom.setX(this.carouselList, 
	currX - this.scrollAmountPerInc*inc);
	}
	}

	}
	this._priorFirstVisible = newStart;
	this._priorLastVisible = newEnd;	

	this._enableDisableControls();
	return false;
	},

	_scrollPrevInc: function(dec, showAnimation) {
	var numVisible = this.cfg.getProperty("numVisible");
	var currStart = this._priorFirstVisible;
	var currEnd = this._priorLastVisible;
	var size = this.cfg.getProperty("size");

	var newStart = currStart - dec;	

	var scrollExtent = this._calculateAllowableScrollExtent();
	
	newStart = (newStart < scrollExtent.start) ? scrollExtent.start : newStart;
	
	// if we are going to extend past the end, then we need to correct the start
	var newEnd = newStart + numVisible - 1;
	if(newEnd > scrollExtent.end) {
	newEnd = scrollExtent.end;
	newStart = newEnd - numVisible + 1;
	}
	
	dec = currStart - newStart;

	this.cfg.setProperty("firstVisible", newStart, true);
	
	if(dec > 0) {	
	if(this._isValidObj(this.cfg.getProperty("loadPrevHandler"))) {	
	var visibleExtent = this._calculateVisibleExtent(newStart, newEnd);
	var cacheEnd = (currStart-1) > visibleExtent.end ? (currStart-1) : visibleExtent.end;	
	var alreadyCached = this._areAllItemsLoaded(visibleExtent.start, cacheEnd);
	
	this._loadPrevHandlerEvt.fire(visibleExtent.start, visibleExtent.end, alreadyCached);
	}

	if(showAnimation) {
	 	var prevParams = { points: { by: [this.scrollAmountPerInc*dec, 0] } };
	 	if(this.isVertical()) {
	 	prevParams = { points: { by: [0, this.scrollAmountPerInc*dec] } };
	 	}
 	
	 	this._scrollPrevAnim = new CCAN.util.Motion(this.carouselList,
	 	prevParams, 
   	this.cfg.getProperty("animationSpeed"), this.cfg.getProperty("animationMethod"));
	if(this.cfg.getProperty("animationCompleteHandler")) {
	this._scrollPrevAnim.onComplete.subscribe(this._handleAnimationComplete, [this, "prev"]);
	}
	this._scrollPrevAnim.animate();
	} else {
	if(this.isVertical()) {
	var currY = CCAN.util.Dom.getY(this.carouselList);
	CCAN.util.Dom.setY(this.carouselList, currY + 
	this.scrollAmountPerInc*dec);	
	} else {
	var currX = CCAN.util.Dom.getX(this.carouselList);
	CCAN.util.Dom.setX(this.carouselList, currX + 
	this.scrollAmountPerInc*dec);
	}
	}
	}
	this._priorFirstVisible = newStart;
	this._priorLastVisible = newEnd;	
	
	this._enableDisableControls();

	return false;
	},
	
	_enableDisableControls: function() {
	
	var firstVisible = this.cfg.getProperty("firstVisible");
	var lastVisible = this.getLastVisible();
	var scrollExtent = this._calculateAllowableScrollExtent();
	
	if(this._prevEnabled) {
	if(firstVisible === scrollExtent.start) {
	this._disablePrev();
	}
	}

	if(this._prevEnabled === false) {
	if(firstVisible > scrollExtent.start) {
	this._enablePrev();
	}
	}
	
	if(this._nextEnabled) {
	if(lastVisible === scrollExtent.end) {
	this._disableNext();
	}
	}

	if(this._nextEnabled === false) {
	if(lastVisible < scrollExtent.end) {
	this._enableNext();
	}
	}	
	},
	
	_loadInitial: function() {
	var firstVisible = this.cfg.getProperty("firstVisible");
	this._priorLastVisible = this.getLastVisible();

	if(this._loadInitHandlerEvt) {
	var visibleExtent = this._calculateVisibleExtent(firstVisible, this._priorLastVisible);
	var alreadyCached = this._areAllItemsLoaded(1, visibleExtent.end);
	
	this._loadInitHandlerEvt.fire(visibleExtent.start, visibleExtent.end, alreadyCached); 
	}
	
	if(this.cfg.getProperty("autoPlay") !== 0) {
	this._autoPlayTimer = this.startAutoPlay();
	}	
	
	this._enableDisableControls();	
    },
	
	_calculateAllowableScrollExtent: function() {
	var scrollBeforeAmount = this.cfg.getProperty("scrollBeforeAmount");
	var scrollAfterAmount = this.cfg.getProperty("scrollAfterAmount");
	var size = this.cfg.getProperty("size");
	
	var extent = {start: 1-scrollBeforeAmount, end: size+scrollAfterAmount};
	return extent;
	
	},
	
	_calculateVisibleExtent: function(start, end) {
	if(!start) {
	start = this.cfg.getProperty("firstVisible");
	end = this.getLastVisible();
	}
	
	var size = this.cfg.getProperty("size");
	
	start = start<1?1:start;
	end = end>size?size:end;
	
	var extent = {start: start, end: end};
	
	this._firstItemRevealed = -1;
	this._lastItemRevealed = -1;
	if(this._isExtraRevealed()) {
	if(start > 1) {
	this._firstItemRevealed = start - 1;
	extent.start = this._firstItemRevealed;
	}
	if(end < size) {
	this._lastItemRevealed = end + 1;
	extent.end = this._lastItemRevealed;
	}
	}

	return extent;
	},
	
	_disablePrev: function() {
	this._prevEnabled = false;
	if(this._prevButtonStateHandlerEvt) {
	this._prevButtonStateHandlerEvt.fire(false, this._carouselPrev);
	}
	if(this._isValidObj(this._carouselPrev)) {
	CCAN.util.Event.removeListener(this._carouselPrev, "click", this._scrollPrev);
	}
	},
	
	_enablePrev: function() {
	this._prevEnabled = true;
	if(this._prevButtonStateHandlerEvt) {
	this._prevButtonStateHandlerEvt.fire(true, this._carouselPrev);
	}
	if(this._isValidObj(this._carouselPrev)) {
	CCAN.util.Event.addListener(this._carouselPrev, "click", this._scrollPrev, this);
	}
	},
	
	_disableNext: function() {
	if(this.cfg.getProperty("wrap")) {
	return;
	}
	this._nextEnabled = false;
	if(this._isValidObj(this._nextButtonStateHandlerEvt)) {
	this._nextButtonStateHandlerEvt.fire(false, this._carouselNext);
	}
	if(this._isValidObj(this._carouselNext)) {
	CCAN.util.Event.removeListener(this._carouselNext, "click", this._scrollNext);
	}
	},
	
	_enableNext: function() {
	this._nextEnabled = true;
	if(this._isValidObj(this._nextButtonStateHandlerEvt)) {
	this._nextButtonStateHandlerEvt.fire(true, this._carouselNext);
	}
	if(this._isValidObj(this._carouselNext)) {
	CCAN.util.Event.addListener(this._carouselNext, "click", this._scrollNext, this);
	}
	},
	
	_isValidObj: function(obj) {

	if (null == obj) {
	return false;
	}
	if ("undefined" == typeof(obj) ) {
	return false;
	}
	return true;
	}
};

