/**
 * Sometimes you want to show several screens worth of information but you've only got a 
 * small screen to work with.
 * TabPanels and Carousels both enable you to see one screen of many at a time, and underneath 
 * they both use a Card
 * Layout.
 *
 * Card Layout takes the size of the Container it is applied to and sizes the currently active 
 * item to fill the Container completely. It then hides the rest of the items, allowing you to 
 * change which one is currently visible but only showing one at once.
 *
 * Here we create a Panel with a Card Layout and later set the second item active (the active 
 * item index is zero-based, so 1 corresponds to the second item). You might consider using a 
 * {@link Ext.tab.Panel tab panel} or a {@link Ext.carousel.Carousel carousel}.
 *
 *     var panel = Ext.create('Ext.Panel', {
 *         layout: 'card',
 *         items: [
 *             {
 *                 html: "First Item"
 *             },
 *             {
 *                 html: "Second Item"
 *             },
 *             {
 *                 html: "Third Item"
 *             },
 *             {
 *                 html: "Fourth Item"
 *             }
 *         ]
 *     });
 *
 *     panel.setActiveItem(1); // make "Second Item" card visible
 *
 * You may specify an animation type for how activating cards looks to the user.  Without
 * an animation specified, the  cards will immediately appear as setActiveItem() is called.
 * The animation config may be a string or an object:of the form:
 *      animation: {
 *          type: one of 'cover', 'cube', 'fade', 'flip', 'pop', 'reveal', 'scroll', or 'slide',
 *          direction: (optional) one of 'up', 'down', 'left', or 'right'
 *      }
 *
 * Or the animation config may be a string, one of 'cover', 'cube', 'fade', 'flip', 'pop',
 * 'reveal', 'scroll', or 'slide'
 *
 * If an animation type is specified, the direction of the animation will be chosen based
 * upon the animation's initial direction left/right (horizontal) or up/down (vertical).
 * If the new card setActiveItem index is less than the current card's, then a right (or down)
 * animation will be applied.  If the new card index is greater, then a left (or up) animation
 * is applied.
 *
 * If you call setAnimation() with an animation config or instance, the animation direction will
 * obey what you specify  - the direction will not be chosen automatically.
 */
Ext.define('Ext.layout.Card', {
    extend: 'Ext.layout.Auto',
 
    alias: 'layout.card',
 
    type: 'card',
 
    isCard: true,
 
    /**
     * @cfg {String/Object} animation
     * The animation to use when switching between cards. The possible animation
     * types are:
     * - `'cover'`
     * - `'cube'`
     * - `'fade'`
     * - `'flip'`
     * - `'pop'`
     * - `'reveal'`
     * - `'scroll'`
     * - `'slide'`
     *
     * If a string, the value should be one of the above types. If an object, the type
     * should be one of the above types.
     *
     * @cfg {Number} [animation.duration]
     * The duration of the animation.
     * 
     * @cfg {String} [animation.direction]
     *
     * For animations that support a direction, the direction of the animation can be specified. 
     * The possible values are:
     * - `'horizontal'`
     * - `'vertical'`
     * - `'top'`
     * - `'right'`
     * - `'bottom'`
     * - `'left'`
     *
     * If a particular direction is specified (`top`/`right`/`bottom`/`left`), then the layout
     * will always animate in that direction. If `horizontal`/`vertical` is used, the direction
     * will be determined based on the position in the items collection. If the new item is 
     * before the current item, the direction will be "back" (`left`/`top`). If the new item is 
     * after the current item, the direction will be "forward" (`right`/`top`).
     */
 
    /**
     * @event activeitemchange
     * @preventable
     * Fires when an card is made active
     * @param {Ext.layout.Card} this The layout instance
     * @param {Mixed} newActiveItem The new active item
     * @param {Mixed} oldActiveItem The old active item
     */
 
    config: {
        /**
         * @cfg {Ext.Indicator} indicator
         * Creates an {@link Ext.Indicator} instance that can be used to visualize
         * the number of items and which item is active.
         */
        indicator: {
            lazy: true,
            $value: {
                xtype: 'indicator',
                flex: 1
            }
        }
    },
 
    /**
     * @cfg {Boolean} [deferRender=true]
     * By default, items not initially shown in the Card layout are rendered when first shown.  
     * This provides a performance benefit, but if the hidden items contain components that are 
     * bound, the bindings do not immediately take effect.  If you have a form with bnound fields
     * that spans several cards, the initially hidden items won't have their values bound and 
     * validation will not be done properly.  In those cases, you will want to set deferRender 
     * to false.
     */
    deferRender: true,
 
    cls: Ext.baseCSSPrefix + 'layout-card',
 
    itemCls: Ext.baseCSSPrefix + 'layout-card-item',
 
    requires: [
        'Ext.Indicator',
        'Ext.layout.card.fx.*'
    ],
 
    /**
     * @private
     */
    applyAnimation: function(animation) {
        return animation ? new Ext.Factory.layoutCardFx(animation) : null;
    },
 
    /**
     * @private
     */
    updateAnimation: function(animation, oldAnimation) {
        var me = this,
            direction;
 
        me.autoDirection = null;
 
        if (animation && animation.isAnimation) {
            animation.setLayout(me);
 
            direction = animation.getDirection();
 
            if (!direction || me.autoDirectionMap[direction]) {
                me.autoDirection = direction || 'horizontal';
                // If we got horizontal or vertical, clear it out
                animation.setDirection(null);
            }
        }
 
        if (oldAnimation) {
            oldAnimation.destroy();
        }
    },
 
    applyIndicator: function(indicator, currentIndicator) {
        return Ext.updateWidget(currentIndicator, indicator, this, 'createIndicator');
    },
 
    createIndicator: function(indicator) {
        return Ext.apply({
            ownerCmp: this.getContainer()
        }, indicator);
    },
 
    updateIndicator: function(indicator) {
        var container, innerItems, activeItem;
 
        if (indicator) {
            container = this.getContainer();
            innerItems = container.getInnerItems();
            activeItem = container.getActiveItem();
 
            indicator
                .sync(innerItems.length, innerItems.indexOf(activeItem))
                .on({
                    indicatortap: 'onIndicatorTap',
                    next: 'next',
                    previous: 'previous',
                    scope: this
                });
        }
    },
 
    onContainerInitialized: function(container) {
        var me = this,
            firstItem = container.getInnerAt(0),
            activeItem = container.getActiveItem(),
            minHeight = container.getMinHeight();
 
        // card layouts use position absolute to animate change of items, 
        // if minHeight is set but height isn't, IE does not flex items 
        // when flex-direction is column
        if (Ext.isIE && minHeight != null && container.getHeight() == null) {
            container.setHeight(minHeight);
        }
 
        me.callParent([ container ]);
 
        if (activeItem) {
            // Don't call showItem here, since the component will get rendered by the
            // render cycle
            activeItem.show();
 
            if (firstItem && firstItem !== activeItem) {
                firstItem.hide();
            }
        }
 
        container.on('activeitemchange', 'onContainerActiveItemChange', me);
    },
 
    /**
     * @private
     */
    onContainerActiveItemChange: function(container, newItem, oldItem) {
        var me = this,
            innerItems = container.getInnerItems(),
            newIndex = innerItems.indexOf(newItem),
            oldIndex = innerItems.indexOf(oldItem),
            animation = me.getAnimation(),
            autoDirection = me.autoDirection,
            horizontal = autoDirection && autoDirection === 'horizontal',
            direction;
 
        if (autoDirection && newIndex !== -1 && oldIndex !== -1) {
            if (newIndex < oldIndex) {
                direction = horizontal ? 'right' : 'up';
            }
            else {
                direction = horizontal ? 'left' : 'down';
            }
 
            animation.setDirection(direction);
        }
 
        me.fireEventedAction('activeitemchange', [me, newItem, oldItem],
                             'doActiveItemChange', me);
    },
 
    onItemInnerStateChange: function(item, isInner, destroying) {
        var container, activeItem;
 
        this.callParent([item, isInner, destroying]);
 
        container = this.getContainer();
        activeItem = container.getActiveItem();
 
        if (isInner) {
            if (activeItem !== container.innerIndexOf(item) &&
                activeItem !== item &&
                item !== container.pendingActiveItem
            ) {
                item.hide();
            }
        }
        else {
            if (!destroying && !item.destroyed && item.destroying !== true) {
                item.show();
            }
        }
    },
 
    /**
     * @private
     */
    doActiveItemChange: function(me, newActiveItem, oldActiveItem) {
        var indicator = me.getConfig('indicator', null, true),
            container, innerItems;
 
        if (oldActiveItem && !oldActiveItem.destroyed) {
            oldActiveItem.hide();
        }
 
        if (newActiveItem && !newActiveItem.destroyed) {
            me.showItem(newActiveItem);
 
            if (indicator) {
                container = this.getContainer();
                innerItems = container.getInnerItems();
 
                indicator.setActiveIndex(innerItems.indexOf(newActiveItem));
            }
        }
    },
 
    onItemAdd: function(item, index) {
        var indicator,
            style;
 
        this.callParent([item, index]);
 
        if (item.isInnerItem()) {
            indicator = this.getConfig('indicator', null, true);
 
            if (indicator) {
                indicator.add();
            }
 
            // Clear any styles as they come in, we should be
            // 100% 100%
            style = item.element.dom.style;
            style.width = style.height = '';
        }
    },
 
    onItemRemove: function(item, index, destroying) {
        var indicator, w, h;
 
        this.callParent([item, index, destroying]);
 
        if (item.isInnerItem()) {
            indicator = this.getConfig('indicator', null, true);
 
            if (indicator) {
                indicator.remove();
            }
 
            // Restore inline sizes on the way out
            w = item.getWidth();
            h = item.getHeight();
 
            item.setWidth(null).setWidth(w);
            item.setHeight(null).setHeight(h);
        }
    },
 
    /**
     * Moves to the next item if not on the last item.
     */
    next: function() {
        var container = this.getContainer(),
            activeItem = container.getActiveItem(),
            innerItems = container.getInnerItems(),
            index = innerItems.indexOf(activeItem);
 
        activeItem = innerItems[index + 1];
 
        if (activeItem) {
            container.setActiveItem(activeItem);
        }
    },
 
    /**
     * Moves to the previous item if not on the first item.
     */
    previous: function() {
        var container = this.getContainer(),
            activeItem = container.getActiveItem(),
            innerItems = container.getInnerItems(),
            index = innerItems.indexOf(activeItem);
 
        activeItem = innerItems[index - 1];
 
        if (activeItem) {
            container.setActiveItem(activeItem);
        }
    },
 
    onIndicatorTap: function(indicator, index) {
        var container = this.getContainer();
 
        container.setActiveItem(index);
    },
 
    destroy: function() {
        Ext.destroy(this.getAnimation(), this.getIndicator());
 
        this.callParent();
    },
 
    privates: {
        autoDirectionMap: {
            horizontal: 1,
            vertical: 1
        },
 
        renderInnerItem: function(item, asRoot) {
            if (!this.deferRender || this.getContainer().getActiveItem() === item) {
                this.callParent([item, asRoot]);
            }
        },
 
        showItem: function(item) {
            item.show();
 
            if (this.getContainer().rendered) {
                item.setRendered(true, true);
            }
        }
    }
});