/**
 * This abstract base class is used by grid filters that have a three
 * {@link Ext.data.Store#cfg-filters store filter}.
 * @protected
 */
Ext.define('Ext.grid.filters.filter.TriFilter', {
    extend: 'Ext.grid.filters.filter.Base',
 
    /**
     * @property {String[]} menuItems
     * The items to be shown in this menu.  Items are added to the menu
     * according to their position within this array.
     * Defaults to:
     *      menuItems : ['lt', 'gt', '-', 'eq']
     * @private
     */
    menuItems: ['lt', 'gt', '-', 'eq'],
 
    constructor: function (config) {
        var me = this,
            stateful = false,
            filter = {},
            filterGt, filterLt, filterEq, value, operator;
 
        me.callParent([config]);
 
        value = me.value;
 
        filterLt = me.getStoreFilter('lt');
        filterGt = me.getStoreFilter('gt');
        filterEq = me.getStoreFilter('eq');
 
        if (filterLt || filterGt || filterEq) {
            // This filter was restored from stateful filters on the store so enforce it as active.
            stateful = me.active = true;
            if (filterLt) {
                me.onStateRestore(filterLt);
            }
            if (filterGt) {
                me.onStateRestore(filterGt);
            }
            if (filterEq) {
                me.onStateRestore(filterEq);
            }
        } else {
            // Once we've reached this block, we know that this grid filter doesn't have a stateful filter, so if our
            // flag to begin saving future filter mutations is set we know that any configured filter must be nulled
            // out or it will replace our stateful filter.
            if (me.grid.stateful && me.getGridStore().saveStatefulFilters) {
                value = undefined;
            }
 
            // TODO: What do we mean by value === null ?
            me.active = me.getActiveState(config, value);
        }
 
        // Note that stateful filters will have already been gotten above. If not, or if all filters aren't stateful, we
        // need to make sure that there is an actual filter instance created, with or without a value.
        //
        // Note use the alpha alias for the operators ('gt', 'lt', 'eq') so they map in Filters.onFilterRemove().
        filter.lt = filterLt || me.createFilter({
            operator: 'lt',
            value: (!stateful && value && Ext.isDefined(value.lt)) ?
                value.lt :
                null
        }, 'lt');
 
        filter.gt = filterGt || me.createFilter({
            operator: 'gt',
            value: (!stateful && value && Ext.isDefined(value.gt)) ?
                value.gt :
                null
        }, 'gt');
 
        filter.eq = filterEq || me.createFilter({
            operator: 'eq',
            value: (!stateful && value && Ext.isDefined(value.eq)) ?
                value.eq :
                null
        }, 'eq');
 
        me.filter = filter;
 
        if (me.active) {
            me.setColumnActive(true);
            if (!stateful) {
                for (operator in value) {
                    me.addStoreFilter(me.filter[operator]);
                }
            }
            // TODO: maybe call this.activate?
        }
    },
 
    /**
     * @private
     * This method will be called when a column's menu trigger is clicked as well as when a filter is
     * activated. Depending on the caller, the UI and the store will be synced.
     */
    activate: function (showingMenu) {
        var me = this,
            filters = me.filter,
            fields = me.fields,
            filter, field, operator, value, isRootMenuItem;
 
        if (me.preventFilterRemoval) {
            return;
        }
 
        for (operator in filters) {
            filter = filters[operator];
            field = fields[operator];
            value = filter.getValue();
 
            if (value || value === 0) {
                if (field.isComponent) {
                    field.setValue(value);
 
                    // Some types, such as Date, have additional menu check items in their Filter menu hierarchy. Others, such as Number, do not.
                    // Because of this, it is necessary to make sure that the direct menuitem ancestor of the fields is not the rootMenuItem (the
                    // "Filters" menu item), which has its checked state controlled elsewhere.
                    //
                    // In other words, if the ancestor is not the rootMenuItem, check it.
                    if (isRootMenuItem === undefined) {
                        isRootMenuItem = me.owner.activeFilterMenuItem === field.up('menuitem');
                    }
 
                    if (!isRootMenuItem) {
                        field.up('menuitem').setChecked(true, /*suppressEvents*/ true);
                    }
                } else {
                    field.value = value;
                }
 
                // Note that we only want to add store filters when they've been removed, which means that when Filter.showMenu() is called
                // we DO NOT want to add a filter as they've already been added!
                if (!showingMenu) {
                    me.addStoreFilter(filter);
                }
            }
        }
    },
 
    /**
     * @private
     * This method will be called when a filter is deactivated. The UI and the store will be synced.
     */
    deactivate: function () {
        var me = this,
            filters = me.filter,
            f, filter, value;
 
        if (!me.countActiveFilters() || me.preventFilterRemoval) {
            return;
        }
 
        me.preventFilterRemoval = true;
 
        for (in filters) {
            filter = filters[f];
 
            value = filter.getValue();
            if (value || value === 0) {
                me.removeStoreFilter(filter);
            }
        }
 
        me.preventFilterRemoval = false;
    },
 
    countActiveFilters: function () {
        var filters = this.filter,
            filterCollection = this.getGridStore().getFilters(),
            prefix = this.getBaseIdPrefix(),
            i = 0,
            filter;
 
        if (filterCollection.length) {
            for (filter in filters) {
                if (filterCollection.get(prefix + '-' + filter)) {
                    i++;
                }
            }
        }
 
        return i;
    },
 
    onFilterRemove: function (operator) {
        var me = this,
            value;
 
        // Filters can be removed at any time, even before a column filter's menu has been created (i.e.,
        // store.clearFilter()). So, only call setValue() if the menu has been created since that method
        // assumes that menu fields exist.
        if (!me.menu && me.countActiveFilters()) {
            me.active = false;
        } else if (me.menu) {
            value = {};
            value[operator] = null;
            me.setValue(value);
        }
    },
 
    onStateRestore: Ext.emptyFn,
 
    setValue: function (value) {
        var me = this,
            filters = me.filter,
            add = [],
            remove = [],
            active = false,
            filterCollection = me.getGridStore().getFilters(),
            filter, v, i, rLen, aLen;
 
        if (me.preventFilterRemoval) {
            return;
        }
 
        me.preventFilterRemoval = true;
 
        if ('eq' in value) {
            v = filters.lt.getValue();
            if (|| v === 0) {
                remove.push(filters.lt);
            }
 
            v = filters.gt.getValue();
            if (|| v === 0) {
                remove.push(filters.gt);
            }
 
            v = value.eq;
            if (|| v === 0) {
                add.push(filters.eq);
                filters.eq.setValue(v);
            } else {
                remove.push(filters.eq);
            }
        } else {
            v = filters.eq.getValue();
            if (|| v === 0) {
                remove.push(filters.eq);
            }
 
            if ('lt' in value) {
                v = value.lt;
                if (|| v === 0) {
                    add.push(filters.lt);
                    filters.lt.setValue(v);
                } else {
                    remove.push(filters.lt);
                }
            }
 
            if ('gt' in value) {
                v = value.gt;
                if (|| v === 0) {
                    add.push(filters.gt);
                    filters.gt.setValue(v);
                } else {
                    remove.push(filters.gt);
                }
            }
        }
 
        // Note that we don't want to update the filter collection unnecessarily, so we must know the
        // current number of active filters that this TriFilter has +/- the number of filters we're
        // adding and removing, respectively. This will determine the present active state of the
        // TriFilter which we can use to not only help determine if the condition below should pass
        // but (if it does) how the active state should then be updated.
        rLen = remove.length;
        aLen = add.length;
        active = !!(me.countActiveFilters() + aLen - rLen);
 
        if (rLen || aLen || active !== me.active) {
            // Begin the update now because the update could also be triggered if #setActive is called.
            // We must wrap all the calls that could change the filter collection.
            filterCollection.beginUpdate();
 
            if (rLen) {
                for (= 0; i < rLen; i++) {
                    filter = remove[i];
 
                    me.fields[filter.getOperator()].setValue(null);
                    filter.setValue(null);
                    me.removeStoreFilter(filter);
                }
            }
 
            if (aLen) {
                for (= 0; i < aLen; i++) {
                    me.addStoreFilter(add[i]);
                }
            }
 
            me.setActive(active);
            filterCollection.endUpdate();
        }
 
        me.preventFilterRemoval = false;
    }
});