var o = Ext.Container.prototype.lookupComponent;
Ext.override(Ext.Container, {

    // Override of our container's method to allow raw Elements to be added.
    // This is called in the context of the container.
    lookupComponent: function(comp) {
        if (comp instanceof Ext.Element) {
            return comp;
        } else if (comp.nodeType && (comp.nodeType == 1)) {
            return Ext.get(comp);
        } else {
            return o.call(this, comp);
        }
    }
});

Ext.layout.CarouselLayout = Ext.extend(Ext.layout.ContainerLayout, {
    constructor: function(config) {
        config = config || {};

//      Non-chunked, then animation makes no sense.
        if (!(config.chunkedScroll || config.pagedScroll)) {
            Ext.applyIf(config, {
                scrollIncrement: 10,
                scrollRepeatInterval: 10,
                animScroll: false
            });
        }
        Ext.layout.CarouselLayout.superclass.constructor.call(this, config);

//      Set up animation config depending upon animation requirements.
        if (this.chunkedScroll || this.pagedScroll) {
            this.scrollRepeatInterval = this.scrollDuration * 1000;
            this.scrollAnimationConfig = {
                duration: this.chunkedScroll ? this.scrollDuration : this.scrollDuration * 2,
                callback: this.updateScrollButtons,
                scope: this
            }
        } else {
            this.scrollAnimationConfig = this.animScroll ? {
                duration: this.scrollDuration,
                callback: this.updateScrollButtons,
                scope: this
            } : false;
        }
    },

    /**
     * @cfg scrollElementTag {String} The tag name of the carousel item container. If each item's main Element
     * is an &lt;LI> then this could be specified as '&lt;UL>. Defaults to '&lt;DIV>'.
    /**
     * @cfg {Number} scrollIncrement The number of pixels to scroll each time a tab scroll button is pressed (defaults
     * to 10, or if {@link #resizeTabs} = true, the calculated tab width).
     */
    scrollIncrement : 10,
    /**
     * @cfg {Number} scrollRepeatInterval Number of milliseconds between each scroll while a scroll button is
     * continuously pressed (defaults to 10).
     */
    scrollRepeatInterval : 10,
    /**
     * @cfg {Float} scrollDuration The number of seconds that each scroll animation should last (defaults to .35).
     * Only applies when {@link #animScroll} = true.
     */
    scrollDuration : .35,
    /**
     * @cfg {Boolean} animScroll True to animate item scrolling so that hidden items slide smoothly into view (defaults
     * to true).
     */
    animScroll : true,
    /**
     * @cfg {Boolean} chunkedScroll True to animate item  scrolling so that the carousel scrolls the whole of the next
     * item in from the side you are scrolling towards.
     */
    chunkedScroll: false,
    /**
     * @cfg {Boolean} pagedScroll True to animate item  scrolling so that the carousel scrolls a full page of
     * items in from the side you are scrolling towards.
     */
    pagedScroll: false,

    // private
    monitorResize: true,
    
    /**
     * @cfg {String} scrollButtonPosition 'left', 'right' or 'split'. Position of the scroll arrow buttons.
     */
    scrollButtonPosition: 'right',

    // private
    onLayout : function(ct, target){
        var e = ct.getEl();
        e.setStyle({
            'padding-left': '0px'
        });
        if (e.dom.tagName.toLowerCase() == 'fieldset') {
            var l = e.child('legend');
            if (l) l.setStyle('margin-left', '10px');
        }
        var cs = ct.items.items, len = cs.length, c, i;

        if(!this.scrollWrap){
            this.scrollWrap = target.createChild({
                cls: 'x-carousel-layout',
                cn: [
                {
                    tag: this.scrollElementTag || 'div',
                    cls: 'x-carousel-scroller',
                    cn: {
                        cls: 'x-carousel-body'
                    }
                }, {
                    cls: 'x-carousel-left-scrollbutton',
                    style: {
                        height: '100%'
                    }
                }, {
                    cls: 'x-carousel-right-scrollbutton',
                    style: {
                        height: '100%'
                    }
                }]
            });

//          Add the class that defines element positions
            this.scrollWrap.addClass('x-scroll-button-position-' + this.scrollButtonPosition);

            this.scrollLeft = this.scrollWrap.child('.x-carousel-left-scrollbutton');
            this.scrollRight = this.scrollWrap.child('.x-carousel-right-scrollbutton');
            this.scroller = this.scrollWrap.child('.x-carousel-scroller');
            this.strip = this.scroller.child('.x-carousel-body');
            
            if (this.pagedScroll) {
                this.scrollLeft.on('click',this.onScrollLeftClick, this);
                this.scrollRight.on('click',this.onScrollRightClick, this);
            } else {
                this.leftRepeater = new Ext.util.ClickRepeater(this.scrollLeft, {
                    interval : this.pagedScroll ? 10000 : this.scrollRepeatInterval,
                    delay: 0,
                    handler: this.onScrollLeftClick,
                    scope: this
                });
                this.rightRepeater = new Ext.util.ClickRepeater(this.scrollRight, {
                    interval : this.pagedScroll ? 10000 : this.scrollRepeatInterval,
                    delay: 0,
                    handler: this.onScrollRightClick,
                    scope: this
                });
            }
            this.renderAll(ct, this.strip);
        } else {
            this.renderAll(ct, this.strip);
        }
        this.scroller.setWidth(this.container.getLayoutTarget().getWidth() - (this.scrollLeft.getWidth() + this.scrollRight.getWidth() + 12));
        this.updateScrollButtons.defer(10, this);
    },

    // private
    renderItem : function(c, position, target){
        if(c) {
            if (c.initialConfig) {
                if (c.rendered){
                    if(typeof position == 'number'){
                        position = target.dom.childNodes[position];
                    }
                    target.dom.insertBefore(c.getEl().dom, position || null);
                } else {
                    c.render(target, position);
                }
            } else if (c instanceof Ext.Element) {
                c.el = c;
                if(typeof position == 'number'){
                    position = target.dom.childNodes[position];
                }
                target.dom.insertBefore(c.dom, position || null);
            }
        }
        c.el.addClass('x-carousel-item');
    },

    // private
    onResize : function(c, position, target){
        Ext.layout.CarouselLayout.superclass.onResize.apply(this, arguments);

//      Quit if we are empty
        if (!this.container.items.items.length) return;

        this.scroller.setWidth(this.container.getLayoutTarget().getWidth() - (this.scrollLeft.getWidth() + this.scrollRight.getWidth() + 12));
        if (Ext.isIE) {
            this.scrollLeft.setHeight(this.scroller.getHeight());
            this.scrollRight.setHeight(this.scroller.getHeight());
        }
        this.setItemsEdges();

//      If width increase has introduced spare space to the right, close it up.
        var r = this.getMaxScrollPos();
        if (this.getScrollPos() > r) {
            this.scroller.scrollTo('left', r);
        }
    },

    setItemsEdges: function() {
//      Register strip-relative left/right edges for easy chunked scrolling
        var t = this.container.items.items;
        var lt = t.length;

//      Quit if we are empty
        if (!lt) return;

        var stripLeft = this.strip.getLeft();
        for (var i = 0; i < lt; i++) {
            var c = t[i];
            var e = c.el;
            if (!e) continue;
            var b = e.getBox();
            var l = b.x - stripLeft;
            var r = b.right - stripLeft;

//          "left" is the leftmost visible pixel.
//          "leftAnchor" is the postition to scroll to to bring the
//          item correctly into view with half the inter-item gap visible.
//          Same principle applies to "right"
            c.edges = {
                left: l,
                leftAnchor: l,
                right: r,
                rightAnchor: r
            };

//          Adjust anchors to be halfway between items.
            if (i == 0) {
                e.setStyle({'margin-left': '0px'});
            } else {
                if (i == t.length - 1) {
                    e.setStyle({'margin-right': '0px'});
                } else {
                    e.setStyle({'margin-right': ''});
                }
                var prev = t[i - 1];
                var halfGap = ((l - prev.edges.right) / 2);
                prev.edges.rightAnchor += halfGap;
                c.edges.leftAnchor -= halfGap;
            } 
        }

//      Work out average item width if we have rendered items
        if (t[0].edges) {
            this.itemWidth = t[lt - 1].edges.rightAnchor / lt;
        }
    },

/**
 *  Return the next item to the left which is not fully visible.
 *  Returns undefined if none available,
 */
    getNextOnLeft: function() {
        var t = this.container.items.items;
        if (t.length) {
            for (var i = t.length - 1; i > -1; i--) {
                if (t[i].edges.left < this.getScrollPos()) {
                    return t[i];
                }
            }
        }
    },

/**
 *  Return the next item to the right which is not fully visible.
 *  Returns undefined if none available,
 */
    getNextOnRight: function() {
        var t = this.container.items.items;
        if (t.length) {
            var scrollRight = this.scroller.dom.scrollLeft + this.getClientWidth();
            for (var i = 0, l = t.length; i < l; i++) {
                if (t[i].edges.right > scrollRight) {
                    return t[i];
                }
            }
        }
    },

/**
 *  Called when a click event is fired by the right scroll button.
 *  May also be used to programatically trigger a right scroll event.
 */
    onScrollRightClick : function(){
        var s = this.getScrollTo(1);
        if (s) {
            s = Math.min(this.getMaxScrollPos(), s);
            if(s != this.getScrollPos()) {
                this.scrollTo(s);
            }
        }
    },

/**
 *  Called when a click event is fired by the left scroll button.
 *  May also be used to programatically trigger a left scroll event.
 */
    onScrollLeftClick : function(){
        var s = Math.max(0, this.getScrollTo(-1));
        if(s != this.getScrollPos()) {
            this.scrollTo(s);
        }
    },

    // private
    scrollTo : function(pos){

//      Calculate scroll duration based on how far we have to scroll.
        if (this.scrollAnimationConfig) {
            var distance = Math.abs(this.getScrollPos() - pos);
            this.scrollAnimationConfig.duration = this.scrollDuration * (distance / this.itemWidth);
        }

        this.scroller.scrollTo('left', pos, this.scrollAnimationConfig);

//      Scroll animation will have called this in its callback.
        if(!this.scrollAnimationConfig){
            this.updateScrollButtons();
        }
    },

    // private
    updateScrollButtons : function(){
        if (!this.container.items.items.length) return;
        var pos = this.getScrollPos();
        this.scrollLeft[(pos == 0) ? 'addClass' : 'removeClass']('x-tab-scroller-left-disabled');
        this.scrollRight[(pos >= this.getMaxScrollPos()) ? 'addClass' : 'removeClass']('x-tab-scroller-right-disabled');
    },

    getScrollWidth : function(){
        var t = this.container.items.items;
        if (!t[t.length - 1].edges) {
            this.setItemsEdges();
        }
        return (t.length && t[t.length - 1].edges) ? t[t.length - 1].edges.rightAnchor : 0;
    },

    // private
    getScrollPos : function(){
        return this.scroller.dom.scrollLeft || 0;
    },

    getMaxScrollPos: function() {
        if (!this.container.items.items.length) {
            return 0;
        }
        return this.getScrollWidth() - this.getClientWidth();
    },

    // private
    getClientWidth : function(){
        return this.scroller.dom.clientWidth || 0;
    },

    // private
    getScrollTo : function(dir){
        var pos = this.getScrollPos();

        if (this.chunkedScroll || this.pagedScroll) {
//          -1 for left, 1 for right
            if (dir == -1) {
                var nextLeft = this.getNextOnLeft();
                if (nextLeft) {
                    if (this.pagedScroll) {
                        return nextLeft.edges.rightAnchor - this.getClientWidth();
                    } else {
                        return nextLeft.edges.leftAnchor;
                    }
                }
            } else {
                var nextRight = this.getNextOnRight();
                if (nextRight) {
                    if (this.pagedScroll) {
                        return nextRight.edges.leftAnchor;
                    } else {
                        return (nextRight.edges.rightAnchor - this.getClientWidth());
                    }
                }
            }
        } else {
            return (dir == -1) ? pos - this.scrollIncrement : pos + this.scrollIncrement;
        }
    },

    // private
    isValidParent : function(c, target){
        return true;
    }

});

Ext.Container.LAYOUTS['carousel'] = Ext.layout.CarouselLayout;
