/**
 * Base implementation of a filter. It handles common type of filters.
 */
Ext.define('Ext.pivot.filter.Base', {
    alternateClassName: [
        'Mz.aggregate.filter.Abstract'
    ],
 
    alias: 'pivotfilter.base',
 
    mixins: [
        'Ext.mixin.Factoryable'
    ],
 
    /**
     * @cfg {String} [type=abstract]
     *
     * Used when you define a filter on a dimension to set what kind of filter is to be
     * instantiated.
     */
 
    /**
     * @cfg {String} operator
     * @required
     *
     * Operator to use to compare labels/values to this Filter's {@link #value}.
     *
     * The `between` and `not between` operators expect this filter's {@link #value} to be an array with two values.
     *
     * Possible values are:
     *
     *    * `<`
     *    * `<=`
     *    * `=`
     *    * `>=`
     *    * `>`
     *    * `!=`
     *    * `between`
     *    * `not between`
     */
    operator:       null,
 
    /**
     * @cfg {String/Array} value (required)
     *
     * Value to filter by. For 'between' and 'not between' operators this should be an array of values.
     */
    value:          null,
 
    /**
     * @cfg {Boolean} [caseSensitive=true]
     *
     * During filtering should we use case sensitive comparison?
     *
     */
    caseSensitive:  true,
 
    /**
     * @property {Ext.pivot.dimension.Item} parent Reference to the parent dimension object.
     * @readonly
     *
     * @private
     */
    parent:         null,
 
    isFilter:       true,
 
    constructor: function(config){
        var me = this,
            fmt = Ext.util.Format;
 
        // define thousand and decimal separator regexp
        me.thousandRe = new RegExp('[' + fmt.thousandSeparator + ']', 'g');
        me.decimalRe = new RegExp('[' + fmt.decimalSeparator + ']');
 
        Ext.apply(this, config);
        return this.callParent([config]);
    },
 
    destroy: function(){
        var me = this;
 
        me.parent = me.thousandRe = me.decimalRe = null;
        me.callParent();
    },
    
    /**
     * Returns the serialized filter data as an object.
     *
     * @returns {Object} 
     */
    serialize: function(){
        var me = this;
        
        return Ext.apply({
            type:           me.type,
            operator:       me.operator,
            value:          me.value,
            caseSensitive:  me.caseSensitive
        }, me.getSerialArgs() || {});
    },
    
    /**
     * @method
     * Template method to be implemented by all subclasses that is used to
     * get and return serialized filter data.
     *
     * Defaults to Ext.emptyFn.
     *
     */
    getSerialArgs: Ext.emptyFn,
    
    /**
     * Check if the specified value matches the filter.
     *
     * @returns Boolean True if the value matches the filter
     * @param value
     *
     */
    isMatch: function(value){
        var me = this,
            v = me.value,
            ret, retFrom, retTo, from, to;
 
        v = (Ext.isArray(v) ? v[0] : v) || '';
        ret = me.compare(value, v);
 
        if(me.operator == '='){
            return (ret === 0);
        }
 
        if(me.operator == '!='){
            return (ret !== 0);
        }
 
        if(me.operator == '>'){
            return (ret > 0);
        }
 
        if(me.operator == '>='){
            return (ret >= 0);
        }
 
        if(me.operator == '<'){
            return (ret < 0);
        }
 
        if(me.operator == '<='){
            return (ret <= 0);
        }
 
        v = me.value;
        from = (Ext.isArray(v) ? v[0] : v) || '';
        to = (Ext.isArray(v) ? v[1] : v) || '';
        retFrom = me.compare(value, from);
        retTo = me.compare(value, to);
 
        if(me.operator == 'between'){
            return (retFrom >= 0 && retTo <= 0);
        }
 
        if(me.operator == 'not between'){
            return !(retFrom >= 0 && retTo <= 0);
        }
 
        // no valid operator was specified. ignore filtering
        return true;
    },
 
    /**
     * Check if the specified value is a number and returns it.
     *
     * Takes care of the regional settings (decimal and thousand separators).
     *
     * @param value
     * @return {Number} Returns the number or null if the value is not numeric
     * @private
     */
    parseNumber: function(value){
        var v;
 
        if(typeof value === 'number'){
            return value;
        }
 
        if(Ext.isEmpty(value)){
            value = '';
        }
 
        // if the value comes as a string it may be a number formatted using locale settings
        // which means that we need to convert it to a number with `.` decimal separator.
        v = String(value).replace(this.thousandRe, '');
        v = v.replace(this.decimalRe, '.');
 
        if(Ext.isNumeric(v)){
            return parseFloat(v);
        }
        return null;
    },
 
    /**
     * Compare 2 values and return the result.
     *
     * @param a
     * @param b
     * @private
     */
    compare: function(a, b){
        var sorter = Ext.pivot.matrix.Base.prototype.naturalSort,
            v1 = this.parseNumber(a),
            v2 = this.parseNumber(b);
 
        if(Ext.isNumber(v1) && Ext.isNumber(v2)){
            return sorter(v1, v2);
        }
 
        if( Ext.isDate(a) ){
            if(Ext.isDate(b)){
                return sorter(a, b);
            }else{
                return sorter(a, Ext.Date.parse(b, Ext.Date.defaultFormat));
            }
        }
 
        return (this.caseSensitive ? sorter(|| '', b || '') : sorter(String(|| '').toLowerCase(), String(|| '').toLowerCase()));
 
    },
 
    deprecated: {
        '6.0': {
            properties: {
                /**
                 * @cfg {String} mztype
                 *
                 * @deprecated 6.0 Use {@link #type} instead. The old type config was renamed to {@link #operator}.
                 */
                mztype: null,
                /**
                 * @cfg {String} from
                 * @deprecated 6.0 Use {@link #value} instead as an array with 2 values.
                 *
                 * Used in case of a 'between/not between' type of filter
                 *
                 */
                from: null,
                /**
                 * @cfg {String} to
                 * @deprecated 6.0 Use {@link #value} instead as an array with 2 values.
                 *
                 * Used in case of a 'between/not between' operator
                 *
                 */
                to: null
            }
        }
    }
});