/**
 * This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
 * visible at any given time.  This layout style is most commonly used for wizards, tab implementations, etc.
 * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,
 * and should generally not need to be created directly via the new keyword.
 *
 * The CardLayout's focal method is {@link #setActiveItem}.  Since only one panel is displayed at a time,
 * the only way to move from one Component to the next is by calling setActiveItem, passing the next panel to display
 * (or its id or index).  The layout itself does not provide a user interface for handling this navigation,
 * so that functionality must be provided by the developer.
 *
 * To change the active card of a container, call the setActiveItem method of its layout:
 *
 *     @example
 *     var p = Ext.create('Ext.panel.Panel', {
 *         layout: 'card',
 *         items: [
 *             { html: 'Card 1' },
 *             { html: 'Card 2' }
 *         ],
 *         renderTo: Ext.getBody()
 *     });
 *
 *     p.getLayout().setActiveItem(1);
 * 
 * The {@link Ext.Component#beforedeactivate beforedeactivate} and {@link Ext.Component#beforeactivate beforeactivate}
 * events can be used to prevent a card from activating or deactivating by returning `false`.
 * 
 *     @example   
 *     var active = 0;
 *     var main = Ext.create('Ext.panel.Panel', {
 *         renderTo: Ext.getBody(),
 *         width: 200,
 *         height: 200,
 *         layout: 'card',
 *         tbar: [{
 *             text: 'Next',
 *             handler: function(){
 *                 var layout = main.getLayout();
 *                 ++active;
 *                 layout.setActiveItem(active);
 *                 active = main.items.indexOf(layout.getActiveItem());
 *             }
 *         }],
 *         items: [{
 *             title: 'P1'
 *         }, {
 *             title: 'P2'
 *         }, {
 *             title: 'P3',
 *             listeners: {
 *                 beforeactivate: function(){
 *                     return false;
 *                 }
 *             }
 *         }]
 *     });
 *
 * In the following example, a simplistic wizard setup is demonstrated.  A button bar is added
 * to the footer of the containing panel to provide navigation buttons.  The buttons will be handled by a
 * common navigation routine.  Note that other uses of a CardLayout (like a tab control) would require a
 * completely different implementation.  For serious implementations, a better approach would be to extend
 * CardLayout to provide the custom functionality needed.
 *
 *     @example
 *     var navigate = function(panel, direction){
 *         // This routine could contain business logic required to manage the navigation steps.
 *         // It would call setActiveItem as needed, manage navigation button state, handle any
 *         // branching logic that might be required, handle alternate actions like cancellation
 *         // or finalization, etc.  A complete wizard implementation could get pretty
 *         // sophisticated depending on the complexity required, and should probably be
 *         // done as a subclass of CardLayout in a real-world implementation.
 *         var layout = panel.getLayout();
 *         layout[direction]();
 *         Ext.getCmp('move-prev').setDisabled(!layout.getPrev());
 *         Ext.getCmp('move-next').setDisabled(!layout.getNext());
 *     };
 *
 *     Ext.create('Ext.panel.Panel', {
 *         title: 'Example Wizard',
 *         width: 300,
 *         height: 200,
 *         layout: 'card',
 *         bodyStyle: 'padding:15px',
 *         defaults: {
 *             // applied to each contained panel
 *             border: false
 *         },
 *         // just an example of one possible navigation scheme, using buttons
 *         bbar: [
 *             {
 *                 id: 'move-prev',
 *                 text: 'Back',
 *                 handler: function(btn) {
 *                     navigate(btn.up("panel"), "prev");
 *                 },
 *                 disabled: true
 *             },
 *             '->', // greedy spacer so that the buttons are aligned to each side
 *             {
 *                 id: 'move-next',
 *                 text: 'Next',
 *                 handler: function(btn) {
 *                     navigate(btn.up("panel"), "next");
 *                 }
 *             }
 *         ],
 *         // the panels (or "cards") within the layout
 *         items: [{
 *             id: 'card-0',
 *             html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
 *         },{
 *             id: 'card-1',
 *             html: '<p>Step 2 of 3</p>'
 *         },{
 *             id: 'card-2',
 *             html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
 *         }],
 *         renderTo: Ext.getBody()
 *     });
 */
Ext.define('Ext.layout.container.Card', {

    /* Begin Definitions */

    extend: 'Ext.layout.container.Fit',

    alternateClassName: 'Ext.layout.CardLayout',

    alias: 'layout.card',

    /* End Definitions */

    type: 'card',

    hideInactive: true,

    /**
     * @cfg {Boolean} deferredRender
     * True to render each contained item at the time it becomes active, false to render all contained items
     * as soon as the layout is rendered (defaults to false).  If there is a significant amount of content or
     * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
     * true might improve performance.
     */
    deferredRender : false,

    getRenderTree: function () {
        var me = this,
            activeItem = me.getActiveItem();

        if (activeItem) {

            // If they veto the activate, we have no active item
            if (activeItem.hasListeners.beforeactivate && activeItem.fireEvent('beforeactivate', activeItem) === false) {
 
                // We must null our activeItem reference, AND the one in our owning Container.
                // Because upon layout invalidation, renderChildren will use this.getActiveItem which
                // uses this.activeItem || this.owner.activeItem
                activeItem = me.activeItem = me.owner.activeItem = null;
            }
            
            // Item is to be the active one. Fire event after it is first layed out
            else if (activeItem.hasListeners.activate) {
                activeItem.on({
                    boxready: function() {
                        activeItem.fireEvent('activate', activeItem);
                    },
                    single: true
                });
            }

            if (me.deferredRender) {
                if (activeItem) {
                    return me.getItemsRenderTree([activeItem]);
                }
            } else {
                return me.callParent(arguments);
            }
        }
    },

    renderChildren: function () {
        var me = this,
            active = me.getActiveItem();

        if (!me.deferredRender) {
            me.callParent();
        } else if (active) {
            // ensure the active item is configured for the layout
            me.renderItems([active], me.getRenderTarget());
        }
    },

    isValidParent : function(item, target, position) {
        // Note: Card layout does not care about order within the target because only one is ever visible.
        // We only care whether the item is a direct child of the target.
        var itemEl = item.el ? item.el.dom : Ext.getDom(item);
        return (itemEl && itemEl.parentNode === (target.dom || target)) || false;
    },

    /**
     * Return the active (visible) component in the layout.
     * @return {Ext.Component}
     */
    getActiveItem: function() {
        var me = this,
            // It's necessary to check that me.activeItem is not undefined as it could be 0 (falsey). We're more interested in
            // checking the layout's activeItem property, since that is the source of truth for an activeItem.  If it's
            // determined to be empty, check the owner. Note that a default item is returned if activeItem is `undefined` but
            // not `null`. Also, note that `null` is legitimate value and completely different from `undefined`.
            item = me.activeItem === undefined ? (me.owner && me.owner.activeItem) : me.activeItem,
            result = me.parseActiveItem(item);

        // Sanitize the result in case the active item is no longer there.
        if (result && me.owner.items.indexOf(result) !== -1) {
            me.activeItem = result;
        }

        // Note that in every use case me.activeItem will have a truthy value except for when a container or tabpanel is explicity
        // configured with activeItem/Tab === null or when an out-of-range index is given for an active tab (as it will be undefined).
        // In those cases, it is meaningful to return the null value, so do so.
        return result == null ? null : (me.activeItem || me.owner.activeItem);
    },

    /**
     * @private
     */
    parseActiveItem: function (item) {
        var activeItem;

        if (item && item.isComponent) {
            activeItem = item;
        } else if (typeof item === 'number' || item === undefined) {
            activeItem = this.getLayoutItems()[item || 0];
        } else if (item === null) {
            activeItem = null;
        } else {
            activeItem = this.owner.getComponent(item);
        }

        return activeItem;
    },

    /**
     * @private
     * Called before both dynamic render, and bulk render.
     * Ensure that the active item starts visible, and inactive ones start invisible.
     */
    configureItem: function(item) {
        item.setHiddenState(item !== this.getActiveItem());
        this.callParent(arguments);
    },

    onAdd: function (item, pos) {
        this.callParent([item, pos]);
        this.setItemHideMode(item);
    },

    onRemove: function(component) {
        var me = this;

        me.callParent([component]);
        me.resetItemHideMode(component);

        if (component === me.activeItem) {
            // Note setting to `undefined` is intentional. Don't null it out since null now has a specific meaning in
            // tab management (it specifies not setting an active item).
            me.activeItem = undefined;
        }
    },

    /**
     * @private
     */
    getAnimation: function(newCard, owner) {
        var newAnim = (newCard || {}).cardSwitchAnimation;
        if (newAnim === false) {
            return false;
        }
        return newAnim || owner.cardSwitchAnimation;
    },

    /**
     * Return the active (visible) component in the layout to the next card
     * @return {Ext.Component} The next component or false.
     */
    getNext: function() {
        var wrap = arguments[0],
            items = this.getLayoutItems(),
            index = Ext.Array.indexOf(items, this.activeItem);
            
        return items[index + 1] || (wrap ? items[0] : false);
    },

    /**
     * Sets the active (visible) component in the layout to the next card
     * @return {Ext.Component} the activated component or false when nothing activated.
     */
    next: function() {
        var anim = arguments[0], 
            wrap = arguments[1];
        return this.setActiveItem(this.getNext(wrap), anim);
    },

    /**
     * Return the active (visible) component in the layout to the previous card
     * @return {Ext.Component} The previous component or false.
     */
    getPrev: function() {
        var wrap = arguments[0],
            items = this.getLayoutItems(),
            index = Ext.Array.indexOf(items, this.activeItem);
            
        return items[index - 1] || (wrap ? items[items.length - 1] : false);
    },

    /**
     * Sets the active (visible) component in the layout to the previous card
     * @return {Ext.Component} the activated component or false when nothing activated.
     */
    prev: function() {
        var anim = arguments[0], 
            wrap = arguments[1];
        return this.setActiveItem(this.getPrev(wrap), anim);
    },

    /**
     * Makes the given card active.
     *
     *     var card1 = Ext.create('Ext.panel.Panel', {itemId: 'card-1'});
     *     var card2 = Ext.create('Ext.panel.Panel', {itemId: 'card-2'});
     *     var panel = Ext.create('Ext.panel.Panel', {
     *         layout: 'card',
     *         activeItem: 0,
     *         items: [card1, card2]
     *     });
     *     // These are all equivalent
     *     panel.getLayout().setActiveItem(card2);
     *     panel.getLayout().setActiveItem('card-2');
     *     panel.getLayout().setActiveItem(1);
     *
     * @param {Ext.Component/Number/String} newCard  The component, component {@link Ext.Component#id id},
     * {@link Ext.Component#itemId itemId}, or index of component.
     * @return {Ext.Component} the activated component or false when nothing activated.
     * False is returned also when trying to activate an already active card.
     */
    setActiveItem: function(newCard) {
        var me = this,
            owner = me.owner,
            oldCard = me.activeItem,
            rendered = owner.rendered,
            newIndex, focusNewCard;

        newCard = me.parseActiveItem(newCard);
        newIndex = owner.items.indexOf(newCard);

        // If the card is not a child of the owner, then add it.
        // Without doing a layout!
        if (newIndex === -1) {
            newIndex = owner.items.items.length;
            Ext.suspendLayouts();
            newCard = owner.add(newCard);
            Ext.resumeLayouts();
        }

        // Is this a valid, different card?
        if (newCard && oldCard !== newCard) {
            // Fire the beforeactivate and beforedeactivate events on the cards
            if (newCard.fireEvent('beforeactivate', newCard, oldCard) === false) {
                return false;
            }
            if (oldCard && oldCard.fireEvent('beforedeactivate', oldCard, newCard) === false) {
                return false;
            }

            if (rendered) {
                Ext.suspendLayouts();

                // If the card has not been rendered yet, now is the time to do so.
                if (!newCard.rendered) {
                    me.renderItem(newCard, me.getRenderTarget(), owner.items.length);
                }

                if (oldCard) {
                    if (me.hideInactive) {
                        focusNewCard = oldCard.el.contains(Ext.Element.getActiveElement());

                        oldCard.hide();

                        if (oldCard.hidden) {
                            oldCard.hiddenByLayout = true;
                            oldCard.fireEvent('deactivate', oldCard, newCard);
                        }
                        // Hide was vetoed, we cannot change cards.
                        else {
                            return false;
                        }
                    }
                }

                // Make sure the new card is shown
                if (newCard.hidden) {
                    newCard.show();
                }

                // Layout needs activeItem to be correct, so clear it if the show has been vetoed,
                // set it if the show has *not* been vetoed.
                if (newCard.hidden) {
                    me.activeItem = newCard = null;
                } else {
                    me.activeItem = newCard;

                    // If the card being hidden contained focus, attempt to focus the new card
                    // So as not to leave focus undefined.
                    // The focus() call will focus the defaultFocus if it is a container
                    // so ensure there is a defaultFocus.
                    if (focusNewCard) {
                        if (!newCard.defaultFocus) {
                            newCard.defaultFocus = ':focusable';
                        }
                        newCard.focus();
                    }
                }

                Ext.resumeLayouts(true);
            } else {
                me.activeItem = newCard;
            }

            newCard.fireEvent('activate', newCard, oldCard);

            return me.activeItem;
        }
        return false;
    },

    /**
     * @private
     * Reset back to initial config when item is removed from the panel.
     */
    resetItemHideMode: function (item) {
        item.hideMode = item.originalHideMode;
        delete item.originalHideMode;
    },

    /**
     * @private
     * A card layout items must have its visibility mode set to OFFSETS so its scroll
     * positions isn't reset when hidden.
     *
     * Do this automatically when an item is added to the panel.
     */
    setItemHideMode: function (item) {
        item.originalHideMode = item.hideMode;
        item.hideMode = 'offsets';
    }
});
