/*
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.util.fxdesigner.popups;

import static java.lang.Double.max;
import static java.lang.Math.min;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.stream.Collectors;

import org.apache.commons.io.IOUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.kordamp.ikonli.javafx.FontIcon;
import org.reactfx.EventSource;
import org.reactfx.EventStream;

import net.sourceforge.pmd.PMDVersion;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.util.fxdesigner.Designer;
import net.sourceforge.pmd.util.fxdesigner.app.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.util.AuxLanguageRegistry;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;

import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.Transition;
import javafx.geometry.Bounds;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane;
import javafx.stage.Popup;
import javafx.util.Duration;


/**
 * @author Clément Fournier
 */
public final class SimplePopups {

    private static final String LICENSE_FILE_PATH = "/net/sourceforge/pmd/util/fxdesigner/LICENSE";
    private static final int DEFAULT_XOFFSET = 20;


    private SimplePopups() {

    }


    public static EventStream<?> showActionFeedback(@NonNull Node owner, AlertType type, @NonNull String message) {
        Node icon = getIconLiteral(type);
        return showActionFeedback(owner, icon, message, DEFAULT_XOFFSET, false, type.name().toLowerCase(Locale.ROOT));
    }

    public static EventStream<?> showStickyNotification(@NonNull Node owner, AlertType type, @NonNull String message) {
        return showStickyNotification(owner, type, message, DEFAULT_XOFFSET);
    }

    public static EventStream<?> showStickyNotification(@NonNull Node owner, AlertType type, @NonNull String message, double offsetX) {
        Node icon = getIconLiteral(type);
        return showActionFeedback(owner, icon, message, offsetX, true, type.name().toLowerCase(Locale.ROOT));
    }

    @Nullable
    private static Node getIconLiteral(AlertType type) {
        @Nullable String iconLit;
        switch (type) {
        case ERROR:
            iconLit = "fas-times";
            break;
        case CONFIRMATION:
            iconLit = "fas-check";
            break;
        case INFORMATION:
            iconLit = "fas-info";
            break;
        case WARNING:
            iconLit = "fas-exclamation";
            break;
        default:
            iconLit = null;
            break;
        }

        return iconLit == null ? null : new FontIcon(iconLit);
    }


    /**
     * Show a transient popup with a message, to let the user know an action
     * was performed.
     *
     * @param owner Node next to which the popup will be shown
     * @return
     */
    public static EventStream<?> showActionFeedback(@NonNull Node owner,
                                                    @Nullable Node graphic,
                                                    @NonNull String message,
                                                    double offsetX,
                                                    boolean stick,
                                                    String... cssClasses) {

        Popup popup = new Popup();
        Label label = new Label(message, graphic);
        StackPane pane = new StackPane();

        DesignerUtil.addCustomStyleSheets(pane, "designer");
        pane.getStyleClass().addAll("action-feedback");
        pane.getStyleClass().addAll(cssClasses);

        pane.getChildren().addAll(label);
        popup.getContent().addAll(pane);

        Animation fadeTransition = stick ? fadeInAnimation(pane) : bounceFadeAnimation(pane);
        EventSource<?> closeTick = new EventSource<>();
        if (stick) {
            pane.setOnMouseClicked(evt -> {
                popup.hide();
                closeTick.push(null);
            });
        } else {
            fadeTransition.setOnFinished(e -> {
                popup.hide();
                closeTick.push(null);
            });
        }

        popup.setOnShowing(e -> fadeTransition.play());

        Bounds screenBounds = owner.localToScreen(owner.getBoundsInLocal());
        popup.show(owner, screenBounds.getMaxX() + offsetX, screenBounds.getMinY());
        return closeTick;
    }


    private static Animation bounceFadeAnimation(Node owner) {
        return bounceFadeAnimation(owner, 2000, .3, 1);
    }

    private static Animation fadeInAnimation(Node owner) {
        return bounceFadeAnimation(owner, 2000, .3, .5);
    }

    private static Animation bounceFadeAnimation(final Node owner,
                                                 final int durationMs,
                                                 final double plateauWidth,
                                                 final double end) {
        assert plateauWidth >= 0 && plateauWidth <= 1;
        assert end >= 0 && end <= 1;

        return new Transition() {

            {
                setCycleDuration(Duration.millis(durationMs));
                setInterpolator(Interpolator.EASE_OUT);
            }


            @Override
            protected void interpolate(double frac) {
                double mapped = clamp(map(frac));
                owner.setOpacity(mapped);
            }

            private double map(double x) {
                if (x > end) {
                    x = end;
                }
                double t = x - .5; // translate

                double plateau = x > .5 - plateauWidth && x < .5 + plateauWidth ? 1 : 0;
                return (.25 - t * t) * 4 + plateau;
            }

            private double clamp(double i) {
                return min(1, max(0, i));
            }
        };
    }

    public static void showLicensePopup() {
        Alert licenseAlert = new Alert(AlertType.INFORMATION);
        licenseAlert.setWidth(500);
        licenseAlert.setHeaderText("License");

        ScrollPane scroll = new ScrollPane();
        try {
            scroll.setContent(new TextArea(IOUtils.toString(SimplePopups.class.getResourceAsStream(LICENSE_FILE_PATH), StandardCharsets.UTF_8)));
        } catch (IOException e) {
            e.printStackTrace();
        }

        licenseAlert.getDialogPane().setContent(scroll);
        licenseAlert.showAndWait();
    }


    public static void showAboutPopup(DesignerRoot root) {
        Alert licenseAlert = new Alert(AlertType.INFORMATION);
        licenseAlert.setWidth(500);
        licenseAlert.setHeaderText("About");

        ScrollPane scroll = new ScrollPane();
        TextArea textArea = new TextArea();

        String sb =
            "PMD core version:\t\t\t" + PMDVersion.VERSION + "\n"
                + "Designer version:\t\t\t" + Designer.getCurrentVersion()
                + " (supports PMD core " + Designer.getPmdCoreMinVersion() + ")\n"
                + "Designer settings dir:\t\t"
                + root.getService(DesignerRoot.DISK_MANAGER).getSettingsDirectory() + "\n"
                + "Available languages:\t\t"
                + AuxLanguageRegistry.getSupportedLanguages().map(Language::getTerseName).collect(Collectors.toList())
                + "\n";

        textArea.setText(sb);
        scroll.setContent(textArea);

        licenseAlert.getDialogPane().setContent(scroll);
        licenseAlert.showAndWait();
    }

}