/*******************************************************************************
 * 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
 *     Dirk Fauth <dirk.fauth@googlemail.com> - Bug 453177
 ******************************************************************************/
package org.eclipse.nebula.widgets.nattable.extension.glazedlists.groupBy;

import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.groupBy.command.GroupByColumnCommandHandler;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.groupBy.command.UngroupByColumnCommandHandler;
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.DimensionallyDependentLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.layer.cell.LayerCell;
import org.eclipse.nebula.widgets.nattable.layer.event.RowStructuralRefreshEvent;

/**
 * The layer that is used to render the groupBy region where columns can be
 * dragged from to perform grouping and ungrouping actions.
 */
public class GroupByHeaderLayer extends DimensionallyDependentLayer {

    public static final String GROUP_BY_REGION = "GROUP_BY_REGION"; //$NON-NLS-1$

    private final GroupByModel groupByModel;

    private GroupByHeaderPainter groupByHeaderPainter;

    private boolean visible = true;

    /**
     * Create a {@link GroupByHeaderLayer} that uses the default
     * {@link GroupByHeaderConfiguration}.
     *
     * @param groupByModel
     *            The {@link GroupByModel} needed for grouping operations.
     * @param horizontalLayerDependency
     *            The {@link ILayer} to which this header layer is horizontally
     *            dependent.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header which is needed
     *            to create the {@link GroupByHeaderConfiguration}
     */
    public GroupByHeaderLayer(
            GroupByModel groupByModel, ILayer horizontalLayerDependency,
            IDataProvider columnHeaderDataProvider) {
        this(groupByModel, horizontalLayerDependency, columnHeaderDataProvider, null, null);
    }

    /**
     * Create a {@link GroupByHeaderLayer} that uses the default
     * {@link GroupByHeaderConfiguration}.
     *
     * @param groupByModel
     *            The {@link GroupByModel} needed for grouping operations.
     * @param horizontalLayerDependency
     *            The {@link ILayer} to which this header layer is horizontally
     *            dependent.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header which is needed
     *            to create the {@link GroupByHeaderConfiguration}
     * @param columnHeaderLayer
     *            The {@link ColumnHeaderLayer} which should be used to create
     *            the {@link GroupByHeaderConfiguration} if it should support
     *            showing the renamed column headers. Can be <code>null</code>
     *            which results in using either the given
     *            columnHeaderDataProvider or the given
     *            {@link GroupByHeaderConfiguration}.
     *
     * @since 1.5
     */
    public GroupByHeaderLayer(
            GroupByModel groupByModel, ILayer horizontalLayerDependency,
            IDataProvider columnHeaderDataProvider, ColumnHeaderLayer columnHeaderLayer) {
        this(groupByModel, horizontalLayerDependency, columnHeaderDataProvider, columnHeaderLayer, null);
    }

    /**
     * Create a {@link GroupByHeaderLayer} that uses the given
     * {@link GroupByHeaderConfiguration}.
     *
     * @param groupByModel
     *            The {@link GroupByModel} needed for grouping operations.
     * @param horizontalLayerDependency
     *            The {@link ILayer} to which this header layer is horizontally
     *            dependent.
     * @param groupByHeaderConfiguration
     *            The {@link GroupByHeaderConfiguration} that should be added to
     *            this {@link GroupByHeaderLayer}. Needs to be a
     *            {@link GroupByHeaderConfiguration} because we retrieve the
     *            necessary {@link GroupByHeaderPainter} out of the
     *            configuration to setup this layer accordingly.
     */
    public GroupByHeaderLayer(
            GroupByModel groupByModel, ILayer horizontalLayerDependency,
            GroupByHeaderConfiguration groupByHeaderConfiguration) {
        this(groupByModel, horizontalLayerDependency, null, groupByHeaderConfiguration);
    }

    /**
     * Create a {@link GroupByHeaderLayer} by either using the given
     * {@link GroupByHeaderConfiguration} or creating a new
     * {@link GroupByHeaderConfiguration} using the given column header
     * {@link IDataProvider}. Note that either the {@link IDataProvider} or the
     * {@link GroupByHeaderConfiguration} parameter must be set. If both are
     * <code>null</code> an {@link IllegalArgumentException} will be thrown.
     *
     * @param groupByModel
     *            The {@link GroupByModel} needed for grouping operations.
     * @param horizontalLayerDependency
     *            The {@link ILayer} to which this header layer is horizontally
     *            dependent.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header which is needed
     *            to create the {@link GroupByHeaderConfiguration}. Can be
     *            <code>null</code> if groupByHeaderConfiguration is not
     *            <code>null</code>.
     * @param groupByHeaderConfiguration
     *            The {@link GroupByHeaderConfiguration} that should be added to
     *            this {@link GroupByHeaderLayer}. Needs to be a
     *            {@link GroupByHeaderConfiguration} because we retrieve the
     *            necessary {@link GroupByHeaderPainter} out of the
     *            configuration to setup this layer accordingly. Can be
     *            <code>null</code> if columnHeaderDataProvider is not
     *            <code>null</code>.
     */
    public GroupByHeaderLayer(
            GroupByModel groupByModel, ILayer horizontalLayerDependency,
            IDataProvider columnHeaderDataProvider,
            GroupByHeaderConfiguration groupByHeaderConfiguration) {
        this(groupByModel, horizontalLayerDependency, columnHeaderDataProvider, null, groupByHeaderConfiguration);
    }

    /**
     * Create a {@link GroupByHeaderLayer} by either using the given
     * {@link GroupByHeaderConfiguration} or creating a new
     * {@link GroupByHeaderConfiguration} using the given column header
     * {@link IDataProvider}. Note that either the {@link IDataProvider} or the
     * {@link GroupByHeaderConfiguration} parameter must be set. If both are
     * <code>null</code> an {@link IllegalArgumentException} will be thrown.
     *
     * @param groupByModel
     *            The {@link GroupByModel} needed for grouping operations.
     * @param horizontalLayerDependency
     *            The {@link ILayer} to which this header layer is horizontally
     *            dependent.
     * @param columnHeaderDataProvider
     *            The {@link IDataProvider} of the column header which is needed
     *            to create the {@link GroupByHeaderConfiguration}. Can be
     *            <code>null</code> if groupByHeaderConfiguration is not
     *            <code>null</code>.
     * @param columnHeaderLayer
     *            The {@link ColumnHeaderLayer} which should be used to create
     *            the {@link GroupByHeaderConfiguration} if it should support
     *            showing the renamed column headers. Can be <code>null</code>
     *            which results in using either the given
     *            columnHeaderDataProvider or the given
     *            {@link GroupByHeaderConfiguration}.
     * @param groupByHeaderConfiguration
     *            The {@link GroupByHeaderConfiguration} that should be added to
     *            this {@link GroupByHeaderLayer}. Needs to be a
     *            {@link GroupByHeaderConfiguration} because we retrieve the
     *            necessary {@link GroupByHeaderPainter} out of the
     *            configuration to setup this layer accordingly. Can be
     *            <code>null</code> if columnHeaderDataProvider is not
     *            <code>null</code>.
     *
     * @since 1.5
     */
    public GroupByHeaderLayer(
            GroupByModel groupByModel, ILayer horizontalLayerDependency,
            IDataProvider columnHeaderDataProvider, ColumnHeaderLayer columnHeaderLayer,
            GroupByHeaderConfiguration groupByHeaderConfiguration) {

        super(new DataLayer(new IDataProvider() {
            @Override
            public Object getDataValue(int columnIndex, int rowIndex) {
                return null;
            }

            @Override
            public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
                // no local data storage
            }

            @Override
            public int getRowCount() {
                return 1;
            }

            @Override
            public int getColumnCount() {
                return 1;
            }
        }));

        if (groupByModel == null) {
            throw new IllegalArgumentException("GroupByModel can not be null!"); //$NON-NLS-1$
        }
        if (columnHeaderDataProvider == null && groupByHeaderConfiguration == null) {
            throw new IllegalArgumentException("You must either specify a GroupByHeaderConfiguration " //$NON-NLS-1$
                    + "or a columnHeaderDataProvider to be able to create an internal GroupByHeaderConfiguration!"); //$NON-NLS-1$
        }

        setHorizontalLayerDependency(horizontalLayerDependency);
        setVerticalLayerDependency(getBaseLayer());

        this.groupByModel = groupByModel;
        registerPersistable(this.groupByModel);

        registerCommandHandler(new GroupByColumnCommandHandler(this));
        registerCommandHandler(new UngroupByColumnCommandHandler(this));

        GroupByHeaderConfiguration configuration = null;
        if (groupByHeaderConfiguration != null) {
            configuration = groupByHeaderConfiguration;
        } else {
            configuration = new GroupByHeaderConfiguration(groupByModel, columnHeaderDataProvider, columnHeaderLayer);
        }
        addConfiguration(configuration);

        this.groupByHeaderPainter = configuration.getGroupByHeaderPainter();
        ((DataLayer) getBaseLayer()).setRowHeightByPosition(0, this.groupByHeaderPainter.getPreferredHeight());
    }

    public GroupByModel getGroupByModel() {
        return this.groupByModel;
    }

    public void setVisible(boolean visible) {
        this.visible = visible;
        fireLayerEvent(new RowStructuralRefreshEvent(getBaseLayer()));
    }

    public boolean isVisible() {
        return this.visible;
    }

    @Override
    public int getHeight() {
        if (this.visible) {
            return super.getHeight();
        }
        return 0;
    }

    @Override
    public int getRowHeightByPosition(int rowPosition) {
        if (this.visible) {
            return super.getRowHeightByPosition(rowPosition);
        }
        return 0;
    }

    @Override
    public ILayerCell getCellByPosition(int columnPosition, int rowPosition) {
        return new LayerCell(this, 0, 0, 0, 0, getColumnCount(), 1);
    }

    public int getGroupByColumnIndexAtXY(int x, int y) {
        return this.groupByHeaderPainter.getGroupByColumnIndexAtXY(x, y);
    }

}
