/**
 * This plugin allows inline cell editing of the pivot grid results.
 *
 * This plugin requires editors to be defined on the aggregate dimensions.
 *
 *      {
 *          xtype: 'pivotgrid',
 *          plugins: {
 *              pivotcellediting: {
 *                  clicksToEdit:   1,
 *                  // Can be 'overwrite', 'increment', 'percentage', 'uniform'
 *                  defaultUpdater: 'overwrite'
 *              }
 *          },
 *          matrix: {
 *              aggregate: [{
 *                  dataIndex:  'value',
 *                  header:     'Total',
 *                  aggregator: 'sum',
 *
 *                  // Specify "editor" to edit an aggregate dimension
 *                  editor:     'numberfield'
 *              }]
 *              // ...
 *          }
 *      }
 *
 *  The new value is applied to the records behind that cell using different approaches:
 *
 * - `percentage`: the user fills in a percentage that is applied to each record.
 * - `increment`:  the user fills in a value that is added to each record.
 * - `overwrite`:  the new value filled in by the user overwrites each record.
 * - `uniform`:  replace sum of values with a provided value using uniform distribution
 *
 * More pivot updater types can be defined by extending {@link Ext.pivot.update.Base}.
 *
 * **Note:** Only works when using a {@link Ext.pivot.matrix.Local} matrix on a pivot grid.
 */
Ext.define('Ext.pivot.plugin.CellEditing', {
    extend: 'Ext.grid.plugin.CellEditing',

    alias: 'plugin.pivotcellediting',

    requires: [
        'Ext.pivot.update.Increment',
        'Ext.pivot.update.Overwrite',
        'Ext.pivot.update.Percentage',
        'Ext.pivot.update.Uniform'
    ],

    /**
     * @cfg {String} defaultUpdater
     *
     * Define which pivot updater is used when the cell value changes.
     */
    defaultUpdater: 'uniform',
    /**
     * @cfg {Number/String} defaultValue
     *
     * Define a default value to show when cell editor is active.
     */
    defaultValue: null,

    updater: null,

    init: function(grid) {
        var me = this;

        /**
         * Fires on the pivot grid before updating all result records.
         *
         * @event pivotbeforeupdate
         * @param {Ext.pivot.update.Base} updater Reference to the updater object
         */

        /**
         * Fires on the pivot grid after updating all result records.
         *
         * @event pivotupdate
         * @param {Ext.pivot.update.Base} updater Reference to the updater object
         */

        me.callParent([grid]);

        me.pivot = grid.isPivotGrid ? grid : grid.up('pivotgrid');

        me.mon(me, {
            beforeedit: me.onBeforeEdit,
            // if users are listening to this event too then they should be allowed to change
            // the context value so we run first
            priority: 1000,
            scope: me
        });

        if (!me.pivot.isCellEditingAttached) {
            me.mon(me.pivot, {
                pivotcolumnsbuilt: me.addColumnEditors,
                scope: me
            });
            me.pivot.isCellEditingAttached = true;
        }

        me.updater = Ext.Factory.pivotupdate({ type: me.defaultUpdater });
        me.pivot.relayEvents(me.updater, ['beforeupdate', 'update'], 'pivot');
    },

    destroy: function() {
        var me = this;

        me.pivot.isCellEditingAttached = false;
        me.pivot = me.updater = Ext.destroy(me.updater);

        me.callParent();
    },

    /**
     * Attach column editors when column configs are generated by the pivot grid.
     *
     * @param matrix
     * @param columns
     *
     * @private
     */
    addColumnEditors: function(matrix, columns) {
        var len = columns.length,
            col, i;

        for (i = 0; i < len; i++) {
            col = columns[i];

            if (col.dimension && col.dimension.editor) {
                // it has an aggregate dimension assigned
                col.editor = Ext.clone(col.dimension.editor);
            }

            if (col.columns) {
                this.addColumnEditors(matrix, col.columns);
            }
        }
    },

    /**
     * Check if the cell is editable or not.
     *
     * A cell that doesn't have any pivot result it's not editable, which means that no record
     * is available.
     *
     * @param {Ext.data.Model} record
     * @param {Ext.grid.column.Column} columnHeader
     * @return {Boolean}
     */
    isCellEditable: function(record, columnHeader) {
        return this.callParent([record, columnHeader]) &&
               Boolean(this.getPivotResult(this.getEditingContext(record, columnHeader)));
    },

    onBeforeEdit: function(plugin, context) {
        if (!Ext.isEmpty(this.defaultValue)) {
            context.value = this.defaultValue;
        }

        return this.isCellEditable(context.record, context.column);
    },

    getPivotResult: function(context) {
        var matrix = this.pivot.getMatrix(),
            leftItem = this.pivot.getLeftAxisItem(context.record),
            topItem = this.pivot.getTopAxisItem(context.column);

        return matrix.results.get(
            leftItem ? leftItem.key : matrix.grandTotalKey,
            topItem ? topItem.key : matrix.grandTotalKey
        );
    },

    updateRecords: function(context, value) {
        var pivot = this.pivot,
            matrix = pivot.getMatrix(),
            leftItem = pivot.getLeftAxisItem(context.record),
            topItem = pivot.getTopAxisItem(context.column);

        this.updater.setConfig({
            leftKey: leftItem ? leftItem.key : matrix.grandTotalKey,
            topKey: topItem ? topItem.key : matrix.grandTotalKey,
            matrix: matrix,
            dataIndex: context.column.dimension.dataIndex,
            value: value
        });

        this.updater.update();
    },

    onEditComplete: function(ed, value, startValue) {
        var me = this,
            context = ed.context,
            view, record;

        view = context.view;
        record = context.record;
        context.value = value;

        if (!me.validateEdit(context)) {
            me.editing = false;

            return;
        }

        me.updateRecords(context, value);
        // Changing the record may impact the position
        context.rowIdx = view.indexOf(record);

        me.fireEvent('edit', me, context);

        // We clear down our context here in response to the CellEditor completing.
        // We only do this if we have not already started editing a new context.
        if (me.context === context) {
            me.setActiveEditor(null);
            me.setActiveColumn(null);
            me.setActiveRecord(null);
            me.editing = false;
        }
    }
});
