/**
 * Pivot Simlet does remote pivot calculations.
 * Filtering the pivot results doesn't work.
 */
Ext.define('Ext.ux.ajax.PivotSimlet', {
    extend: 'Ext.ux.ajax.JsonSimlet',
    alias: 'simlet.pivot',
 
    lastPost: null, // last Ajax params sent to this simlet
    lastResponse: null, // last JSON response produced by this simlet
    keysSeparator: '',
    grandTotalKey: '',
 
    doPost: function(ctx) {
        var me = this,
            ret = me.callParent(arguments); // pick up status/statusText
 
        me.lastResponse = me.processData(me.getData(ctx), Ext.decode(ctx.xhr.body));
        ret.responseText = Ext.encode(me.lastResponse);
 
        return ret;
    },
 
    processData: function(data, params) {
        var me = this,
            len = data.length,
            response = {
                success: true,
                leftAxis: [],
                topAxis: [],
                results: []
            },
            leftAxis = new Ext.util.MixedCollection(),
            topAxis = new Ext.util.MixedCollection(),
            results = new Ext.util.MixedCollection(),
            i, j, k, leftKeys, topKeys, item, agg;
 
        me.lastPost = params;
        me.keysSeparator = params.keysSeparator;
        me.grandTotalKey = params.grandTotalKey;
 
        for (= 0; i < len; i++) {
            leftKeys = me.extractValues(data[i], params.leftAxis, leftAxis);
            topKeys = me.extractValues(data[i], params.topAxis, topAxis);
 
            // add record to grand totals
            me.addResult(data[i], me.grandTotalKey, me.grandTotalKey, results);
 
            for (= 0; j < leftKeys.length; j++) {
                // add record to col grand totals
                me.addResult(data[i], leftKeys[j], me.grandTotalKey, results);
 
                // add record to left/top keys pair
                for (= 0; k < topKeys.length; k++) {
                    me.addResult(data[i], leftKeys[j], topKeys[k], results);
                }
            }
 
            // add record to row grand totals
            for (= 0; j < topKeys.length; j++) {
                me.addResult(data[i], me.grandTotalKey, topKeys[j], results);
            }
        }
 
        // extract items from their left/top collections and build the json response
        response.leftAxis = leftAxis.getRange();
        response.topAxis = topAxis.getRange();
 
        len = results.getCount();
 
        for (= 0; i < len; i++) {
            item = results.getAt(i);
            item.values = {};
 
            for (= 0; j < params.aggregate.length; j++) {
                agg = params.aggregate[j];
 
                item.values[agg.id] = me[agg.aggregator](
                    item.records, agg.dataIndex, item.leftKey, item.topKey
                );
            }
 
            delete(item.records);
            response.results.push(item);
        }
 
        leftAxis.clear();
        topAxis.clear();
        results.clear();
 
        return response;
    },
 
    getKey: function(value) {
        var me = this;
 
        me.keysMap = me.keysMap || {};
 
        if (!Ext.isDefined(me.keysMap[value])) {
            me.keysMap[value] = Ext.id();
        }
 
        return me.keysMap[value];
    },
 
    extractValues: function(record, dimensions, col) {
        var len = dimensions.length,
            keys = [],
            j, key, item, dim;
 
        key = '';
 
        for (= 0; j < len; j++) {
            dim = dimensions[j];
            key += (> 0 ? this.keysSeparator : '') + this.getKey(record[dim.dataIndex]);
            item = col.getByKey(key);
 
            if (!item) {
                item = col.add(key, {
                    key: key,
                    value: record[dim.dataIndex],
                    dimensionId: dim.id
                });
            }
 
            keys.push(key);
        }
 
        return keys;
    },
 
    addResult: function(record, leftKey, topKey, results) {
        var item = results.getByKey(leftKey + '/' + topKey);
 
        if (!item) {
            item = results.add(leftKey + '/' + topKey, {
                leftKey: leftKey,
                topKey: topKey,
                records: []
            });
        }
 
        item.records.push(record);
    },
 
    sum: function(records, measure, rowGroupKey, colGroupKey) {
        var length = records.length,
            total = 0,
            i;
 
        for (= 0; i < length; i++) {
            total += Ext.Number.from(records[i][measure], 0);
        }
 
        return total;
    },
 
    avg: function(records, measure, rowGroupKey, colGroupKey) {
        var length = records.length,
            total = 0,
            i;
 
        for (= 0; i < length; i++) {
            total += Ext.Number.from(records[i][measure], 0);
        }
 
        return length > 0 ? (total / length) : 0;
    },
 
    min: function(records, measure, rowGroupKey, colGroupKey) {
        var data = [],
            length = records.length,
            i, v;
 
        for (= 0; i < length; i++) {
            data.push(records[i][measure]);
        }
 
        v = Ext.Array.min(data);
 
        return v;
    },
 
    max: function(records, measure, rowGroupKey, colGroupKey) {
        var data = [],
            length = records.length,
            i, v;
 
        for (= 0; i < length; i++) {
            data.push(records[i][measure]);
        }
 
        v = Ext.Array.max(data);
 
        return v;
    },
 
    count: function(records, measure, rowGroupKey, colGroupKey) {
        return records.length;
    },
 
    variance: function(records, measure, rowGroupKey, colGroupKey) {
        var me = Ext.pivot.Aggregators,
            length = records.length,
            avg = me.avg.apply(me, arguments),
            total = 0,
            i;
 
        if (avg > 0) {
            for (= 0; i < length; i++) {
                total += Math.pow(Ext.Number.from(records[i][measure], 0) - avg, 2);
            }
        }
 
        return (total > 0 && length > 1) ? (total / (length - 1)) : 0;
    },
 
    varianceP: function(records, measure, rowGroupKey, colGroupKey) {
        var me = Ext.pivot.Aggregators,
            length = records.length,
            avg = me.avg.apply(me, arguments),
            total = 0,
            i;
 
        if (avg > 0) {
            for (= 0; i < length; i++) {
                total += Math.pow(Ext.Number.from(records[i][measure], 0) - avg, 2);
            }
        }
 
        return (total > 0 && length > 0) ? (total / length) : 0;
    },
 
    stdDev: function(records, measure, rowGroupKey, colGroupKey) {
        var me = Ext.pivot.Aggregators,
            v = me.variance.apply(me, arguments);
 
        return v > 0 ? Math.sqrt(v) : 0;
    },
 
    stdDevP: function(records, measure, rowGroupKey, colGroupKey) {
        var me = Ext.pivot.Aggregators,
            v = me.varianceP.apply(me, arguments);
 
        return v > 0 ? Math.sqrt(v) : 0;
    }
 
});