package de.duehl.swing.ui.handler.error;

/*
 * Copyright 2019 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.awt.Component;
import java.awt.Image;
import java.awt.Point;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import de.duehl.basics.logging.Logger;
import de.duehl.basics.logic.ErrorHandler;
import de.duehl.basics.system.ExceptionHelper;
import de.duehl.basics.text.Text;
import de.duehl.swing.ui.elements.DisabledGlassPane;
import de.duehl.swing.ui.error.ErrorDialog;

/**
 * Diese Klasse stellt einen ErrorHandler dar, der beim Anzeigen von Warnungen und Fehlern die
 * GlassPane deaktiviert.
 *
 * Optional kann ein Logger übergeben werden, dann werden Fehlermeldungen auch auf dem Logger
 * ausgegeben.
 *
 * @version 1.01     2019-07-02
 * @author Christian Dühl
 */

public class GlassPaneDeactivatingErrorHandler implements ErrorHandler {

    private static final Point DEFAULT_PARENT_LOCATION = new Point(400, 150);

    /** Icon für das Programm. */
    private final Image programImage;

    /** Komponente vor dem der Error angezeigt wird. */
    private final Component parentComponent;

    /** GlassPane, die die Bedienung der Buttons verhindert, wenn ein Prozess im Gang ist. */
    private final DisabledGlassPane glassPane;

    /** Der Logger. */
    protected final Logger logger;

    /** Gibt an, ob nach dem Auftreten eines Fehlers noch weiterer Code ausgeführt werden soll. */
    private boolean weHaveSpecialReactOnError;

    /** Code der nach dem Auftreten eines Fehlers ausgeführt wird. */
    private Runnable specialReactOnError;

    /**
     * Konstruktor.
     *
     * @param programImage
     *            Icon für das Programm.
     * @param parentComponent
     *            Komponente vor dem der Error angezeigt wird (kann null sein).
     * @param glassPane
     *            GlassPane, die die Bedienung der Buttons verhindert, wenn ein Prozess im Gang
     *            ist.
     */
    public GlassPaneDeactivatingErrorHandler(Image programImage, Component parentComponent,
            DisabledGlassPane glassPane) {
        this(programImage, parentComponent, glassPane, null);
    }

    /**
     * Konstruktor.
     *
     * @param programImage
     *            Icon für das Programm.
     * @param parentComponent
     *            Komponente vor dem der Error angezeigt wird (kann null sein).
     * @param glassPane
     *            GlassPane, die die Bedienung der Buttons verhindert, wenn ein Prozess im Gang
     *            ist.
     * @param logger
     *            Der Logger (kann null sein).
     */
    public GlassPaneDeactivatingErrorHandler(Image programImage, Component parentComponent,
            DisabledGlassPane glassPane, Logger logger) {
        this.programImage = programImage;
        this.parentComponent = parentComponent;
        this.glassPane = glassPane;
        this.logger = logger;

        weHaveSpecialReactOnError = false;
    }

    /** Fügt ein Runnable hinzu, dass nach dem Auftreten eines Fehlers im EDT ausgeführt wird. */
    public GlassPaneDeactivatingErrorHandler setReactOnError(Runnable runnable) {
        weHaveSpecialReactOnError = true;
        specialReactOnError = runnable;
        return this;
    }

    /** Zeigt dem Benutzer eine Warnung an. */
    @Override
    public void warning(String message) {
        log("Warnung: " + message);
        SwingUtilities.invokeLater(() -> warningInEDT(message));
    }

    /** Zeigt dem Benutzer eine Warnung an. */
    private void warningInEDT(String message) {
        glassPane.deactivate();
        /* Text umbrechen, damit er nicht zu lang wird: */
        String brokenMessage = Text.addLineBreaks(message, 80);
        JOptionPane.showMessageDialog(parentComponent, brokenMessage, "Warnung",
                JOptionPane.WARNING_MESSAGE);
    }

    /** Zeigt dem Benutzer einen Fehler an. */
    @Override
    public void error(String message) {
        error(message, null);
    }

    /** Zeigt dem Benutzer einen Fehler mit Exception an. */
    @Override
    public void error(String message, Exception exception) {
        ErrorDialog dialog = createErrorDialog(message, exception);
        if (null != logger) {
            dialog.addLogger(logger);
        }
        log("Fehler: " + message +
                (null == exception
                        ? ""
                        : " - " +  ExceptionHelper.getExceptionNameAndMessage(exception)
                        ));
        SwingUtilities.invokeLater(() -> errorInEDT(dialog));
    }

    /**
     * Erstellt den Dialog zur Anzeige des Fehlers. Diese Methode kann überschrieben werden, um
     * abgeleitete Klassen von ErrorDialog zu verwenden.
     */
    protected ErrorDialog createErrorDialog(String message, Exception exception) {
        return new ErrorDialog(message, exception, programImage, calculateLocation());
    }

    /** Zeigt dem Benutzer einen Fehler mit Exception an.  */
    private void errorInEDT(ErrorDialog dialog) {
        glassPane.deactivate();
        dialog.setVisible(true);
        if (weHaveSpecialReactOnError) {
            specialReactOnError.run();
        }
    }

    /** Bestimmt die aktuellen Lokation des Rahmens des übergeordneten Programms. */
    protected Point calculateLocation() {
        if (parentComponent == null) {
            return DEFAULT_PARENT_LOCATION;
        }
        else {
            return parentComponent.getLocation();
        }
    }

    private void log(String message) {
        if (null != logger) {
            logger.log(message);
        }
    }

}
