YAHOO.namespace("extension");

YAHOO.extension.Carousel = function(carouselElementID, carouselCfg) {
    this.init(carouselElementID, carouselCfg);
};

YAHOO.extension.Carousel.prototype = {
    UNBOUNDED_SIZE: 1e6,
    init: function(carouselElementID, carouselCfg) {
        var oThis = this;
        this.getCarouselItem = this.getItem;
        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;
        this.cfg = new YAHOO.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) {},
            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: .25,
            handler: function(type, args, carouselElem) {
                oThis.animationSpeed = args[0];
            },
            validator: oThis.cfg.checkNumber
        });
        this.cfg.addProperty("animationMethod", {
            value: YAHOO.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 YAHOO.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
        });
        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);
            }
        });
        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);
            }
        });
        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);
                }
            }
        });
        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);
                }
            }
        });
        this.cfg.addProperty("disableSelection", {
            value: true,
            handler: function(type, args, carouselElem) {},
            validator: oThis.cfg.checkBoolean
        });
        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 YAHOO.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 YAHOO.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 YAHOO.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 YAHOO.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 YAHOO.util.CustomEvent("onNextButtonStateChange", oThis);
                    }
                    oThis._nextButtonStateHandlerEvt.subscribe(oThis._currNextButtonStateHandler, oThis);
                }
            }
        });
        if (carouselCfg) {
            this.cfg.applyConfig(carouselCfg);
        }
        YAHOO.util.Event.addListener(this.carouselElem, "mousedown", this._handleMouseDownForSelection, this, true);
        this._origFirstVisible = this.cfg.getProperty("firstVisible");
        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.carouselList = YAHOO.util.Dom.getElementsByClassName(carouselListClass, "ul", this.carouselElem)[0];
        if (this._nextElementID == null) {
            this._carouselNext = YAHOO.util.Dom.getElementsByClassName(carouselNextClass, "div", this.carouselElem)[0];
        } else {
            this._carouselNext = YAHOO.util.Dom.get(this._nextElementID);
        }
        if (this._prevElementID == null) {
            this._carouselPrev = YAHOO.util.Dom.getElementsByClassName(carouselPrevClass, "div", this.carouselElem)[0];
        } else {
            this._carouselPrev = YAHOO.util.Dom.get(this._prevElementID);
        }
        this._clipReg = YAHOO.util.Dom.getElementsByClassName(carouselClipRegionClass, "div", this.carouselElem)[0];
        if (this.isVertical()) {
            YAHOO.util.Dom.addClass(this.carouselList, "carousel-vertical");
        }
        this._scrollNextAnim = new YAHOO.util.Motion(this.carouselList, this.scrollNextParams, this.cfg.getProperty("animationSpeed"), this.cfg.getProperty("animationMethod"));
        this._scrollPrevAnim = new YAHOO.util.Motion(this.carouselList, this.scrollPrevParams, this.cfg.getProperty("animationSpeed"), this.cfg.getProperty("animationMethod"));
        if (this._carouselNext) {
            YAHOO.util.Event.addListener(this._carouselNext, "click", this._scrollNext, this);
        }
        if (this._carouselPrev) {
            YAHOO.util.Event.addListener(this._carouselPrev, "click", this._scrollPrev, this);
        }
        var loadInitHandler = this.cfg.getProperty("loadInitHandler");
        if (loadInitHandler) {
            this._loadInitHandlerEvt = new YAHOO.util.CustomEvent("onLoadInit", this);
            this._loadInitHandlerEvt.subscribe(loadInitHandler, this);
        }
        var loadNextHandler = this.cfg.getProperty("loadNextHandler");
        if (loadNextHandler) {
            this._loadNextHandlerEvt = new YAHOO.util.CustomEvent("onLoadNext", this);
            this._loadNextHandlerEvt.subscribe(loadNextHandler, this);
        }
        var loadPrevHandler = this.cfg.getProperty("loadPrevHandler");
        if (loadPrevHandler) {
            this._loadPrevHandlerEvt = new YAHOO.util.CustomEvent("onLoadPrev", this);
            this._loadPrevHandlerEvt.subscribe(loadPrevHandler, this);
        }
        var animationCompleteHandler = this.cfg.getProperty("animationCompleteHandler");
        if (animationCompleteHandler) {
            this._animationCompleteEvt = new YAHOO.util.CustomEvent("onAnimationComplete", this);
            this._animationCompleteEvt.subscribe(animationCompleteHandler, this);
        }
        var prevButtonStateHandler = this.cfg.getProperty("prevButtonStateHandler");
        if (prevButtonStateHandler) {
            this._prevButtonStateHandlerEvt = new YAHOO.util.CustomEvent("onPrevButtonStateChange", this);
            this._prevButtonStateHandlerEvt.subscribe(prevButtonStateHandler, this);
        }
        var nextButtonStateHandler = this.cfg.getProperty("nextButtonStateHandler");
        if (nextButtonStateHandler) {
            this._nextButtonStateHandlerEvt = new YAHOO.util.CustomEvent("onNextButtonStateChange", this);
            this._nextButtonStateHandlerEvt.subscribe(nextButtonStateHandler, this);
        }
        var visibleExtent = this._calculateVisibleExtent();
        YAHOO.util.Event.onAvailable(this._carouselElemID + "-item-" + visibleExtent.start, this._calculateSize, this);
        if (this.cfg.getProperty("loadOnStart")) this._loadInitial();
    },
    _handleMouseDownForSelection: function(e) {
        if (this.cfg.getProperty("disableSelection")) {
            YAHOO.util.Event.preventDefault(e);
            YAHOO.util.Event.stopPropagation(e);
        }
    },
    clear: function() {
        var loadInitHandler = this.cfg.getProperty("loadInitHandler");
        if (loadInitHandler) {
            this._removeChildrenFromNode(this.carouselList);
            this._lastPrebuiltIdx = 0;
        }
        this.stopAutoPlay();
        this._priorLastVisible = this._priorFirstVisible = this._origFirstVisible;
        this.cfg.setProperty("firstVisible", this._origFirstVisible, true);
        this.moveTo(this._origFirstVisible);
    },
    reload: function(numVisible) {
        if (this._isValidObj(numVisible)) {
            this.cfg.setProperty("numVisible", numVisible);
        }
        this.clear();
        var visibleExtent = this._calculateVisibleExtent();
        YAHOO.util.Event.onAvailable(this._carouselElemID + "-item-" + visibleExtent.start, this._calculateSize, this);
        this._loadInitial();
    },
    load: function() {
        var visibleExtent = this._calculateVisibleExtent();
        YAHOO.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);
        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 (this._isValidObj(itemClass)) {
            YAHOO.util.Dom.addClass(liElem, itemClass);
        }
        if (this.isVertical()) setTimeout(function() {
            liElem.style.display = "block";
        }, 1);
        return liElem;
    },
    insertBefore: function(refIdx, innerHTML) {
        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);
        }
        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 (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);
        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 (this._isValidObj(interval)) {
            this.cfg.setProperty("autoPlay", interval, true);
        }
        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 = YAHOO.util.Dom.get(elemName);
        return liElem;
    },
    show: function() {
        YAHOO.util.Dom.setStyle(this.carouselElem, "display", "block");
        this.calculateSize();
    },
    hide: function() {
        YAHOO.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");
        YAHOO.util.Dom.removeClass(this.carouselList, "carousel-vertical");
        YAHOO.util.Dom.removeClass(this.carouselList, "carousel-horizontal");
        if (this.isVertical()) {
            var liPaddingMarginWidth = pl + pr + ml + mr;
            YAHOO.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";
            var revealTop = this._isExtraRevealed() ? revealAmt - (Math.abs(mt - mb) + Math.abs(pt - pb)) / 2 : 0;
            YAHOO.util.Dom.setStyle(this.carouselList, "position", "relative");
            YAHOO.util.Dom.setStyle(this.carouselList, "top", "" + revealTop + "px");
            var currY = YAHOO.util.Dom.getY(this.carouselList);
            YAHOO.util.Dom.setY(this.carouselList, currY - this.scrollAmountPerInc * (firstVisible - 1));
        } else {
            YAHOO.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;
            YAHOO.util.Dom.setStyle(this.carouselList, "position", "relative");
            YAHOO.util.Dom.setStyle(this.carouselList, "left", "" + revealLeft + "px");
            var currX = YAHOO.util.Dom.getX(this.carouselList);
            YAHOO.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 = YAHOO.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();
    },
    _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) {
        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();
        }
        carousel._scrollPrevInc(carousel.cfg.getProperty("scrollInc"), carousel.cfg.getProperty("animationSpeed") !== 0);
    },
    _scrollNext: function(e, carousel) {
        if (e !== null) {
            carousel.stopAutoPlay();
        }
        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) {
        if (this._scrollNextAnim.isAnimated() || this._scrollPrevAnim.isAnimated()) {
            return false;
        }
        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);
            return;
        }
        var newStart = currStart + inc;
        var newEnd = newStart + numVisible - 1;
        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 YAHOO.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 = YAHOO.util.Dom.getY(this.carouselList);
                    YAHOO.util.Dom.setY(this.carouselList, currY - this.scrollAmountPerInc * inc);
                } else {
                    var currX = YAHOO.util.Dom.getX(this.carouselList);
                    YAHOO.util.Dom.setX(this.carouselList, currX - this.scrollAmountPerInc * inc);
                }
            }
        }
        this._priorFirstVisible = newStart;
        this._priorLastVisible = newEnd;
        this._enableDisableControls();
        return false;
    },
    _scrollPrevInc: function(dec, showAnimation) {
        if (this._scrollNextAnim.isAnimated() || this._scrollPrevAnim.isAnimated()) {
            return false;
        }
        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;
        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 YAHOO.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 = YAHOO.util.Dom.getY(this.carouselList);
                    YAHOO.util.Dom.setY(this.carouselList, currY + this.scrollAmountPerInc * dec);
                } else {
                    var currX = YAHOO.util.Dom.getX(this.carouselList);
                    YAHOO.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)) {
            YAHOO.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)) {
            YAHOO.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)) {
            YAHOO.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)) {
            YAHOO.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;
    }
};
