/**
 * This class manages a drag-drop Dashboard similar to the legacy Ext JS Portal example.
 * The user-directed layout of the Dashboard is preserved the Ext JS `stateful` mechanism
 * to preserve potentially dynamic user sizing and collapsed states as well as order of
 * items in their columns.
 */
Ext.define('Ext.dashboard.Dashboard', {
    extend: 'Ext.panel.Panel',
    xtype: 'dashboard',
 
    requires: [
        'Ext.layout.container.Dashboard',
        'Ext.dashboard.DropZone',
        'Ext.dashboard.Column',
        'Ext.dashboard.Part'
    ],
 
    isDashboard: true,
 
    cls: Ext.baseCSSPrefix + 'dashboard',
 
    bodyCls: Ext.baseCSSPrefix + 'dashboard-body',
 
    defaultType: 'dashboard-column',
 
    scrollable: true,
 
    layout: {
        type: 'dashboard'
    },
 
    stateful: false,
 
    idSeed: 1,
 
    config: {
        /**
         * @cfg {Object} parts
         * An object keyed by `id` for the parts that can be created for this `Dashboard`.
         */
        parts: null
    },
 
    renderConfig: {
        /**
         * @cfg {Number} maxColumns
         * The maximum number of visible columns.
         * @accessor
         */
        maxColumns: 4
    },
 
    /**
     * @cfg {Number[]} columnWidths
     * An array designating the width of columns in your dashboard's default state as described
     * by the {@link #cfg-defaultContent} property. For example:
     *
     *     columnWidths: [
     *        0.35,
     *        0.40,
     *        0.25
     *     ]
     *    
     * As you can see, this array contains the default widths for the 3 columns in the dashboard's 
     * initial view. The column widths should total to an integer value, typically 1 as shown
     * above. When column widths exceed 1, they will be wrapped effectively creating "rows". This
     * means that if your column widths add up to more than 1, you would still want the first few
     * to total 1 to ensure that the first row fills the dashboard space. This applies whenever
     * the column widths extend past an integer value.
     * 
     * **Note:** columnWidths will not be utilized if there is stateful information that 
     * dictates different user saved column widths.
     */
 
    /**
     * @cfg {Object[]} defaultContent
     * An array of {@link Ext.dashboard.Part part} configuration objects that define your
     * dashboard's default state. These should not be confused with component configurations.
     *
     * Each config object should also include:
     *
     * + `type` - The type of {@link Ext.dashboard.Part part} that you want to be generated.
     * + `columnIndex` - The column position in which the {@link Ext.dashboard.Part part} should
     * reside.
     * + `height` - The desired height of the {@link Ext.dashboard.Part part} to be generated.
     *
     * The remaining properties are specific to your part's config object. For example:      
     *
     *       defaultContent: [{
     *           type: 'rss',
     *           columnIndex: 0,
     *           height: 500,
     *           feedUrl: 'http://feeds.feedburner.com/extblog'
     *       }, {
     *           type: 'stockTicker',
     *           columnIndex: 1,
     *           height: 300
     *       }, {
     *           type: 'stocks',
     *           columnIndex: 1,
     *           height: 300
     *       }, {
     *           type: 'rss',
     *           columnIndex: 2,
     *           height: 350,
     *           feedUrl: 'http://rss.cnn.com/rss/edition.rss'
     *       }]
     *
     * Default column widths are defined by {@link #cfg-columnWidths} and not in these
     * part config objects.
     * 
     * **Note:** defaultContent will not be utilized if there is stateful information that 
     * dictates different user saved positioning and componentry.
     */
 
    /**
     * @event validatedrop
     */
 
    /**
     * @event beforedragover
     */
 
    /**
     * @event dragover
     */
 
    /**
     * @event beforedrop
     */
 
    /**
     * @event drop
     */
 
    initComponent: function() {
        var me = this;
 
        me.callParent();
        me.addStateEvents('remove');
    },
 
    applyParts: function(parts, collection) {
        var id, part;
 
        if (!collection) {
            collection = new Ext.util.Collection({
                decoder: Ext.Factory.part
            });
        }
 
        for (id in parts) {
            part = parts[id];
 
            if (Ext.isString(part)) {
                part = {
                    type: part
                };
            }
 
            part.id = id;
            part.dashboard = this;
            collection.add(part);
        }
 
        return collection;
    },
 
    /**
     * @private
     */
    getPart: function(type) {
        var parts = this.getParts();
 
        return parts.getByKey(type);
    },
 
    addNew: function(type, columnIndex, beforeAfter) {
        var me = this,
            part = me.getPart(type);
 
        part.displayForm(null, null, function(config) {
            config.type = type;
            me.addView(config, columnIndex, beforeAfter);
        });
    },
 
    addView: function(instance, columnIndex, beforeAfter) {
        var me = this,
            // We are only concerned with columns (ignore splitters).
            items = me.query('dashboard-column'),
            count = items.length,
            index = columnIndex || 0,
            view = instance.id ? instance : me.createView(instance),
            columnWidths = me.columnWidths,
            column;
 
        if (!count) {
            // if the layout is empty, assert a full initially
            column = me.add(0, me.createColumn({
                columnWidth: (Ext.isArray(columnWidths) ? columnWidths[0] : 1)
            }));
 
            items = [column];
            count = 1;
        }
 
        if (index >= count) {
            index = count - 1;
            beforeAfter = 1; // after
        }
 
        if (!beforeAfter) {
            column = items[index];
 
            if (column) {
                return column.add(view);
            }
        }
 
        if (beforeAfter > 0) {
            // after...
            ++index;
        }
 
        column = me.createColumn();
 
        if (columnWidths) {
            column.columnWidth = columnWidths[index] || (columnWidths[index] = 1);
        }
 
        if (!column.items) {
            column.items = [];
        }
 
        column.items.push(view);
        column = me.add(column);
 
        return column.items.first();
    },
 
    createColumn: function(config) {
        var cycle = this.cycleLayout;
 
        return Ext.apply({
            items: [],
            bubbleEvents: ['add', 'childmove', 'resize'],
            listeners: {
                expand: cycle,
                collapse: cycle,
                scope: this
            }
        }, config);
    },
 
    createView: function(config) {
        var me = this,
            type = config.type,
            part = me.getPart(type),
            view = part.createView(config);
 
        if (!view.id) {
            view.id = me.id + '_' + type + (me.idSeed++);
        }
 
        view.bubbleEvents = Ext.Array.from(view.bubbleEvents).concat(['expand', 'collapse']);
        view.stateful = me.stateful;
 
        view.listeners = {
            removed: this.onItemRemoved,
            scope: this
        };
 
        return view;
    },
 
    initEvents: function() {
        this.callParent();
 
        this.dd = new Ext.dashboard.DropZone(this, this.dropConfig);
    },
 
    /**
     * Readjust column/splitter heights for collapsing child panels
     * @private
     */
    cycleLayout: function() {
        this.updateLayout();
    },
 
    doDestroy: function() {
        if (this.dd) {
            Ext.destroy(this.dd);
        }
 
        this.callParent();
    },
 
    //-------------------------------------------------
    // State and Item Persistence
 
    applyState: function(state) {
        delete state.items;
 
        this.callParent([state]);
 
        // eslint-disable-next-line vars-on-top
        var me = this,
            columnWidths = state.columnWidths,
            items = me.items.items,
            length = items.length,
            columnLength = columnWidths ? columnWidths.length : 0,
            i;
 
        // Splitters have not been inserted so the length is sans-splitter
        if (columnLength) {
            me.columnWidths = [];
 
            for (= 0; i < length; ++i) {
                me.columnWidths.push(
                    items[i].columnWidth = (< columnLength) ? columnWidths[i] : (1 / length)
                );
            }
        }
    },
 
    getState: function() {
        var me = this,
            columnWidths = [],
            items = me.items.items,
            state = me.callParent() || {},
            length = items.length,
            i, item;
 
        for (= 0; i < length; ++i) {
            if (!(item = items[i]).isSplitter) {
                columnWidths.push(item.columnWidth);
            }
        }
 
        state.idSeed = me.idSeed;
        state.items = me.serializeItems();
 
        // only overwrite column widths if they are defined in the state
        if (columnWidths.length) {
            state.columnWidths = me.columnWidths = columnWidths;
        }
        // no column widths are defined from the state, let's remove it so any defaults
        // can be used instead
        else {
            delete state.columnWidths;
        }
 
        return state;
    },
 
    initItems: function() {
        var me = this,
            defaultContent = me.defaultContent,
            state;
 
        if (me.stateful) {
            state = Ext.state.Manager.get(me.getStateId());
            defaultContent = (state && state.items) || defaultContent;
        }
 
        if (!me.items && defaultContent) {
            me.items = me.deserializeItems(defaultContent);
        }
 
        me.callParent();
    },
 
    deserializeItems: function(serialized) {
        var me = this,
            length = serialized.length,
            columns = [],
            columnWidths = me.columnWidths,
            maxColumns = me.getMaxColumns(),
            column, columnIndex, columnWidth, i, item, partConfig;
 
        for (= 0; i < length; ++i) {
            partConfig = serialized[i];
            columnIndex = Math.min(partConfig.columnIndex || 0, maxColumns - 1);
            delete partConfig.columnIndex;
 
            if (!(column = columns[columnIndex])) {
                columns[columnIndex] = column = me.createColumn();
 
                columnWidth = columnWidths && columnWidths[columnIndex];
 
                if (columnWidth) {
                    column.columnWidth = columnWidth;
                }
            }
 
            item = me.createView(partConfig);
            column.items.push(item);
        }
 
        for (= 0, length = columns.length; i < length; ++i) {
            column = columns[i];
 
            if (!column.columnWidth) {
                column.columnWidth = 1 / length;
            }
        }
 
        return columns;
    },
 
    serializeItem: function(item) {
        return Ext.apply({
            type: item.part.id,
            id: item.id,
            columnIndex: item.columnIndex
        }, item._partConfig);
    },
 
    serializeItems: function() {
        var me = this,
            items = me.items.items,
            length = items.length,
            ret = [],
            columnIndex = 0,
            child, childItems, i, item, j, k;
 
        for (= 0; i < length; ++i) {
            item = items[i];
 
            if (!item.isSplitter) {
                childItems = item.items.items;
 
                for (= 0, k = childItems.length; j < k; ++j) {
                    child = childItems[j];
                    child.columnIndex = columnIndex;
                    ret.push(me.serializeItem(child));
                }
 
                ++columnIndex;
            }
        }
 
        return ret;
    },
 
    onItemRemoved: function(item, column) {
        // Removing items from a Dashboard is a persistent action, so we must remove the
        // state data for it or leak it.
        if (item.stateful && !item.isMoving) {
            Ext.state.Manager.clear(item.getStateId());
        }
    }
});