/*******************************************************************************
 * Copyright (c) 2012, 2020 Original authors 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:
 *     Original authors and others - initial API and implementation
 ******************************************************************************/
package org.eclipse.nebula.widgets.nattable.examples.examples._100_Layers._101_Header;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;

import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.columnChooser.command.DisplayColumnChooserCommandHandler;
import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDoubleDisplayConverter;
import org.eclipse.nebula.widgets.nattable.dataset.fixture.data.PricingTypeBean;
import org.eclipse.nebula.widgets.nattable.dataset.fixture.data.RowDataFixture;
import org.eclipse.nebula.widgets.nattable.dataset.fixture.data.RowDataListFixture;
import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor;
import org.eclipse.nebula.widgets.nattable.examples.AbstractNatExample;
import org.eclipse.nebula.widgets.nattable.examples.fixtures.PricingTypeBeanDisplayConverter;
import org.eclipse.nebula.widgets.nattable.examples.runner.StandaloneNatExampleRunner;
import org.eclipse.nebula.widgets.nattable.export.ExportConfigAttributes;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.DefaultGlazedListsFilterStrategy;
import org.eclipse.nebula.widgets.nattable.extension.poi.HSSFExcelExporter;
import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataLayer;
import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowHeaderComposite;
import org.eclipse.nebula.widgets.nattable.filterrow.TextMatchingMode;
import org.eclipse.nebula.widgets.nattable.filterrow.config.FilterRowConfigAttributes;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
import org.eclipse.nebula.widgets.nattable.group.ColumnGroupHeaderLayer;
import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.layer.stack.ColumnGroupBodyLayerStack;
import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.style.Style;
import org.eclipse.nebula.widgets.nattable.ui.menu.HeaderMenuConfiguration;
import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;

import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists;

public class DynamicColumnHeaderHeightExample extends AbstractNatExample {

    public static void main(String[] args) throws Exception {
        StandaloneNatExampleRunner.run(new DynamicColumnHeaderHeightExample());
    }

    @Override
    public String getDescription() {
        return "This example demonstrates dynamic column header height configuration.\n"
                + "It uses column grouping and filter row which are are related for height calculation.\n"
                + "\n"
                + "Following functions will change the column header height:\n"
                + "* GROUP SELECTED COLUMNS with ctrl-g or popup menu.\n"
                + "* UNGROUP SELECTED COLUMNS with ctrl-u or popup menu until no column group is available.\n"
                + "* Press button to enable/disable the filter row.";
    }

    private final ColumnGroupModel columnGroupModel = new ColumnGroupModel();
    private ColumnHeaderLayer columnHeaderLayer;

    @Override
    public Control createExampleControl(Composite parent) {
        IConfigRegistry configRegistry = new ConfigRegistry();

        // Underlying data source
        EventList<RowDataFixture> eventList = GlazedLists.eventList(RowDataListFixture.getList(200));
        FilterList<RowDataFixture> filterList = new FilterList<>(eventList);
        String[] propertyNames = RowDataListFixture.getPropertyNames();
        Map<String, String> propertyToLabelMap = RowDataListFixture.getPropertyToLabelMap();

        // Body
        IColumnPropertyAccessor<RowDataFixture> columnPropertyAccessor =
                new ReflectiveColumnPropertyAccessor<>(propertyNames);
        ListDataProvider<RowDataFixture> bodyDataProvider =
                new ListDataProvider<>(filterList, columnPropertyAccessor);
        DataLayer bodyDataLayer =
                new DataLayer(bodyDataProvider);
        ColumnGroupBodyLayerStack bodyLayer =
                new ColumnGroupBodyLayerStack(bodyDataLayer, this.columnGroupModel);

        ColumnOverrideLabelAccumulator bodyLabelAccumulator =
                new ColumnOverrideLabelAccumulator(bodyDataLayer);
        bodyDataLayer.setConfigLabelAccumulator(bodyLabelAccumulator);

        bodyLabelAccumulator.registerColumnOverrides(
                RowDataListFixture.getColumnIndexOfProperty(RowDataListFixture.PRICING_TYPE_PROP_NAME),
                "PRICING_TYPE_PROP_NAME");

        // Column header
        IDataProvider columnHeaderDataProvider =
                new DefaultColumnHeaderDataProvider(propertyNames, propertyToLabelMap);
        DataLayer columnHeaderDataLayer =
                new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);
        this.columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, bodyLayer, bodyLayer.getSelectionLayer());
        ColumnGroupHeaderLayer columnGroupHeaderLayer =
                new ColumnGroupHeaderLayer(
                        this.columnHeaderLayer, bodyLayer.getSelectionLayer(),
                        this.columnGroupModel);

        columnGroupHeaderLayer.addColumnsIndexesToGroup("Group 1", 1, 2);

        // calculate the height of the column header area dependent if column
        // groups exist or not
        columnGroupHeaderLayer.setCalculateHeight(true);

        // Note: The column header layer is wrapped in a filter row composite.
        // This plugs in the filter row functionality
        final FilterRowHeaderComposite<RowDataFixture> filterRowHeaderLayer =
                new FilterRowHeaderComposite<>(
                        new DefaultGlazedListsFilterStrategy<>(filterList, columnPropertyAccessor, configRegistry),
                        columnGroupHeaderLayer,
                        columnHeaderDataProvider,
                        configRegistry);

        filterRowHeaderLayer.setFilterRowVisible(false);

        ColumnOverrideLabelAccumulator labelAccumulator =
                new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);
        columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);

        // Register labels
        labelAccumulator.registerColumnOverrides(
                RowDataListFixture.getColumnIndexOfProperty(RowDataListFixture.RATING_PROP_NAME),
                "CUSTOM_COMPARATOR_LABEL");

        // Row header

        final DefaultRowHeaderDataProvider rowHeaderDataProvider =
                new DefaultRowHeaderDataProvider(bodyDataProvider);
        DefaultRowHeaderDataLayer rowHeaderDataLayer =
                new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
        ILayer rowHeaderLayer =
                new RowHeaderLayer(rowHeaderDataLayer, bodyLayer, bodyLayer.getSelectionLayer());

        // Corner

        final DefaultCornerDataProvider cornerDataProvider =
                new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);
        DataLayer cornerDataLayer =
                new DataLayer(cornerDataProvider);
        ILayer cornerLayer =
                new CornerLayer(cornerDataLayer, rowHeaderLayer, filterRowHeaderLayer);

        // Grid
        GridLayer gridLayer =
                new GridLayer(bodyLayer, filterRowHeaderLayer, rowHeaderLayer, cornerLayer);

        NatTable natTable = new NatTable(parent, gridLayer, false);

        // Register create column group command handler

        // Register column chooser
        DisplayColumnChooserCommandHandler columnChooserCommandHandler =
                new DisplayColumnChooserCommandHandler(
                        bodyLayer.getSelectionLayer(),
                        bodyLayer.getColumnHideShowLayer(),
                        this.columnHeaderLayer,
                        columnHeaderDataLayer,
                        columnGroupHeaderLayer,
                        this.columnGroupModel);
        bodyLayer.registerCommandHandler(columnChooserCommandHandler);

        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
        natTable.addConfiguration(new HeaderMenuConfiguration(natTable) {
            @Override
            protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
                return super.createColumnHeaderMenu(natTable)
                        .withColumnChooserMenuItem();
            }
        });
        natTable.addConfiguration(new AbstractRegistryConfiguration() {
            @Override
            public void configureRegistry(IConfigRegistry configRegistry) {
                configRegistry.registerConfigAttribute(
                        ExportConfigAttributes.EXPORTER,
                        new HSSFExcelExporter());
            }
        });
        natTable.addConfiguration(new FilterRowCustomConfiguration());

        natTable.setConfigRegistry(configRegistry);
        natTable.configure();

        // add button
        Button button = new Button(parent, SWT.NONE);
        button.setText("Switch FilterRow visibility");
        button.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                filterRowHeaderLayer.setFilterRowVisible(!filterRowHeaderLayer.isFilterRowVisible());
            }
        });

        return natTable;
    }

    public static class FilterRowCustomConfiguration extends AbstractRegistryConfiguration {

        final DefaultDoubleDisplayConverter doubleDisplayConverter = new DefaultDoubleDisplayConverter();

        @Override
        public void configureRegistry(IConfigRegistry configRegistry) {
            // Configure custom comparator on the rating column
            configRegistry.registerConfigAttribute(
                    FilterRowConfigAttributes.FILTER_COMPARATOR,
                    getIngnorecaseComparator(),
                    DisplayMode.NORMAL,
                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + 2);

            // If threshold comparison is used we have to convert the string
            // entered by the user to the correct underlying type (double), so
            // that it can be compared

            // Configure Bid column
            configRegistry.registerConfigAttribute(
                    FilterRowConfigAttributes.FILTER_DISPLAY_CONVERTER,
                    this.doubleDisplayConverter,
                    DisplayMode.NORMAL,
                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + 5);
            configRegistry.registerConfigAttribute(
                    FilterRowConfigAttributes.TEXT_MATCHING_MODE,
                    TextMatchingMode.REGULAR_EXPRESSION,
                    DisplayMode.NORMAL,
                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + 5);

            // Configure Ask column
            configRegistry.registerConfigAttribute(
                    FilterRowConfigAttributes.FILTER_DISPLAY_CONVERTER,
                    this.doubleDisplayConverter,
                    DisplayMode.NORMAL,
                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + 6);
            configRegistry.registerConfigAttribute(
                    FilterRowConfigAttributes.TEXT_MATCHING_MODE,
                    TextMatchingMode.REGULAR_EXPRESSION,
                    DisplayMode.NORMAL,
                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + 6);

            // Configure a combo box on the pricing type column

            // Register a combo box editor to be displayed in the filter row
            // cell when a value is selected from the combo, the object is
            // converted to a string using the converter (registered below)
            configRegistry.registerConfigAttribute(
                    EditConfigAttributes.CELL_EDITOR,
                    new ComboBoxCellEditor(Arrays.asList(new PricingTypeBean("MN"), new PricingTypeBean("AT"))),
                    DisplayMode.NORMAL,
                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + 4);

            // The pricing bean object in column is converted to using this
            // display converter
            // A 'text' match is then performed against the value from the combo
            // box
            configRegistry.registerConfigAttribute(
                    FilterRowConfigAttributes.FILTER_DISPLAY_CONVERTER,
                    new PricingTypeBeanDisplayConverter(),
                    DisplayMode.NORMAL,
                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + 4);

            configRegistry.registerConfigAttribute(
                    CellConfigAttributes.DISPLAY_CONVERTER,
                    new PricingTypeBeanDisplayConverter(),
                    DisplayMode.NORMAL,
                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + 4);

            configRegistry.registerConfigAttribute(
                    CellConfigAttributes.DISPLAY_CONVERTER,
                    new PricingTypeBeanDisplayConverter(),
                    DisplayMode.NORMAL,
                    "PRICING_TYPE_PROP_NAME");

            // Shade the row to be slightly darker than the blue background.
            final Style rowStyle = new Style();
            rowStyle.setAttributeValue(
                    CellStyleAttributes.BACKGROUND_COLOR,
                    GUIHelper.getColor(197, 212, 231));
            configRegistry.registerConfigAttribute(
                    CellConfigAttributes.CELL_STYLE,
                    rowStyle,
                    DisplayMode.NORMAL,
                    GridRegion.FILTER_ROW);
        }
    }

    private static Comparator<?> getIngnorecaseComparator() {
        return new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareToIgnoreCase(o2);
            }
        };
    };

}
