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

import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractEditErrorHandler;
import org.eclipse.nebula.widgets.nattable.edit.editor.ControlDecorationProvider;
import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
import org.eclipse.nebula.widgets.nattable.edit.editor.IEditErrorHandler;
import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
import org.eclipse.nebula.widgets.nattable.style.CellStyleUtil;
import org.eclipse.nebula.widgets.nattable.style.IStyle;
import org.eclipse.nebula.widgets.nattable.style.Style;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.widgets.Control;

/**
 * {@link IEditErrorHandler} implementation that will directly change the
 * rendering of the value inside the editor control.
 */
public class RenderErrorHandling extends AbstractEditErrorHandler {

    /**
     * The default error styling used for rendering an error.
     */
    protected IStyle defaultErrorStyle;
    {
        this.defaultErrorStyle = new Style();
        this.defaultErrorStyle.setAttributeValue(
                CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_RED);
    }

    /**
     * The original foreground color, needed to be able to restore the normal
     * rendering if the error is removed.
     */
    protected Color originalFgColor;
    /**
     * The original background color, needed to be able to restore the normal
     * rendering if the error is removed.
     */
    protected Color originalBgColor;
    /**
     * The original font, needed to be able to restore the normal rendering if
     * the error is removed.
     */
    protected Font originalFont;
    /**
     * The style that should be used to render an error.
     */
    protected IStyle errorStyle;
    /**
     * The decoration provider that should be used for decorating the editor
     * control on error.
     */
    protected final ControlDecorationProvider decorationProvider;

    /**
     * Flag to know whether currently a error styling is applied or not. This is
     * necessary because first the error styling will be removed and afterwards
     * it will be applied again if the value is still invalid. Without this flag
     * the wrong original values would be stored.
     */
    private boolean errorStylingActive = false;

    /**
     * Create a {@link RenderErrorHandling} with no underlying error handler and
     * no decoration provider.
     */
    public RenderErrorHandling() {
        this(null);
    }

    /**
     * Create a {@link RenderErrorHandling} with no underlying error handler and
     * the specified decoration provider.
     *
     * @param decorationProvider
     *            The decoration provider that should be used for decorating the
     *            editor control on error.
     */
    public RenderErrorHandling(ControlDecorationProvider decorationProvider) {
        this(null, decorationProvider);
    }

    /**
     * Create a {@link RenderErrorHandling} with the underlying error handler
     * and the specified decoration provider. By default the error style is set
     * to render the value in the editor control with red foreground color. You
     * can override that style by calling setErrorStyle(IStyle)
     *
     * @param underlyingErrorHandler
     *            The underlying error handler.
     * @param decorationProvider
     *            The decoration provider that should be used for decorating the
     *            editor control on error.
     */
    public RenderErrorHandling(IEditErrorHandler underlyingErrorHandler, ControlDecorationProvider decorationProvider) {
        super(underlyingErrorHandler);
        this.decorationProvider = decorationProvider;
        this.errorStyle = this.defaultErrorStyle;
    }

    /**
     * {@inheritDoc} After the error remove is handled by its underlying
     * {@link IEditErrorHandler}, the original style will be applied to the
     * editor control.
     */
    @Override
    public void removeError(ICellEditor cellEditor) {
        super.removeError(cellEditor);

        if (this.errorStylingActive) {
            Control editorControl = cellEditor.getEditorControl();

            // reset the rendering information to normal
            editorControl.setBackground(this.originalBgColor);
            editorControl.setForeground(this.originalFgColor);
            editorControl.setFont(this.originalFont);

            // ensure to reset the stored original values so possible
            // dynamic rendering aspects are also covered
            this.originalBgColor = null;
            this.originalFgColor = null;
            this.originalFont = null;

            if (this.decorationProvider != null) {
                this.decorationProvider.setErrorDecorationText(null);
                this.decorationProvider.hideDecoration();
            }

            this.errorStylingActive = false;
        }
    }

    /**
     * {@inheritDoc} After the error is handled by its underlying
     * {@link IEditErrorHandler}, the configured error style will be applied to
     * the editor control.
     *
     * @since 2.0
     */
    @Override
    public void displayError(ICellEditor cellEditor, IConfigRegistry configRegistry, Exception e) {
        super.displayError(cellEditor, configRegistry, e);

        if (!this.errorStylingActive) {
            Control editorControl = cellEditor.getEditorControl();

            // store the current rendering information to be able to reset again
            this.originalBgColor = editorControl.getBackground();
            this.originalFgColor = editorControl.getForeground();
            this.originalFont = editorControl.getFont();

            // set the rendering information out of the error style
            editorControl.setBackground(this.errorStyle.getAttributeValue(CellStyleAttributes.BACKGROUND_COLOR));
            editorControl.setForeground(this.errorStyle.getAttributeValue(CellStyleAttributes.FOREGROUND_COLOR));
            editorControl.setFont(CellStyleUtil.getFont(this.errorStyle, configRegistry));

            this.errorStylingActive = true;
        }

        // we should always update the error decoration text
        if (this.decorationProvider != null) {
            this.decorationProvider.setErrorDecorationText(e.getLocalizedMessage());
            this.decorationProvider.showDecoration();
        }
    }

    /**
     * @param errorStyle
     *            The style that should be used to render an error. Supported
     *            style attributes are foreground color, background color and
     *            font.
     */
    public void setErrorStyle(IStyle errorStyle) {
        this.errorStyle = errorStyle != null ? errorStyle : this.defaultErrorStyle;
    }

}
