/**
 * A plugin for use in grids which use the {@link Ext.grid.selection.SpreadsheetModel spreadsheet} 
 * selection model, with {@link Ext.grid.selection.Model#extensible extensible} configured as 
 * `true` or `"y"`, meaning that the selection may be extended up or down using a draggable 
 * extension handle.
 *
 * This plugin propagates values from the selection into the extension area.
 *
 * If just *one* row is selected, the values in that row are replicated unchanged into the 
 * extension area.
 *
 * If more than one row is selected, the two rows closest to the selected block are taken to
 * provide a numeric difference, and that difference is used to calculate the sequence of values 
 * all the way into the extension area.
 *
 */
Ext.define('Ext.grid.selection.Replicator', {
    extend: 'Ext.plugin.Abstract',
    alias: 'plugin.selectionreplicator',
 
    /**
     * 
     * @property {Ext.grid.column.Column[]} columns
     * An array of the columns encompassed by the selection block. This is gathered before 
     * {@link #replicateSelection} is called, so is available to subclasses which implement their 
     * own {@link #replicateSelection} method.
     */
 
    init: function(grid) {
        this.gridListeners = grid.on({
            beforeselectionextend: this.onBeforeSelectionExtend,
            scope: this,
            destroyable: true
        });
    },
 
    onBeforeSelectionExtend: function(ownerGrid, sel, extension) {
        var columns = this.columns = [];
 
        sel.eachColumn(function(column) {
            columns.push(column);
        });
 
        return this.replicateSelection(ownerGrid, sel, extension);
    },
 
    /**
     * This is the method which is called when the
     * {@link Ext.grid.selection.SpreadsheetModel spreadsheet} selection model's extender handle is
     * dragged and released.
     *
     * It is passed contextual information about the selection and the extension area.
     *
     * Subclass authors may override it to gain access to the event and perform their own data 
     * replication.
     *
     * By default, the selection is extended to encompass the selection area. Returning `false` from
     * this method vetoes that.
     *
     * @param {Ext.panel.Table} ownerGrid The owning grid.
     * @param {Ext.dataview.selection.Selection} sel An object describing the contiguous 
     *        selected area.
     * @param {Object} extension An object describing the type and size of extension.
     * @param {String} extension.type `"rows"` or `"columns"`
     * @param {Ext.grid.Location} extension.start The start (top left) cell of the extension area.
     * @param {Ext.grid.Location} extension.end The end (bottom right) cell of the extension area.
     * @param {number} [extension.columns] The number of columns extended
     *        (-ve means on the left side).
     * @param {number} [extension.rows] The number of rows extended (-ve means on the top side).
     */
    replicateSelection: function(ownerGrid, sel, extension) {
        var me = this,
            columns, colCount, j, column, values, startIdx, endIdx, selFirstRowIdx,
            selLastRowIdx, i, increment, store, record, prevValues, prevValue, selectedRowCount,
            lastTwoRecords, x, y;
 
        // This can only handle extending rows
        if (extension.columns || sel.isColumns) {
            return;
        }
 
        columns = me.columns;
        selFirstRowIdx = sel.getFirstRowIndex();
        selLastRowIdx = sel.getLastRowIndex();
        selectedRowCount = selLastRowIdx - selFirstRowIdx + 1;
        lastTwoRecords = [];
 
        colCount = columns.length;
        store = columns[0].getGrid().getStore();
 
        // Single row, just duplicate values into extension
        if (selectedRowCount === 1) {
            values = me.getColumnValues(sel.view.getStore().getAt(selFirstRowIdx));
        }
        // Multiple rows, take the numeric values from the closest two rows, calculate an array
        // of differences and propagate it
        else {
            values = new Array(colCount);
 
            if (extension.rows < 0) {
                lastTwoRecords = [
                    store.getAt(selFirstRowIdx + 1),
                    store.getAt(selFirstRowIdx)
                ];
            }
            else {
                lastTwoRecords = [
                    store.getAt(selLastRowIdx - 1),
                    store.getAt(selLastRowIdx)
                ];
            }
 
            lastTwoRecords[0] = me.getColumnValues(lastTwoRecords[0]);
            lastTwoRecords[1] = me.getColumnValues(lastTwoRecords[1]);
 
            // The values array will be the differences between all numeric columns in 
            // the selection of the closet two records.
            for (= 0; j < colCount; j++) {
                x = lastTwoRecords[1][j];
                y = lastTwoRecords[0][j];
 
                if (!isNaN(x) && !isNaN(y)) {
                    values[j] = Number(x) - Number(y);
                }
            }
        }
 
        // Loop from end to start of extension area
        if (extension.rows < 0) {
            startIdx = extension.end.recordIndex;
            endIdx = extension.start.recordIndex - 1;
            increment = -1;
        }
        else {
            startIdx = extension.start.recordIndex;
            endIdx = extension.end.recordIndex + 1;
            increment = 1;
        }
 
        // Replicate single selected row
        if (selectedRowCount === 1) {
            for (= startIdx; i !== endIdx; i += increment) {
                record = store.getAt(i);
 
                for (= 0; j < colCount; j++) {
                    column = columns[j];
 
                    if (column.getDataIndex()) {
                        record.set(column.getDataIndex(), values[j]);
                    }
                }
            }
        }
        // Add differences from closest two rows
        else {
            for (= startIdx; i !== endIdx; i += increment) {
                record = store.getAt(i);
                prevValues = me.getColumnValues(store.getAt(- increment));
 
                for (= 0; j < colCount; j++) {
                    column = columns[j];
 
                    if (column.getDataIndex()) {
                        prevValue = prevValues[j];
 
                        if (!isNaN(prevValue)) {
                            record.set(
                                column.getDataIndex(),
                                Ext.coerce(Number(prevValue) + values[j], prevValue)
                            );
                        }
                    }
                }
            }
        }
    },
 
    /**
     * A utility method, which, when passed a record, uses the {@link #columns} property to
     * extract the values of that record which are encompassed by the selection.
     *
     * Note that columns with no {@link Ext.grid.column.Column#dataIndex dataIndex} cannot 
     * yield a value.
     * @param {Ext.data.Model} record The record from which to read values.
     * @return {Mixed[]} The values of the fields used by the selected column range for the 
     * passed record.
     */
    getColumnValues: function(record) {
        var columns = this.columns,
            len = columns.length,
            i,
            column,
            result = new Array(columns.length);
 
        for (= 0; i < len; i++) {
            column = columns[i];
 
            // If there's a dataIndex, get the value
            if (column.getDataIndex()) {
                result[i] = record.get(column.getDataIndex());
            }
        }
 
        return result;
    },
 
    destroy: function() {
        this.gridListeners = Ext.destroy(this.gridListeners);
        this.callParent();
    }
});