/*******************************************************************************
 * Copyright (c) 2013, 2020 Dirk Fauth and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation
 *******************************************************************************/
package org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.eclipse.nebula.widgets.nattable.command.DisposeResourcesCommand;
import org.eclipse.nebula.widgets.nattable.command.ILayerCommand;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.edit.EditConstants;
import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataLayer;
import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataProvider;
import org.eclipse.nebula.widgets.nattable.filterrow.combobox.ComboBoxFilterRowConfiguration;
import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxDataProvider;
import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboUpdateEvent;
import org.eclipse.nebula.widgets.nattable.filterrow.combobox.IFilterRowComboUpdateListener;
import org.eclipse.nebula.widgets.nattable.filterrow.command.ClearAllFiltersCommand;
import org.eclipse.nebula.widgets.nattable.filterrow.command.ClearFilterCommand;
import org.eclipse.nebula.widgets.nattable.filterrow.command.ToggleFilterRowCommand;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.event.RowStructuralRefreshEvent;

import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.matchers.CompositeMatcherEditor;

/**
 * CompositeLayer that is build out of the columnheader layer stack and a filter
 * row layer. The column header layer stack needs to be provided at creation
 * time while the filter row layer is created in here.
 * <p>
 * The special about this ComboBoxFilterRowHeaderComposite is that it creates a
 * filter row that behaves and looks like the Excel filter. So it doesn't
 * provide text fields for free filtering but adds comboboxes for all columns
 * that contain all available values for that column that can be de-/selected
 * via checkboxes.
 */
public class ComboBoxFilterRowHeaderComposite<T> extends CompositeLayer implements IFilterRowComboUpdateListener {

    /**
     * The FilterRowDataLayer which serves as the filter row layer.
     */
    protected final FilterRowDataLayer<T> filterRowDataLayer;
    /**
     * Flag that allows to set the filter row visible or invisible.
     */
    protected boolean filterRowVisible = true;
    /**
     * The IComboBoxDataProvider that is used to fill the filter row comboboxes.
     */
    protected final FilterRowComboBoxDataProvider<T> comboBoxDataProvider;
    /**
     * The IFilterStrategy that is used by this
     * ComboBoxFilterRowHeaderComposite.
     */
    protected final ComboBoxGlazedListsFilterStrategy<T> filterStrategy;

    /**
     * Creates a new ComboBoxFilterRowHeaderComposite based on the given
     * informations. Using this constructor will create the
     * FilterRowComboBoxDataProvider needed for filtering and add the default
     * ComboBoxFilterRowConfiguration.
     *
     * @param filterList
     *            The FilterList that will be used for filtering.
     * @param bodyLayer
     *            A layer in the body region. Usually the DataLayer or a layer
     *            that is responsible for list event handling. Needed for
     *            creation of the FilterRowComboBoxDataProvider.
     * @param baseCollection
     *            The base collection that is used to fill the body. Needed to
     *            determine the values to show in the filter comboboxes and
     *            initially pre-select them.
     * @param bodyDataColumnAccessor
     *            The IColumnAccessor that is needed by the IFilterStrategy to
     *            perform filtering.
     * @param columnHeaderLayer
     *            The columnheader layer the filter row layer is related to.
     *            Needed for building this CompositeLayer, dimensionally connect
     *            the filter row to and retrieve information and perform actions
     *            related to filtering.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header needed to
     *            retrieve the real column count of the column header and not a
     *            transformed one.
     * @param configRegistry
     *            The {@link IConfigRegistry} needed to retrieve various
     *            configurations.
     */
    public ComboBoxFilterRowHeaderComposite(
            FilterList<T> filterList,
            ILayer bodyLayer,
            Collection<T> baseCollection,
            IColumnAccessor<T> bodyDataColumnAccessor,
            ILayer columnHeaderLayer,
            IDataProvider columnHeaderDataProvider,
            IConfigRegistry configRegistry) {

        this(filterList,
                bodyLayer,
                baseCollection,
                bodyDataColumnAccessor,
                columnHeaderLayer,
                columnHeaderDataProvider,
                configRegistry,
                true);
    }

    /**
     * Creates a new ComboBoxFilterRowHeaderComposite based on the given
     * informations. Using this constructor will create the
     * FilterRowComboBoxDataProvider needed for filtering .
     *
     * @param filterList
     *            The FilterList that will be used for filtering.
     * @param bodyLayer
     *            A layer in the body region. Usually the DataLayer or a layer
     *            that is responsible for list event handling. Needed for
     *            creation of the FilterRowComboBoxDataProvider.
     * @param baseCollection
     *            The base collection that is used to fill the body. Needed to
     *            determine the values to show in the filter comboboxes and
     *            initially pre-select them.
     * @param bodyDataColumnAccessor
     *            The IColumnAccessor that is needed by the IFilterStrategy to
     *            perform filtering.
     * @param columnHeaderLayer
     *            The columnheader layer the filter row layer is related to.
     *            Needed for building this CompositeLayer, dimensionally connect
     *            the filter row to and retrieve information and perform actions
     *            related to filtering.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header needed to
     *            retrieve the real column count of the column header and not a
     *            transformed one.
     * @param configRegistry
     *            The {@link IConfigRegistry} needed to retrieve various
     *            configurations.
     * @param useDefaultConfiguration
     *            Tell whether the default configuration should be used or not.
     *            If not you need to ensure to add a configuration that adds at
     *            least the needed configuration specified in
     *            ComboBoxFilterRowConfiguration
     */
    public ComboBoxFilterRowHeaderComposite(
            FilterList<T> filterList,
            ILayer bodyLayer,
            Collection<T> baseCollection,
            IColumnAccessor<T> bodyDataColumnAccessor,
            ILayer columnHeaderLayer,
            IDataProvider columnHeaderDataProvider,
            IConfigRegistry configRegistry,
            boolean useDefaultConfiguration) {

        this(filterList,
                new GlazedListsFilterRowComboBoxDataProvider<>(
                        bodyLayer,
                        baseCollection,
                        bodyDataColumnAccessor),
                bodyDataColumnAccessor,
                columnHeaderLayer,
                columnHeaderDataProvider,
                configRegistry,
                useDefaultConfiguration);
    }

    /**
     * Creates a new ComboBoxFilterRowHeaderComposite based on the given
     * informations. Using this constructor will create the
     * FilterRowComboBoxDataProvider needed for filtering .
     *
     * @param filterList
     *            The FilterList that will be used for filtering.
     * @param bodyLayer
     *            A layer in the body region. Usually the DataLayer or a layer
     *            that is responsible for list event handling. Needed for
     *            creation of the FilterRowComboBoxDataProvider.
     * @param baseCollection
     *            The base collection that is used to fill the body. Needed to
     *            determine the values to show in the filter comboboxes and
     *            initially pre-select them.
     * @param bodyDataColumnAccessor
     *            The IColumnAccessor that is needed by the IFilterStrategy to
     *            perform filtering.
     * @param lazyLoading
     *            Flag to configure whether the content of the comboboxes should
     *            be loaded lazily or not. Default is <code>true</code>
     * @param columnHeaderLayer
     *            The columnheader layer the filter row layer is related to.
     *            Needed for building this CompositeLayer, dimensionally connect
     *            the filter row to and retrieve information and perform actions
     *            related to filtering.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header needed to
     *            retrieve the real column count of the column header and not a
     *            transformed one.
     * @param configRegistry
     *            The {@link IConfigRegistry} needed to retrieve various
     *            configurations.
     * @param useDefaultConfiguration
     *            Tell whether the default configuration should be used or not.
     *            If not you need to ensure to add a configuration that adds at
     *            least the needed configuration specified in
     *            ComboBoxFilterRowConfiguration
     */
    public ComboBoxFilterRowHeaderComposite(
            FilterList<T> filterList,
            ILayer bodyLayer,
            Collection<T> baseCollection,
            IColumnAccessor<T> bodyDataColumnAccessor,
            boolean lazyLoading,
            ILayer columnHeaderLayer,
            IDataProvider columnHeaderDataProvider,
            IConfigRegistry configRegistry,
            boolean useDefaultConfiguration) {

        this(filterList,
                new GlazedListsFilterRowComboBoxDataProvider<>(
                        bodyLayer,
                        baseCollection,
                        bodyDataColumnAccessor,
                        lazyLoading),
                bodyDataColumnAccessor,
                columnHeaderLayer,
                columnHeaderDataProvider,
                configRegistry,
                useDefaultConfiguration);
    }

    /**
     * Creates a new ComboBoxFilterRowHeaderComposite based on the given
     * informations. Using this constructor will create the
     * FilterRowComboBoxDataProvider needed for filtering .
     * <p>
     * <b>Note:</b>This constructor should only be used in cases where it is
     * absolutely necessary to use a custom CompositeMatcherEditor. This could
     * be for example to create a complex NatTable instance where several filter
     * rows need to be combined.
     *
     * @param filterList
     *            The FilterList that will be used for filtering.
     * @param matcherEditor
     *            The CompositeMatcherEditor that is set to the FilterList and
     *            needs to be used by the ComboBoxGlazedListsFilterStrategy to
     *            apply the filters via filter row.
     * @param bodyLayer
     *            A layer in the body region. Usually the DataLayer or a layer
     *            that is responsible for list event handling. Needed for
     *            creation of the FilterRowComboBoxDataProvider.
     * @param baseCollection
     *            The base collection that is used to fill the body. Needed to
     *            determine the values to show in the filter comboboxes and
     *            initially pre-select them.
     * @param bodyDataColumnAccessor
     *            The IColumnAccessor that is needed by the IFilterStrategy to
     *            perform filtering.
     * @param columnHeaderLayer
     *            The columnheader layer the filter row layer is related to.
     *            Needed for building this CompositeLayer, dimensionally connect
     *            the filter row to and retrieve information and perform actions
     *            related to filtering.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header needed to
     *            retrieve the real column count of the column header and not a
     *            transformed one.
     * @param configRegistry
     *            The {@link IConfigRegistry} needed to retrieve various
     *            configurations.
     * @param useDefaultConfiguration
     *            Tell whether the default configuration should be used or not.
     *            If not you need to ensure to add a configuration that adds at
     *            least the needed configuration specified in
     *            ComboBoxFilterRowConfiguration
     */
    public ComboBoxFilterRowHeaderComposite(
            FilterList<T> filterList,
            CompositeMatcherEditor<T> matcherEditor,
            ILayer bodyLayer,
            Collection<T> baseCollection,
            IColumnAccessor<T> bodyDataColumnAccessor,
            ILayer columnHeaderLayer,
            IDataProvider columnHeaderDataProvider,
            IConfigRegistry configRegistry,
            boolean useDefaultConfiguration) {

        this(filterList,
                matcherEditor,
                new GlazedListsFilterRowComboBoxDataProvider<>(
                        bodyLayer,
                        baseCollection,
                        bodyDataColumnAccessor),
                bodyDataColumnAccessor,
                columnHeaderLayer,
                columnHeaderDataProvider,
                configRegistry,
                useDefaultConfiguration);
    }

    /**
     * Creates a new ComboBoxFilterRowHeaderComposite based on the given
     * informations. Using this constructor will create the
     * FilterRowComboBoxDataProvider needed for filtering .
     * <p>
     * <b>Note:</b>This constructor should only be used in cases where it is
     * absolutely necessary to use a custom CompositeMatcherEditor. This could
     * be for example to create a complex NatTable instance where several filter
     * rows need to be combined.
     *
     * @param filterList
     *            The FilterList that will be used for filtering.
     * @param matcherEditor
     *            The CompositeMatcherEditor that is set to the FilterList and
     *            needs to be used by the ComboBoxGlazedListsFilterStrategy to
     *            apply the filters via filter row.
     * @param bodyLayer
     *            A layer in the body region. Usually the DataLayer or a layer
     *            that is responsible for list event handling. Needed for
     *            creation of the FilterRowComboBoxDataProvider.
     * @param baseCollection
     *            The base collection that is used to fill the body. Needed to
     *            determine the values to show in the filter comboboxes and
     *            initially pre-select them.
     * @param bodyDataColumnAccessor
     *            The IColumnAccessor that is needed by the IFilterStrategy to
     *            perform filtering.
     * @param lazyLoading
     *            Flag to configure whether the content of the comboboxes should
     *            be loaded lazily or not. Default is <code>true</code>
     * @param columnHeaderLayer
     *            The columnheader layer the filter row layer is related to.
     *            Needed for building this CompositeLayer, dimensionally connect
     *            the filter row to and retrieve information and perform actions
     *            related to filtering.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header needed to
     *            retrieve the real column count of the column header and not a
     *            transformed one.
     * @param configRegistry
     *            The {@link IConfigRegistry} needed to retrieve various
     *            configurations.
     * @param useDefaultConfiguration
     *            Tell whether the default configuration should be used or not.
     *            If not you need to ensure to add a configuration that adds at
     *            least the needed configuration specified in
     *            ComboBoxFilterRowConfiguration
     */
    public ComboBoxFilterRowHeaderComposite(
            FilterList<T> filterList,
            CompositeMatcherEditor<T> matcherEditor,
            ILayer bodyLayer,
            Collection<T> baseCollection,
            IColumnAccessor<T> bodyDataColumnAccessor,
            boolean lazyLoading,
            ILayer columnHeaderLayer,
            IDataProvider columnHeaderDataProvider,
            IConfigRegistry configRegistry,
            boolean useDefaultConfiguration) {

        this(filterList,
                matcherEditor,
                new GlazedListsFilterRowComboBoxDataProvider<>(
                        bodyLayer,
                        baseCollection,
                        bodyDataColumnAccessor,
                        lazyLoading),
                bodyDataColumnAccessor,
                columnHeaderLayer,
                columnHeaderDataProvider,
                configRegistry,
                useDefaultConfiguration);
    }

    /**
     * Creates a new ComboBoxFilterRowHeaderComposite based on the given
     * informations. Using this constructor will add the default
     * ComboBoxFilterRowConfiguration.
     *
     * @param filterList
     *            The FilterList that will be used for filtering.
     * @param comboBoxDataProvider
     *            The FilterRowComboBoxDataProvider that should be used to fill
     *            the filter comboboxes.
     * @param bodyDataColumnAccessor
     *            The IColumnAccessor that is needed by the IFilterStrategy to
     *            perform filtering.
     * @param columnHeaderLayer
     *            The columnheader layer the filter row layer is related to.
     *            Needed for building this CompositeLayer, dimensionally connect
     *            the filter row to and retrieve information and perform actions
     *            related to filtering.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header needed to
     *            retrieve the real column count of the column header and not a
     *            transformed one.
     * @param configRegistry
     *            The {@link IConfigRegistry} needed to retrieve various
     *            configurations.
     */
    public ComboBoxFilterRowHeaderComposite(
            FilterList<T> filterList,
            FilterRowComboBoxDataProvider<T> comboBoxDataProvider,
            IColumnAccessor<T> bodyDataColumnAccessor,
            ILayer columnHeaderLayer,
            IDataProvider columnHeaderDataProvider,
            IConfigRegistry configRegistry) {

        this(filterList,
                comboBoxDataProvider,
                bodyDataColumnAccessor,
                columnHeaderLayer,
                columnHeaderDataProvider,
                configRegistry,
                true);
    }

    /**
     * Creates a new ComboBoxFilterRowHeaderComposite based on the given
     * informations. Will use the given FilterList for creating the
     * ComboBoxGlazedListsFilterStrategy and the given
     * FilterRowComboBoxDataProvider instead of creating a new one.
     *
     * @param filterList
     *            The FilterList that will be used for filtering.
     * @param comboBoxDataProvider
     *            The FilterRowComboBoxDataProvider that should be used to fill
     *            the filter comboboxes.
     * @param bodyDataColumnAccessor
     *            The IColumnAccessor that is needed by the IFilterStrategy to
     *            perform filtering.
     * @param columnHeaderLayer
     *            The columnheader layer the filter row layer is related to.
     *            Needed for building this CompositeLayer, dimensionally connect
     *            the filter row to and retrieve information and perform actions
     *            related to filtering.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header needed to
     *            retrieve the real column count of the column header and not a
     *            transformed one.
     * @param configRegistry
     *            The {@link IConfigRegistry} needed to retrieve various
     *            configurations.
     * @param useDefaultConfiguration
     *            Tell whether the default configuration should be used or not.
     *            If not you need to ensure to add a configuration that adds at
     *            least the needed configuration specified in
     *            ComboBoxFilterRowConfiguration
     */
    public ComboBoxFilterRowHeaderComposite(
            FilterList<T> filterList,
            FilterRowComboBoxDataProvider<T> comboBoxDataProvider,
            IColumnAccessor<T> bodyDataColumnAccessor,
            ILayer columnHeaderLayer,
            IDataProvider columnHeaderDataProvider,
            IConfigRegistry configRegistry,
            boolean useDefaultConfiguration) {

        this(
                new ComboBoxGlazedListsFilterStrategy<>(
                        comboBoxDataProvider,
                        filterList,
                        bodyDataColumnAccessor,
                        configRegistry),
                comboBoxDataProvider,
                columnHeaderLayer,
                columnHeaderDataProvider,
                configRegistry,
                useDefaultConfiguration);
    }

    /**
     * Creates a new ComboBoxFilterRowHeaderComposite based on the given
     * informations. Will use the given FilterList and MatcherEditor for
     * creating the ComboBoxGlazedListsFilterStrategy and the given
     * FilterRowComboBoxDataProvider instead of creating a new one.
     * <p>
     * <b>Note:</b>This constructor should only be used in cases where it is
     * absolutely necessary to use a custom CompositeMatcherEditor. This could
     * be for example to create a complex NatTable instance where several filter
     * rows need to be combined.
     *
     * @param filterList
     *            The FilterList that will be used for filtering.
     * @param matcherEditor
     *            The CompositeMatcherEditor that is set to the FilterList and
     *            needs to be used by the ComboBoxGlazedListsFilterStrategy to
     *            apply the filters via filter row.
     * @param comboBoxDataProvider
     *            The FilterRowComboBoxDataProvider that should be used to fill
     *            the filter comboboxes.
     * @param bodyDataColumnAccessor
     *            The IColumnAccessor that is needed by the IFilterStrategy to
     *            perform filtering.
     * @param columnHeaderLayer
     *            The columnheader layer the filter row layer is related to.
     *            Needed for building this CompositeLayer, dimensionally connect
     *            the filter row to and retrieve information and perform actions
     *            related to filtering.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header needed to
     *            retrieve the real column count of the column header and not a
     *            transformed one.
     * @param configRegistry
     *            The {@link IConfigRegistry} needed to retrieve various
     *            configurations.
     * @param useDefaultConfiguration
     *            Tell whether the default configuration should be used or not.
     *            If not you need to ensure to add a configuration that adds at
     *            least the needed configuration specified in
     *            ComboBoxFilterRowConfiguration
     */
    public ComboBoxFilterRowHeaderComposite(
            FilterList<T> filterList,
            CompositeMatcherEditor<T> matcherEditor,
            FilterRowComboBoxDataProvider<T> comboBoxDataProvider,
            IColumnAccessor<T> bodyDataColumnAccessor,
            ILayer columnHeaderLayer,
            IDataProvider columnHeaderDataProvider,
            IConfigRegistry configRegistry,
            boolean useDefaultConfiguration) {

        this(
                new ComboBoxGlazedListsFilterStrategy<>(
                        comboBoxDataProvider,
                        filterList,
                        matcherEditor,
                        bodyDataColumnAccessor,
                        configRegistry),
                comboBoxDataProvider,
                columnHeaderLayer,
                columnHeaderDataProvider,
                configRegistry,
                useDefaultConfiguration);
    }

    /**
     * Creates a new ComboBoxFilterRowHeaderComposite based on the given
     * informations. Will use the given ComboBoxGlazedListsFilterStrategy
     * instead of creating a new one.
     *
     * @param filterStrategy
     *            The ComboBoxGlazedListsFilterStrategy that should be used for
     *            filtering.
     * @param comboBoxDataProvider
     *            The FilterRowComboBoxDataProvider that should be used to fill
     *            the filter comboboxes.
     * @param columnHeaderLayer
     *            The columnheader layer the filter row layer is related to.
     *            Needed for building this CompositeLayer, dimensionally connect
     *            the filter row to and retrieve information and perform actions
     *            related to filtering.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header needed to
     *            retrieve the real column count of the column header and not a
     *            transformed one.
     * @param configRegistry
     *            The {@link IConfigRegistry} needed to retrieve various
     *            configurations.
     * @param useDefaultConfiguration
     *            Tell whether the default configuration should be used or not.
     *            If not you need to ensure to add a configuration that adds at
     *            least the needed configuration specified in
     *            ComboBoxFilterRowConfiguration
     */
    public ComboBoxFilterRowHeaderComposite(
            ComboBoxGlazedListsFilterStrategy<T> filterStrategy,
            FilterRowComboBoxDataProvider<T> comboBoxDataProvider,
            ILayer columnHeaderLayer,
            IDataProvider columnHeaderDataProvider,
            IConfigRegistry configRegistry,
            boolean useDefaultConfiguration) {

        super(1, 2);

        setChildLayer("columnHeader", columnHeaderLayer, 0, 0); //$NON-NLS-1$

        this.filterStrategy = filterStrategy;

        this.comboBoxDataProvider = comboBoxDataProvider;
        this.comboBoxDataProvider.addCacheUpdateListener(this);

        this.filterRowDataLayer =
                new FilterRowDataLayer<>(
                        this.filterStrategy,
                        columnHeaderLayer,
                        columnHeaderDataProvider,
                        configRegistry);

        setAllValuesSelected();

        setChildLayer(GridRegion.FILTER_ROW, this.filterRowDataLayer, 0, 1);

        if (useDefaultConfiguration) {
            addConfiguration(new ComboBoxFilterRowConfiguration(this.comboBoxDataProvider));
        }
    }

    @Override
    public void loadState(String prefix, Properties properties) {
        this.comboBoxDataProvider.disableUpdateEvents();
        super.loadState(prefix, properties);
        this.comboBoxDataProvider.enableUpdateEvents();
    }

    /**
     * @return The FilterRowDataLayer which serves as the filter row layer.
     */
    public FilterRowDataLayer<T> getFilterRowDataLayer() {
        return this.filterRowDataLayer;
    }

    /**
     * @return The CompositeMatcherEditor that is used for filtering. As this
     *         one is filled with MatcherEditors by the IFilterStrategy, direct
     *         modifications may be overridden on changing cell values in the
     *         filter row.
     */
    public CompositeMatcherEditor<T> getMatcherEditor() {
        return this.filterStrategy.getMatcherEditor();
    }

    /**
     * Returns the IFilterStrategy that is used by this
     * ComboBoxFilterRowHeaderComposite. This is needed to integrate static
     * filters, e.g. using GlazedListsRowHideShowLayer together with this
     * ComboBoxFilterRowHeaderComposite by adding the MatcherEditor as static
     * filter to the ComboBoxGlazedListsFilterStrategy.
     *
     * @return The IFilterStrategy that is used by this
     *         ComboBoxFilterRowHeaderComposite.
     */
    public ComboBoxGlazedListsFilterStrategy<T> getFilterStrategy() {
        return this.filterStrategy;
    }

    /**
     * @return The IComboBoxDataProvider that is used to fill the filter row
     *         comboboxes.
     */
    public FilterRowComboBoxDataProvider<T> getComboBoxDataProvider() {
        return this.comboBoxDataProvider;
    }

    /**
     * @return <code>true</code> if the filter row is visible,
     *         <code>false</code> if not.
     */
    public boolean isFilterRowVisible() {
        return this.filterRowVisible;
    }

    /**
     * Sets the visibility state of the filter row.
     *
     * @param filterRowVisible
     *            <code>true</code> to set the filter row visible,
     *            <code>false</code> to hide it.
     */
    public void setFilterRowVisible(boolean filterRowVisible) {
        this.filterRowVisible = filterRowVisible;
        fireLayerEvent(new RowStructuralRefreshEvent(this.filterRowDataLayer));
    }

    /**
     * Sets all values for all comboboxes as selected. This is needed because
     * selecting all items in the comboboxes mean to have no filter applied.
     * <p>
     * Note: The filter row IDataProvider is filled by modifying the Map of
     * filter objects directly instead of calling setDataValue(). This is
     * because calling setDataValue() will transform indexes which is not
     * necessary on initialization and causes strange behaviour in some
     * compositions.
     */
    public void setAllValuesSelected() {
        FilterRowDataProvider<T> dataProvider = this.filterRowDataLayer.getFilterRowDataProvider();
        for (int i = 0; i < dataProvider.getColumnCount(); i++) {
            dataProvider.getFilterIndexToObjectMap().put(i, EditConstants.SELECT_ALL_ITEMS_VALUE);
        }
        getFilterStrategy().applyFilter(dataProvider.getFilterIndexToObjectMap());
    }

    @Override
    public int getHeight() {
        if (this.filterRowVisible) {
            return super.getHeight();
        } else {
            return getHeightOffset(1);
        }
    }

    @Override
    public int getRowCount() {
        if (this.filterRowVisible) {
            return super.getRowCount();
        } else {
            return super.getRowCount() - 1;
        }
    }

    @Override
    public boolean doCommand(ILayerCommand command) {
        boolean handled = false;
        if (command instanceof ToggleFilterRowCommand) {
            setFilterRowVisible(!this.filterRowVisible);
            return true;
        }
        // as clearing the filter means to select all items in the combo, we
        // need to handle clear filter commands in here instead of delegating it
        // to the FilterRowDataLayer
        else if (command instanceof ClearFilterCommand
                && command.convertToTargetLayer(this)) {
            int columnPosition = ((ClearFilterCommand) command).getColumnPosition();
            this.filterRowDataLayer.setDataValueByPosition(
                    columnPosition,
                    0,
                    getComboBoxDataProvider().getValues(columnPosition, 0));
            handled = true;
        } else if (command instanceof ClearAllFiltersCommand) {
            setAllValuesSelected();
            handled = true;
        } else if (command instanceof DisposeResourcesCommand) {
            this.comboBoxDataProvider.dispose();
        }

        if (handled) {
            fireLayerEvent(new RowStructuralRefreshEvent(this));
            return true;
        } else {
            return super.doCommand(command);
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public void handleEvent(FilterRowComboUpdateEvent event) {
        Map<Integer, Object> filterIndexToObjectMap =
                this.filterRowDataLayer.getFilterRowDataProvider().getFilterIndexToObjectMap();
        Object filterObject = filterIndexToObjectMap.get(event.getColumnIndex());
        if (filterObject instanceof Collection) {
            Collection filterCollection = (Collection) filterObject;
            // if a new value was added than ensure it is also added to the
            // filter
            if (event.getAddedItems() != null
                    && !event.getAddedItems().isEmpty()) {
                // as the filter collection is a list, we need to ensure that no
                // double values are added
                List itemsToAdd = new ArrayList(event.getAddedItems());
                itemsToAdd.removeAll(filterCollection);

                filterCollection.addAll(itemsToAdd);
            }
            // if a value was removed than ensure it is also removed from the
            // filter
            if (event.getRemovedItems() != null
                    && !event.getRemovedItems().isEmpty()) {
                filterCollection.removeAll(event.getRemovedItems());
            }
        }

        // apply the filter to be sure filter row and applied filter are the
        // same
        getFilterStrategy().applyFilter(
                this.filterRowDataLayer.getFilterRowDataProvider().getFilterIndexToObjectMap());
    }
}
