/**
 * 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: [{
 *              ptype:          'pivotcellediting',
 *              clicksToEdit:   1,
 *              // define here the pivot updater to use: 'overwrite', 'increment', 'percentage', 'uniform'
 *              defaultUpdater: 'overwrite'
 *          },
 *          matrix: {
 *              aggregate: [{
 *                  dataIndex:  'value',
 *                  header:     'Total',
 *                  aggregator: 'sum',
 *                  // if you want an aggregate dimension to be editable you need to specify its editor
 *                  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(= 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, result;
 
        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;
        }
    }
});