/*******************************************************************************
 * Copyright (c) 2016, 2020 Dirk Fauth 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:
 *     Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation
 ******************************************************************************/
package org.eclipse.nebula.widgets.nattable.ui;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Dialog that can be used to show an error with an additional stacktrace.
 *
 * @since 1.5
 */
public class ExceptionDialog extends Dialog {

    private static final Logger LOG = LoggerFactory.getLogger(ExceptionDialog.class);

    private String title;
    private String message;
    private Exception exception;

    private Button detailsButton;
    private Text exceptionText;

    private boolean exceptionAreaCreated = false;

    protected ExceptionDialog(Shell parentShell, String title, String message, Exception exception) {
        super(parentShell);

        this.title = title;
        this.message = message != null ? message : exception.getLocalizedMessage();
        this.exception = exception;
    }

    @Override
    protected Control createDialogArea(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout layout = new GridLayout();
        layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
        layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
        layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
        layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
        layout.numColumns = 2;
        composite.setLayout(layout);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(composite);
        createMessageArea(composite);

        return composite;
    }

    protected Control createMessageArea(Composite composite) {
        // create composite
        // create image
        Image image = getParentShell().getDisplay().getSystemImage(SWT.ICON_ERROR);
        Label imageLabel = new Label(composite, SWT.NULL);
        image.setBackground(imageLabel.getBackground());
        imageLabel.setImage(image);
        GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.BEGINNING).applyTo(imageLabel);

        // create message
        if (this.message != null) {
            Label messageLabel = new Label(composite, SWT.WRAP);
            messageLabel.setText(this.message);
            GridDataFactory
                    .fillDefaults()
                    .align(SWT.FILL, SWT.CENTER)
                    .grab(true, false)
                    .hint(convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH), SWT.DEFAULT)
                    .applyTo(messageLabel);
        }

        return composite;
    }

    @Override
    protected void createButtonsForButtonBar(Composite parent) {
        // create OK and Details buttons
        createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
        if (this.exception != null) {
            this.detailsButton = createButton(parent, IDialogConstants.DETAILS_ID, IDialogConstants.SHOW_DETAILS_LABEL, false);
        }
    }

    @Override
    protected void buttonPressed(int id) {
        if (id == IDialogConstants.DETAILS_ID) {
            // was the details button pressed?
            toggleDetailsArea();
        } else {
            super.buttonPressed(id);
        }
    }

    private void toggleDetailsArea() {
        boolean opened = false;
        Point windowSize = getShell().getSize();
        if (this.exceptionAreaCreated) {
            this.exceptionText.dispose();
            this.exceptionAreaCreated = false;
            this.detailsButton.setText(IDialogConstants.SHOW_DETAILS_LABEL);
            opened = false;
        } else {
            this.exceptionText = createExceptionText((Composite) getContents());
            this.detailsButton.setText(IDialogConstants.HIDE_DETAILS_LABEL);
            getContents().getShell().layout();
            opened = true;
        }
        Point newSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT);
        int diffY = newSize.y - windowSize.y;
        // increase the dialog height if details were opened and such increase
        // is necessary
        // decrease the dialog height if details were closed and empty space
        // appeared
        if ((opened && diffY > 0) || (!opened && diffY < 0)) {
            getShell().setSize(new Point(windowSize.x, windowSize.y + (diffY)));
        }
    }

    private Text createExceptionText(Composite parent) {
        Text exceptionText = new Text(parent, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
        GridDataFactory
                .fillDefaults()
                .grab(true, true)
                .span(2, 1)
                .applyTo(exceptionText);

        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        try {
            this.exception.printStackTrace(pw);
            exceptionText.setText(sw.toString());
        } finally {
            try {
                sw.close();
                pw.close();
            } catch (IOException e) {
                LOG.error("Closing stream failed", e); //$NON-NLS-1$
            }
        }
        this.exceptionAreaCreated = true;
        return exceptionText;
    }

    /**
     * Opens an error dialog to display the given error.
     *
     * @param parentShell
     *            the parent shell of the dialog, or <code>null</code> if none
     * @param title
     *            the title to use for this dialog, or <code>null</code> to
     *            indicate that the default title should be used
     * @param exception
     *            the error to show to the user
     * @return the code of the button that was pressed that resulted in this
     *         dialog closing. This will be <code>Dialog.OK</code> if the OK
     *         button was pressed, or <code>Dialog.CANCEL</code> if this
     *         dialog's close window decoration or the ESC key was used.
     */
    public static int open(Shell parentShell, String title, Exception exception) {
        return open(parentShell, title, exception.getLocalizedMessage(), exception);
    }

    /**
     * Opens an error dialog to display the given error.
     *
     * @param parentShell
     *            the parent shell of the dialog, or <code>null</code> if none
     * @param title
     *            the title to use for this dialog, or <code>null</code> to
     *            indicate that the default title should be used
     * @param message
     *            the message to show in this dialog, or <code>null</code> to
     *            indicate that the error's message should be shown as the
     *            primary message
     * @param exception
     *            the error to show to the user
     * @return the code of the button that was pressed that resulted in this
     *         dialog closing. This will be <code>Dialog.OK</code> if the OK
     *         button was pressed, or <code>Dialog.CANCEL</code> if this
     *         dialog's close window decoration or the ESC key was used.
     */
    public static int open(Shell parentShell, String title, String message, Exception exception) {
        ExceptionDialog dialog = new ExceptionDialog(parentShell, title, message, exception);
        return dialog.open();
    }

    @Override
    protected boolean isResizable() {
        return true;
    }

    @Override
    protected void configureShell(Shell newShell) {
        super.configureShell(newShell);
        newShell.setText(this.title);
        newShell.setImage(getParentShell().getDisplay().getSystemImage(SWT.ICON_ERROR));
    }
}
