/**
 * The grouping panel used by {@link Ext.grid.plugin.GroupingPanel}
 */
Ext.define('Ext.grid.plugin.grouping.Panel', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.groupingpanel',
 
    requires: [
        'Ext.grid.plugin.grouping.Column',
        'Ext.grid.plugin.grouping.DragZone',
        'Ext.grid.plugin.grouping.DropZone',
        'Ext.layout.container.Column'
    ],
 
    mixins: [
        'Ext.mixin.FocusableContainer'
    ],
 
    isGroupingPanel: true,
 
    position: 'top',
    border: false,
    enableFocusableContainer: true,
 
    // the column header container has a weight of 100 so we want to dock it before that.
    weight: 50,
    height: 'auto',
    layout: 'column',
 
    childEls: ['innerCt', 'targetEl'],
 
    cls: Ext.baseCSSPrefix + 'grid-group-panel-body',
    hintTextCls: Ext.baseCSSPrefix + 'grid-group-panel-hint',
 
    config: {
        grid: null,
        store: null,
        columnConfig: {
            xtype: 'groupingpanelcolumn'
        }
    },
 
    keyEventRe: /^key/,
 
    groupingPanelText: 'Drag a column header here to group by that column',
    showGroupingPanelText: 'Show Group By Panel',
    hideGroupingPanelText: 'Hide Group By Panel',
    clearGroupText: 'Clear Group',
    sortAscText: 'Sort Ascending',
    sortDescText: 'Sort Descending',
    moveLeftText: 'Move left',
    moveRightText: 'Move right',
    moveBeginText: 'Move to beginning',
    moveEndText: 'Move to end',
    removeText: 'Remove',
 
    ascSortIconCls: Ext.baseCSSPrefix + 'grid-group-column-sort-icon-asc',
    descSortIconCls: Ext.baseCSSPrefix + 'grid-group-column-sort-icon-desc',
    groupingPanelIconCls: Ext.baseCSSPrefix + 'grid-group-panel-icon',
    clearGroupIconCls: Ext.baseCSSPrefix + 'grid-group-panel-clear-icon',
 
    initComponent: function() {
        var me = this;
 
        Ext.apply(me, {
            header: {
                dock: 'left',
                title: {
                    hidden: true
                },
                padding: 0,
                tools: [{
                    type: 'gear',
                    handler: me.showPanelMenu,
                    scope: me
                }]
            }
        });
 
        me.callParent(arguments);
    },
 
    doDestroy: function() {
        var me = this;
 
        // eslint-disable-next-line max-len
        Ext.destroyMembers(me, 'infoEl', 'dragZone', 'dropZone', 'contextMenu', 'panelListeners', 'columnListeners', 'storeListeners', 'columnMenu', 'panelMenu');
 
        me.setGrid(null);
 
        me.callParent();
    },
 
    afterRender: function() {
        var me = this,
            el = me.getEl();
 
        me.callParent();
 
        me.dragZone = new Ext.grid.plugin.grouping.DragZone(me);
        me.dropZone = new Ext.grid.plugin.grouping.DropZone(me);
 
        me.panelListeners = me.mon(el, {
            contextmenu: me.showPanelMenu,
            scope: me,
            destroyable: true
        });
 
        me.columnListeners = me.mon(el, {
            delegate: '.' + Ext.baseCSSPrefix + 'grid-group-column',
            click: me.handleColumnEvent,
            keypress: me.handleColumnEvent,
            scope: me,
            destroyable: true
        });
 
        me.infoEl = me.innerCt.createChild({
            cls: me.hintTextCls + ' ' + Ext.baseCSSPrefix + 'unselectable',
            html: me.groupingPanelText
        });
        me.setInfoElVisibility();
 
        me.initGroupingColumns();
    },
 
    show: function() {
        var me = this,
            dragZone = me.dragZone,
            dropZone = me.dropZone,
            grid = me.getGrid();
 
        if (dragZone) {
            dragZone.enable();
        }
 
        if (dropZone) {
            dropZone.enable();
        }
 
        me.callParent();
        grid.fireEvent('showgroupingpanel', me);
    },
 
    hide: function() {
        var me = this,
            dragZone = me.dragZone,
            dropZone = me.dropZone,
            grid = me.getGrid();
 
        if (dragZone) {
            dragZone.disable();
        }
 
        if (dropZone) {
            dropZone.disable();
        }
 
        me.callParent();
        grid.fireEvent('hidegroupingpanel', me);
    },
 
    updateGrid: function(grid) {
        var me = this,
            store = null;
 
        Ext.destroy(me.gridListeners);
 
        if (grid) {
            // catch this event to refresh the grouping columns in case one of them changed its name
            me.gridListeners = grid.on({
                reconfigure: me.onGridReconfigure,
                scope: me,
                destroyable: true
            });
 
            store = grid.getStore();
        }
 
        me.setStore(store);
    },
 
    updateStore: function(store) {
        var me = this;
 
        Ext.destroy(me.storeListeners);
 
        if (store) {
            me.storeListeners = store.on({
                groupchange: me.initGroupingColumns,
                groupschange: me.initGroupingColumns,
                scope: me,
                destroyable: true
            });
            me.initGroupingColumns();
        }
    },
 
    onAdd: function() {
        this.setInfoElVisibility();
    },
 
    onRemove: function() {
        this.setInfoElVisibility();
    },
 
    /**
     * The container has an info text displayed inside. This function makes it visible or hidden.
     *
     * @private
     */
    setInfoElVisibility: function() {
        var el = this.infoEl;
 
        if (!el) {
            return;
        }
 
        if (!this.items.length) {
            el.show();
        }
        else {
            el.hide();
        }
    },
 
    handleColumnEvent: function(e) {
        var isKeyEvent = this.keyEventRe.test(e.type),
            fly, cmp;
 
        if ((isKeyEvent && e.getKey() === e.SPACE) || (e.button === 0)) {
            fly = Ext.fly(e.target);
 
            if (fly && (cmp = fly.component) && cmp.isGroupingPanelColumn) {
                this.showColumnMenu(e, cmp);
            }
        }
    },
 
    showColumnMenu: function(e, target) {
        var me = this,
            grid = me.getGrid(),
            menu, options;
 
        Ext.destroy(me.columnMenu);
 
        menu = me.columnMenu = Ext.menu.Manager.get(me.getColumnMenu(target));
 
        options = {
            menu: menu,
            column: target
        };
 
        if (grid.fireEvent('beforeshowgroupingcolumnmenu', me, options) !== false) {
            menu.showBy(target);
            menu.focus();
            grid.fireEvent('showgroupingcolumnmenu', me, options);
        }
        else {
            Ext.destroy(menu);
        }
 
        e.stopEvent();
    },
 
    getColumnMenu: function(target) {
        var me = this,
            items = [],
            owner = target.ownerCt,
            sibling;
 
        items.push({
            text: me.sortAscText,
            direction: 'ASC',
            iconCls: me.ascSortIconCls,
            column: target,
            handler: me.sortColumn
        }, {
            text: me.sortDescText,
            direction: 'DESC',
            iconCls: me.descSortIconCls,
            column: target,
            handler: me.sortColumn
        }, {
            xtype: 'menuseparator'
        }, {
            text: me.removeText,
            handler: Ext.bind(me.removeColumn, me, [target])
        }, {
            text: me.moveLeftText,
            disabled: !(sibling = target.previousSibling()),
            handler: Ext.bind(me.moveColumn, me, [target, sibling, 'before'])
        }, {
            text: me.moveRightText,
            disabled: !(sibling = target.nextSibling()),
            handler: Ext.bind(me.moveColumn, me, [target, sibling, 'after'])
        }, {
            text: me.moveBeginText,
            disabled: !(sibling = target.previousSibling()),
            handler: Ext.bind(me.moveColumn, me, [target, owner.items.first(), 'before'])
        }, {
            text: me.moveEndText,
            disabled: !(sibling = target.nextSibling()),
            handler: Ext.bind(me.moveColumn, me, [target, owner.items.last(), 'after'])
        });
 
        return {
            defaults: {
                scope: me
            },
            items: items
        };
    },
 
    showPanelMenu: function(e, target) {
        var me = this,
            grid = me.getGrid(),
            isKeyEvent = me.keyEventRe.test(e.type),
            menu, options;
 
        Ext.destroy(me.panelMenu);
        target.focus();
 
        menu = me.panelMenu = Ext.menu.Manager.get(me.getPanelMenu());
 
        options = {
            menu: menu
        };
 
        if (grid.fireEvent('beforeshowgroupingpanelmenu', me, options) !== false) {
            if (isKeyEvent) {
                menu.showBy(target);
            }
            else {
                menu.show();
                menu.setPosition(e.getX(), e.getY());
            }
 
            menu.focus();
            grid.fireEvent('showgroupingpanelmenu', me, options);
        }
        else {
            Ext.destroy(menu);
        }
 
        e.stopEvent();
    },
 
    getPanelMenu: function() {
        var me = this,
            items = [],
            groupers = me.getStore().getGroupers();
 
        items.push({
            iconCls: me.groupingPanelIconCls,
            text: me.hideGroupingPanelText,
            handler: me.hide
        }, {
            iconCls: me.clearGroupIconCls,
            text: me.clearGroupText,
            disabled: !groupers || !groupers.length,
            handler: me.clearGrouping
        });
 
        return {
            defaults: {
                scope: me
            },
            items: items
        };
    },
 
    clearGrouping: function() {
        var me = this,
            items = me.items.items,
            length = items.length,
            i, item, column;
 
        Ext.suspendLayouts();
 
        // make column headers visible in the grid
        for (= 0; i < length; i++) {
            item = items[i];
            column = item.getColumn();
 
            if (column) {
                column.show();
            }
        }
 
        me.getStore().group(null);
        me.getHeader().focus();
        Ext.resumeLayouts(true);
    },
 
    sortColumn: function(target) {
        var grouper = target.column.getGrouper();
 
        if (grouper) {
            grouper.setDirection(target.direction);
        }
    },
 
    /**
     * Check if the specified grid column is already added to the panel
     *
     * @param {Ext.grid.column.Column} col 
     */
    isNewColumn: function(col) {
        return this.items.findIndex('idColumn', col.id) < 0;
    },
 
    addColumn: function(config, pos, notify) {
        var me = this,
            colConfig = Ext.apply({}, me.getColumnConfig()),
            newCol;
 
        Ext.suspendLayouts();
 
        newCol = Ext.create(Ext.apply(colConfig, config));
 
        if (pos !== -1) {
            me.insert(pos, newCol);
        }
        else {
            me.add(newCol);
        }
 
        if (notify === true) {
            newCol.focus();
            me.notifyGroupChange();
        }
 
        Ext.resumeLayouts(true);
    },
 
    getColumnPosition: function(column, position) {
        var me = this,
            pos;
 
        if (column.isGroupingPanelColumn) {
            // we have to insert before or after this column
            pos = me.items.indexOf(column);
            pos = (position === 'before') ? pos : pos + 1;
        }
        else {
            pos = -1;
        }
 
        return pos;
    },
 
    moveColumn: function(from, to, position) {
        var me = this;
 
        Ext.suspendLayouts();
 
        if (from !== to) {
            if (position === 'before') {
                me.moveBefore(from, to);
            }
            else {
                me.moveAfter(from, to);
            }
 
            me.notifyGroupChange();
        }
 
        Ext.resumeLayouts(true);
    },
 
    removeColumn: function(column) {
        var me = this,
            col = column.getColumn(),
            sibling = column.nextSibling() || column.previousSibling() || me.getHeader();
 
        Ext.suspendLayouts();
 
        if (col) {
            col.show();
        }
 
        if (sibling) {
            sibling.focus();
        }
 
        me.remove(column, true);
        me.notifyGroupChange();
        Ext.resumeLayouts(true);
    },
 
    showGridColumn: function(col) {
        col.show();
    },
 
    hideGridColumn: function(col) {
        col.hide();
    },
 
    notifyGroupChange: function() {
        var me = this,
            store = me.getStore(),
            items = me.items.items,
            length = items.length,
            groupers = [],
            i, column, grouper;
 
        for (= 0; i < length; i++) {
            column = items[i];
            grouper = column.getGrouper();
 
            if (grouper) {
                groupers.push(grouper);
            }
        }
 
        if (!groupers.length) {
            store.group(null);
        }
        else {
            store.setGroupers(groupers);
        }
    },
 
    onGridReconfigure: function(grid, store) {
        if (store) {
            this.setStore(store);
        }
    },
 
    /**
     * Check if the grid store has groupers and add them to the grouping panel
     */
    initGroupingColumns: function() {
        var me = this,
            grid = me.getGrid(),
            store = me.getStore(),
            groupers = store.getGroupers(),
            length = groupers.length,
            columns = Ext.Array.toValueMap(grid.headerCt.getGridColumns(), 'dataIndex'),
            i, column, grouper;
 
        Ext.suspendLayouts();
 
        // remove all previously created columns
        me.removeAll(true);
 
        for (= 0; i < length; i++) {
            grouper = groupers.items[i];
            column = columns[grouper.getProperty()];
 
            if (column) {
                me.addColumn({
                    header: column.text,
                    idColumn: column.id,
                    grouper: grouper,
                    column: column
                }, -1);
            }
        }
 
        Ext.resumeLayouts(true);
    }
});