/*******************************************************************************
 * Copyright (c) 2014, 2020 Dirk Fauth.
 *
 * 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.summaryrow;

import java.util.Collection;

import org.eclipse.nebula.widgets.nattable.command.ILayerCommand;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.coordinate.Range;
import org.eclipse.nebula.widgets.nattable.grid.command.ClientAreaResizeCommand;
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
import org.eclipse.nebula.widgets.nattable.layer.AbstractLayer;
import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
import org.eclipse.nebula.widgets.nattable.layer.LayerUtil;
import org.eclipse.nebula.widgets.nattable.layer.event.IVisualChangeEvent;
import org.eclipse.nebula.widgets.nattable.painter.layer.GridLineCellLayerPainter;
import org.eclipse.nebula.widgets.nattable.painter.layer.ILayerPainter;

/**
 * This layer is a specialization of the {@link SummaryRowLayer} and is intended
 * to be used in a composition below a {@link GridLayer} or a vertical
 * composition like one with a column header and a body. It is horizontal
 * dependent to the layer above and configured as a standalone summary row,
 * which means that only the summary row is rendered.
 * <p>
 * A typical composition to use this layer could look like this:<br>
 *
 * <pre>
 * +---------------+---------------+<br>
 * |         CompositeLayer        |<br>
 * |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+<br>
 * | +---------------------------+ |<br>
 * | |          GridLayer        | |<br>
 * | +~~~~~~~~~~~~~~~~~~~~~~~~~~~+ |<br>
 * | |  Corner   |   ColHeader   | |<br>
 * | +-----------+---------------+ |<br>
 * | | RowHeader |     Body      | |<br>
 * | +-----------+---------------+ |<br>
 * +-------------------------------+<br>
 * |      FixedSummaryRowLayer     |<br>
 * +-------------------------------+<br>
 * </pre>
 * <p>
 * It would be created by the following code:
 * </p>
 *
 * <pre>
 *      GridLayer gridLayer = new GridLayer(...);
 *      FixedSummaryRowLayer summaryRowLayer =
 *          new FixedSummaryRowLayer(bodyDataLayer, gridLayer, configRegistry);
 *
 *      CompositeLayer composite = new CompositeLayer(1, 2);
 *      composite.setChildLayer("GRID", gridLayer, 0, 0);
 *      composite.setChildLayer(SUMMARY_REGION, summaryRowLayer, 0, 1);
 *
 *      NatTable natTable = new NatTable(panel, composite);
 * </pre>
 * <p>
 * Note that the <i>bodyDataLayer</i> needs to be accessible somehow and the
 * creation of the {@link GridLayer} is not specified in detail in the above
 * example.
 * </p>
 * <p>
 * Using this layer in a composition as shown above will result in a fixed
 * summary row that doesn't scroll if the viewport is scrolled. This is
 * different to the typical approach of adding the {@link SummaryRowLayer} on
 * top of the body {@link DataLayer} where the summary row will scroll as part
 * of the body region.
 * </p>
 */
public class FixedSummaryRowLayer extends SummaryRowLayer {

    public static final String DEFAULT_SUMMARY_ROW_LABEL = "Summary"; //$NON-NLS-1$
    protected String summaryRowLabel = DEFAULT_SUMMARY_ROW_LABEL;

    /**
     * The layer to which fixed summary row should be horizontally dependent.
     * Typically a {@link GridLayer} or the body layer stack in case of a simple
     * vertical composition.
     */
    protected ILayer horizontalLayerDependency;

    /**
     * Flag to tell whether the horizontal dependency is a composite or not. For
     * example, if the horizontal dependency is a {@link GridLayer} it contains
     * a row header, which means this {@link FixedSummaryRowLayer} needs to
     * handle an additional column at position 1.
     */
    private boolean horizontalCompositeDependency = true;

    /**
     * Creates a standalone {@link FixedSummaryRowLayer} that is horizontal
     * dependent to the given layer and calculates the summary values from the
     * given bodyDataLayer. It will register the default configurations and
     * perform smooth value updates.
     *
     * <p>
     * <b>Note:</b><br>
     * The {@link FixedSummaryRowLayer} constructor is setting a
     * {@link GridLineCellLayerPainter} that is configured for clipTop to the
     * given bodyDataLayer. This is necessary as otherwise the body region would
     * paint over the summary row. In case you want to use a different
     * {@link ILayerPainter} ensure to set it AFTER creating the
     * {@link FixedSummaryRowLayer}.
     * </p>
     *
     * @param bodyDataLayer
     *            The underlying layer on which this layer should be build.
     *            Typically the {@link DataLayer} of the body region.
     * @param horizontalLayerDependency
     *            The layer that is above this layer in the surrounding
     *            composition. Typically a {@link GridLayer}.
     * @param configRegistry
     *            The ConfigRegistry for retrieving the ISummaryProvider per
     *            column.
     */
    public FixedSummaryRowLayer(
            IUniqueIndexLayer bodyDataLayer, ILayer horizontalLayerDependency, IConfigRegistry configRegistry) {
        this(bodyDataLayer, horizontalLayerDependency, configRegistry, true, true);
    }

    /**
     * Creates a standalone {@link FixedSummaryRowLayer} that is horizontal
     * dependent to the given layer and calculates the summary values from the
     * given bodyDataLayer. It will perform smooth value updates.
     *
     * <p>
     * <b>Note:</b><br>
     * The {@link FixedSummaryRowLayer} constructor is setting a
     * {@link GridLineCellLayerPainter} that is configured for clipTop to the
     * given bodyDataLayer. This is necessary as otherwise the body region would
     * paint over the summary row. In case you want to use a different
     * {@link ILayerPainter} ensure to set it AFTER creating the
     * {@link FixedSummaryRowLayer}.
     * </p>
     *
     * @param bodyDataLayer
     *            The underlying layer on which this layer should be build.
     *            Typically the {@link DataLayer} of the body region.
     * @param horizontalLayerDependency
     *            The layer that is above this layer in the surrounding
     *            composition. Typically a {@link GridLayer}.
     * @param configRegistry
     *            The ConfigRegistry for retrieving the ISummaryProvider per
     *            column.
     * @param autoConfigure
     *            <code>true</code> to use the DefaultSummaryRowConfiguration,
     *            <code>false</code> if a custom configuration will be set after
     *            the creation.
     */
    public FixedSummaryRowLayer(
            IUniqueIndexLayer bodyDataLayer, ILayer horizontalLayerDependency, IConfigRegistry configRegistry,
            boolean autoConfigure) {
        this(bodyDataLayer, horizontalLayerDependency, configRegistry, true, autoConfigure);
    }

    /**
     * Creates a standalone {@link FixedSummaryRowLayer} that is horizontal
     * dependent to the given layer and calculates the summary values from the
     * given bodyDataLayer.
     *
     * <p>
     * <b>Note:</b><br>
     * The {@link FixedSummaryRowLayer} constructor is setting a
     * {@link GridLineCellLayerPainter} that is configured for clipTop to the
     * given bodyDataLayer. This is necessary as otherwise the body region would
     * paint over the summary row. In case you want to use a different
     * {@link ILayerPainter} ensure to set it AFTER creating the
     * {@link FixedSummaryRowLayer}.
     * </p>
     *
     * @param bodyDataLayer
     *            The underlying layer on which this layer should be build.
     *            Typically the {@link DataLayer} of the body region.
     *            <p>
     *            <b>Note</b>: When using a different layer than the DataLayer,
     *            e.g. the GlazedListsEventLayer to receive automatic updates,
     *            you need to ensure that the GridLineCellLayerPainter
     *            configured for clipping on top is set to the DataLayer for
     *            correct rendering of the fixed summary row.
     *            </p>
     * @param horizontalLayerDependency
     *            The layer that is above this layer in the surrounding
     *            composition. Typically a {@link GridLayer}.
     * @param configRegistry
     *            The ConfigRegistry for retrieving the ISummaryProvider per
     *            column.
     * @param smoothUpdates
     *            <code>true</code> if the summary value updates should be
     *            performed smoothly, <code>false</code> if on re-calculation
     *            the value should be immediately shown as not calculated.
     * @param autoConfigure
     *            <code>true</code> to use the DefaultSummaryRowConfiguration,
     *            <code>false</code> if a custom configuration will be set after
     *            the creation.
     */
    public FixedSummaryRowLayer(
            IUniqueIndexLayer bodyDataLayer, ILayer horizontalLayerDependency, IConfigRegistry configRegistry,
            boolean smoothUpdates, boolean autoConfigure) {
        super(bodyDataLayer, configRegistry, smoothUpdates, autoConfigure);
        this.horizontalLayerDependency = horizontalLayerDependency;
        setStandalone(true);

        // register a GridLineCellLayerPainter that is configured for clipping
        // on top
        if (bodyDataLayer instanceof AbstractLayer) {
            ((AbstractLayer) bodyDataLayer).setLayerPainter(new GridLineCellLayerPainter(false, true));
        }

        // if the layer we are dependent to has changed, we need to update
        if (this.horizontalLayerDependency != null) {
            horizontalLayerDependency.addLayerListener(event -> {
                // we only propagate events further if we need to handle
                // them upwards, otherwise we only clear the cache like the
                // SummaryRowLayer does without further processing upwards
                if (event.convertToLocal(FixedSummaryRowLayer.this)) {
                    FixedSummaryRowLayer.this.handleLayerEvent(event.cloneEvent());
                } else if (event instanceof IVisualChangeEvent) {
                    clearCache();
                }
            });
        }
    }

    @Override
    public boolean doCommand(ILayerCommand command) {
        if (command instanceof ClientAreaResizeCommand) {
            // we do not handle the command here as the fixed summary row is not
            // responsible for handling scrollbars. we avoid further processing
            // down the layer stack here, as the command could then get consumed
            // too early in the body data layer.
            return false;
        }
        return super.doCommand(command);
    }

    @Override
    public Object getDataValueByPosition(int columnPosition, int rowPosition) {
        if (!isBodyColumn(columnPosition)) {
            return getSummaryRowLabel();
        }

        int columnIndex = LayerUtil.convertColumnPosition(
                this.horizontalLayerDependency, columnPosition, (IUniqueIndexLayer) this.underlyingLayer);
        return super.getDataValueByPosition(columnIndex, rowPosition);
    }

    /**
     *
     * @param columnPosition
     *            The column position that should be checked.
     * @return <code>true</code> if the column at the given position is a column
     *         of the body, <code>false</code> if it is a column of another
     *         region, e.g. the row header in a grid.
     */
    protected boolean isBodyColumn(int columnPosition) {
        return !(this.horizontalCompositeDependency && (columnPosition == 0));
    }

    /**
     *
     * @return <code>true</code> if the horizontal dependency is itself a
     *         composite that has an additional column, e.g. a {@link GridLayer}
     *         with a row header. <code>false</code> if the horizontal
     *         dependency is not a composite, e.g. the body layer stack.
     */
    public boolean hasHorizontalCompositeDependency() {
        return this.horizontalCompositeDependency;
    }

    /**
     * Specify if the horizontal dependency is a {@link CompositeLayer} that
     * adds additional columns.
     *
     * @param compositeDependency
     *            <code>true</code> to specify that the horizontal dependency is
     *            itself a composite that has an additional column, e.g. a
     *            {@link GridLayer} with a row header. <code>false</code> if the
     *            horizontal dependency is not a composite, e.g. the body layer
     *            stack.
     */
    public void setHorizontalCompositeDependency(boolean compositeDependency) {
        this.horizontalCompositeDependency = compositeDependency;
    }

    /**
     *
     * @return The label that is used as data value for the horizontal dependent
     *         cell to the row header column.
     */
    public String getSummaryRowLabel() {
        return this.summaryRowLabel;
    }

    /**
     *
     * @param summaryRowLabel
     *            The label that should be used as data value for the horizontal
     *            dependent cell to the row header column.
     */
    public void setSummaryRowLabel(String summaryRowLabel) {
        this.summaryRowLabel = summaryRowLabel;
    }

    /**
     * This implementation directly calls the super implementation. This is done
     * to skip the column position-index transformation since it was done
     * already.
     */
    @Override
    protected LabelStack getConfigLabelsByPositionWithoutTransformation(int columnPosition, int rowPosition) {
        return super.getConfigLabelsByPosition(columnPosition, rowPosition);
    }

    @Override
    public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {
        if (!isBodyColumn(columnPosition)) {
            LabelStack labelStack = this.horizontalLayerDependency.getConfigLabelsByPosition(
                    columnPosition, this.horizontalLayerDependency.getRowCount() - 1);

            labelStack.addLabelOnTop(SummaryRowLayer.DEFAULT_SUMMARY_ROW_CONFIG_LABEL);

            if (getConfigLabelAccumulator() != null) {
                getConfigLabelAccumulator().accumulateConfigLabels(labelStack, columnPosition, rowPosition);
            }
            return labelStack;
        }

        int columnIndex = LayerUtil.convertColumnPosition(
                this.horizontalLayerDependency, columnPosition, (IUniqueIndexLayer) this.underlyingLayer);
        return super.getConfigLabelsByPosition(columnIndex, rowPosition);
    }

    // Columns

    @Override
    public int getColumnCount() {
        return this.horizontalLayerDependency.getColumnCount();
    }

    @Override
    public int getPreferredColumnCount() {
        return this.horizontalLayerDependency.getPreferredColumnCount();
    }

    @Override
    public int getColumnIndexByPosition(int columnPosition) {
        return this.horizontalLayerDependency.getColumnIndexByPosition(columnPosition);
    }

    @Override
    public int localToUnderlyingColumnPosition(int localColumnPosition) {
        return this.horizontalLayerDependency.localToUnderlyingColumnPosition(localColumnPosition);
    }

    @Override
    public int underlyingToLocalColumnPosition(ILayer sourceUnderlyingLayer, int underlyingColumnPosition) {
        if (sourceUnderlyingLayer == this.horizontalLayerDependency) {
            return underlyingColumnPosition;
        }
        return this.horizontalLayerDependency.underlyingToLocalColumnPosition(
                sourceUnderlyingLayer, underlyingColumnPosition);
    }

    @Override
    public Collection<Range> underlyingToLocalColumnPositions(
            ILayer sourceUnderlyingLayer,
            Collection<Range> underlyingColumnPositionRanges) {

        if (sourceUnderlyingLayer == this.horizontalLayerDependency) {
            return underlyingColumnPositionRanges;
        }
        return this.horizontalLayerDependency.underlyingToLocalColumnPositions(
                sourceUnderlyingLayer, underlyingColumnPositionRanges);
    }

    // Width

    @Override
    public int getWidth() {
        return this.horizontalLayerDependency.getWidth();
    }

    @Override
    public int getPreferredWidth() {
        return this.horizontalLayerDependency.getPreferredWidth();
    }

    @Override
    public int getColumnWidthByPosition(int columnPosition) {
        return this.horizontalLayerDependency.getColumnWidthByPosition(columnPosition);
    }

    // Column resize

    @Override
    public boolean isColumnPositionResizable(int columnPosition) {
        return this.horizontalLayerDependency.isColumnPositionResizable(columnPosition);
    }

    // X

    @Override
    public int getColumnPositionByX(int x) {
        return this.horizontalLayerDependency.getColumnPositionByX(x);
    }

    @Override
    public int getStartXOfColumnPosition(int columnPosition) {
        return this.horizontalLayerDependency.getStartXOfColumnPosition(columnPosition);
    }

}
