    /*!
     * Ext JS Library 3.3.0
     * Copyright(c) 2006-2010 Ext JS, Inc.
     * licensing@extjs.com
     * http://www.extjs.com/license
     */
    Ext.ns('Ext.ux');

    Ext.ux.Carousel = Ext.extend(Ext.util.Observable, {
        interval: 3,
        transitionDuration: 1,
        transitionType: 'carousel',
        transitionEasing: 'easeOut',
        itemSelector: 'img',
        activeSlide: 0,
        autoPlay: false,
        showPlayButton: false,
        pauseOnNavigate: false,
        wrap: false,
        freezeOnHover: false,
        navigationOnHover: false,
        hideNavigation: false,
        width: null,
        height: null,

        constructor: function(elId, config) {
            config = config || {};
            Ext.apply(this, config);

            Ext.ux.Carousel.superclass.constructor.call(this, config);

            this.addEvents(
                'beforeprev',
                'prev',
                'beforenext',
                'next',
                'change',
                'play',
                'pause',
                'freeze',
                'unfreeze'
            );

            this.el = Ext.get(elId);
            this.slides = this.els = [];

            if(this.autoPlay || this.showPlayButton) {
                this.wrap = true;
            };

            if(this.autoPlay && typeof config.showPlayButton === 'undefined') {
                this.showPlayButton = true;
            }

            this.initMarkup();
            this.initEvents();

            if(this.carouselSize > 0) {
                this.refresh();
            }
        },

        initMarkup: function() {
            var dh = Ext.DomHelper;

            this.carouselSize = 0;
            var items = this.el.select(this.itemSelector);
            this.els.container = dh.append(this.el, {cls: 'ux-carousel-container'}, true);
            this.els.slidesWrap = dh.append(this.els.container, {cls: 'ux-carousel-slides-wrap'}, true);

            this.els.navigation = dh.append(this.els.container, {cls: 'ux-carousel-nav'}, true).hide();
            this.els.caption = dh.append(this.els.navigation, {tag: 'h2', cls: 'ux-carousel-caption'}, true);
            this.els.navNext = dh.append(this.els.navigation, {tag: 'a', href: '#', cls: 'ux-carousel-nav-next'}, true);
            if(this.showPlayButton) {
                this.els.navPlay = dh.append(this.els.navigation, {tag: 'a', href: '#', cls: 'ux-carousel-nav-play'}, true)
            }
            this.els.navPrev = dh.append(this.els.navigation, {tag: 'a', href: '#', cls: 'ux-carousel-nav-prev'}, true);

            // set the dimensions of the container
            this.slideWidth = this.width || this.el.getWidth(true);
            this.slideHeight = this.height || this.el.getHeight(true);
            this.els.container.setStyle({
                width: this.slideWidth + 'px',
                height: this.slideHeight + 'px'
            });

            this.els.caption.setWidth((this.slideWidth - (this.els.navNext.getWidth()*2) - (this.showPlayButton ? this.els.navPlay.getWidth() : 0) - 20) + 'px')

            items.appendTo(this.els.slidesWrap).each(function(item) {
                item = item.wrap({cls: 'ux-carousel-slide'});
                this.slides.push(item);
                item.setWidth(this.slideWidth + 'px').setHeight(this.slideHeight + 'px');
            }, this);
            this.carouselSize = this.slides.length;
            if(this.navigationOnHover) {
                this.els.navigation.setStyle('top', (-1*this.els.navigation.getHeight()) + 'px');
            }
            this.el.clip();
        },

        initEvents: function() {
            this.els.navPrev.on('click', function(ev) {
                ev.preventDefault();
                var target = ev.getTarget();
                target.blur();
                if(Ext.fly(target).hasClass('ux-carousel-nav-disabled')) return;
                this.prev();
            }, this);

            this.els.navNext.on('click', function(ev) {
                ev.preventDefault();
                var target = ev.getTarget();
                target.blur();
                if(Ext.fly(target).hasClass('ux-carousel-nav-disabled')) return;
                this.next();
            }, this);

            if(this.showPlayButton) {
                this.els.navPlay.on('click', function(ev){
                    ev.preventDefault();
                    ev.getTarget().blur();
                    if(this.playing) {
                        this.pause();
                    }
                    else {
                        this.play();
                    }
                }, this);
            };

            if(this.freezeOnHover) {
                this.els.container.on('mouseenter', function(){
                    if(this.playing) {
                        this.fireEvent('freeze', this.slides[this.activeSlide]);
                        Ext.TaskMgr.stop(this.playTask);
                    }
                }, this);
                this.els.container.on('mouseleave', function(){
                    if(this.playing) {
                        this.fireEvent('unfreeze', this.slides[this.activeSlide]);
                        Ext.TaskMgr.start(this.playTask);
                    }
                }, this, {buffer: (this.interval/2)*1000});
            };

            if(this.navigationOnHover) {
                this.els.container.on('mouseenter', function(){
                    if(!this.navigationShown) {
                        this.navigationShown = true;
                        this.els.navigation.stopFx(false).shift({
                            y: this.els.container.getY(),
                            duration: this.transitionDuration
                        })
                    }
                }, this);

                this.els.container.on('mouseleave', function(){
                    if(this.navigationShown) {
                        this.navigationShown = false;
                        this.els.navigation.stopFx(false).shift({
                            y: this.els.navigation.getHeight() - this.els.container.getY(),
                            duration: this.transitionDuration
                        })
                    }
                }, this);
            }

            if(this.interval && this.autoPlay) {
                this.play();
            };
        },

        prev: function() {
            if (this.fireEvent('beforeprev') === false) {
                return;
            }
            if(this.pauseOnNavigate) {
                this.pause();
            }
            this.setSlide(this.activeSlide - 1);

            this.fireEvent('prev', this.activeSlide);
            return this;
        },

        next: function() {
            if(this.fireEvent('beforenext') === false) {
                return;
            }
            if(this.pauseOnNavigate) {
                this.pause();
            }
            this.setSlide(this.activeSlide + 1);

            this.fireEvent('next', this.activeSlide);
            return this;
        },

        play: function() {
            if(!this.playing) {
                this.playTask = this.playTask || {
                    run: function() {
                        this.playing = true;
                        this.setSlide(this.activeSlide+1);
                    },
                    interval: this.interval*1000,
                    scope: this
                };

                this.playTaskBuffer = this.playTaskBuffer || new Ext.util.DelayedTask(function() {
                    Ext.TaskMgr.start(this.playTask);
                }, this);

                this.playTaskBuffer.delay(this.interval*1000);
                this.playing = true;
                if(this.showPlayButton) {
                    this.els.navPlay.addClass('ux-carousel-playing');
                }
                this.fireEvent('play');
            }
            return this;
        },

        pause: function() {
            if(this.playing) {
                Ext.TaskMgr.stop(this.playTask);
                this.playTaskBuffer.cancel();
                this.playing = false;
                if(this.showPlayButton) {
                    this.els.navPlay.removeClass('ux-carousel-playing');
                }
                this.fireEvent('pause');
            }
            return this;
        },

        clear: function() {
            this.els.slidesWrap.update('');
            this.slides = [];
            this.carouselSize = 0;
            this.pause();
            return this;
        },

        add: function(el, refresh) {
            var item = Ext.fly(el).appendTo(this.els.slidesWrap).wrap({cls: 'ux-carousel-slide'});
            item.setWidth(this.slideWidth + 'px').setHeight(this.slideHeight + 'px');
            this.slides.push(item);
            if(refresh) {
                this.refresh();
            }
            return this;
        },

        refresh: function() {
            this.carouselSize = this.slides.length;
            this.els.slidesWrap.setWidth((this.slideWidth * this.carouselSize) + 'px');
            if(this.carouselSize > 0) {
                if(!this.hideNavigation) this.els.navigation.show();
                this.activeSlide = 0;
                this.setSlide(0, true);
            }
            return this;
        },

        setSlide: function(index, initial) {
            if(!this.wrap && !this.slides[index]) {
                return;
            }
            else if(this.wrap) {
                if(index < 0) {
                    index = this.carouselSize-1;
                }
                else if(index > this.carouselSize-1) {
                    index = 0;
                }
            }
            if(!this.slides[index]) {
                return;
            }

            this.els.caption.update(this.slides[index].child(':first-child', true).title || '');
            var offset = index * this.slideWidth;
            if (!initial) {
                switch (this.transitionType) {
                    case 'fade':
                        this.slides[index].setOpacity(0);
                        this.slides[this.activeSlide].stopFx(false).fadeOut({
                            duration: this.transitionDuration / 2,
                            callback: function(){
                                this.els.slidesWrap.setStyle('left', (-1 * offset) + 'px');
                                this.slides[this.activeSlide].setOpacity(1);
                                this.slides[index].fadeIn({
                                    duration: this.transitionDuration / 2
                                });
                            },
                            scope: this
                        })
                        break;

                    default:
                        var xNew = (-1 * offset) + this.els.container.getX();
                        this.els.slidesWrap.stopFx(false);
                        this.els.slidesWrap.shift({
                            duration: this.transitionDuration,
                            x: xNew,
                            easing: this.transitionEasing
                        });
                        break;
                }
            }
            else {
                this.els.slidesWrap.setStyle('left', '0');
            }

            this.activeSlide = index;
            this.updateNav();
            this.fireEvent('change', this.slides[index], index);
        },

        updateNav: function() {
            this.els.navPrev.removeClass('ux-carousel-nav-disabled');
            this.els.navNext.removeClass('ux-carousel-nav-disabled');
            if(!this.wrap) {
                if(this.activeSlide === 0) {
                    this.els.navPrev.addClass('ux-carousel-nav-disabled');
                }
                if(this.activeSlide === this.carouselSize-1) {
                    this.els.navNext.addClass('ux-carousel-nav-disabled');
                }
            }
        }
    });