/**
 * This class is used to initialize the dimensions defined on the pivot grid leftAxis,
 * topAxis and aggregate.
 */
Ext.define('Ext.pivot.dimension.Item', {
    alternateClassName: [
        'Mz.aggregate.dimension.Item'
    ],
 
    requires: [
        'Ext.pivot.MixedCollection',
        'Ext.pivot.filter.Label',
        'Ext.pivot.filter.Value',
        'Ext.app.bind.Template'
    ],
 
    $configPrefixed: false,
    $configStrict: false,
 
    config: {
        /**
         * @cfg {String} id 
         * Unique id of this dimension.
         */
        id: null,
 
        /**
         * @cfg {String} header (required)
         *
         * This text is visible in the pivot grid in the following cases:
         *
         * - the dimension is defined on the left axis. The pivot grid will generate one grid column per dimension and
         * this header will go into the grid column header.
         *
         * - the dimension is defined on the aggregate. The pivot grid will generate one grid column per dimension per top
         * axis label. If there are at least 2 aggregate dimensions then this header will be visible. When only one is
         * defined the aggregate dimension header is replaced by the top axis label.
         *
         * - if the {@link Ext.pivot.plugin.Configurator Configurator plugin} is used then this header will be visible
         * in the axis panels.
         *
         */
        header: '',
 
        /**
         * @cfg {String} dataIndex (required)
         * The field name on the record from where this dimension extracts data.
         */
        dataIndex: '',
 
        /**
         * @cfg {String} sortIndex 
         * Field name on the record used when sorting this dimension results. Defaults to {@link #dataIndex} if
         * none is specified.
         */
        sortIndex: '',
 
        /**
         * @cfg {Number} [width=100]
         * Default column width when this dimension is a left axis or aggregate dimension.
         * Used by the generated columns.
         */
        width: 100,
 
        /**
         * @cfg {Number} [flex=0]
         * Column flex when this dimension is a left axis or aggregate dimension.
         * Used by the generated columns.
         */
        flex: 0,
 
        /**
         * @cfg {String} [align="left"]
         * Column alignment when this dimension is a left axis or aggregate dimension.
         * Used by the generated columns.
         */
        align: 'left',
 
        /**
         * @cfg {Boolean} [sortable=true]
         * Is this dimension sortable when the pivot is generated?
         */
        sortable: true,
 
        /**
         * @cfg {"ASC"/"DESC"} [direction="ASC"]
         * If this dimension is sortable then this is the type of sorting.
         */
        direction: 'ASC',
 
        /**
         * @cfg {Function} sorterFn 
         * Provide here your own sorting function for this dimension.
         * If none is specified then the defaultSorterFn is used.
         */
        sorterFn: null,
 
        /**
         * @cfg {Boolean} [caseSensitiveSort=true]
         * If this dimension is sortable, should we do a case sensitive sort?
         */
        caseSensitiveSort: true,
 
        /**
         * @cfg {Ext.pivot.filter.Base} filter
         * Provide a filter configuration to filter your axis items.
         * This works only on left/top axis dimensions.
         *
         * Example for a label filter:
         *
         *      {
         *          dataIndex:  'year',
         *          header:     'Year',
         *          filter: {
         *              type:       'label',
         *              operator:   '=',
         *              value:      2012
         *          }
         *      }
         *
         *
         * Example for a value filter:
         *
         *      {
         *          dataIndex:  'year',
         *          header:     'Year',
         *          filter: {
         *              type:       'value',
         *              operator:   'between',
         *              value:      [2012, 2015]
         *          }
         *      }
         *
         *
         * Example for a top 10 value filter:
         *
         *      {
         *          dataIndex:  'year',
         *          header:     'Year',
         *          filter: {
         *              type:           'value',
         *              operator:       'top10',
         *              dimensionId:    'value',   // this is the id of an aggregate dimension
         *              topType:        'items',
         *              topOrder:       'bottom'
         *          }
         *      }
         */
        filter: null,
 
        /**
         * @cfg {String/Function} labelRenderer
         *
         * Callback function or the name of the callback function to execute when labels
         * are generated for this dimension.
         *
         * **Note:** This works when the dimension is used as either left or top axis dimension.
         *
         * Example:
         *
         *      {
         *          xtype: 'pivot',
         *
         *          matrix: {
         *              topAxis: [{
         *                  dataIndex: 'month'
         *                  labelRenderer: function(monthValue){
         *                      return Ext.Date.monthNames[monthValue];
         *                  }
         *              }]
         *
         *              // ...
         *          }
         *      }
         *
         * The above labelRenderer will convert the month value to a textual month name.
         *
         * @param {Mixed} value Value that needs to be formatted
         * @return {String} The label value displayed in the pivot grid
         */
        labelRenderer: null,
 
        /**
         * @cfg {String/Function} renderer
         *
         * Callback function or the name of the callback function that will be attached to the grid column
         * generated for this dimension.
         *
         * **Note:** This works when the dimension is used as either left axis or aggregate dimension.
         *
         * The following example describes how columns are generated by the pivot grid:
         *
         *      {
         *          xtype: 'pivot',
         *
         *          matrix: {
         *              leftAxis: [{
         *                  dataIndex: 'country'
         *              }],
         *
         *              topAxis: [{
         *                  dataIndex: 'year',
         *                  labelRenderer: function(v) {
         *                      return 'Year ' + v;
         *                  }
         *              }],
         *
         *              aggregate: [{
         *                  dataIndex: 'value',
         *                  aggregator: 'sum',
         *                  renderer: function(value, metaData, record, rowIndex, colIndex, store, view){
         *                      metaData.tdCls = (value < 0) ? 'redCls' : 'greenCls';
         *                      return Ext.util.Format(value, '0,000.00');
         *                  }
         *              },{
         *                  dataIndex: 'qty',
         *                  aggregator: 'sum',
         *                  renderer: function(value, metaData, record, rowIndex, colIndex, store, view){
         *                      metaData.tdCls = (value < 0) ? 'redCls' : 'greenCls';
         *                      return Ext.util.Format(value, '0.00');
         *                  }
         *              }]
         *          }
         *      }
         *
         * Let's say that we have records for the years 2015 and 2016. In this scenario the resulting grid will have:
         *
         *  - 1 column for the left axis dimension defined. This column will use the renderer defined on the left
         *  axis dimension
         *  - 4 columns calculated for the top axis results (2) multiplied by the number of aggregate dimensions (2). These columns will
         *  use the renderers defined on the aggregate dimensions and each group column header is generated using
         *  labelRenderer defined on the top axis dimension.
         *
         * Read more about grid column renderers {@link Ext.grid.column.Column#renderer here}.
         *
         */
        renderer: null,
 
        /**
         * @cfg {String} formatter 
         *
         * This formatter will be attached to the grid column generated for this dimension.
         *
         * **Note:** This works when the dimension is used either as a left axis or an aggregate dimension.
         *
         * Read more about grid column formatters {@link Ext.grid.column.Column#formatter here}.
         */
        formatter: null,
 
        /**
         * @cfg {Ext.exporter.file.Style/Ext.exporter.file.Style[]} exportStyle
         *
         * Style used during export by the {@link Ext.pivot.plugin.Exporter exporter plugin}. This style will
         * be applied to the columns generated for the aggregate or left axis dimensions in the exported file.
         *
         * You could define it as a single object that will be used by all exporters:
         *
         *      aggregate: [{
         *          dataIndex: 'price',
         *          header: 'Total',
         *          aggregator: 'sum',
         *          exportStyle: {
         *              format: 'Currency',
         *              alignment: {
         *                  horizontal: 'Right'
         *              },
         *              font: {
         *                  italic: true
         *              }
         *          }
         *      }]
         *
         * You could also define it as an array of objects, each object having a `type` that specifies by
         * which exporter will be used:
         *
         *      aggregate: [{
         *          dataIndex: 'price',
         *          header: 'Total',
         *          aggregator: 'sum',
         *          exportStyle: [{
         *              type: 'html', // used by the `html` exporter
         *              format: 'Currency',
         *              alignment: {
         *                  horizontal: 'Right'
         *              },
         *              font: {
         *                  italic: true
         *              }
         *          },{
         *              type: 'csv', // used by the `csv` exporter
         *              format: 'General'
         *          }]
         *      }]
         *
         *
         * Or you can define it as an array of objects that has:
         *
         * - one object with no `type` key that is considered the style to use by all exporters
         * - objects with the `type` key defined that are exceptions of the above rule
         *
         *
         *      aggregate: [{
         *          dataIndex: 'price',
         *          header: 'Total',
         *          aggregator: 'sum',
         *          exportStyle: [{
         *              // no type defined means this is the default
         *              format: 'Currency',
         *              alignment: {
         *                  horizontal: 'Right'
         *              },
         *              font: {
         *                  italic: true
         *              }
         *          },{
         *              type: 'csv', // only the CSV exporter has a special style
         *              format: 'General'
         *          }]
         *      }]
         *
         */
        exportStyle: null,
 
        /**
         * @cfg {Object} scope 
         *
         * Scope to run all custom functions defined on the dimension item.
         */
        scope: null,
 
        /**
         * @cfg {Function} grouperFn 
         *
         * This function is used when the groups are generated for the axis.
         * It will return the value that will uniquely identify a group on the axis.
         *
         * ie: you have a Date field that you want to group by year.
         * This renderer could return the year from that Date value.
         *
         * The function receives one parameter and that is the record.
         *
         * It will run using Ext.callback so you can also provide a String that resolves to the view controller.
         *
         * @param {Ext.data.Model} record Record used to extract the group value
         * @return {String/Number}
         */
        grouperFn: null,
 
        /**
         * @cfg {String} [blankText="(blank)"]
         * Default text to use when a group name is blank. This value is applied even if you set your own label renderer.
         */
        blankText: '(blank)',
 
        /**
         * @cfg {Boolean} [showZeroAsBlank=false]
         * Should 0 values be displayed as blank? This config is used when
         * this is an aggregate dimension.
         */
        showZeroAsBlank: false,
 
        /**
         * @cfg {String/Function} [aggregator="sum"]
         * This is the function that should be used to aggregate when this is an aggregate dimension.
         *
         * You can either provide a function name available in {@link Ext.pivot.Aggregators} or
         * set your own function.
         *
         * It's probably best to override {@link Ext.pivot.Aggregators} to add you own function
         * and use that function name on this config. This way the stateles pivot will save this value.
         */
        aggregator: 'sum',
 
        /**
         * @cfg {Ext.util.MixedCollection} values
         * Collection of unique values on this dimension; each item has a "value" and a "display".
         */
        values: []
    },
 
    /**
     * @property {Boolean} isAggregate 
     * True to identify a dimension of an aggregate configuration.
     */
    isAggregate:        false,
 
    /**
     * @property {Ext.pivot.matrix.Base} matrix
     * @readonly
     * Reference to the matrix object.
     */
    matrix:             null,
 
    constructor: function(config){
        var me = this;
 
        this.initConfig(config);
 
        if(!me.getId()){
            // generate an internal id used by the matrix 
            me.setId(Ext.id());
        }
 
        //<debug> 
        if(Ext.isEmpty(me.dataIndex)){
            Ext.raise('No dataIndex provided to the dimension!');
        }
        //</debug> 
 
        if(!me.grouperFn){
            me.groupFn = Ext.bind(me.defaultGrouperFn, me);
        }
        if(me.sortable){
            if(!me.sorterFn){
                me.sortFn = Ext.bind(me.defaultSorterFn, me);
            }
        } else {
            me.sortFn = Ext.bind(me.manualSorterFn, me);
        }
        if(Ext.isEmpty(me.getSortIndex())){
            me.setSortIndex(me.getDataIndex());
        }
 
        if(me.isAggregate && !me.getFormatter() && !me.getRenderer()){
            // in most cases the aggregate value is a number 
            me.setRenderer(me.getDefaultFormatRenderer('0,000.00'));
        }
 
        return this.callParent([config]);
    },
 
    destroy: function(){
        this.setConfig({
            values: null,
            grouperFn: null,
            sorterFn: null,
            filter: null,
            renderer: null,
            labelRenderer: null,
            aggregator: null
        });
 
        this.callParent();
    },
    
    /**
     * Returns the serialized dimension data.
     * @return {Object}
     */
    serialize: function(){
        return this.getConfiguration(true);
    },
 
    /**
     * Returns the actual configuration of this dimension.
     *
     * @param {Boolean} [serializable=false] Set to true if the result is serializable and should not include functions
     * @return {Object}
     */
    getConfiguration: function(serializable){
        var me = this,
            cfg = me.getConfig();
 
        delete(cfg.values);
 
        if(cfg.filter){
            cfg.filter = cfg.filter.serialize();
        }
 
        if(serializable && typeof cfg.aggregator === 'function'){
            cfg.aggregator = 'sum';
        }
        if(serializable && typeof cfg.renderer === 'function'){
            cfg.renderer = null;
        }
        if(serializable && typeof cfg.labelRenderer === 'function'){
            cfg.labelRenderer = null;
        }
 
        return cfg;
    },
 
    applyId: function(id){
        return id ? id : Ext.id();
    },
 
    updateExportStyle: function(style){
        if(style && !style.id){
            style.id = this.getId();
        }
    },
 
    applyFilter: function(filter, oldFilter){
        if(filter == null){
            return filter;
        }
 
        if(filter && filter.isFilter){
            filter.parent = this;
            return filter;
        }
 
 
        if(Ext.isObject(filter)){
            Ext.applyIf(filter, {
                type:   'label',
                parent: this
            });
            filter = Ext.Factory.pivotfilter(filter);
        }else{
            filter = false;
        }
 
        return filter;
    },
 
    updateAggregator: function(fn){
        var aggregators = Ext.pivot.Aggregators;
 
        if (Ext.isString(fn) && Ext.isFunction(aggregators[fn])) {
            this.aggregatorFn = Ext.bind(aggregators[fn], aggregators);
        } else {
            this.aggregatorFn = fn || 'sum';
        }
    },
 
    updateGrouperFn: function(fn){
        this.groupFn = (Ext.isFunction(fn) ? Ext.bind(fn, this) : fn);
    },
 
    updateSorterFn: function(fn){
        this.sortFn = (Ext.isFunction(fn) ? Ext.bind(fn, this) : fn);
    },
 
    /**
     * Add unique values available for this dimension. These are used when filtering.
     *
     * @param value
     * @param display
     */
    addValue: function(value, display){
        var values = this.values;
 
        if(!values.getByKey(value)){
            values.add({
                value:      value,
                display:    display
            });
        }
    },
 
    applyValues: function(values, oldValues){
        var ret;
 
        Ext.destroy(oldValues);
 
        if(values && !values.isInstance){
            ret = new Ext.pivot.MixedCollection();
            ret.getKey = function (item) {
                return item.value;
            };
            ret.addAll(values);
            return ret;
        }
        return values;
    },
 
    sortValues: function(){
        if(this.sortable) {
            this.values.sortBy(this.sortFn);
        }
    },
 
    /**
     * Default sorter function used to sort the axis dimensions on the same tree level.
     *
     * @param o1
     * @param o2
     *
     * @return {Number}
     */
    defaultSorterFn: function(o1, o2){
        var me = this,
            s1 = o1.sortValue,
            s2 = o2.sortValue,
            result;
        
        if(s1 instanceof Date){
            s1 = s1.getTime();
        }
        if(s2 instanceof Date){
            s2 = s2.getTime();
        }
        if(!me.caseSensitiveSort){
            s1 = typeof s1 === 'string' ? s1.toUpperCase() : s1;
            s2 = typeof s2 === 'string' ? s2.toUpperCase() : s2;
        }
 
        if(me.matrix.useNaturalSorting) {
            result = me.matrix.naturalSort(s1, s2);
        } else {
            result = (s1 === s2 ? 0 : (s1 < s2 ? -1 : 1));
        }
        
        if(result < 0 && me.direction === 'DESC'){
            return 1;
        }
        if(result > 0 && me.direction === 'DESC'){
            return -1;
        }
        return result;
    },
 
    /**
     * When we have manual sorting then we need to sort the items by the order they appear in the
     * internal `values` collection (unique values for this dimension).
     *
     * @param o1
     * @param o2
     * @return {number}
     * @private
     */
    manualSorterFn: function(o1, o2){
        // sort items by their appearance in the list of values 
        var v = this.values,
            i1 = v ? v.indexOfKey(o1.value) : 0,
            i2 = v ? v.indexOfKey(o2.value) : 0;
 
        return (i1 === i2 ? 0 : (i1 < i2 ? -1 : 1));
    },
 
    /**
     * Builds a renderer function by using the specified format.
     *
     * @param format Could be either a function or a string
     */
    getDefaultFormatRenderer: function(format){
        var me = this;
        
        return function(v){
            var positive;
            
            if(Ext.isEmpty(format)){
                return v;
            }
            
            if(Ext.isFunction(format)){
                return format.apply(me, arguments);
            }
            
            if(!Ext.isNumber(v)) {
                return v;
            }
 
            if(me.isAggregate && v === 0 && me.showZeroAsBlank){
                return '';
            }
            
            positive = (>= 0);
            v = Math.abs(v);
            v = Ext.util.Format.number(v, format);
 
            return positive ? v : '-' + v;
        }
    },
    
    /**
     * Default grouper function used for rendering axis item values.
     * The grouper function can be used to group together multiple items.
     * Returns a group value
     *
     * @param record
     * @return {String/Number/Date}
     */
    defaultGrouperFn: function(record) {
        return record.get(this.dataIndex);
    },
 
    getFormatterFn: function(){
        var me = this,
            format = me.getFormatter(),
            scoped;
 
        if(format){
            scoped = format.indexOf('this.') === 0;
            if(scoped){
                format = format.substring(5);
            }
            format = Ext.app.bind.Template.prototype.parseFormat(format);
            if(scoped){
                format.scope = null;
            }
 
            return function(v){
                return format.format(v, format.scope || me.getScope() || me.matrix.cmp.resolveListenerScope('self.controller') || this);
            }
        }
    },
 
    /**
     * @method
     * This function is used when we summarize the records for a left/top pair.
     *
     * @private
     */
    aggregatorFn: Ext.emptyFn,
    /**
     * @method
     * This function is used when the axis item value is generated. It will either be the defaultGrouperFn or a custom one.
     * It will run using Ext.callback to you can also provide a String that resolves to the view controller.
     *
     * @private
     */
    groupFn: Ext.emptyFn,
    /**
     * @method
     * This function is used for sorting axis items
     *
     * @private
     */
    sortFn: Ext.emptyFn
 
 
});