package com.ht.ui.controls.window;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DataFormat;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;

import com.ht.ui.MainApplication;

/**
 *
 * @author Anton Smirnov (dev@antonsmirnov.name)
 */
public class MessageDialog extends BorderPane {

    protected static final int STACKTRACE_LABEL_MAXHEIGHT = 240;
    protected static final int MESSAGE_MIN_WIDTH = 180;
    protected static final int MESSAGE_MAX_WIDTH = 800;
    protected static final int BUTTON_WIDTH = 80;
    protected static final double MARGIN = 10;
    protected static final String ICON_PATH = "/com/ht/ui/images/";
    
    protected String stacktrace;
    protected double originalWidth, originalHeight;
    protected Label title;
    protected ImageView icon;
    protected VBox messageBox;
    protected Label messageLabel;
    protected boolean stacktraceVisible;
    protected HBox stacktraceButtonsPanel;
    protected ToggleButton viewStacktraceButton;
    protected Button copyStacktraceButton;
    protected ScrollPane scrollPane;
    protected Label stackTraceLabel;
    protected HBox buttonsPanel;
    protected Button okButton;

    public MessageDialog() {
        getStylesheets().add(getClass().getResource("message_dlg.css").toExternalForm());
        
        setId("MessageDialog");
        setMaxSize(430, USE_PREF_SIZE);
        getStyleClass().addAll("window", "shadow");
        
		// block mouse clicks
		setOnMouseClicked(new EventHandler<MouseEvent>() {

			@Override
			public void handle(MouseEvent t) {
				t.consume();
			}
		});
        
        title = new Label("信息");
        title.setId("title");
        title.setMinHeight(24);
        title.setPrefHeight(24);
        title.setMaxWidth(Double.MAX_VALUE);
        title.setAlignment(Pos.CENTER);
        setTop(title);

        // icon
        icon = new ImageView();
        setLeft(icon);
        BorderPane.setMargin(icon, new Insets(MARGIN));

        // message
        messageBox = new VBox();
        messageBox.setAlignment(Pos.CENTER_LEFT);

        messageLabel = new Label();
        messageLabel.setId("message");
        messageLabel.setAlignment(Pos.CENTER_LEFT);
        messageLabel.setWrapText(true);
        messageLabel.setMinWidth(MESSAGE_MIN_WIDTH);
        messageLabel.setMaxWidth(MESSAGE_MAX_WIDTH);

        messageBox.getChildren().add(messageLabel);
        setCenter(messageBox);
        BorderPane.setAlignment(messageBox, Pos.CENTER);
        //BorderPane.setMargin(messageBox, new Insets(MARGIN, MARGIN, MARGIN, 0));
        BorderPane.setMargin(messageBox, new Insets(MARGIN));

        // buttons
        buttonsPanel = new HBox();
        buttonsPanel.setSpacing(MARGIN);
        buttonsPanel.setAlignment(Pos.BOTTOM_CENTER);
        BorderPane.setMargin(buttonsPanel, new Insets(0, 0, 1.5 * MARGIN, 0));
        setBottom(buttonsPanel);
        widthProperty().addListener(new ChangeListener<Number>() {

            public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
                buttonsPanel.layout();
            }
        });
        
    }

    /**
     * Extracts stack trace from Throwable
     */
    protected static class StacktraceExtractor {

        public String extract(Throwable t) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            t.printStackTrace(pw);
            return sw.toString();
        }
    }

    /**
     * Dialog builder
     */
    public static class Builder {

        protected MessageDialog dialog;
//        protected Popup popup;

        public Builder create() {
            dialog = new MessageDialog();
//            popup = new Popup();
//            popup.getContent().add(dialog);
            return this;
        }

        public Builder setTitle(String title) {
            dialog.title.setText(title);
            return this;
        }

        public Builder setMessage(String message) {
            dialog.messageLabel.setText(message);
            return this;
        }

        private void alignScrollPane() {
            dialog.setWidth(
                    dialog.icon.getImage().getWidth()
                    + Math.max(
                    dialog.messageLabel.getWidth(),
                    (dialog.stacktraceVisible
                    ? Math.max(
                    dialog.stacktraceButtonsPanel.getWidth(),
                    dialog.stackTraceLabel.getWidth())
                    : dialog.stacktraceButtonsPanel.getWidth()))
                    + 5 * MARGIN);

            dialog.setHeight(
                    Math.max(
                    dialog.icon.getImage().getHeight(),
                    dialog.messageLabel.getHeight()
                    + dialog.stacktraceButtonsPanel.getHeight()
                    + (dialog.stacktraceVisible
                    ? Math.min(
                    dialog.stackTraceLabel.getHeight(),
                    STACKTRACE_LABEL_MAXHEIGHT)
                    : 0))
                    + dialog.buttonsPanel.getHeight()
                    + 3 * MARGIN);
            if (dialog.stacktraceVisible) {
                dialog.scrollPane.setPrefHeight(
                        dialog.getHeight()
                        - dialog.messageLabel.getHeight()
                        - dialog.stacktraceButtonsPanel.getHeight()
                        - 2 * MARGIN);
            }

        }

        // NOTE: invoke once during Dialog creating
        private Builder setStackTrace(Throwable t) {
            // view button
            dialog.viewStacktraceButton = new ToggleButton("View stacktrace");

            // copy button
            dialog.copyStacktraceButton = new Button("Copy to clipboard");
            HBox.setMargin(dialog.copyStacktraceButton, new Insets(0, 0, 0, MARGIN));

            dialog.stacktraceButtonsPanel = new HBox();
            dialog.stacktraceButtonsPanel.getChildren().addAll(
                    dialog.viewStacktraceButton, dialog.copyStacktraceButton);
            VBox.setMargin(dialog.stacktraceButtonsPanel, new Insets(MARGIN, MARGIN, MARGIN, 0));
            dialog.messageBox.getChildren().add(dialog.stacktraceButtonsPanel);

            // stacktrace text
            dialog.stackTraceLabel = new Label();
            dialog.stackTraceLabel.widthProperty().addListener(new ChangeListener<Number>() {

                public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) {
                    alignScrollPane();
                }
            });

            dialog.stackTraceLabel.heightProperty().addListener(new ChangeListener<Number>() {

                public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) {
                    alignScrollPane();
                }
            });

            StacktraceExtractor extractor = new StacktraceExtractor();
            dialog.stacktrace = extractor.extract(t);

            dialog.scrollPane = new ScrollPane();
            dialog.scrollPane.setContent(dialog.stackTraceLabel);

            dialog.viewStacktraceButton.setOnAction(new EventHandler<ActionEvent>() {

                public void handle(ActionEvent t) {
                    dialog.stacktraceVisible = !dialog.stacktraceVisible;
                    if (dialog.stacktraceVisible) {
                        dialog.messageBox.getChildren().add(dialog.scrollPane);
                        dialog.stackTraceLabel.setText(dialog.stacktrace);

                        alignScrollPane();
                    } else {
                        dialog.messageBox.getChildren().remove(dialog.scrollPane);

                        //alignScrollPane();
                        dialog.setWidth(dialog.originalWidth);
                        dialog.setHeight(dialog.originalHeight);
                        dialog.stackTraceLabel.setText(null);
                    }
                    dialog.messageBox.layout();
                }
            });

            dialog.copyStacktraceButton.setOnAction(new EventHandler<ActionEvent>() {

                public void handle(ActionEvent t) {
                    Clipboard clipboard = Clipboard.getSystemClipboard();
                    Map<DataFormat, Object> map = new HashMap<DataFormat, Object>();
                    map.put(DataFormat.PLAIN_TEXT, dialog.stacktrace);
                    clipboard.setContent(map);
                }
            });

            return this;
        }

        protected void setIconFromResource(String resourceName) {
            final Image image = new Image(getClass().getResourceAsStream(resourceName));
            dialog.icon.setImage(image);
        }

        protected Builder setWarningIcon() {
            setIconFromResource(ICON_PATH + "warningIcon.png");
            return this;
        }

        protected Builder setErrorIcon() {
            setIconFromResource(ICON_PATH + "errorIcon.png");
            return this;
        }

        protected Builder setThrowableIcon() {
            setIconFromResource(ICON_PATH + "bugIcon.png");
            return this;
        }

        protected Builder setInfoIcon() {
            setIconFromResource(ICON_PATH + "infoIcon.png");
            return this;
        }

        protected Builder setConfirmationIcon() {
            setIconFromResource(ICON_PATH + "confirmationIcon.png");
            return this;
        }

        protected Builder addOkButton() {
            dialog.okButton = new Button("确定");
            dialog.okButton.setDefaultButton(true);
            dialog.okButton.setPrefWidth(BUTTON_WIDTH);
            dialog.okButton.setOnAction(new EventHandler<ActionEvent>() {

                public void handle(ActionEvent t) {
                    MainApplication.getMainApp().hideModalMessage();
                }
            });
            dialog.buttonsPanel.getChildren().add(dialog.okButton);
            return this;
        }

        protected Builder addConfirmationButton(String buttonCaption, final EventHandler<Event> actionHandler, boolean isDefault) {
            Button confirmationButton = new Button(buttonCaption);
            confirmationButton.setMinWidth(BUTTON_WIDTH);
            confirmationButton.setDefaultButton(isDefault);
            confirmationButton.setOnAction(new EventHandler<ActionEvent>() {

                public void handle(ActionEvent t) {
                    MainApplication.getMainApp().hideModalMessageFast();
                    if (actionHandler != null) {
                        actionHandler.handle(t);
                    }
                }
            });

            dialog.buttonsPanel.getChildren().add(confirmationButton);
            return this;
        }

        /**
         * Add Yes button to confirmation dialog
         *
         * @param actionHandler action handler
         * @return
         */
        public Builder addYesButton(EventHandler<Event> actionHandler) {
            return addConfirmationButton("是", actionHandler, true);
        }

        /**
         * Add No button to confirmation dialog
         *
         * @param actionHandler action handler
         * @return
         */
        public Builder addNoButton(EventHandler<Event> actionHandler) {
            return addConfirmationButton("否", actionHandler, false);
        }

        /**
         * Add Cancel button to confirmation dialog
         *
         * @param actionHandler action handler
         * @return
         */
        public Builder addCancelButton(EventHandler<Event> actionHandler) {
            return addConfirmationButton("取消", actionHandler, false);
        }

        /**
         * Build dialog
         *
         * @return dialog instance
         */
        public MessageDialog build() {
            if (dialog.buttonsPanel.getChildren().size() == 0) {
                throw new RuntimeException("Add one dialog button at least");
            }

            dialog.buttonsPanel.getChildren().get(0).requestFocus();
            return dialog;
        }
        
        public void showDialog() {
//        	popup.show(MainApplication.getMainApp().getMainPane(), 0, 0);
//        	popup.centerOnScreen();
            MainApplication.getMainApp().showModalMessage(this.build());
        }
    }

    /**
     * Show information dialog box as parentStage child
     *
     * @param title dialog title
     * @param message dialog message
     */
    public static void showInfo(String title, String message) {
        new Builder().create().setTitle(title).setInfoIcon().setMessage(message).addOkButton().showDialog();
    }

    /**
     * Show warning dialog box
     *
     * @param title dialog title
     * @param message dialog message
     */
    public static void showWarning(String title, String message) {
        new Builder().create().setTitle(title).setWarningIcon().setMessage(message).addOkButton().showDialog();
    }

    /**
     * Show error dialog box
     *
     * @param title dialog title
     * @param message dialog message
     */
    public static void showError(String title, String message) {
        new Builder().create().setTitle(title).setErrorIcon().setMessage(message).addOkButton().showDialog();
    }

    /**
     * Show error dialog box with stacktrace
     *
     * @param title dialog title
     * @param message dialog message
     * @param t throwable
     */
    public static void showThrowable(String title, String message, Throwable t) {
        new Builder().create().setTitle(title).setThrowableIcon().setMessage(message).setStackTrace(t).addOkButton().showDialog();
    }

    /**
     * Build confirmation dialog builder
     *
     * @param title dialog title
     * @param message dialog message
     * @return
     */
    public static Builder buildConfirmation(String title, String message) {
        return new Builder().create().setTitle(title).setConfirmationIcon().setMessage(message);
    }

    /**
     * Build confirmation dialog builder
     *
     * @param title dialog title
     * @param message dialog message
     * @return
     */
    public static void buildConfirmation(String title, String message, EventHandler<Event> yesHandler, EventHandler<Event> noHandler, EventHandler<Event> cancelHandler) {
        buildConfirmation(title, message).addYesButton(yesHandler).addNoButton(noHandler).addCancelButton(cancelHandler).showDialog();
    }
    
    public static void buildConfirmation(String title, String message, EventHandler<Event> yesHandler, EventHandler<Event> noHandler) {
        buildConfirmation(title, message).addYesButton(yesHandler).addNoButton(noHandler).showDialog();
    }
}
