/*******************************************************************************
 * 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.edit.editor;

import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;

/**
 * This class adds support for adding a {@link ControlDecoration} to the editor
 * controls of a {@link ICellEditor}. It is currently only used by the
 * {@link TextCellEditor}.
 * <p>
 * The default location for the {@link ControlDecoration} is the top right of
 * the editor. If the editor is located such that the {@link ControlDecoration}
 * would not be visible here (i.e. cell is at/extends beyond the right edge of
 * the NatTable) then the decoration is placed at the top left of the editor.
 * <p>
 * The location can be overridden, in which case the above behaviour does not
 * get used.
 */
public class ControlDecorationProvider {
    /**
     * The String to determine the {@link FieldDecoration} to use by the
     * {@link ControlDecoration} that is provided by this
     * {@link ControlDecorationProvider}.
     */
    private String fieldDecorationId;
    /**
     * Flag to determine whether this provider is enabled to add
     * {@link ControlDecoration}s or not.
     */
    private boolean errorDecorationEnabled;
    /**
     * The created {@link ControlDecoration} if this provider created one.
     */
    private ControlDecoration errorDecoration;
    /**
     * The text to be shown as a description for the decoration, or
     * <code>null</code> if none has been set.
     */
    private String errorDecorationText;
    /**
     * The position configuration where the decoration should be rendered
     * relative to the control that should be decorated.
     */
    private int decorationPositionOverride = SWT.DEFAULT;

    /**
     * Create a default {@link ControlDecorationProvider} for handling error
     * decorations.
     */
    public ControlDecorationProvider() {
        this(FieldDecorationRegistry.DEC_ERROR);
    }

    /**
     * @param fieldDecorationId
     *            The field decoration to use by this provider.
     * @see FieldDecorationRegistry
     */
    public ControlDecorationProvider(String fieldDecorationId) {
        this.fieldDecorationId = fieldDecorationId;
    }

    /**
     * Enables/disables the error decoration.
     *
     * @param enabled
     *            <code>true</code> if a decoration should be added,
     *            <code>false</code> if not.
     */
    public void setErrorDecorationEnabled(boolean enabled) {
        this.errorDecorationEnabled = enabled;
    }

    /**
     * @param errorText
     *            the text to be shown as a description for the decoration, or
     *            <code>null</code> if none has been set.
     * @see ControlDecoration#setDescriptionText(String)
     */
    public void setErrorDecorationText(String errorText) {
        this.errorDecorationText = errorText;
        if (this.errorDecoration != null) {
            this.errorDecoration.setDescriptionText(errorText);
        }
    }

    /**
     * Will show the control decoration adding the given text as description
     * text.
     *
     * @param errorText
     *            the text to be shown in the info hover, or <code>null</code>
     *            if no text should be shown.
     * @see ControlDecoration#show()
     * @see ControlDecoration#showHoverText(String)
     */
    public void showErrorDecorationHover(String errorText) {
        if (this.errorDecoration != null) {
            this.errorDecoration.show();
            this.errorDecoration.showHoverText(errorText);
        }
    }

    /**
     * Configure the id that should be used to retrieve the
     * {@link FieldDecoration} to be used by this
     * {@link ControlDecorationProvider}.
     *
     * @param fieldDecorationId
     *            The String to determine the {@link FieldDecoration} to use by
     *            the {@link ControlDecoration} that is provided by this
     *            {@link ControlDecorationProvider}.
     */
    public void setFieldDecorationId(String fieldDecorationId) {
        this.fieldDecorationId = fieldDecorationId;
    }

    /**
     * Set the position configuration where the decoration should be rendered
     * relative to the control that should be decorated.
     *
     * @param decorationPositionOverride
     *            bit-wise or of position constants (<code>SWT.TOP</code>,
     *            <code>SWT.BOTTOM</code>, <code>SWT.LEFT</code>,
     *            <code>SWT.RIGHT</code>, and <code>SWT.CENTER</code>).
     */
    public void setDecorationPositionOverride(int decorationPositionOverride) {
        this.decorationPositionOverride = decorationPositionOverride;
    }

    /**
     * Will show the control decoration.
     *
     * @see ControlDecoration#show()
     */
    public void showDecoration() {
        if (this.errorDecoration != null) {
            this.errorDecoration.show();
        }
    }

    /**
     * Will hide the control decoration.
     *
     * @see ControlDecoration#hide()
     */
    public void hideDecoration() {
        if (this.errorDecoration != null) {
            this.errorDecoration.hide();
        }
    }

    /**
     * Ensure to hide the decoration and dispose any resources related to the
     * {@link ControlDecoration}
     */
    public void dispose() {
        if (this.errorDecoration != null) {
            this.errorDecoration.hide();
            this.errorDecoration.dispose();
            this.errorDecoration = null;
        }
    }

    /**
     * If showing an error decoration is enabled, this method will create and
     * add a {@link ControlDecoration} for the given {@link Control} by using
     * the configured information.
     *
     * @param controlToDecorate
     *            The {@link Control} to create the decoration for.
     */
    public void createErrorDecorationIfRequired(final Control controlToDecorate) {

        if (this.errorDecorationEnabled) {

            final Image errorImage = FieldDecorationRegistry.getDefault()
                    .getFieldDecoration(this.fieldDecorationId).getImage();
            if (this.decorationPositionOverride == SWT.DEFAULT) {
                controlToDecorate.addPaintListener(new PaintListener() { // Using
                                                                         // a
                                                                         // PaintListener
                                                                         // as
                                                                         // bounds
                                                                         // are
                                                                         // only
                                                                         // set
                                                                         // AFTER
                                                                         // activateCell()

                    @Override
                    public void paintControl(PaintEvent e) {

                        controlToDecorate.removePaintListener(this);
                        int position = SWT.TOP;
                        final Rectangle textBounds = controlToDecorate
                                .getBounds();
                        final Rectangle parentClientArea = controlToDecorate
                                .getParent().getClientArea();
                        if ((parentClientArea.x + parentClientArea.width) > (textBounds.x
                                + textBounds.width + errorImage
                                        .getBounds().width)) {
                            position |= SWT.RIGHT;
                        } else {
                            position |= SWT.LEFT;
                        }
                        ControlDecorationProvider.this.errorDecoration = newControlDecoration(
                                controlToDecorate, errorImage, position);
                    }
                });
            } else {
                this.errorDecoration = newControlDecoration(controlToDecorate,
                        errorImage, this.decorationPositionOverride);
            }
        }
    }

    /**
     * Will create a new {@link ControlDecoration} for the given {@link Control}
     * . Setting position, image and text to the decoration, hiding it
     * initially.
     *
     * @param controlToDecorate
     *            The {@link Control} to create the decoration for.
     * @param errorImage
     *            The image to be shown adjacent to the control. Should never be
     *            <code>null</code>.
     * @param position
     *            bit-wise or of position constants (<code>SWT.TOP</code>,
     *            <code>SWT.BOTTOM</code>, <code>SWT.LEFT</code>,
     *            <code>SWT.RIGHT</code>, and <code>SWT.CENTER</code>).
     * @return The created {@link ControlDecoration}
     */
    private ControlDecoration newControlDecoration(Control controlToDecorate,
            Image errorImage, int position) {
        final ControlDecoration errorDecoration = new ControlDecoration(
                controlToDecorate, position);
        errorDecoration.setImage(errorImage);
        errorDecoration.setDescriptionText(this.errorDecorationText);
        errorDecoration.hide();
        return errorDecoration;
    }

}
