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

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.CellPainterWrapper;
import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;
import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.CellPainterDecorator;
import org.eclipse.nebula.widgets.nattable.tree.config.DefaultTreeLayerConfiguration;
import org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;

/**
 * Implementation of CellPainterWrapper that is used to render tree structures
 * in NatTable. It puts indentation to tree nodes to visualize the tree
 * structure and adds expand/collapse icons corresponding to the state if a tree
 * node has children.
 */
public class IndentedTreeImagePainter extends CellPainterWrapper {
    /**
     * The number of pixels to indent per depth.
     */
    private final int treeIndent;

    private final CellPainterDecorator internalPainter;

    /**
     * Creates an IndentedTreeImagePainter. Will use 10 pixels for indentation
     * per depth and a default TreeImagePainter for rendering the icons in the
     * tree.
     */
    public IndentedTreeImagePainter() {
        this(10, new TreeImagePainter());
    }

    /**
     * Creates an IndentedTreeImagePainter. Will use the given number of pixels
     * for indentation per depth and a default {@link TreeImagePainter} for
     * rendering the icons in the tree.
     *
     * @param treeIndent
     *            The number of pixels to indent per depth.
     */
    public IndentedTreeImagePainter(int treeIndent) {
        this(treeIndent, new TreeImagePainter());
    }

    /**
     * Creates an IndentedTreeImagePainter using the given indentation per depth
     * and {@link ICellPainter} for painting the icons in the tree.
     *
     * @param treeIndent
     *            The number of pixels to indent per depth.
     * @param treeImagePainter
     *            The {@link ICellPainter} that should be used to paint the
     *            images in the tree. When using the
     *            DefaultTreeLayerConfiguration the content painter needs to be
     *            of type of {@link TreeImagePainter} that paints
     *            expand/collapse/leaf icons regarding the node state, because
     *            the ui bindings for expand/collapse are registered against
     *            that type.
     * @since 1.6
     */
    public IndentedTreeImagePainter(int treeIndent, ICellPainter treeImagePainter) {
        this(treeIndent, CellEdgeEnum.LEFT, treeImagePainter);
    }

    /**
     * Creates an IndentedTreeImagePainter using the given indentation per depth
     * and {@link ICellPainter} for painting the icons in the tree to the
     * specified cell edge.
     *
     * @param treeIndent
     *            The number of pixels to indent per depth.
     * @param cellEdge
     *            the edge of the cell on which the tree state indicator
     *            decoration should be applied
     * @param treeImagePainter
     *            The {@link ICellPainter} that should be used to paint the
     *            images in the tree. When using the
     *            DefaultTreeLayerConfiguration the content painter needs to be
     *            of type of {@link TreeImagePainter} that paints
     *            expand/collapse/leaf icons regarding the node state, because
     *            the ui bindings for expand/collapse are registered against
     *            that type.
     * @since 1.6
     */
    public IndentedTreeImagePainter(
            int treeIndent,
            CellEdgeEnum cellEdge,
            ICellPainter treeImagePainter) {

        this.treeIndent = treeIndent;
        this.internalPainter =
                new CellPainterDecorator(null, cellEdge, treeImagePainter);

        setWrappedPainter(this.internalPainter);
    }

    // the following constructors are intended to configure the
    // CellPainterDecorator that is created as
    // the wrapped painter of this IndentedTreeImagePainter

    /**
     * Creates a {@link IndentedTreeImagePainter} that uses the given
     * {@link ICellPainter} as base {@link ICellPainter}. It will use the
     * {@link TreeImagePainter} as decorator for tree state related decorations
     * at the specified cell edge, which can be configured to render the
     * background or not via method parameter. With the additional parameters,
     * the behaviour of the created {@link CellPainterDecorator} can be
     * configured in terms of rendering.
     *
     * @param treeIndent
     *            The number of pixels to indent per depth.
     * @param interiorPainter
     *            the base {@link ICellPainter} to use
     * @param cellEdge
     *            the edge of the cell on which the tree state indicator
     *            decoration should be applied
     * @param paintBg
     *            flag to configure whether the {@link TreeImagePainter} should
     *            paint the background or not
     * @param spacing
     *            the number of pixels that should be used as spacing between
     *            cell edge and decoration
     * @param paintDecorationDependent
     *            flag to configure if the base {@link ICellPainter} should
     *            render decoration dependent or not. If it is set to
     *            <code>false</code>, the base painter will always paint at the
     *            same coordinates, using the whole cell bounds,
     *            <code>true</code> will cause the bounds of the cell to shrink
     *            for the base painter.
     */
    public IndentedTreeImagePainter(
            int treeIndent,
            ICellPainter interiorPainter,
            CellEdgeEnum cellEdge,
            boolean paintBg,
            int spacing,
            boolean paintDecorationDependent) {

        this.treeIndent = treeIndent;

        ICellPainter painter = new TreeImagePainter(paintBg);
        this.internalPainter =
                new CellPainterDecorator(
                        interiorPainter,
                        cellEdge,
                        spacing,
                        painter,
                        paintDecorationDependent,
                        paintBg);
        setWrappedPainter(this.internalPainter);
    }

    /**
     * Creates a {@link IndentedTreeImagePainter} that uses the given
     * {@link ICellPainter} as base {@link ICellPainter}. It will use the given
     * {@link ICellPainter} as decorator for tree state related decorations at
     * the specified cell edge, which can be configured to render the background
     * or not via method parameter. With the additional parameters, the
     * behaviour of the created {@link CellPainterDecorator} can be configured
     * in terms of rendering.
     *
     * @param treeIndent
     *            The number of pixels to indent per depth.
     * @param interiorPainter
     *            the base {@link ICellPainter} to use
     * @param cellEdge
     *            the edge of the cell on which the tree state indicator
     *            decoration should be applied
     * @param decoratorPainter
     *            the {@link ICellPainter} that should be used to paint the tree
     *            state related decoration
     * @param paintBg
     *            flag to configure whether the {@link CellPainterDecorator}
     *            should paint the background or not
     * @param spacing
     *            the number of pixels that should be used as spacing between
     *            cell edge and decoration
     * @param paintDecorationDependent
     *            flag to configure if the base {@link ICellPainter} should
     *            render decoration dependent or not. If it is set to
     *            <code>false</code>, the base painter will always paint at the
     *            same coordinates, using the whole cell bounds,
     *            <code>true</code> will cause the bounds of the cell to shrink
     *            for the base painter.
     */
    public IndentedTreeImagePainter(
            int treeIndent,
            ICellPainter interiorPainter,
            CellEdgeEnum cellEdge,
            ICellPainter decoratorPainter,
            boolean paintBg,
            int spacing,
            boolean paintDecorationDependent) {

        this.treeIndent = treeIndent;

        this.internalPainter =
                new CellPainterDecorator(
                        interiorPainter,
                        cellEdge,
                        spacing,
                        decoratorPainter,
                        paintDecorationDependent,
                        paintBg);
        setWrappedPainter(this.internalPainter);
    }

    /**
     * Creates a {@link IndentedTreeImagePainter} that uses the given
     * {@link ICellPainter} as base {@link ICellPainter} and decorate it with
     * the {@link TreeImagePainter} on the right edge of the cell. This
     * constructor gives the opportunity to configure the behaviour of the
     * {@link TreeImagePainter} and the {@link CellPainterDecorator} for some
     * attributes. Remains because of downwards compatibility.
     *
     * @param treeIndent
     *            The number of pixels to indent per depth.
     * @param interiorPainter
     *            the base {@link ICellPainter} to use
     * @param paintBg
     *            flag to configure whether the {@link TreeImagePainter} should
     *            paint the background or not
     * @param interiorPainterToSpanFullWidth
     *            flag to configure how the bounds of the base painter should be
     *            calculated
     */
    public IndentedTreeImagePainter(
            int treeIndent,
            ICellPainter interiorPainter,
            boolean paintBg,
            boolean interiorPainterToSpanFullWidth) {

        this.treeIndent = treeIndent;

        ICellPainter painter = new TreeImagePainter(paintBg);
        this.internalPainter =
                new CellPainterDecorator(
                        interiorPainter,
                        CellEdgeEnum.RIGHT,
                        0,
                        painter,
                        !interiorPainterToSpanFullWidth,
                        paintBg);
        setWrappedPainter(this.internalPainter);
    }

    /**
     * @return The ICellPainter that is used to paint the images in the tree.
     *         Usually it is some type of TreeImagePainter that paints
     *         expand/collapse/leaf icons regarding the node state.
     */
    public ICellPainter getTreeImagePainter() {
        return this.internalPainter.getDecoratorCellPainter();
    }

    /**
     * @param cellPainter
     *            The {@link ICellPainter} that should be used to paint the
     *            images in the tree. Usually it is some type of
     *            {@link TreeImagePainter} that paints expand/collapse/leaf
     *            icons regarding the node state.
     */
    public void setTreeImagePainter(ICellPainter cellPainter) {
        this.internalPainter.setDecoratorCellPainter(cellPainter);
    }

    /**
     * @param cellPainter
     *            The base {@link ICellPainter} that should be used to render
     *            the cell content.
     */
    public void setBaseCellPainter(ICellPainter cellPainter) {
        this.internalPainter.setBaseCellPainter(cellPainter);
    }

    /**
     *
     * @return The {@link CellPainterDecorator} that is wrapped by this
     *         {@link IndentedTreeImagePainter}. Can be used to perform specific
     *         decoration configurations, e.g. set the decoration dependent
     *         rendering option.
     * @since 1.6
     */
    public CellPainterDecorator getInternalPainter() {
        return this.internalPainter;
    }

    @Override
    public Rectangle getWrappedPainterBounds(
            ILayerCell cell, GC gc, Rectangle bounds, IConfigRegistry configRegistry) {
        int depth = getDepth(cell);
        int indent = getIndent(depth, configRegistry);

        return new Rectangle(
                bounds.x + indent,
                bounds.y,
                bounds.width - indent,
                bounds.height);
    }

    @Override
    public void paintCell(ILayerCell cell, GC gc, Rectangle bounds, IConfigRegistry configRegistry) {
        super.paintCell(
                cell,
                gc,
                getWrappedPainterBounds(cell, gc, bounds, configRegistry),
                configRegistry);
    }

    @Override
    public int getPreferredWidth(ILayerCell cell, GC gc, IConfigRegistry configRegistry) {
        int depth = getDepth(cell);
        int indent = getIndent(depth, configRegistry);
        return indent + super.getPreferredWidth(cell, gc, configRegistry);
    }

    /**
     * @param depth
     *            The depth/level in the tree structure for which the indent is
     *            requested.
     * @param configRegistry
     *            The {@link IConfigRegistry} needed for accessing the dpi
     *            converter.
     * @return The number of pixels the content should be indented.
     * @since 2.0
     */
    protected int getIndent(int depth, IConfigRegistry configRegistry) {
        return GUIHelper.convertHorizontalPixelToDpi(this.treeIndent * depth, configRegistry);
    }

    /**
     * @param cell
     *            The cell for which the depth/level in the tree structure is
     *            requested.
     * @return The depth/level in the tree structure the given cell is located.
     *
     * @since 1.4
     */
    protected int getDepth(ILayerCell cell) {
        int depth = 0;

        for (String configLabel : cell.getConfigLabels()) {
            if (configLabel.startsWith(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE)) {
                String[] tokens = configLabel.split("_"); //$NON-NLS-1$
                depth = Integer.parseInt(tokens[tokens.length - 1]);
            }
        }

        return depth;
    }

}
