/**
 * This matrix allows you to do all the calculations on the backend.
 * This is handy when you have large data sets.
 *
 * Basically this class sends to the specified URL the following configuration object:
 *
 * - leftAxis: array of serialized dimensions on the left axis
 *
 * - topAxis: array of serialized dimensions on the top axis
 *
 * - aggregate: array of serialized dimensions on the aggregate
 *
 * - keysSeparator: the separator used by the left/top items keys. It's the one defined on the matrix
 *
 * - grandTotalKey: the key for the grand total items. It's the one defined on the matrix
 *
 *
 * The expected JSON should have the following format:
 *
 * - success - true/false
 *
 * - leftAxis - array of items that were generated for the left axis. Each item is an
 * object with keys for: key, value, name, dimensionId. If you send back the item name and you also
 * have a renderer defined then the renderer is not called anymore.
 *
 * - topAxis - array of items that were generated for the top axis.
 *
 * - results - array of results for all left/top axis items. Each result is an object
 * with keys for: leftKey, topKey, values. The 'values' object has keys for each
 * aggregate id that was sent to the backend.
 *
 * Example
 *
 *      // let's assume that we have this configuration on the pivot grid
 *      {
 *          xtype:  'pivotgrid',
 *
 *          matrixConfig: {
 *              type:   'remote',
 *              url:    'your-backend-url'
 *          },
 *
 *          aggregate: [{
 *              id:         'agg1',
 *              dataIndex:  'value',
 *              header:     'Sum of value',
 *              aggregator: 'sum'
 *          },{
 *              id:         'agg2',
 *              dataIndex:  'value',
 *              header:     '# records',
 *              aggregator: 'count',
 *              align:      'right',
 *              renderer:   Ext.util.Format.numberRenderer('0')
 *          }],
 *
 *          leftAxis: [{
 *              id:         'person',
 *              dataIndex:  'person',
 *              header:     'Person',
 *              sortable:   false
 *          },{
 *              id:         'country',
 *              dataIndex:  'country',
 *              header:     'Country'
 *          }],
 *
 *          topAxis: [{
 *              id:         'year',
 *              dataIndex:  'year',
 *              header:     'Year'
 *          },{
 *              id:         'month',
 *              dataIndex:  'month',
 *              header:     'Month'
 *          }]
 *      }
 *
 *      // this is the expected result from the server
 *      {
 *          "success": true,
 *          "leftAxis": [{
 *              "key": "john",
 *              "value": "John",
 *              "name": "John",
 *              "dimensionId": "person"
 *          }, {
 *              "key": "john#_#usa",
 *              "value": "USA",
 *              "name": "United States of America",
 *              "dimensionId": "country"
 *          }, {
 *              "key": "john#_#canada",
 *              "value": "Canada",
 *              "name": "Canada",
 *              "dimensionId": "country"
 *          }],
 *          "topAxis": [{
 *              "key": "2014",
 *              "value": "2014",
 *              "name": "2014",
 *              "dimensionId": "year"
 *          }, {
 *              "key": "2014#_#2",
 *              "value": "2",
 *              "name": "February",
 *              "dimensionId": "month"
 *          }],
 *          "results": [{
 *              "leftKey": "grandtotal",
 *              "topKey": "grandtotal",
 *              "values": {
 *                  "agg1": 100,
 *                  "agg2": 20
 *              }
 *          }, {
 *              "leftKey": "john",
 *              "topKey": "grandtotal",
 *              "values": {
 *                  "agg1": 100,
 *                  "agg2": 20
 *              }
 *          }, {
 *              "leftKey": "john#_#usa",
 *              "topKey": "grandtotal",
 *              "values": {
 *                  "agg1": 30,
 *                  "agg2": 7
 *              }
 *          }, {
 *              "leftKey": "john#_#canada",
 *              "topKey": "grandtotal",
 *              "values": {
 *                  "agg1": 70,
 *                  "agg2": 13
 *              }
 *          }, {
 *              "leftKey": "grandtotal",
 *              "topKey": "2014",
 *              "values": {
 *                  "agg1": 100,
 *                  "agg2": 20
 *              }
 *          }, {
 *              "leftKey": "grandtotal",
 *              "topKey": "2014#_#2",
 *              "values": {
 *                  "agg1": 50,
 *                  "agg2": 70
 *              }
 *          }, {
 *              "leftKey": "john",
 *              "topKey": "2014",
 *              "values": {
 *                  "agg1": 100,
 *                  "agg2": 20
 *              }
 *          }, {
 *              "leftKey": "john",
 *              "topKey": "2014#_#2",
 *              "values": {
 *                  "agg1": 100,
 *                  "agg2": 20
 *              }
 *          }, {
 *              "leftKey": "john#_#usa",
 *              "topKey": "2014",
 *              "values": {
 *                  "agg1": 70,
 *                  "agg2": 13
 *              }
 *          }, {
 *              "leftKey": "john#_#usa",
 *              "topKey": "2014#_#2",
 *              "values": {
 *                  "agg1": 70,
 *                  "agg2": 13
 *              }
 *          }, {
 *              "leftKey": "john#_#canada",
 *              "topKey": "2014",
 *              "values": {
 *                  "agg1": 30,
 *                  "agg2": 7
 *              }
 *          }, {
 *              "leftKey": "john#_#canada",
 *              "topKey": "2014#_#2",
 *              "values": {
 *                  "agg1": 30,
 *                  "agg2": 7
 *              }
 *          }]
 *      }
 *
 *
 * It is very important to use the dimension IDs that were sent to the backend
 * instead of creating new ones.
 *
 * This class can also serve as an example for implementing various types of
 * remote matrix.
 *
 *
 */
Ext.define('Ext.pivot.matrix.Remote', {
    alternateClassName: [
        'Mz.aggregate.matrix.Remote'
    ],
 
    extend: 'Ext.pivot.matrix.Base',
    
    alias:  'pivotmatrix.remote',
 
    /**
     * Fires before requesting data from the server side.
     * Return false if you don't want to make the Ajax request.
     *
     * @event beforerequest
     * @param {Ext.pivot.matrix.Remote} matrix Reference to the Matrix object
     * @param {Object} params Params sent by the Ajax request
     */
 
    /**
     * Fires if there was any Ajax exception or the success value in the response was false.
     *
     * @event requestexception
     * @param {Ext.pivot.matrix.Remote} matrix Reference to the Matrix object
     * @param {Object} response The Ajax response object
     */
 
    /**
     * @cfg {String} url 
     *
     * URL on the server side where the calculations are performed.
     */
    url:        '',
    /**
     * @cfg {Number} timeout 
     *
     * The timeout in miliseconds to be used for the server side request.
     * Default to 30 seconds.
     */
    timeout:    3000,
 
    /**
     * @method
     *
     * Template function called before doing the Ajax request
     * You could change the request params in a subclass if you implement this method.
     * Return false if you don't want to make the Ajax request.
     *
     * @param {Object} params 
     */
    onBeforeRequest: Ext.emptyFn,
    
    /**
     * @method
     *
     * Template function called if there was any Ajax exception or the success value
     * in the response was false.
     * You could handle the exception in a subclass if you implement this method.
     *
     * @param {Object} response 
     */
    onRequestException: Ext.emptyFn,
 
    onInitialize: function(){
        var me = this;
        
        me.remoteDelayedTask = new Ext.util.DelayedTask(me.delayedProcess, me);
        
        me.callParent(arguments);
    },
    
    startProcess: function(){
        var me = this;
        
        if(Ext.isEmpty(me.url)){
            // nothing to do 
            return;
        }
        
        me.clearData();
        
        // let's start the process 
        me.fireEvent('start', me);
 
        me.statusInProgress = false;
        
        me.remoteDelayedTask.delay(5);
    },
    
    delayedProcess: function(){
        var me = this,
            leftAxis = [],
            topAxis = [],
            aggregate = [],
            ret, params;
        
        me.leftAxis.dimensions.each(function(item){
            leftAxis.push(item.serialize());
        });
        
        me.topAxis.dimensions.each(function(item){
            topAxis.push(item.serialize());
        });
        
        me.aggregate.each(function(item){
            aggregate.push(item.serialize());
        });
        
        params = {
            keysSeparator:  me.keysSeparator,
            grandTotalKey:  me.grandTotalKey,
            leftAxis:       leftAxis,
            topAxis:        topAxis,
            aggregate:      aggregate
        };
        
        ret = me.fireEvent('beforerequest', me, params);
        
        if(ret !== false){
            if(Ext.isFunction(me.onBeforeRequest)){
                ret = me.onBeforeRequest(params);
            }
        }
        
        if(ret === false){
            me.endProcess();        
        }else{
            // do an Ajax call to the configured URL and fetch the results 
            Ext.Ajax.request({
                url:        me.url,
                timeout:    me.timeout,
                jsonData:   params,
                callback:   me.processRemoteResults,
                scope:      me
            });
        }
    },
    
    /**
     * Ajax request callback
     *
     * @private
     */
    processRemoteResults: function(options, success, response){
        var me = this,
            exception = !success,
            data = Ext.JSON.decode(response.responseText, true);
        
        if(success){
            exception = (!data || !data['success']);
        }
        
        if(exception){
            // handle exception 
            me.fireEvent('requestexception', me, response);
            
            if(Ext.isFunction(me.onRequestException)){
                me.onRequestException(response);
            }
 
            me.endProcess();
            return;
        }
        
        Ext.Array.each(Ext.Array.from(data.leftAxis || []), function(item){
            if(Ext.isObject(item)){
                me.leftAxis.addItem(item);
            }
        });
        
        Ext.Array.each(Ext.Array.from(data.topAxis || []), function(item){
            if(Ext.isObject(item)){
                me.topAxis.addItem(item);
            }
        });
        
        Ext.Array.each(Ext.Array.from(data.results || []), function(item){
            if(Ext.isObject(item)){
                var result = me.results.add(item.leftKey || '', item.topKey || '');
                Ext.Object.each(item.values || {}, result.addValue, result);
            }
        });
        
        me.endProcess();
    }
    
});