/*******************************************************************************
 * 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.painter.cell.decorator;

import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;
import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
import org.eclipse.nebula.widgets.nattable.style.CellStyleUtil;
import org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;

/**
 * Decorates a cell painter with another cell painter.
 */
public class CellPainterDecorator implements ICellPainter {

    /**
     * The base {@link ICellPainter} that is decorated.
     */
    private ICellPainter baseCellPainter;
    /**
     * The edge of the cell at which the decoration is applied.
     */
    private CellEdgeEnum cellEdge;
    /**
     * The {@link ICellPainter} that is used to render the decoration.
     */
    private ICellPainter decoratorCellPainter;
    /**
     * The spacing to use between base painter and decoration painter. Note: If
     * you want to add <b>padding</b> between the decoration and the cell border
     * you need to add a PaddingDecorator to your painter stack.
     */
    private int spacing;
    /**
     * Flag to specify whether the base painter should render dependent to the
     * decoration painter or not. This will have effect on the boundary
     * calculation. Setting this flag to <code>true</code> the bounds of the
     * base painter will be modified regarding the bounds of the decoration
     * painter. This means that the starting coordinates for the base painter
     * are moving e.g. if the base painter renders centered the text will move
     * to the left because the decoration consumes space. If this flag is set to
     * <code>false</code> you can think of the decoration painter painting on
     * top of the base painter, possibly painting over the base painter.
     */
    private boolean paintDecorationDependent;
    /**
     * Flag to specify whether this decorator should paint the background or
     * not. Typically it will paint the background to avoid rendering gaps for
     * the configured spacing. Using a default background this will work well as
     * all painters will use the same background color. Using e.g. the
     * GradientBackgroundPainter, this flag needs to be <code>false</code> as
     * otherwise there will be no gradient background because it gets
     * overpainted by the internal background painting.
     */
    private boolean paintBg;

    /**
     * Creates a {@link CellPainterDecorator} without any configuration to
     * support initialization afterwards.
     *
     * @since 1.4
     */
    public CellPainterDecorator() {
        this(null, null, 0, null, true, true);
    }

    /**
     * Will create a {@link CellPainterDecorator} with the default spacing of 2
     * between base and decoration painter, where the base painter is rendered
     * dependent to the decoration.
     *
     * @param baseCellPainter
     *            The base {@link ICellPainter} that should be decorated
     * @param cellEdge
     *            The edge of the cell at which the decoration should be applied
     * @param decoratorCellPainter
     *            The {@link ICellPainter} that should be used to render the
     *            decoration.
     */
    public CellPainterDecorator(
            ICellPainter baseCellPainter,
            CellEdgeEnum cellEdge,
            ICellPainter decoratorCellPainter) {
        this(baseCellPainter, cellEdge, 2, decoratorCellPainter);
    }

    /**
     * Will create a {@link CellPainterDecorator} with the default spacing of 2
     * between base and decoration painter. If paintDecorationDependent is set
     * to <code>false</code>, the spacing will be ignored.
     *
     * @param baseCellPainter
     *            The base {@link ICellPainter} that should be decorated
     * @param cellEdge
     *            The edge of the cell at which the decoration should be applied
     * @param decoratorCellPainter
     *            The {@link ICellPainter} that should be used to render the
     *            decoration.
     * @param paintDecorationDependent
     *            Flag to specify whether the base painter should render
     *            dependent to the decoration painter or not.
     */
    public CellPainterDecorator(
            ICellPainter baseCellPainter,
            CellEdgeEnum cellEdge,
            ICellPainter decoratorCellPainter,
            boolean paintDecorationDependent) {
        this(baseCellPainter, cellEdge, 2, decoratorCellPainter, paintDecorationDependent);
    }

    /**
     * Will create a {@link CellPainterDecorator} with the given amount of
     * pixels as spacing between base and decoration painter, where the base
     * painter is rendered dependent to the decoration.
     *
     * @param baseCellPainter
     *            The base {@link ICellPainter} that should be decorated
     * @param cellEdge
     *            The edge of the cell at which the decoration should be applied
     * @param spacing
     *            The amount of pixels that should be used as spacing between
     *            decoration and base painter
     * @param decoratorCellPainter
     *            The {@link ICellPainter} that should be used to render the
     *            decoration.
     */
    public CellPainterDecorator(
            ICellPainter baseCellPainter,
            CellEdgeEnum cellEdge,
            int spacing,
            ICellPainter decoratorCellPainter) {
        this(baseCellPainter, cellEdge, spacing, decoratorCellPainter, true);
    }

    /**
     * Will create a {@link CellPainterDecorator} with the given amount of
     * pixels as spacing between base and decoration painter. If
     * paintDecorationDependent is set to <code>false</code>, the spacing will
     * be ignored while the decoration is mainly rendered over the base painter.
     *
     * @param baseCellPainter
     *            The base {@link ICellPainter} that should be decorated
     * @param cellEdge
     *            The edge of the cell at which the decoration should be applied
     * @param decoratorCellPainter
     *            The {@link ICellPainter} that should be used to render the
     *            decoration.
     * @param paintDecorationDependent
     *            Flag to specify whether the base painter should render
     *            dependent to the decoration painter or not.
     */
    public CellPainterDecorator(
            ICellPainter baseCellPainter,
            CellEdgeEnum cellEdge,
            int spacing,
            ICellPainter decoratorCellPainter,
            boolean paintDecorationDependent) {
        this(baseCellPainter, cellEdge, spacing, decoratorCellPainter, paintDecorationDependent, true);
    }

    /**
     * Will create a {@link CellPainterDecorator} with the given amount of
     * pixels as spacing between base and decoration painter. If
     * paintDecorationDependent is set to <code>false</code>, the spacing will
     * be ignored while the decoration is mainly rendered over the base painter.
     *
     * @param baseCellPainter
     *            The base {@link ICellPainter} that should be decorated
     * @param cellEdge
     *            The edge of the cell at which the decoration should be applied
     * @param decoratorCellPainter
     *            The {@link ICellPainter} that should be used to render the
     *            decoration.
     * @param paintDecorationDependent
     *            Flag to specify whether the base painter should render
     *            dependent to the decoration painter or not.
     * @param paintBg
     *            <code>true</code> if the PaddingDecorator should paint the
     *            background, <code>false</code> if not.
     */
    public CellPainterDecorator(
            ICellPainter baseCellPainter,
            CellEdgeEnum cellEdge,
            int spacing,
            ICellPainter decoratorCellPainter,
            boolean paintDecorationDependent,
            boolean paintBg) {
        this.baseCellPainter = baseCellPainter;
        this.cellEdge = cellEdge;
        this.spacing = spacing;
        this.decoratorCellPainter = decoratorCellPainter;
        this.paintDecorationDependent = paintDecorationDependent;
        this.paintBg = paintBg;
    }

    /**
     * @param paintDecorationDependent
     *            <code>true</code> if the base painter should render dependent
     *            to the decoration painter, <code>false</code> if the
     *            decoration should be rendered over the base painter.
     */
    public void setPaintDecorationDependent(boolean paintDecorationDependent) {
        this.paintDecorationDependent = paintDecorationDependent;
    }

    /**
     * Configure whether this CellPainterDecorator should paint the background
     * or not. By default it will paint the background to ensure the spacing is
     * also painted in the configured background color. This will only cause
     * issues in case another background painter like the
     * GradientBackroundPainter should be used.
     *
     * @param paintBg
     *            <code>true</code> if this CellPainterDecorator should also
     *            paint the background, <code>false</code> if the background
     *            should not be painted.
     */
    public void setPaintBackground(boolean paintBg) {
        this.paintBg = paintBg;
    }

    @Override
    public int getPreferredWidth(ILayerCell cell, GC gc, IConfigRegistry configRegistry) {

        int baseWidth = (this.baseCellPainter != null
                ? this.baseCellPainter.getPreferredWidth(cell, gc, configRegistry)
                : 0);
        int decorationWidth = (this.decoratorCellPainter != null
                ? this.decoratorCellPainter.getPreferredWidth(cell, gc, configRegistry)
                : 0);

        switch (this.cellEdge) {
            case TOP_LEFT:
            case TOP_RIGHT:
            case BOTTOM_LEFT:
            case BOTTOM_RIGHT:
            case BOTTOM:
            case TOP:
                return this.spacing + Math.max(baseWidth, decorationWidth);
            default:
                break;
        }

        return baseWidth + this.spacing + decorationWidth;
    }

    @Override
    public int getPreferredHeight(ILayerCell cell, GC gc, IConfigRegistry configRegistry) {

        int baseHeight = (this.baseCellPainter != null
                ? this.baseCellPainter.getPreferredHeight(cell, gc, configRegistry)
                : 0);
        int decorationHeight = (this.decoratorCellPainter != null
                ? this.decoratorCellPainter.getPreferredHeight(cell, gc, configRegistry)
                : 0);

        switch (this.cellEdge) {
            case TOP_LEFT:
            case TOP_RIGHT:
            case BOTTOM_LEFT:
            case BOTTOM_RIGHT:
            case LEFT:
            case RIGHT:
                return this.spacing + Math.max(baseHeight, decorationHeight);
            default:
                break;
        }

        return baseHeight + this.spacing + decorationHeight;
    }

    @Override
    public void paintCell(ILayerCell cell, GC gc, Rectangle adjustedCellBounds, IConfigRegistry configRegistry) {
        Rectangle baseCellPainterBounds = this.paintDecorationDependent
                ? getBaseCellPainterBounds(cell, gc, adjustedCellBounds, configRegistry)
                : adjustedCellBounds;
        Rectangle decoratorCellPainterBounds = getDecoratorCellPainterBounds(cell, gc, adjustedCellBounds, configRegistry);

        if (this.paintBg) {
            Color originalBg = gc.getBackground();
            gc.setBackground(CellStyleUtil
                    .getCellStyle(cell, configRegistry)
                    .getAttributeValue(CellStyleAttributes.BACKGROUND_COLOR));
            gc.fillRectangle(adjustedCellBounds);
            gc.setBackground(originalBg);
        }

        if (this.baseCellPainter != null) {
            this.baseCellPainter.paintCell(cell, gc, baseCellPainterBounds, configRegistry);
        }

        if (this.decoratorCellPainter != null) {
            this.decoratorCellPainter.paintCell(cell, gc, decoratorCellPainterBounds, configRegistry);
        }
    }

    /**
     *
     * @return The Rectangle which can be used by the base cell painter.
     */
    public Rectangle getBaseCellPainterBounds(
            ILayerCell cell,
            GC gc,
            Rectangle adjustedCellBounds,
            IConfigRegistry configRegistry) {

        int preferredDecoratorWidth = (this.decoratorCellPainter != null
                ? this.decoratorCellPainter.getPreferredWidth(cell, gc, configRegistry)
                : 0);
        int preferredDecoratorHeight = (this.decoratorCellPainter != null
                ? this.decoratorCellPainter.getPreferredHeight(cell, gc, configRegistry)
                : 0);

        // grab any extra space:
        int grabbedPreferredWidth = adjustedCellBounds.width - preferredDecoratorWidth - this.spacing;
        int grabbedPreferredHeight = adjustedCellBounds.height - preferredDecoratorHeight - this.spacing;

        switch (this.cellEdge) {
            case LEFT:
                return new Rectangle(
                        adjustedCellBounds.x + preferredDecoratorWidth + this.spacing,
                        adjustedCellBounds.y,
                        grabbedPreferredWidth,
                        adjustedCellBounds.height)
                                .intersection(adjustedCellBounds);
            case RIGHT:
                return new Rectangle(
                        adjustedCellBounds.x,
                        adjustedCellBounds.y,
                        grabbedPreferredWidth,
                        adjustedCellBounds.height)
                                .intersection(adjustedCellBounds);
            case TOP:
                return new Rectangle(
                        adjustedCellBounds.x,
                        adjustedCellBounds.y + preferredDecoratorHeight + this.spacing,
                        adjustedCellBounds.width,
                        grabbedPreferredHeight)
                                .intersection(adjustedCellBounds);
            case BOTTOM:
                return new Rectangle(
                        adjustedCellBounds.x,
                        adjustedCellBounds.y,
                        adjustedCellBounds.width,
                        grabbedPreferredHeight)
                                .intersection(adjustedCellBounds);
            case TOP_LEFT:
                return new Rectangle(
                        adjustedCellBounds.x + preferredDecoratorWidth + this.spacing,
                        adjustedCellBounds.y + preferredDecoratorHeight + this.spacing,
                        grabbedPreferredWidth,
                        grabbedPreferredHeight)
                                .intersection(adjustedCellBounds);
            case TOP_RIGHT:
                return new Rectangle(
                        adjustedCellBounds.x,
                        adjustedCellBounds.y + preferredDecoratorHeight + this.spacing,
                        grabbedPreferredWidth,
                        grabbedPreferredHeight)
                                .intersection(adjustedCellBounds);
            case BOTTOM_LEFT:
                return new Rectangle(
                        adjustedCellBounds.x + preferredDecoratorWidth + this.spacing,
                        adjustedCellBounds.y,
                        grabbedPreferredWidth,
                        grabbedPreferredHeight)
                                .intersection(adjustedCellBounds);
            case BOTTOM_RIGHT:
                return new Rectangle(
                        adjustedCellBounds.x,
                        adjustedCellBounds.y,
                        grabbedPreferredWidth,
                        grabbedPreferredHeight)
                                .intersection(adjustedCellBounds);
            case NONE:
                break;
        }

        return null;
    }

    /**
     * @return The Rectangle to paint the decoration.
     */
    public Rectangle getDecoratorCellPainterBounds(
            ILayerCell cell,
            GC gc,
            Rectangle adjustedCellBounds,
            IConfigRegistry configRegistry) {

        int preferredDecoratorWidth = (this.decoratorCellPainter != null
                ? this.decoratorCellPainter.getPreferredWidth(cell, gc, configRegistry)
                : 0);
        int preferredDecoratorHeight = (this.decoratorCellPainter != null
                ? this.decoratorCellPainter.getPreferredHeight(cell, gc, configRegistry)
                : 0);

        switch (this.cellEdge) {
            case LEFT:
                return new Rectangle(
                        adjustedCellBounds.x,
                        adjustedCellBounds.y + ((adjustedCellBounds.height - preferredDecoratorHeight) / 2),
                        preferredDecoratorWidth,
                        preferredDecoratorHeight);
            case RIGHT:
                return new Rectangle(
                        adjustedCellBounds.x + adjustedCellBounds.width - preferredDecoratorWidth,
                        adjustedCellBounds.y + ((adjustedCellBounds.height - preferredDecoratorHeight) / 2),
                        preferredDecoratorWidth,
                        preferredDecoratorHeight);
            case TOP:
                return new Rectangle(
                        adjustedCellBounds.x + ((adjustedCellBounds.width - preferredDecoratorWidth) / 2),
                        adjustedCellBounds.y,
                        preferredDecoratorWidth,
                        preferredDecoratorHeight);
            case BOTTOM:
                return new Rectangle(
                        adjustedCellBounds.x + ((adjustedCellBounds.width - preferredDecoratorWidth) / 2),
                        adjustedCellBounds.y + adjustedCellBounds.height - preferredDecoratorHeight,
                        preferredDecoratorWidth,
                        preferredDecoratorHeight);
            case TOP_LEFT:
                return new Rectangle(
                        adjustedCellBounds.x,
                        adjustedCellBounds.y,
                        preferredDecoratorWidth,
                        preferredDecoratorHeight);
            case TOP_RIGHT:
                return new Rectangle(
                        adjustedCellBounds.x + adjustedCellBounds.width - preferredDecoratorWidth,
                        adjustedCellBounds.y,
                        preferredDecoratorWidth,
                        preferredDecoratorHeight);
            case BOTTOM_LEFT:
                return new Rectangle(
                        adjustedCellBounds.x,
                        adjustedCellBounds.y + adjustedCellBounds.height - preferredDecoratorHeight,
                        preferredDecoratorWidth,
                        preferredDecoratorHeight);
            case BOTTOM_RIGHT:
                return new Rectangle(
                        adjustedCellBounds.x + adjustedCellBounds.width - preferredDecoratorWidth,
                        adjustedCellBounds.y + adjustedCellBounds.height - preferredDecoratorHeight,
                        preferredDecoratorWidth,
                        preferredDecoratorHeight);
            case NONE:
                break;
        }

        return null;
    }

    @Override
    public ICellPainter getCellPainterAt(
            int x,
            int y,
            ILayerCell cell,
            GC gc,
            Rectangle adjustedCellBounds,
            IConfigRegistry configRegistry) {

        Rectangle decoratorCellPainterBounds =
                getDecoratorCellPainterBounds(cell, gc, adjustedCellBounds, configRegistry);
        if (this.decoratorCellPainter != null && decoratorCellPainterBounds.contains(x, y)) {
            return this.decoratorCellPainter.getCellPainterAt(x, y, cell, gc, decoratorCellPainterBounds, configRegistry);
        } else {
            Rectangle baseCellPainterBounds = this.paintDecorationDependent
                    ? getBaseCellPainterBounds(cell, gc, adjustedCellBounds, configRegistry)
                    : adjustedCellBounds;
            if (this.baseCellPainter != null && baseCellPainterBounds.contains(x, y)) {
                return this.baseCellPainter.getCellPainterAt(x, y, cell, gc, baseCellPainterBounds, configRegistry);
            }
        }
        return this;
    }

    /**
     * @return The base {@link ICellPainter} that is decorated.
     */
    public ICellPainter getBaseCellPainter() {
        return this.baseCellPainter;
    }

    /**
     * @param baseCellPainter
     *            The base {@link ICellPainter} that should be decorated.
     */
    public void setBaseCellPainter(ICellPainter baseCellPainter) {
        this.baseCellPainter = baseCellPainter;
    }

    /**
     * @return The {@link ICellPainter} that is used to render the decoration.
     */
    public ICellPainter getDecoratorCellPainter() {
        return this.decoratorCellPainter;
    }

    /**
     * @param decoratorCellPainter
     *            The {@link ICellPainter} that should be used to render the
     *            decoration.
     */
    public void setDecoratorCellPainter(ICellPainter decoratorCellPainter) {
        this.decoratorCellPainter = decoratorCellPainter;
    }

    /**
     * @return The edge of the cell at which the decoration is applied.
     */
    public CellEdgeEnum getCellEdge() {
        return this.cellEdge;
    }

    /**
     * @param cellEdge
     *            The edge of the cell at which the decoration should be
     *            applied.
     */
    public void setCellEdge(CellEdgeEnum cellEdge) {
        this.cellEdge = cellEdge;
    }

    /**
     * @return The spacing that is used between base painter and decoration
     *         painter.
     */
    public int getSpacing() {
        return this.spacing;
    }

    /**
     * @param spacing
     *            The spacing that should be used between base painter and
     *            decoration painter.
     */
    public void setSpacing(int spacing) {
        this.spacing = spacing;
    }
}
