/**
 * This is a layout that manages multiple Panels in an expandable accordion style such that
 * by default only one Panel can be expanded at any given time (set {@link #multi} config
 * to have more open). Each Panel has built-in support for expanding and collapsing.
 *
 * Note: Only Ext Panels and all subclasses of Ext.panel.Panel may be used in an accordion layout
 * Container.
 *
 *     @example
 *     Ext.create('Ext.panel.Panel', {
 *         title: 'Accordion Layout',
 *         width: 300,
 *         height: 300,
 *         defaults: {
 *             // applied to each contained panel
 *             bodyStyle: 'padding:15px'
 *         },
 *         layout: {
 *             // layout-specific configs go here
 *             type: 'accordion',
 *             titleCollapse: false,
 *             animate: true,
 *             activeOnTop: true
 *         },
 *         items: [{
 *             title: 'Panel 1',
 *             html: 'Panel content!'
 *         },{
 *             title: 'Panel 2',
 *             html: 'Panel content!'
 *         },{
 *             title: 'Panel 3',
 *             html: 'Panel content!'
 *         }],
 *         renderTo: Ext.getBody()
 *     });
 */
Ext.define('Ext.layout.container.Accordion', {
    extend: 'Ext.layout.container.VBox',
    alias: 'layout.accordion',
    type: 'accordion',
 
    alternateClassName: 'Ext.layout.AccordionLayout',
 
    targetCls: Ext.baseCSSPrefix + 'accordion-layout-ct',
    itemCls: [Ext.baseCSSPrefix + 'box-item', Ext.baseCSSPrefix + 'accordion-item'],
 
    align: 'stretch',
 
    enableSplitters: false,
 
    /**
     * @cfg {Boolean} fill
     * True to adjust the active item's height to fill the available space in the container,
     * false to use the item's current height, or auto height if not explicitly set.
     */
    fill: true,
 
    /**
     * @cfg {Boolean} autoWidth
     * Child Panels have their width actively managed to fit within the accordion's width.
     * @removed This config is ignored in ExtJS 4
     */
 
    /**
     * @cfg {Boolean} titleCollapse
     * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar,
     * false to allow
     * expand/collapse only when the toggle tool button is clicked.  When set to false,
     * {@link #hideCollapseTool} should be false also. An explicit
     * {@link Ext.panel.Panel#titleCollapse} declared on the panel will override this setting.
     */
    titleCollapse: true,
 
    /**
     * @cfg {Boolean} hideCollapseTool
     * True to hide the contained Panels' collapse/expand toggle buttons, false to display them.
     * When set to true, {@link #titleCollapse} is automatically set to true.
     */
    hideCollapseTool: false,
 
    /**
     * @cfg {Boolean} collapseFirst
     * True to make sure the collapse/expand toggle button always renders first (to the left of)
     * any other tools in the contained Panels' title bars, false to render it last. By default,
     * this will use the {@link Ext.panel.Panel#collapseFirst} setting on the panel.
     * If the config option is specified on the layout, it will override the panel value.
     */
    collapseFirst: undefined,
 
    /**
     * @cfg {Boolean} animate
     * True to slide the contained panels open and closed during expand/collapse using animation,
     * false to open and close directly with no animation. Note: The layout performs animated
     * collapsing and expanding, *not* the child Panels.
     */
    animate: true,
    
    /**
     * @cfg {Boolean} activeOnTop
     * Only valid when {@link #multi} is `false` and {@link #animate} is `false`.
     *
     * True to swap the position of each panel as it is expanded so that it becomes the first item
     * in the container, false to keep the panels in the rendered order.
     */
    activeOnTop: false,
    
    /**
     * @cfg {Boolean} multi
     * Set to true to enable multiple accordion items to be open at once.
     */
    multi: false,
    
    /**
     * @cfg {Boolean} [wrapOver=true] When `true`, pressing Down or Right arrow key on the
     * focused last accordion panel header will navigate to the first panel; pressing Up
     * or Left arrow key on the focused first accordion panel header will navigate to the
     * last panel.
     * Set this to `false` to prevent keyboard navigation from wrapping over the edges.
     */
    wrapOver: true,
 
    panelCollapseMode: 'header',
 
    defaultAnimatePolicy: {
        y: true,
        height: true
    },
 
    constructor: function() {
        var me = this;
 
        me.callParent(arguments);
 
        if (me.animate) {
            me.animatePolicy = {};
 
            /* Animate our parallel dimension and position.
               So in the default vertical accordion, this will be
                {
                    y: true,
                    height: true
                }
            */
            me.animatePolicy[me.names.x] = true;
            me.animatePolicy[me.names.width] = true;
        }
        else {
            me.animatePolicy = null;
        }
    },
 
    beforeRenderItems: function(items) {
        var me = this,
            ln = items.length,
            owner = me.owner,
            collapseFirst = me.collapseFirst,
            hasCollapseFirst = Ext.isDefined(collapseFirst),
            expandedItem = me.getExpanded(true)[0],
            multi = me.multi,
            comp, i;
 
        for (= 0; i < ln; i++) {
            comp = items[i];
            
            if (!comp.rendered) {
                // Set up initial properties for Panels in an accordion.
                comp.isAccordionPanel = true;
                comp.bodyAriaRole = 'tabpanel';
                comp.accordionWrapOver = me.wrapOver;
                
                if (!multi || comp.collapsible !== false) {
                    comp.collapsible = true;
                }
 
                if (comp.collapsible) {
                    if (hasCollapseFirst) {
                        comp.collapseFirst = collapseFirst;
                    }
 
                    if (me.hideCollapseTool) {
                        comp.hideCollapseTool = me.hideCollapseTool;
                        comp.titleCollapse = true;
                    }
                    else if (me.titleCollapse && comp.titleCollapse === undefined) {
                        // Only force titleCollapse if we don't explicitly
                        // set one on the child panel
                        comp.titleCollapse = me.titleCollapse;
                    }
                }
 
                comp.hideHeader = comp.width = null;
                comp.title = comp.title || '&#160;';
                comp.addBodyCls(Ext.baseCSSPrefix + 'accordion-body');
 
                // If only one child Panel is allowed to be expanded
                // then collapse all except the first one found with collapsed:false
                // If we have hasExpanded set, we've already done this
                if (!multi) {
                    if (expandedItem) {
                        comp.collapsed = expandedItem !== comp;
                    }
                    else if (comp.hasOwnProperty('collapsed') && comp.collapsed === false) {
                        expandedItem = comp;
                    }
                    else {
                        comp.collapsed = true;
                    }
 
                    // If only one child Panel may be expanded, then intercept expand/show requests.
                    owner.mon(comp, 'show', me.onComponentShow, me);
                }
 
                // Need to still check this outside multi because we don't want
                // a single item to be able to collapse
                comp.headerOverCls = Ext.baseCSSPrefix + 'accordion-hd-over';
            }
        }
 
        // If no collapsed:false Panels found, make the first one expanded, only if we're
        // not during an expand/collapse
        if (!me.processing && !multi) {
            if (!expandedItem) {
                if (ln) {
                    items[0].collapsed = false;
                }
            }
            else if (me.activeOnTop) {
                expandedItem.collapsed = false;
                me.configureItem(expandedItem);
 
                if (owner.items.indexOf(expandedItem) > 0) {
                    owner.insert(0, expandedItem);
                }
            }
        }
    },
 
    getItemsRenderTree: function(items) {
        this.beforeRenderItems(items);
 
        return this.callParent(arguments);
    },
 
    renderItems: function(items, target) {
        this.beforeRenderItems(items);
 
        this.callParent(arguments);
    },
 
    configureItem: function(item) {
        this.callParent(arguments);
 
        // Accordion headers are immune to dock layout's border-management rules
        item.ignoreHeaderBorderManagement = true;
 
        // We handle animations for the expand/collapse of items.
        // Items do not have individual borders
        item.animCollapse = false;
 
        // If filling available space, all Panels flex.
        if (this.fill) {
            item.flex = 1;
        }
    },
 
    beginLayout: function(ownerContext) {
        this.callParent(arguments);
        
        // Accordion widgets have the role of tablist along with the attribute
        // aria-multiselectable="true" to indicate that it's an accordion
        // and not just a simple tab panel.
        // We can't set this role on the panel's main el as this panel may be
        // a region in a border layout which yields its own set of ARIA attributes.
        // We also can't set this role on panel's body el, because the panel could be
        // a FormPanel that would have role="form" on the body el, and the tablist
        // needs to be contained within it.
        // innerCt seems to be the most logical choice here.
        this.innerCt.dom.setAttribute('role', 'tablist');
        this.innerCt.dom.setAttribute('aria-multiselectable', true);
        
        this.updatePanelClasses(ownerContext);
    },
 
    updatePanelClasses: function(ownerContext) {
        var children = ownerContext.visibleItems,
            ln = children.length,
            siblingCollapsed = true,
            i, child, header;
 
        for (= 0; i < ln; i++) {
            child = children[i];
            header = child.header;
            header.addCls(Ext.baseCSSPrefix + 'accordion-hd');
 
            if (siblingCollapsed) {
                header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
            }
            else {
                header.addCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
            }
 
            if (+ 1 === ln && child.collapsed) {
                header.addCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
            }
            else {
                header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
            }
 
            siblingCollapsed = child.collapsed;
        }
    },
 
    // When a Component expands, adjust the heights of the other Components
    // to be just enough to accommodate their headers.
    // The expanded Component receives the only flex value, and so gets all remaining space.
    onBeforeComponentExpand: function(toExpand) {
        var me = this,
            owner = me.owner,
            multi = me.multi,
            moveToTop = !multi && !me.animate && me.activeOnTop,
            expanded,
            previousValue, anim;
 
        if (!me.processing) {
            me.processing = true;
            previousValue = owner.deferLayouts;
            owner.deferLayouts = true;
            
            if (!multi) {
                expanded = me.getExpanded()[0];
 
                if (expanded && expanded !== toExpand) {
                    anim = expanded.$layoutAnim;
 
                    // If the item is animating, finish it.
                    if (anim) {
                        anim.jumpToEnd();
                    }
 
                    expanded.collapse();
                }
            }
 
            if (moveToTop) {
                // Prevent extra layout when moving the item
                Ext.suspendLayouts();
                owner.insert(0, toExpand);
                Ext.resumeLayouts();
            }
 
            owner.deferLayouts = previousValue;
            me.processing = false;
        }
    },
 
    onBeforeComponentCollapse: function(comp) {
        var me = this,
            owner = me.owner,
            toExpand,
            expanded,
            previousValue;
 
        if (me.owner.items.getCount() === 1) {
            // do not allow collapse if there is only one item
            return false;
        }
 
        if (!me.processing) {
            me.processing = true;
            previousValue = owner.deferLayouts;
            owner.deferLayouts = true;
            toExpand = comp.next() || comp.prev();
 
            // If we are allowing multi, and the "toCollapse" component
            // is NOT the only expanded Component, then ask the box layout
            // to collapse it to its header.
            if (me.multi) {
                expanded = me.getExpanded();
 
                // If the collapsing Panel is the only expanded one, expand the following Component.
                // All this is handling fill: true, so there must be at least one expanded,
                if (expanded.length === 1) {
                    toExpand.expand();
                }
 
            }
            else if (toExpand) {
                toExpand.expand();
            }
 
            owner.deferLayouts = previousValue;
            me.processing = false;
        }
    },
 
    onComponentShow: function(comp) {
        this.onBeforeComponentExpand(comp);
    },
 
    onAdd: function(item) {
        var me = this;
 
        me.callParent(arguments);
 
        if (item.collapseMode === 'placeholder') {
            item.collapseMode = me.panelCollapseMode;
        }
 
        item.collapseDirection = item.headerPosition;
 
        // If we add to an accordion after its is has run once we need to make sure
        // new items are collapsed on entry. The item is also in the collection now,
        // so only collapse it if we have more than 1.
        if (me.layoutCount && !me.multi && me.owner.items.getCount() > 1) {
            // If we get here, we must already have something expanded, so we don't
            // want to react here.
            me.processing = true;
            item.collapse();
            me.processing = false;
        }
    },
 
    onRemove: function(panel, destroying) {
        var me = this,
            item;
            
        me.callParent(arguments);
        
        if (!me.owner.destroying && !me.multi && !panel.collapsed) {
            item = me.owner.items.first();
 
            if (item) {
                item.expand();
            }
        }
    },
 
    getExpanded: function(explicitCheck) {
        var items = this.owner.items.items,
            len = items.length,
            i = 0,
            out = [],
            add,
            item;
 
        for (; i < len; ++i) {
            item = items[i];
 
            if (!item.hidden) {
                if (explicitCheck) {
                    add = item.hasOwnProperty('collapsed') && item.collapsed === false;
                }
                else {
                    add = !item.collapsed;
                }
 
                if (add) {
                    out.push(item);
                }
            }
        }
 
        return out;
 
    },
 
    // No need to run an extra layout since everything has already achieved the
    // desired size when using an accordion.
    afterCollapse: Ext.emptyFn,
 
    afterExpand: Ext.emptyFn
});