/*******************************************************************************
 * 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
 *     Loris Securo <lorissek@gmail.com> - Bug 499513
 ******************************************************************************/
package org.eclipse.nebula.widgets.nattable.painter.layer;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;

public class GridLineCellLayerPainter extends CellLayerPainter {

    private final Color gridColor;

    /**
     * @since 1.5
     */
    protected boolean renderGridLines = true;

    /**
     * @since 1.4
     */
    protected Integer gridLineWidth = 1;

    /**
     * Create a GridLineCellLayerPainter that renders grid lines in the
     * specified color and uses the default clipping behaviour.
     *
     * @param gridColor
     *            The color that should be used to render the grid lines.
     */
    public GridLineCellLayerPainter(final Color gridColor) {
        this.gridColor = gridColor;
    }

    /**
     * Create a GridLineCellLayerPainter that renders gray grid lines and uses
     * the default clipping behaviour.
     */
    public GridLineCellLayerPainter() {
        this.gridColor = GUIHelper.COLOR_GRAY;
    }

    /**
     * Create a GridLineCellLayerPainter that renders grid lines in the
     * specified color and uses the specified clipping behaviour.
     *
     * @param gridColor
     *            The color that should be used to render the grid lines.
     * @param clipLeft
     *            Configure the rendering behaviour when cells overlap. If set
     *            to <code>true</code> the left cell will be clipped, if set to
     *            <code>false</code> the right cell will be clipped. The default
     *            value is <code>false</code>.
     * @param clipTop
     *            Configure the rendering behaviour when cells overlap. If set
     *            to <code>true</code> the top cell will be clipped, if set to
     *            <code>false</code> the bottom cell will be clipped. The
     *            default value is <code>false</code>.
     */
    public GridLineCellLayerPainter(final Color gridColor, boolean clipLeft, boolean clipTop) {
        super(clipLeft, clipTop);
        this.gridColor = gridColor;
    }

    /**
     * Create a GridLineCellLayerPainter that renders gray grid lines and uses
     * the specified clipping behaviour.
     *
     * @param clipLeft
     *            Configure the rendering behaviour when cells overlap. If set
     *            to <code>true</code> the left cell will be clipped, if set to
     *            <code>false</code> the right cell will be clipped. The default
     *            value is <code>false</code>.
     * @param clipTop
     *            Configure the rendering behaviour when cells overlap. If set
     *            to <code>true</code> the top cell will be clipped, if set to
     *            <code>false</code> the bottom cell will be clipped. The
     *            default value is <code>false</code>.
     */
    public GridLineCellLayerPainter(boolean clipLeft, boolean clipTop) {
        this(GUIHelper.COLOR_GRAY, clipLeft, clipTop);
    }

    /**
     * @return The local configured color that is used to render the grid lines.
     */
    public Color getGridColor() {
        return this.gridColor;
    }

    @Override
    public void paintLayer(ILayer natLayer, GC gc, int xOffset, int yOffset,
            Rectangle rectangle, IConfigRegistry configRegistry) {
        Boolean renderConfig = null;
        LabelStack stack = natLayer.getRegionLabelsByXY(xOffset, yOffset);
        List<String> labels = new ArrayList<>();
        if (stack != null) {
            labels = stack;
            // check if there is a configuration telling to not rendering grid
            // lines
            renderConfig = configRegistry.getConfigAttribute(
                    CellConfigAttributes.RENDER_GRID_LINES,
                    DisplayMode.NORMAL,
                    labels);
        }

        this.renderGridLines = (renderConfig != null) ? renderConfig : true;

        // Draw GridLines
        if (this.renderGridLines) {
            // check if there is a configuration for the grid line width
            Integer width = configRegistry.getConfigAttribute(
                    CellConfigAttributes.GRID_LINE_WIDTH,
                    DisplayMode.NORMAL,
                    labels);
            this.gridLineWidth = (width != null) ? width : 1;

            int oldLineWidth = gc.getLineWidth();
            gc.setLineWidth(this.gridLineWidth);
            drawGridLines(natLayer, gc, rectangle, configRegistry, labels);
            gc.setLineWidth(oldLineWidth);
        }

        super.paintLayer(natLayer, gc, xOffset, yOffset, rectangle, configRegistry);
    }

    @Override
    public Rectangle adjustCellBounds(int columnPosition, int rowPosition, Rectangle bounds) {
        Integer adjustment = this.renderGridLines ? this.gridLineWidth : 0;

        int startAdjustment = (adjustment == 1) ? 0 : Math.round(adjustment.floatValue() / 2);
        int sizeAdjustment = (adjustment == 1) ? 1 : Math.round(adjustment.floatValue() / 2);

        return new Rectangle(
                bounds.x - startAdjustment,
                bounds.y - startAdjustment,
                Math.max(bounds.width - sizeAdjustment, 0),
                Math.max(bounds.height - sizeAdjustment, 0));
    }

    /**
     *
     * @param natLayer
     *            The layer to paint on.
     * @param gc
     *            GC used for painting
     * @param rectangle
     *            area the layer can paint in
     * @param configRegistry
     *            {@link IConfigRegistry} in use, needed to retrieve configured
     *            grid line color.
     * @param labels
     *            region labels needed to retrieve the configured grid line
     *            color.
     * @since 1.4
     */
    protected void drawGridLines(ILayer natLayer, GC gc, Rectangle rectangle, IConfigRegistry configRegistry, List<String> labels) {
        Color gColor = configRegistry.getConfigAttribute(
                CellConfigAttributes.GRID_LINE_COLOR,
                DisplayMode.NORMAL,
                labels);
        gc.setForeground(gColor != null ? gColor : this.gridColor);

        int adjustment = (this.gridLineWidth == 1) ? 1 : Math.round(this.gridLineWidth.floatValue() / 2);

        drawHorizontalLines(natLayer, gc, rectangle, adjustment);
        drawVerticalLines(natLayer, gc, rectangle, adjustment);
    }

    private void drawHorizontalLines(ILayer natLayer, GC gc, Rectangle rectangle, int adjustment) {
        int endX = rectangle.x + Math.min(natLayer.getWidth() - adjustment, rectangle.width);

        // this can happen on resizing if there is no CompositeLayer involved
        // without this check grid line fragments may be rendered below the last
        // column
        if (endX > natLayer.getWidth())
            return;

        int rowPositionByY = natLayer.getRowPositionByY(rectangle.y + rectangle.height);
        int maxRowPosition = rowPositionByY > 0
                ? Math.min(natLayer.getRowCount(), rowPositionByY)
                : natLayer.getRowCount();
        for (int rowPosition = natLayer.getRowPositionByY(rectangle.y); rowPosition < maxRowPosition; rowPosition++) {
            final int size = natLayer.getRowHeightByPosition(rowPosition);
            if (size > 0) {
                int y = natLayer.getStartYOfRowPosition(rowPosition) + size - adjustment;

                gc.drawLine(rectangle.x, y, endX, y);
            }
        }
    }

    private void drawVerticalLines(ILayer natLayer, GC gc, Rectangle rectangle, int adjustment) {
        int endY = rectangle.y + Math.min(natLayer.getHeight() - adjustment, rectangle.height);

        // this can happen on resizing if there is no CompositeLayer involved
        // without this check grid line fragments may be rendered below the last
        // row
        if (endY > natLayer.getHeight())
            return;

        int columnPositionByX = natLayer.getColumnPositionByX(rectangle.x + rectangle.width);
        int maxColumnPosition = columnPositionByX > 0
                ? Math.min(natLayer.getColumnCount(), columnPositionByX)
                : natLayer.getColumnCount();
        for (int columnPosition = natLayer.getColumnPositionByX(rectangle.x); columnPosition < maxColumnPosition; columnPosition++) {
            final int size = natLayer.getColumnWidthByPosition(columnPosition);
            if (size > 0) {
                int x = natLayer.getStartXOfColumnPosition(columnPosition) + size - adjustment;

                gc.drawLine(x, rectangle.y, x, endY);
            }
        }
    }

}
