/**
 * Copyright (c) 2013, 2014 ControlsFX
 * All rights reserved.
 * <p/>
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * * Neither the name of ControlsFX, any associated website, nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 * <p/>
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package be.iminds.ilabt.jfed.ui.javafx.dialogs;

import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Window;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;

/**
 * A customized wrapper, based on an old ControlsFX-API. It has been adapted to support the new JavaFX Dialogs-API
 * that will be introduced in Java8u40.
 */
public final class JFDialogs {
    private static final Logger LOG = LoggerFactory.getLogger(JFDialogs.class);
    private Window owner;
    private String title;
    private Node graphic;
    private String message;
    private String masthead;
    private boolean canCopyMessage;
    private final Set<ButtonType> buttonTypes = new LinkedHashSet<>();


    /**
     * Creates the initial dialog
     *
     * @return dialog instance
     */
    public static JFDialogs create() {
        return new JFDialogs();
    }

    private JFDialogs() {
    }

    /**
     * Assigns the owner of the dialog. If an owner is specified, the dialog will
     * block input to the owner and all parent owners. If no owner is specified,
     * or if owner is null, the dialog will block input to the entire application.
     *
     * @param owner The dialog owner.
     * @return dialog instance.
     */
    public JFDialogs owner(final Window owner) {
        if (owner == null) {
            LOG.warn("Got an null Window as owner!");
        }
        this.owner = owner;
        return this;
    }

    public JFDialogs owner(final Scene scene) {
        if (scene != null) {
            return owner(scene.getWindow());
        } else {
            LOG.warn("Got a null Scene as owner!");
            return this;
        }
    }

    public JFDialogs owner(final Node node) {
        if (node != null) {
            return owner(node.getScene());
        } else {
            LOG.warn("Got a null Node as owner!");
            return this;
        }
    }


    /**
     * Assigns dialog's title
     *
     * @param title dialog title
     * @return dialog instance.
     */

    public JFDialogs title(final String title) {
        this.title = title;
        return this;
    }

    /**
     * Assigns dialog's graphic
     *
     * @param graphic dialog graphic
     * @return dialog instance.
     */
    public JFDialogs graphic(final Node graphic) {
        this.graphic = graphic;
        return this;
    }

    /**
     * Assigns dialog's instructions
     *
     * @param message dialog message
     * @return dialog instance.
     */
    public JFDialogs message(final String message) {
        this.message = message;
        return this;
    }

    /**
     * make the text shown copyable by the user.
     *
     * @return dialog instance
     */
    public JFDialogs allowCopyMessage() {
        this.canCopyMessage = true;
        return this;
    }

    public JFDialogs allowCopyMessage(boolean canCopyMessage) {
        this.canCopyMessage = canCopyMessage;
        return this;
    }

    /**
     * Assigns dialog's masthead
     *
     * @param masthead dialog masthead
     * @return dialog instance.
     */
    public JFDialogs masthead(final String masthead) {
        this.masthead = masthead;
        return this;
    }

    /**
     * Completely replaces standard buttonTypes with provided ones.
     *
     * @param buttonTypes new dialog buttonTypes
     * @return dialog instance.
     */
    public JFDialogs buttonTypes(Collection<? extends ButtonType> buttonTypes) {
        this.buttonTypes.clear();
        this.buttonTypes.addAll(buttonTypes);
        return this;
    }

    /**
     * Completely replaces standard buttonTypes with provided ones.
     *
     * @param buttonTypes new dialog buttonTypes
     * @return dialog instance.
     */
    public JFDialogs buttonTypes(ButtonType... buttonTypes) {
        return buttonTypes(Arrays.asList(buttonTypes));
    }

    /**
     * Shows information dialog.
     */
    public void showInformation() {
        showSimpleContentDialog(Type.INFORMATION);
    }

    /**
     * Shows confirmation dialog.
     *
     * @return action used to close dialog.
     */
    public Optional<ButtonType> showConfirm() {
        return showSimpleContentDialog(Type.CONFIRMATION);
    }

    /**
     * Shows warning dialog
     *
     * @return action used to close dialog
     */
    public Optional<ButtonType> showWarning() {
        return showSimpleContentDialog(Type.WARNING);
    }

    /**
     * Show error dialog
     *
     * @return action used to close dialog
     */
    public Optional<ButtonType> showError() {
        return showSimpleContentDialog(Type.ERROR);
    }

    /**
     * Shows exception dialog with expandable stack trace.
     *
     * @param exception exception to present
     * @return action used to close dialog
     */
    public void showException(Throwable exception) {

        Alert alert = new Alert(Alert.AlertType.ERROR);
        if (title != null)
            alert.setTitle(title);
        if (masthead != null)
            alert.setHeaderText(masthead);
        if (message != null)
            alert.setContentText(message);

        // Create expandable Exception.
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        exception.printStackTrace(pw);
        String exceptionText = sw.toString();

        Label label = new Label("The exception stacktrace was:");

        TextArea textArea = new TextArea(exceptionText);
        textArea.setEditable(false);
        textArea.setWrapText(true);

        textArea.setMaxWidth(Double.MAX_VALUE);
        textArea.setMaxHeight(Double.MAX_VALUE);
        GridPane.setVgrow(textArea, Priority.ALWAYS);
        GridPane.setHgrow(textArea, Priority.ALWAYS);

        GridPane expContent = new GridPane();
        expContent.setMaxWidth(Double.MAX_VALUE);
        expContent.add(label, 0, 0);
        expContent.add(textArea, 0, 1);

// Set expandable Exception into the dialog pane.
        alert.getDialogPane().setExpandableContent(expContent);
        alert.show();
    }


    /**
     * Shows dialog with one text field
     *
     * @param defaultValue text field default value
     * @return Optional of text from input field if OK action is used otherwise Optional.EMPTY
     */
    public Optional<String> showTextInput(String defaultValue) {
        TextInputDialog dlg = new TextInputDialog(defaultValue);

        if (graphic != null)
            dlg.setGraphic(graphic);

        //message
        if (message != null)
            dlg.setContentText(message);
        if (masthead != null)
            dlg.setHeaderText(masthead);
        if (!buttonTypes.isEmpty()) {
            dlg.getDialogPane().getButtonTypes().clear();
            dlg.getDialogPane().getButtonTypes().addAll(buttonTypes);
        }

        if (title != null)
            dlg.setTitle(title);


        return dlg.showAndWait();
    }

    /**
     * Show a dialog with one combobox filled with provided choices. The combobox selection
     * will be set to a default value if one is provided.
     *
     * @param defaultChoice default combobox selection
     * @param choices       dialog choices
     * @return Optional of selected choice or Optional.EMPTY if dialog is cancelled.
     */
    public <T> Optional<T> showChoices(T defaultChoice, Collection<T> choices) {

        ChoiceDialog<T> dlg = new ChoiceDialog<>(defaultChoice, choices);

        if (graphic != null)
            dlg.setGraphic(graphic);

        //message
        if (message != null)
            dlg.setContentText(message);
        if (masthead != null)
            dlg.setHeaderText(masthead);
        if (!buttonTypes.isEmpty()) {
            dlg.getDialogPane().getButtonTypes().clear();
            dlg.getDialogPane().getButtonTypes().addAll(buttonTypes);
        }

        if (title != null)
            dlg.setTitle(title);


        return dlg.showAndWait();
    }

    /**
     * Show a dialog with one combobox filled with provided choices
     *
     * @param choices dialog choices
     * @return Optional of selected choice or Optinal.EMPTY if dialog is cancelled
     */
    public <T> Optional<T> showChoices(Collection<T> choices) {
        return showChoices(null, choices);
    }

    /**
     * ************************************************************************
     * <p/>
     * Private API
     * <p/>
     * ************************************************************************
     */

    private Alert buildDialog(final Type dlgType) {
        Alert alert = new Alert(dlgType.getAlertType());
        alert.initOwner(owner);
        if (title != null)
            alert.setTitle(title);
        if (masthead != null)
            alert.setHeaderText(masthead);
        if (graphic != null)
            alert.setGraphic(graphic);

        return alert;
    }

    private Optional<ButtonType> showSimpleContentDialog(final Type dlgType) {
        Alert dlg = buildDialog(dlgType);
        if (!buttonTypes.isEmpty()) {
            dlg.getButtonTypes().setAll(buttonTypes);
        }

        if (!canCopyMessage) {
            dlg.setContentText(message);
        } else {
            TextArea textArea = new TextArea(message);
            textArea.setPrefColumnCount(10);
            textArea.setPrefRowCount(50);
            textArea.setMinWidth(500);
            textArea.setMinHeight(500);
            textArea.setMaxWidth(Double.MAX_VALUE);
            textArea.setMaxHeight(Double.MAX_VALUE);
            textArea.setWrapText(true);
            textArea.setEditable(false);
            dlg.getDialogPane().setContent(textArea);


        }
        return dlg.showAndWait();
    }

    /**
     * ************************************************************************
     * <p/>
     * Support classes
     * <p/>
     * ************************************************************************
     */

    private enum Type {
        ERROR(Alert.AlertType.ERROR),
        INFORMATION(Alert.AlertType.INFORMATION),
        WARNING(Alert.AlertType.WARNING),
        CONFIRMATION(Alert.AlertType.CONFIRMATION);

        private final Alert.AlertType alertType;

        Type(Alert.AlertType alertType) {
            this.alertType = alertType;
        }

        public Alert.AlertType getAlertType() {
            return alertType;
        }
    }
}
