/*******************************************************************************
 * 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._130_Sorting;

import java.util.Comparator;

import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
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.config.IConfiguration;
import org.eclipse.nebula.widgets.nattable.config.NullComparator;
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.examples.AbstractNatExample;
import org.eclipse.nebula.widgets.nattable.examples.PersistentNatExampleWrapper;
import org.eclipse.nebula.widgets.nattable.examples.fixtures.GlazedListsGridLayer;
import org.eclipse.nebula.widgets.nattable.examples.runner.StandaloneNatExampleRunner;
import org.eclipse.nebula.widgets.nattable.layer.AbstractLayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.persistence.command.DisplayPersistenceDialogCommandHandler;
import org.eclipse.nebula.widgets.nattable.selection.config.DefaultSelectionStyleConfiguration;
import org.eclipse.nebula.widgets.nattable.sort.SortConfigAttributes;
import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer;
import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.ui.menu.HeaderMenuConfiguration;
import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;

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

/**
 * Example to demonstrate sorting of the columns.
 */
public class SortableGridExample extends AbstractNatExample {

    private static final String CUSTOM_COMPARATOR_LABEL = "customComparatorLabel";
    protected static final String NO_SORT_LABEL = "noSortLabel";

    public static void main(String[] args) {
        StandaloneNatExampleRunner.run(700, 400,
                new PersistentNatExampleWrapper(new SortableGridExample()));
    }

    @Override
    public String getDescription() {
        return "Grid demonstrates sorting moving data.\n"
                + "\n"
                + "Features:\n"
                + "The contents of the grid are kept in sorted order as the rows are added/removed.\n"
                + "Custom comparators can be applied to each column.\n"
                + "Ignorecase comparator applied to the 'Rating' column.\n"
                + "Sorting can be turned off on the selective columns.\n"
                + "'Ask price' field is not sortable.\n"
                + "\n"
                + "Key bindings:\n"
                + "Sort by left clicking on the column header.\n"
                + "Add columns to the existing sort by (Alt. + left click) on the column header\n"
                + "\n"
                + "Technical information:\n"
                + "The default implementation uses GlazedLists to sort the backing data source.";
    }

    private TransformedList<RowDataFixture, RowDataFixture> rowObjectsGlazedList;
    private NatTable nattable;

    /**
     * @see GlazedListsGridLayer to see the required stack setup. Basically the
     *      {@link SortHeaderLayer} needs to be a part of the Column header
     *      layer stack.
     */
    @Override
    public Control createExampleControl(Composite parent) {
        EventList<RowDataFixture> eventList = GlazedLists.eventList(RowDataListFixture.getList());
        this.rowObjectsGlazedList = GlazedLists.threadSafeList(eventList);

        ConfigRegistry configRegistry = new ConfigRegistry();
        GlazedListsGridLayer<RowDataFixture> glazedListsGridLayer =
                new GlazedListsGridLayer<>(
                        this.rowObjectsGlazedList,
                        RowDataListFixture.getPropertyNames(),
                        RowDataListFixture.getPropertyToLabelMap(),
                        configRegistry);

        this.nattable = new NatTable(parent, glazedListsGridLayer, false);

        this.nattable.setConfigRegistry(configRegistry);
        this.nattable.addConfiguration(new DefaultNatTableStyleConfiguration());

        // Change the default sort key bindings. Note that 'auto configure' was
        // turned off for the SortHeaderLayer (setup in the
        // GlazedListsGridLayer)
        this.nattable.addConfiguration(new SingleClickSortConfiguration());
        this.nattable.addConfiguration(
                getCustomComparatorConfiguration(
                        glazedListsGridLayer.getColumnHeaderLayerStack().getDataLayer()));
        this.nattable.addConfiguration(new DefaultSelectionStyleConfiguration());

        this.nattable.addConfiguration(new HeaderMenuConfiguration(this.nattable) {
            @Override
            protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
                return super.createColumnHeaderMenu(natTable)
                        .withStateManagerMenuItemProvider();
            }
        });

        this.nattable.configure();

        // add the DisplayPersistenceDialogCommandHandler with the created
        // NatTable instance after configure() so all configuration and states
        // are correctly applied before storing the default state
        glazedListsGridLayer.registerCommandHandler(
                new DisplayPersistenceDialogCommandHandler(this.nattable));
        return this.nattable;
    }

    /**
     * NOTE: The labels for the the custom comparators must go on the
     * columnHeaderDataLayer - since, the SortHeaderLayer will resolve cell
     * labels with respect to its underlying layer i.e columnHeaderDataLayer
     */
    private IConfiguration getCustomComparatorConfiguration(final AbstractLayer columnHeaderDataLayer) {

        return new AbstractRegistryConfiguration() {

            @Override
            public void configureRegistry(IConfigRegistry configRegistry) {
                // Add label accumulator
                ColumnOverrideLabelAccumulator labelAccumulator =
                        new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);
                columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);

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

                labelAccumulator.registerColumnOverrides(
                        RowDataListFixture.getColumnIndexOfProperty(RowDataListFixture.ASK_PRICE_PROP_NAME),
                        NO_SORT_LABEL);

                // Register custom comparator
                configRegistry.registerConfigAttribute(
                        SortConfigAttributes.SORT_COMPARATOR,
                        getCustomComparator(),
                        DisplayMode.NORMAL,
                        CUSTOM_COMPARATOR_LABEL);

                // Register null comparator to disable sort
                configRegistry.registerConfigAttribute(
                        SortConfigAttributes.SORT_COMPARATOR,
                        new NullComparator(),
                        DisplayMode.NORMAL,
                        NO_SORT_LABEL);
            }
        };
    }

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