/**
 * @private
 */
Ext.define('Ext.layout.container.boxOverflow.Menu', {
    extend: 'Ext.layout.container.boxOverflow.None',
    alternateClassName: 'Ext.layout.boxOverflow.Menu',
 
    alias: [
        'box.overflow.menu',
        'box.overflow.Menu' // capitalized for 4.x compat
    ],
 
    requires: [
        'Ext.toolbar.Separator',
        'Ext.button.Button'
    ],
 
    /**
     * @property {String} noItemsMenuText
     * HTML fragment to render into the toolbar overflow menu if there are no items to display
     */
    noItemsMenuText: '<div class="' + Ext.baseCSSPrefix +
                     'toolbar-no-items" role="menuitem">(None)</div>',
 
    menuCls: Ext.baseCSSPrefix + 'box-menu',
 
    /**
     * The CSS class that gets added to toolbar items that have been
     * overflowed to the overflow menu.
     * 
     * @private
     * @since 7.5.0
     */
    menuItemOverflowedCls: Ext.baseCSSPrefix + 'menu-item-overflowed',
 
    constructor: function(config) {
        var me = this;
 
        me.callParent([config]);
 
        /**
         * @property {Array} menuItems
         * Array of all items that are currently hidden and should go into the dropdown menu
         */
        me.menuItems = [];
    },
 
    beginLayout: function(ownerContext) {
        this.callParent([ownerContext]);
 
        // Before layout, we need to re-show all items which we may have hidden due to a
        // previous overflow...
        this.clearOverflow(ownerContext);
    },
 
    beginLayoutCycle: function(ownerContext, firstCycle) {
        this.callParent([ownerContext, firstCycle]);
 
        if (!firstCycle) {
            // if we are being re-run, we need to clear any overflow from the last run and
            // recache the childItems collection
            this.clearOverflow(ownerContext);
 
            this.layout.cacheChildItems(ownerContext);
        }
    },
 
    onRemove: function(comp) {
        Ext.Array.remove(this.menuItems, comp);
    },
 
    clearItem: function(comp) {
        var menu = comp.menu;
 
        if (comp.isButton && menu) {
            // If the button had a menu, forcibly set it
            // again so that the ownerCmp is reset correctly
            // and is no longer pointing at the overflow
            comp.setMenu(menu, false);
        }
    },
 
    // We don't define a prefix in menu overflow.
    getSuffixConfig: function() {
        var me = this,
            layout = me.layout,
            owner = layout.owner,
            oid = owner.id;
 
        /**
         * @private
         * @property {Ext.menu.Menu} menu
         * The expand menu - holds items for every item that cannot be shown
         * because the container is currently not large enough.
         */
        me.menu = new Ext.menu.Menu({
            listeners: {
                scope: me,
                beforeshow: me.beforeMenuShow
            }
        });
 
        /**
         * @private
         * @property {Ext.button.Button} menuTrigger
         * The expand button which triggers the overflow menu to be shown
         */
        me.menuTrigger = new Ext.button.Button({
            id: oid + '-menu-trigger',
            cls: me.menuCls + '-after ' + Ext.baseCSSPrefix + 'toolbar-item',
            plain: owner.usePlainButtons,
            // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
            ownerCt: owner,
            ownerLayout: layout,
            iconCls: Ext.baseCSSPrefix + me.getOwnerType(owner) + '-more-icon',
            ui: owner.defaultButtonUI || 'default',
            menu: me.menu,
            // Menu will be empty when we're showing it because we populate items after
            showEmptyMenu: true,
            getSplitCls: function() {
                return '';
            }
        });
 
        return me.menuTrigger.getRenderTree();
    },
 
    getOverflowCls: function(direction) {
        return this.menuCls + '-body-' + direction;
    },
 
    handleOverflow: function(ownerContext) {
        var me = this,
            layout = me.layout;
 
        me.showTrigger(ownerContext);
 
        // Center the menuTrigger button only if we are not vertical.
        if (layout.direction !== 'vertical') {
            me.menuTrigger.setLocalY(
                (ownerContext.state.boxPlan.maxSize - me.menuTrigger[layout.names.getHeight]()) / 2
            );
        }
 
        return {
            reservedSpace: me.triggerTotalWidth
        };
    },
 
    /**
     * Finishes the render operation of the trigger Button.
     * @private
     */
    captureChildElements: function() {
        var me = this,
            menuTrigger = me.menuTrigger,
            names = me.layout.names;
 
        // The rendering flag is set when getRenderTree is called which we do
        // when returning markup string for the owning layout's "suffix"
        if (menuTrigger.rendering) {
            menuTrigger.finishRender();
            me.triggerTotalWidth = menuTrigger[names.getWidth]() +
                                   menuTrigger.el.getMargin(names.parallelMargins);
        }
    },
 
    /**
     * @private
     * Called by the layout, when it determines that there is no overflow.
     * Also called as an interceptor to the layout's onLayout method to reshow
     * previously hidden overflowing items.
     */
    clearOverflow: function(ownerContext) {
        var me = this,
            items = me.menuItems,
            length = items.length,
            owner = me.layout.owner,
            asLayoutRoot = owner._asLayoutRoot,
            item, i;
 
        owner.suspendLayouts();
        me.captureChildElements();
        me.hideTrigger();
        owner.resumeLayouts();
 
        for (= 0; i < length; i++) {
            item = items[i];
 
            // What we are doing here is preventing the layout bubble from invalidating our
            // owner component. We need just the button to be added to the layout run.
            item.suspendLayouts();
            item.removeCls(me.menuItemOverflowedCls);
            me.clearItem(item);
            item.resumeLayouts(asLayoutRoot);
        }
 
        items.length = 0;
    },
 
    /**
     * @private
     * Shows the overflow trigger when enableOverflow is set to true and the items
     * in the layout are too wide to fit in the space available
     */
    showTrigger: function(ownerContext) {
        var me = this,
            layout = me.layout,
            owner = layout.owner,
            names = layout.names,
            startProp = names.x,
            sizeProp = names.width,
            plan = ownerContext.state.boxPlan,
            available = plan.targetSize[sizeProp],
            childItems = ownerContext.childItems,
            menuTrigger = me.menuTrigger,
            menuItems = me.menuItems,
            childContext, comp, i, props, len;
 
        // We don't want the menuTrigger.show to cause owner's layout to be invalidated, so
        // we force just the button to be invalidated and added to the current run.
        menuTrigger.suspendLayouts();
        menuTrigger.show();
        menuTrigger.resumeLayouts(me._asLayoutRoot);
 
        available -= me.triggerTotalWidth;
 
        owner.suspendLayouts();
 
        // Hide all items which are off the end, and store them to allow them to be restored
        // before each layout operation.
        for (= 0, len = menuItems.length; i < len; ++i) {
            me.clearItem(menuItems[i]);
        }
 
        menuItems.length = 0;
 
        for (= 0, len = childItems.length; i < len; i++) {
            childContext = childItems[i];
            props = childContext.props;
 
            if (props[startProp] + props[sizeProp] > available) {
                comp = childContext.target;
                me.menuItems.push(comp);
                comp.addCls(me.menuItemOverflowedCls);
            }
        }
 
        owner.resumeLayouts();
    },
 
    /**
     * @private
     */
    hideTrigger: function() {
        var menuTrigger = this.menuTrigger;
 
        if (menuTrigger) {
            menuTrigger.hide();
        }
    },
 
    /**
     * @private
     * Called before the overflow menu is shown. This constructs the menu's items,
     * caching them for as long as it can.
     */
    beforeMenuShow: function(menu) {
        var me = this,
            items = me.menuItems,
            i = 0,
            len = items.length,
            item,
            prev,
            needsSep = function(group, prev) {
                return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
            };
 
        menu.suspendLayouts();
        menu.removeAll(false);
 
        for (; i < len; i++) {
            item = items[i];
 
            // Do not show a separator as a first item
            if (!&& (item instanceof Ext.toolbar.Separator)) {
                continue;
            }
 
            if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
                menu.add('-');
            }
 
            me.addComponentToMenu(menu, item);
            prev = item;
        }
 
        // put something so the menu isn't empty if no compatible items found
        if (menu.items.length < 1) {
            menu.add(me.noItemsMenuText);
        }
 
        menu.resumeLayouts();
    },
 
    /**
     * @private
     * Returns a menu config for a given component. This config is used to create a menu item
     * to be added to the expander menu
     * @param {Ext.Component} component The component to create the config for
     * @param {Boolean} hideOnClick Passed through to the menu item
     */
    createMenuConfig: function(component, hideOnClick) {
        var config = Ext.apply({}, component.initialConfig),
            group = component.toggleGroup;
 
        Ext.copy(config, component, [
            'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu', 'tabIndex'
        ]);
 
        Ext.applyIf(config, {
            hideOnClick: hideOnClick,
            destroyMenu: false,
            listeners: null
        });
 
        config.text = component.overflowText || component.text;
        config.masterComponent = component;
 
        // Clone must have same value, and must sync original's value on change
        if (component.isFormField) {
            config.value = component.getValue();
 
            // If the component is a Checkbox/Radio field we replace the config with
            // a menucheckitem so it will give the Menu a better look and feel.
            // See additional information on the #addComponentToMenu method below.
            if (component instanceof Ext.form.field.Checkbox) {
                config = {
                    xtype: 'menucheckitem',
                    group: component.isRadio ? component.name + '_clone' : undefined,
                    text: component.boxLabel || component.fieldLabel,
                    name: component.name,
                    masterComponent: component,
                    checked: component.getValue(),
                    hideOnClick: false,
                    checkChangeDisabled: true
                };
            }
 
            // Sync the original component's value when the clone changes value.
            // This intentionally overwrites any developer-configured change listener on the clone.
            // That's because we monitor the clone's change event, and sync the
            // original field by calling setValue, so the original field's change
            // event will still fire.
            config.listeners = {
                change: function(c, newVal, oldVal) {
                    c.masterComponent.setValue(newVal);
                }
            };
            // Sync the cloned Component's value when the master changes value.
            component.on('change', function(c, newVal, oldVal) {
                c.overflowClone.setValue(newVal);
            });
        }
 
        // ToggleButtons become CheckItems
        else if (group || component.enableToggle) {
            Ext.apply(config, {
                hideOnClick: false,
                group: group,
                checked: component.pressed,
                handler: function(item, e) {
                    item.masterComponent.onClick(e);
                }
            });
        }
 
        // Buttons may have their text or icon changed - this must be propagated
        // to the clone in the overflow menu
        if (component.isButton && !component.changeListenersAdded) {
            component.on({
                textchange: this.onButtonAttrChange,
                iconchange: this.onButtonAttrChange,
                toggle: this.onButtonToggle
            });
            component.changeListenersAdded = true;
        }
 
        // Adding additional listeners
        component.on({
            enable: this.onComponentStatusChange,
            disable: this.onComponentStatusChange
        });
 
        // Typically margins are used to separate items in a toolbar
        // but don't really make a lot of sense in a menu, so we strip
        // them out here.
        delete config.margin;
        delete config.ownerCt;
        delete config.xtype;
        delete config.id;
        delete config.itemId;
 
        return config;
    },
 
    onButtonAttrChange: function(btn) {
        var clone = btn.overflowClone;
 
        clone.suspendLayouts();
        clone.setText(btn.text);
        clone.setIcon(btn.icon);
        clone.setIconCls(btn.iconCls);
        clone.resumeLayouts(true);
    },
 
    onButtonToggle: function(btn, state) {
        // Keep the clone in sync with the original if necessary
        if (btn.overflowClone.checked !== state) {
            btn.overflowClone.setChecked(state);
        }
    },
 
    onComponentStatusChange: function(cmp) {
        var clone = cmp.overflowClone;
 
        if (clone) {
            clone.setDisabled(cmp.disabled);
        }
    },
 
    /**
     * @private
     * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup
     * are added individually.
     * @param {Ext.menu.Menu} menu The menu to add to
     * @param {Ext.Component} component The component to add
     * TODO: Implement overrides in Ext.layout.container.boxOverflow which create overrides
     * for SplitButton, Button, ButtonGroup, and TextField. And a generic one for Component
     * which create clones suitable for use in an overflow menu.
     */
    addComponentToMenu: function(menu, component) {
        var me = this,
            i, items, iLen;
 
        // No equivalent to fill, skip it
        if (component instanceof Ext.toolbar.Fill) {
            return;
        }
        // Separator maps to MenuSeparator
        else if (component instanceof Ext.toolbar.Separator) {
            menu.add('-');
        }
        else if (component.overflowClone) {
            menu.add(component.overflowClone);
        }
        // Other types...
        else if (component.isComponent) {
            if (component.isXType('splitbutton')) {
                component.overflowClone = menu.add(me.createMenuConfig(component, true));
 
            }
            else if (component.isXType('button')) {
                component.overflowClone = menu.add(me.createMenuConfig(component, !component.menu));
 
            }
            else if (component.isXType('buttongroup')) {
                items = component.items.items;
                iLen = items.length;
 
                for (= 0; i < iLen; i++) {
                    me.addComponentToMenu(menu, items[i]);
                }
                // If the component is a CheckBox/Radio field, we are supposed to
                // have a menucheckitem that will replace the original component.
                // Because of that, we need to add a value getter/setter and an event listener that
                // will fire the change event on click, making the menuitem behave as a 
                // checkbox/radio field would have.
            }
            else if (component.isCheckbox) {
                component.overflowClone = menu.add(me.createMenuConfig(component));
 
                Ext.apply(component.overflowClone, {
                    getValue: function() {
                        return component.overflowClone.checked;
                    },
                    setValue: function() {
                        component.overflowClone.setChecked(component.getValue());
                    }
                });
 
                component.overflowClone.on('click', function(item) {
                    item.setChecked(item.masterComponent.isRadio ? true : !item.checked);
                    item.fireEvent('change', item, item.checked);
                });
            }
            else {
                component.overflowClone = menu.add(
                    Ext.create(Ext.getClassName(component), me.createMenuConfig(component))
                );
            }
        }
    },
 
    destroy: function() {
        Ext.destroy(this.menu, this.menuTrigger);
 
        this.callParent();
    }
});