/**
 * 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'
    ],

    isFilter: true,

    /**
     * @cfg {String} type
     * 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
     * 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,

    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));
            }
        }

        // eslint-disable-next-line max-len
        return (this.caseSensitive ? sorter(a || '', b || '') : sorter(String(a || '').toLowerCase(), String(b || '').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
            }
        }
    }
});
