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

import java.util.Collection;

import org.eclipse.nebula.widgets.nattable.columnRename.DisplayColumnRenameDialogCommandHandler;
import org.eclipse.nebula.widgets.nattable.columnRename.RenameColumnHeaderCommandHandler;
import org.eclipse.nebula.widgets.nattable.columnRename.RenameColumnHelper;
import org.eclipse.nebula.widgets.nattable.columnRename.event.RenameColumnHeaderEvent;
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.config.DefaultColumnHeaderLayerConfiguration;
import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
import org.eclipse.nebula.widgets.nattable.layer.event.IStructuralChangeEvent;
import org.eclipse.nebula.widgets.nattable.layer.event.StructuralChangeEventHelper;
import org.eclipse.nebula.widgets.nattable.layer.event.StructuralDiff;
import org.eclipse.nebula.widgets.nattable.painter.layer.ILayerPainter;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.style.SelectionStyleLabels;

/**
 * Responsible for rendering, event handling etc on the column headers.
 */
public class ColumnHeaderLayer extends DimensionallyDependentLayer {

    private final SelectionLayer[] selectionLayer;

    protected RenameColumnHelper renameColumnHelper;

    /**
     * Creates a column header layer using the default configuration and painter
     *
     * @param baseLayer
     *            The base layer for this layer, typically a DataLayer.
     * @param horizontalLayerDependency
     *            The layer to link the horizontal dimension to, typically the
     *            body layer.
     * @param selectionLayer
     *            The SelectionLayer needed to respond to selection events.
     */
    public ColumnHeaderLayer(
            IUniqueIndexLayer baseLayer,
            ILayer horizontalLayerDependency,
            SelectionLayer selectionLayer) {

        this(baseLayer, horizontalLayerDependency, selectionLayer, true);
    }

    /**
     * Creates a column header layer using the default configuration and painter
     *
     * @param baseLayer
     *            The base layer for this layer, typically a DataLayer.
     * @param horizontalLayerDependency
     *            The layer to link the horizontal dimension to, typically the
     *            body layer.
     * @param selectionLayer
     *            0 to multiple SelectionLayer needed to respond to selection
     *            events.
     * @since 1.4
     */
    public ColumnHeaderLayer(
            IUniqueIndexLayer baseLayer,
            ILayer horizontalLayerDependency,
            SelectionLayer... selectionLayer) {

        this(baseLayer, horizontalLayerDependency, selectionLayer, true);
    }

    /**
     * Creates a row header layer using the default painter.
     *
     * @param baseLayer
     *            The base layer for this layer, typically a DataLayer.
     * @param horizontalLayerDependency
     *            The layer to link the horizontal dimension to, typically the
     *            body layer.
     * @param selectionLayer
     *            The SelectionLayer needed to respond to selection events.
     * @param useDefaultConfiguration
     *            Flag to configure whether to use the default configuration or
     *            not.
     */
    public ColumnHeaderLayer(
            IUniqueIndexLayer baseLayer,
            ILayer horizontalLayerDependency,
            SelectionLayer selectionLayer,
            boolean useDefaultConfiguration) {

        this(baseLayer, horizontalLayerDependency, selectionLayer, useDefaultConfiguration, null);
    }

    /**
     * Creates a row header layer using the default painter.
     *
     * @param baseLayer
     *            The base layer for this layer, typically a DataLayer.
     * @param horizontalLayerDependency
     *            The layer to link the horizontal dimension to, typically the
     *            body layer.
     * @param selectionLayer
     *            0 to multiple SelectionLayer needed to respond to selection
     *            events.
     * @param useDefaultConfiguration
     *            Flag to configure whether to use the default configuration or
     *            not.
     * @since 1.4
     */
    public ColumnHeaderLayer(
            IUniqueIndexLayer baseLayer,
            ILayer horizontalLayerDependency,
            SelectionLayer[] selectionLayer,
            boolean useDefaultConfiguration) {

        this(baseLayer, horizontalLayerDependency, selectionLayer, useDefaultConfiguration, null);
    }

    /**
     * @param baseLayer
     *            The base layer for this layer, typically a DataLayer.
     * @param horizontalLayerDependency
     *            The layer to link the horizontal dimension to, typically the
     *            body layer.
     * @param selectionLayer
     *            The SelectionLayer needed to respond to selection events.
     * @param useDefaultConfiguration
     *            Flag to configure whether to use the default configuration or
     *            not.
     * @param layerPainter
     *            The painter for this layer or <code>null</code> to use the
     *            painter of the base layer.
     */
    public ColumnHeaderLayer(
            IUniqueIndexLayer baseLayer,
            ILayer horizontalLayerDependency,
            SelectionLayer selectionLayer,
            boolean useDefaultConfiguration,
            ILayerPainter layerPainter) {

        this(baseLayer, horizontalLayerDependency,
                selectionLayer != null ? new SelectionLayer[] { selectionLayer } : new SelectionLayer[] {},
                useDefaultConfiguration, layerPainter);
    }

    /**
     *
     * @param baseLayer
     *            The base layer for this layer, typically a DataLayer.
     * @param horizontalLayerDependency
     *            The layer to link the horizontal dimension to, typically the
     *            body layer.
     * @param selectionLayer
     *            0 to multiple SelectionLayer needed to respond to selection
     *            events.
     * @param useDefaultConfiguration
     *            Flag to configure whether to use the default configuration or
     *            not.
     * @param layerPainter
     *            The painter for this layer or <code>null</code> to use the
     *            painter of the base layer.
     * @since 1.4
     */
    public ColumnHeaderLayer(
            IUniqueIndexLayer baseLayer,
            ILayer horizontalLayerDependency,
            SelectionLayer[] selectionLayer,
            boolean useDefaultConfiguration,
            ILayerPainter layerPainter) {

        super(baseLayer, horizontalLayerDependency, baseLayer);

        if (selectionLayer == null) {
            this.selectionLayer = new SelectionLayer[] {};
        } else {
            this.selectionLayer = selectionLayer;
        }

        this.layerPainter = layerPainter;

        this.renameColumnHelper = new RenameColumnHelper(this);
        registerPersistable(this.renameColumnHelper);

        for (SelectionLayer sl : this.selectionLayer) {
            sl.addLayerListener(new ColumnHeaderSelectionListener(this));
        }
        registerCommandHandlers();

        if (useDefaultConfiguration) {
            addConfiguration(new DefaultColumnHeaderLayerConfiguration());
        }
    }

    @Override
    public DisplayMode getDisplayModeByPosition(int columnPosition, int rowPosition) {
        DisplayMode displayMode = super.getDisplayModeByPosition(columnPosition, rowPosition);
        if (this.selectionLayer.length > 0) {
            int selectionLayerColumnPosition = LayerUtil.convertColumnPosition(this, columnPosition, this.selectionLayer[0]);
            for (SelectionLayer sl : this.selectionLayer) {
                if (sl.isColumnPositionSelected(selectionLayerColumnPosition)) {
                    if (DisplayMode.HOVER.equals(displayMode)) {
                        return DisplayMode.SELECT_HOVER;
                    }
                    return DisplayMode.SELECT;
                }
            }
        }
        return displayMode;
    }

    @Override
    public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {
        LabelStack labelStack = super.getConfigLabelsByPosition(columnPosition, rowPosition);

        if (this.selectionLayer.length > 0) {
            final int selectionLayerColumnPosition = LayerUtil.convertColumnPosition(this, columnPosition, this.selectionLayer[0]);
            boolean fullySelected = true;
            for (SelectionLayer sl : this.selectionLayer) {
                if (!sl.isColumnPositionFullySelected(selectionLayerColumnPosition)) {
                    fullySelected = false;
                    break;
                }
            }

            if (fullySelected) {
                labelStack.addLabel(SelectionStyleLabels.COLUMN_FULLY_SELECTED_STYLE);
            }
        }

        return labelStack;
    }

    @Override
    public Object getDataValueByPosition(int columnPosition, int rowPosition) {
        int columnIndex = getColumnIndexByPosition(columnPosition);
        if (isColumnRenamed(columnIndex)) {
            return getRenamedColumnLabelByIndex(columnIndex);
        }
        return super.getDataValueByPosition(columnPosition, rowPosition);
    }

    // Configuration

    @Override
    protected void registerCommandHandlers() {
        registerCommandHandler(new RenameColumnHeaderCommandHandler(this));
        registerCommandHandler(new DisplayColumnRenameDialogCommandHandler(this));
    }

    // Column header renaming

    @Override
    public void handleLayerEvent(ILayerEvent event) {
        if (event instanceof IStructuralChangeEvent) {
            IStructuralChangeEvent structuralChangeEvent = (IStructuralChangeEvent) event;
            if (structuralChangeEvent.isHorizontalStructureChanged()) {
                Collection<StructuralDiff> columnDiffs = structuralChangeEvent.getColumnDiffs();

                if (columnDiffs != null && !columnDiffs.isEmpty()
                        && !StructuralChangeEventHelper.isReorder(columnDiffs)) {
                    this.renameColumnHelper.handleStructuralChanges(columnDiffs);
                }
            }
        }
        super.handleLayerEvent(event);
    }

    /**
     * @param columnPosition
     *            the column position for which the original label is requested
     * @return column header as defined by the data source
     */
    public String getOriginalColumnLabel(int columnPosition) {
        Object dataValue = super.getDataValueByPosition(columnPosition, 0);
        return dataValue != null ? dataValue.toString() : ""; //$NON-NLS-1$
    }

    /**
     * @param columnPosition
     *            the column position for which the renamed label is requested
     * @return renamed column header if the column has been renamed, NULL
     *         otherwise
     */
    public String getRenamedColumnLabel(int columnPosition) {
        int index = getColumnIndexByPosition(columnPosition);
        return getRenamedColumnLabelByIndex(index);
    }

    /**
     * @param columnIndex
     *            the column index for which the renamed label is requested
     * @return renamed column header if the column has been renamed, NULL
     *         otherwise
     */
    public String getRenamedColumnLabelByIndex(int columnIndex) {
        return this.renameColumnHelper.getRenamedColumnLabel(columnIndex);
    }

    /**
     * @param columnIndex
     *            the column index that should be checked
     * @return <code>true</code> if the column at the given index has been given
     *         a custom name by the user.
     */
    public boolean isColumnRenamed(int columnIndex) {
        return this.renameColumnHelper.isColumnRenamed(columnIndex);
    }

    public boolean renameColumnPosition(int columnPosition, String customColumnName) {
        boolean renamed = this.renameColumnHelper.renameColumnPosition(columnPosition, customColumnName);
        if (renamed) {
            fireLayerEvent(new RenameColumnHeaderEvent(this, columnPosition));
        }
        return renamed;
    }

    public boolean renameColumnIndex(int columnIndex, String customColumnName) {
        boolean renamed = this.renameColumnHelper.renameColumnIndex(columnIndex, customColumnName);
        if (renamed) {
            // search for the bottom layer in the horizontal dependency to
            // create the event for index and correct layer
            ILayer baseLayer = getHorizontalLayerDependency();
            while (baseLayer.getUnderlyingLayerByPosition(0, 0) != null) {
                baseLayer = baseLayer.getUnderlyingLayerByPosition(0, 0);
            }

            baseLayer.fireLayerEvent(new RenameColumnHeaderEvent(baseLayer, columnIndex));
        }
        return renamed;
    }

}
