/**
 * A small abstract class that contains the shared behaviour for any summary
 * calculations to be used in the grid.
 */
Ext.define('Ext.grid.feature.AbstractSummary', {
 
    extend: 'Ext.grid.feature.Feature',
 
    alias: 'feature.abstractsummary',
 
    summaryRowCls: Ext.baseCSSPrefix + 'grid-row-summary',
    summaryRowSelector: '.' + Ext.baseCSSPrefix + 'grid-row-summary',
 
    // High priority rowTpl interceptor which sees summary rows early, and renders them correctly
    // and then aborts the row rendering chain. This will only see action when summary rows
    // are being updated and Table.onUpdate->Table.bufferRender renders the individual
    // updated sumary row.
    summaryRowTpl: {
        fn: function(out, values, parent) {
            // If a summary record comes through the rendering pipeline,
            // render it simply instead of proceeding through the tplchain
            if (values.record.isSummary) {
                this.summaryFeature.outputSummaryRecord(values.record, values, out, parent);
            }
            else {
                this.nextTpl.applyOut(values, out, parent);
            }
        },
        priority: 1000
    },
 
    /**
     * @cfg {Boolean} 
     * True to show the summary row.
     */
    showSummaryRow: true,
 
    // Listen for store updates. Eg, from an Editor.
    init: function() {
        var me = this;
 
        me.view.summaryFeature = me;
        me.rowTpl = me.view.self.prototype.rowTpl;
        me.readDataOptions = {
            recordCreator: me.readSummaryRecord.bind(me)
        };
 
        // Add a high priority interceptor which renders summary records simply
        // This will only see action ona bufferedRender situation where summary records are updated.
        me.view.addRowTpl(me.summaryRowTpl).summaryFeature = me;
 
        // Define on the instance to store info needed by summary renderers.
        me.summaryData = {};
        me.groupInfo = {};
 
        // Cell widths in the summary table are set directly into the cells.
        // There's no <colgroup><col>
        // Some browsers use content box and some use border box when applying the style width
        // of a TD
        if (!me.summaryTableCls) {
            me.summaryTableCls = Ext.baseCSSPrefix + 'grid-item';
        }
 
        // We have been configured with another class. Revert to building the selector
        if (me.hasOwnProperty('summaryRowCls')) {
            me.summaryRowSelector = '.' + me.summaryRowCls;
        }
    },
 
    bindStore: function(grid, store) {
        var me = this,
            reader = store && store.getProxy().getReader();
 
        me.readerListeners = Ext.destroy(me.readerListeners);
 
        if (reader && (me.remoteRoot || reader.getSummaryRootProperty())) {
            me.readerListeners = reader.on({
                scope: me,
                destroyable: true,
                rawdata: me.onReaderRawData
            });
        }
    },
 
    onReaderRawData: function(data) {
        // Invalidate potentially existing summaryRows to force recalculation
        this.summaryRows = null;
        this.readerRawData = data;
    },
 
    /**
     * Toggle whether or not to show the summary row.
     * @param {Boolean} visible True to show the summary row
     * @param fromLockingPartner (private)
     */
    toggleSummaryRow: function(visible, fromLockingPartner) {
        var me = this,
            prev = me.showSummaryRow,
            doRefresh;
 
        visible = visible != null ? !!visible : !me.showSummaryRow;
        me.showSummaryRow = visible;
 
        if (visible && visible !== prev) {
            // If being shown, something may have changed while not visible, so
            // force the summary records to recalculate
            me.updateSummaryRow = true;
        }
 
        // If there is another side to be toggled, then toggle it (as long as we are not
        // already being commanded from that other side);
        // Then refresh the whole arrangement.
        if (me.lockingPartner) {
            if (!fromLockingPartner) {
                me.lockingPartner.toggleSummaryRow(visible, true);
                doRefresh = true;
            }
        }
        else {
            doRefresh = true;
        }
 
        if (doRefresh) {
            me.grid.ownerGrid.getView().refreshView();
        }
    },
 
    createRenderer: function(column, record) {
        var me = this,
            ownerGroup = record.ownerGroup,
            summaryData = ownerGroup ? me.summaryData[ownerGroup] : me.summaryData,
            // Use the column.getItemId() for columns without a dataIndex.
            // The populateRecord method does the same.
            dataIndex = column.dataIndex || column.getItemId();
 
        return function(value, metaData) {
            return column.summaryRenderer
                ? column.summaryRenderer(record.data[dataIndex], summaryData, dataIndex, metaData)
                // For no summaryRenderer, return the field value in the Feature record.
                : record.data[dataIndex];
        };
    },
 
    outputSummaryRecord: function(summaryRecord, contextValues, out) {
        var view = contextValues.view,
            savedRowValues = view.rowValues,
            columns = contextValues.columns || view.headerCt.getVisibleGridColumns(),
            colCount = columns.length,
            i, column,
            // Set up a row rendering values object so that we can call the rowTpl directly
            // to inject the markup of a grid row into the output stream.
            values = {
                view: view,
                record: summaryRecord,
                rowStyle: '',
                rowClasses: [ this.summaryRowCls, this.summaryItemCls ],
                itemClasses: [],
                recordIndex: -1,
                rowId: view.getRowId(summaryRecord),
                columns: columns
            };
 
        // Because we are using the regular row rendering pathway, temporarily swap out
        // the renderer for the summaryRenderer
        for (= 0; i < colCount; i++) {
            column = columns[i];
            column.savedRenderer = column.renderer;
 
            if (column.summaryType || column.summaryRenderer) {
                column.renderer = this.createRenderer(column, summaryRecord);
            }
            else {
                column.renderer = Ext.emptyFn;
            }
        }
 
        // Use the base template to render a summary row
        view.rowValues = values;
        view.self.prototype.rowTpl.applyOut(values, out, parent);
        view.rowValues = savedRowValues;
 
        // Restore regular column renderers
        for (= 0; i < colCount; i++) {
            column = columns[i];
            column.renderer = column.savedRenderer;
            column.savedRenderer = null;
        }
    },
 
    /**
     * Get the summary data for a field.
     * @private
     * @param {Ext.data.Store} store The store to get the data from
     * @param {String/Function} type The type of aggregation. If a function is specified it will
     * be passed to the stores aggregate function.
     * @param {String} field The field to aggregate on
     * @param {Ext.util.Group} group The group from which to calculate the value
     * @return {Number/String/Object} See the return type for the store functions.
     * if the group parameter is `true` An object is returned with a property named for each group
     * who's value is the summary value.
     */
    getSummary: function(store, type, field, group) {
        var isGrouped = !!group,
            item = isGrouped ? group : store;
 
        if (type) {
            if (Ext.isFunction(type)) {
                if (isGrouped) {
                    return item.aggregate(field, type);
                }
                else {
                    return item.aggregate(type, null, false, [field]);
                }
            }
 
            switch (type) {
                case 'count':
                    return item.count();
 
                case 'min':
                    return item.min(field);
 
                case 'max':
                    return item.max(field);
 
                case 'sum':
                    return item.sum(field);
 
                case 'average':
                    return item.average(field);
 
                default:
                    return '';
 
            }
        }
    },
 
    getRawData: function() {
        var data = this.readerRawData;
 
        if (data) {
            return data;
        }
 
        // Synchronous Proxies such as Memory proxy will set keepRawData to true
        // on their Reader instances, and may have been loaded before we were bound
        // to the store. Or the Reader may have been configured with keepRawData: true
        // manually.
        // In these cases, the Reader should have rawData on the instance.
        return this.view.getStore().getProxy().getReader().rawData;
    },
 
    generateSummaryData: function(groupField) {
        var me = this,
            summaryRows = me.summaryRows,
            convertedSummaryRow = {},
            remoteData = {},
            remoteRoot = me.remoteRoot,
            storeReader, reader, rawData, i, len, rows, store;
 
        // Summary rows may have been cached by previous run
        if (!summaryRows) {
            rawData = me.getRawData();
 
            if (!rawData) {
                return;
            }
 
            // Construct a new Reader instance of the same type to avoid munging the one
            // in the store:
            store = me.view.store;
            storeReader = store.getProxy().getReader();
 
            reader = Ext.apply({
                type: storeReader.type  // not a config
            }, storeReader.getConfig());
 
            // Be sure to create a reader for the summaryModel since it may have different
            // field types. For example, a count summary on a date column. That may not be
            // be ideal UX, but there's no clear winner for a summary on a date...
            reader.model = store.getModel().getSummaryModel();
 
            // reset reader root and rebuild extractors to extract summaries data
            if (remoteRoot) {
                reader.summaryRootProperty = remoteRoot;
            }
 
            reader = Ext.Factory.reader(reader);
 
            // At this point summaryRows is still raw data, e.g. XML node
            summaryRows = reader.getSummaryRoot(rawData);
 
            if (summaryRows) {
                rows = reader.extractData(summaryRows, me.readDataOptions);
 
                me.summaryRows = summaryRows = rows;
            }
 
            // By the next time the configuration may change
            reader.destroy();
 
            // We also no longer need the whole raw dataset
            me.readerRawData = null;
        }
 
        if (summaryRows) {
            for (= 0, len = summaryRows.length; i < len; i++) {
                convertedSummaryRow = summaryRows[i];
 
                if (groupField) {
                    remoteData[convertedSummaryRow[groupField]] = convertedSummaryRow;
                }
            }
        }
 
        return groupField ? remoteData : convertedSummaryRow;
    },
 
    readSummaryRecord: function(data, model) {
        var idProp = model.idProperty,
            hasId = data[idProp] != null,
            rec = new model(data);
 
        // By instantiating the Model we get proper field types for the summary record,
        // but since we only want the underlying data, we may need to remove the id it
        // assigns.
        data = rec.data;
 
        if (!hasId) {
            delete data[idProp];
        }
 
        return data;
    },
 
    setSummaryData: function(record, colId, summaryValue, groupName) {
        var summaryData = this.summaryData;
 
        if (groupName) {
            if (!summaryData[groupName]) {
                summaryData[groupName] = {};
            }
 
            summaryData[groupName][colId] = summaryValue;
        }
        else {
            summaryData[colId] = summaryValue;
        }
    },
 
    destroy: function() {
        Ext.destroy(this.readerListeners);
        this.readerRawData = this.summaryRows = null;
 
        this.callParent();
    }
});