package be.iminds.ilabt.jfed.experimenter_gui.bugreporting;

import be.iminds.ilabt.jfed.JFedWebApiClientModule;
import be.iminds.ilabt.jfed.bugreport.BugReport;
import be.iminds.ilabt.jfed.experimenter_gui.config.JFedGuiConfig;
import be.iminds.ilabt.jfed.experimenter_gui.dialogs.SelectAuthoritiesController;
import be.iminds.ilabt.jfed.experimenter_gui.ui.ResourceCell;
import be.iminds.ilabt.jfed.experimenter_gui.ui.SceneAwareController;
import be.iminds.ilabt.jfed.experimenter_gui.util.StringConverters;
import be.iminds.ilabt.jfed.fedmon.webapi.client.FedmonWebApiClientConfig;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.preferences.GuiPreferenceKey;
import be.iminds.ilabt.jfed.preferences.JFedGuiPreferences;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import be.iminds.ilabt.jfed.util.OSDetector;
import com.google.inject.name.Named;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.input.Clipboard;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import org.controlsfx.dialog.CommandLinksDialog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 12/9/13
 * Time: 8:58 AM
 */
public class BugReportDialog implements SceneAwareController {
    private static final Logger LOG = LoggerFactory.getLogger(BugReportDialog.class);
    private static final String BUGREPORT_EXTENSION = ".xml";

    private final BugReportComposer bugReportComposer;
    private final FedmonWebApiClientConfig fedmonWebApiClientConfig;
    private final JFedGuiPreferences jFedPreferences;
    private final JFedGuiConfig jFedGuiConfig;
    private final TestbedInfoSource testbedInfoSource;
    private final Clipboard systemClipboard = Clipboard.getSystemClipboard();

    private final BooleanProperty submitting = new SimpleBooleanProperty(false);

    @FXML
    private BorderPane root;
    @FXML
    private StackPane centerStack;

    @FXML
    private Label titleLabel;
    @FXML
    private Pane reportTypePane1;
    @FXML
    private Pane reportTypePane2;
    @FXML
    private Pane reportTypePane3;
    @FXML
    private Pane reportTypePane4;
    @FXML
    private Pane reportDetailsPane;

    @FXML
    private TextField subjectTextField;
//    @FXML
//    private TextArea headerTextArea;
    @FXML
    private Label descriptionLabel;
    @FXML
    private TextArea descriptionTextArea;
    @FXML
    private TextField versionTextField;
    @FXML
    private TextField environmentTextField;
    @FXML
    private TextField credentialsTextField;
    @FXML
    private TextField emailTextField;
    @FXML
    private Button saveToFileButton;
    @FXML
    private Button submitButton;
    @FXML
    private Button cancelButton;
    @FXML
    private Label includedCallsLabel;
    @FXML
    private ProgressIndicator submitProgressIndicator;
    @FXML
    private Label progressLabel;

    @FXML
    private StackPane screenshotStackPane;
    @FXML
    private CheckBox includeScreenshotCheckBox;
    @FXML
    private ImageView screenshotImageView;
    @FXML
    private Button pasteScreenshotButton;

    private Task<Integer> reportTask = null;


    @FXML BugReportQuestionButton part1Question;
    @FXML BugReportQuestionButton part1Feature;
    @FXML BugReportQuestionButton part1Bug;
    @FXML BugReportQuestionButton part1Con;
    @FXML BugReportQuestionButton part1Other;

    @FXML BugReportQuestionButton part2jFed;
    @FXML BugReportQuestionButton part2Testbed;
    @FXML BugReportQuestionButton part2Other;

    @FXML Label part3label;
//    @FXML ComboBox<Server> part3Combobox;
    @FXML SelectAuthoritiesController part3AuthChooserController;
    @FXML Button part3ok;

    @FXML CheckBox part4AllowPublic;
    @FXML Label part4note;
    @FXML Button part4ok;

    /** part1 = 1, ...,  part4 = 4, reportDetails = 5*/
    private int currentPart = 1;

    private String fullChoiceText = "";
    private String subjectChoiceText = "";
    private BugReport.ReportType part1Choice;
    private BugReport.ReportTarget part2Choice;
    private Set<Server> part3Choice;
    private Boolean part4Choice;


    @Inject
    BugReportDialog(BugReportComposer bugReportComposer, JFedGuiPreferences jFedPreferences,
                    JFedGuiConfig jFedGuiConfig, TestbedInfoSource testbedInfoSource,
                    @Named(JFedWebApiClientModule.NAMED__CLIENT_AUTH_WEBAPI_CONFIG) FedmonWebApiClientConfig fedmonWebApiClientConfig) {
        this.bugReportComposer = bugReportComposer;
        this.jFedPreferences = jFedPreferences;
        this.jFedGuiConfig = jFedGuiConfig;
        this.testbedInfoSource = testbedInfoSource;
        this.fedmonWebApiClientConfig = fedmonWebApiClientConfig;
    }

    @FXML
    private void initialize() {
        submitButton.disableProperty().bind(submitting);
        saveToFileButton.disableProperty().bind(submitting);
        submitProgressIndicator.visibleProperty().bind(submitting);

        descriptionTextArea.textProperty().bindBidirectional(bugReportComposer.descriptionProperty());
        emailTextField.textProperty().bindBidirectional(bugReportComposer.emailProperty());
        versionTextField.textProperty().bind(bugReportComposer.versionProperty());
        environmentTextField.textProperty().bind(bugReportComposer.environmentProperty());
        credentialsTextField.textProperty().bind(bugReportComposer.reporterUrnProperty());
        includedCallsLabel.textProperty().bind(Bindings.format("%d calls", bugReportComposer.callsCountProperty()));

        subjectTextField.textProperty().bindBidirectional(bugReportComposer.subjectProperty());
        part4AllowPublic.selectedProperty().bindBidirectional(bugReportComposer.postOnPublicListProperty());

        part3AuthChooserController.setHeaderLabelText("");

        screenshotImageView.imageProperty().bindBidirectional(bugReportComposer.screenshotProperty());
        screenshotImageView.fitWidthProperty().bind(screenshotStackPane.widthProperty());
        screenshotImageView.fitHeightProperty().bind(screenshotStackPane.heightProperty());

        //bind screenshot-imageview to clipboard
        screenshotImageView.imageProperty().addListener(observable -> {
            if (screenshotImageView.getImage() != null) {
                includeScreenshotCheckBox.setSelected(true);
                includeScreenshotCheckBox.setDisable(false);
                pasteScreenshotButton.setVisible(false);
            }
        });

        includeScreenshotCheckBox.setOnAction(observable -> {
            screenshotImageView.setImage(null);
            includeScreenshotCheckBox.setDisable(true);
            pasteScreenshotButton.setVisible(true);
        });

        reportTypePane1.getStylesheets().add(CommandLinksDialog.class.getResource("dialogs.css").toExternalForm());
        reportTypePane2.getStylesheets().add(CommandLinksDialog.class.getResource("dialogs.css").toExternalForm());
        reportTypePane3.getStylesheets().add(CommandLinksDialog.class.getResource("dialogs.css").toExternalForm());
        reportTypePane4.getStylesheets().add(CommandLinksDialog.class.getResource("dialogs.css").toExternalForm());

        reportTypePane1.setVisible(true);
        reportTypePane2.setVisible(false);
        reportTypePane3.setVisible(false);
        reportTypePane4.setVisible(false);
        reportDetailsPane.setVisible(false);

        centerStack.requestLayout();
    }

    private void showPart() {
        if (currentPart <= 0 || currentPart > 5)
            throw new RuntimeException("Invalid current part "+currentPart);

        if (currentPart >= 1 && currentPart <= 4) {
            reportTypePane1.setVisible(currentPart == 1);
            reportTypePane2.setVisible(currentPart == 2);
            reportTypePane3.setVisible(currentPart == 3);
            reportTypePane4.setVisible(currentPart == 4);

            reportDetailsPane.setVisible(false);
            submitButton.setVisible(false);
        }
        if (currentPart == 5) {
            reportTypePane1.setVisible(false);
            reportTypePane2.setVisible(false);
            reportTypePane3.setVisible(false);
            reportTypePane4.setVisible(false);

            reportDetailsPane.setVisible(true);
            submitButton.setVisible(true);
        }

        centerStack.requestLayout();
    }

    public void onPartOne(ActionEvent e) {
        if (e.getSource() == part1Question) {
            part1Choice = BugReport.ReportType.QUESTION;
            part4AllowPublic.setSelected(true);
            fullChoiceText = "This is a Question.";
            subjectChoiceText = "Question: ";
            titleLabel.setText("Question");
            descriptionLabel.setText("Question:");
            part4note.setText("You are totally free in this choice.\nOften, it can be very helpful for others to see your question/answer.");
            currentPart = 2;
        }
        if (e.getSource() == part1Feature) {
            part1Choice = BugReport.ReportType.FEATURE;
            fullChoiceText = "This is a jFed Feature Request.";
            subjectChoiceText = "jFed Feature Request: ";
            part4AllowPublic.setSelected(false);
            titleLabel.setText("jFed Feature Request");
            descriptionLabel.setText("jFed Feature Request:");
            part4note.setText("You are totally free in this choice.\njFed feature requests can sometimes be relevant to share with the list.");
            currentPart = 4;
        }
        if (e.getSource() == part1Bug) {
            part1Choice = BugReport.ReportType.BUG;
            fullChoiceText = "This is a Problem and/or Bug Report.";
            subjectChoiceText = "Bug/Problem Report: ";
            part4AllowPublic.setSelected(false);
            titleLabel.setText("Problem/Bug Report");
            descriptionLabel.setText("Bug description:\n(Please include: expected outcome, steps to reproduce)");
            part4note.setText("You are totally free in this choice.\njFed bug reports and/or testbed problems can sometimes be relevant to share with the list.");
            currentPart = 2;
        }
        if (e.getSource() == part1Con) {
            part1Choice = BugReport.ReportType.CONNECTIVITY;
            fullChoiceText = "This is a Connectivity Issue Report.";
            subjectChoiceText = "Connectivity Issue: ";
            part4AllowPublic.setSelected(false);
            titleLabel.setText("Connectivity Issue");
            descriptionLabel.setText("Connectivity Issue:\n(Please include if this related to jFed, and/or to SSH connections setup by jFed, and from which network you are connecting)");
            part4note.setText("You are totally free in this choice.\nConnectivity issues might in some cases be generic, and thus relevant to share with the list.");
            part2Choice = BugReport.ReportTarget.OTHER;
            currentPart = 3;
        }
        if (e.getSource() == part1Other) {
            part1Choice = BugReport.ReportType.OTHER;
            subjectChoiceText = "Feedback: ";
            part4AllowPublic.setSelected(true);
            titleLabel.setText("Feedback");
            descriptionLabel.setText("Feedback:");
            part4note.setText("You are totally free in this choice.");
            currentPart = 2;
        }

        if (part1Choice == null) {
            part1Choice = BugReport.ReportType.OTHER;
            LOG.error("onPartOne() -> Unknown event source! falling back to part1Choice = Part1Choice.OTHER. e.getSource()={}", e.getSource());
        }

        bugReportComposer.setReportType(part1Choice);

        showPart();
    }

    public void onPartTwo(ActionEvent e) {
        if (e.getSource() == part2jFed) {
            part2Choice = BugReport.ReportTarget.JFED;
            fullChoiceText += "\nThis is about the jFed Experimenter GUI.";
            if (part1Choice == BugReport.ReportType.BUG) {
                fullChoiceText = "This is a jFed Bug Report.";
                subjectChoiceText = "jFed Bug Report: ";
                titleLabel.setText("jFed Bug Report");
                descriptionLabel.setText("Bug description:\n(Please include: expected outcome, steps to reproduce)");
            }
            currentPart = 4;
        }
        if (e.getSource() == part2Testbed) {
            part2Choice = BugReport.ReportTarget.TESTBED;
            fullChoiceText += "\nThis is about a Testbed.";
            if (part1Choice == BugReport.ReportType.BUG) {
                fullChoiceText = "This is a Testbed Problem Report.";
                subjectChoiceText = "Testbed Problem Report: ";
                titleLabel.setText("Testbed Problem Report");
                descriptionLabel.setText("Problem description:\n(Please include: expected outcome, steps to reproduce)");
            }
            currentPart = 3;
        }
        if (e.getSource() == part2Other) {
            part2Choice = BugReport.ReportTarget.OTHER;
            currentPart = 3;
        }

        if (part2Choice == null) {
            part2Choice = BugReport.ReportTarget.OTHER;
            LOG.error("onPartTwo() -> Unknown event source! falling back to part1Choice = Part2Choice.OTHER. e.getSource()={}", e.getSource());
        }

        bugReportComposer.setReportTarget(part2Choice);

        showPart();
    }

    public void onPartThree(ActionEvent e) {
        Set<Server> selectedServers = part3AuthChooserController.getSelectedServers();

        LOG.debug("BugReportDialog Part3 selectedServers count = "+(selectedServers==null ? "null" : ""+selectedServers.size()));

        if (selectedServers == null || selectedServers.isEmpty()) {
            part3Choice = null;
            bugReportComposer.setRelatedTestbeds(Collections.emptyList());
        } else {
            part3Choice = selectedServers;//part3Combobox.getSelectionModel().getSelectedItem();
            fullChoiceText += "\nThis is about the "+
                    selectedServers.stream().map(Server::getName).collect(Collectors.joining(" "))
                    +" Testbed(s)";
            bugReportComposer.setRelatedTestbeds(
                    selectedServers.stream()
                            .filter(Objects::nonNull)
                            .map(Server::getDefaultComponentManagerAsGeniUrn)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toList())
            );
        }
        currentPart = 4;

        showPart();
    }

    public void onPartFour(ActionEvent e) {
        part4Choice = part4AllowPublic.isSelected();
        if (part4Choice) {
            fullChoiceText += "\nThis information was shared on the public fed4fire experimenter mailinglist.";
        } else {
            fullChoiceText += "\nThis information was not shared on the public fed4fire experimenter mailinglist.";
        }

        subjectTextField.setText(subjectChoiceText+"YOUR SUBJECT HERE");
//        headerTextArea.setText("This message was sent from the jFed Experimenter GUI.\n\n"+fullChoiceText);

        currentPart = 5;
        showPart();
    }

    @FXML
    private void onSaveToFileAction() {
        if (!checkValidity())
            return;

        FileChooser fc = new FileChooser();
        fc.setTitle("Save Bug Report");
        fc.getExtensionFilters().add(new FileChooser.ExtensionFilter("Bug Report (*." + BUGREPORT_EXTENSION + ")", "*" + BUGREPORT_EXTENSION));
        fc.setInitialFileName(String.format("BugReport-%1$tF-%1$tH-%1$tM.xml", new Date()));


        //set the default save dir
        if (System.getProperty("user.home") != null)
            fc.setInitialDirectory(new File(System.getProperty("user.home")));

        if (jFedPreferences.containsPreference(GuiPreferenceKey.PREF_RSPECFOLDER_REQUEST)) {
            File dir = new File(jFedPreferences.getString(GuiPreferenceKey.PREF_RSPECFOLDER_REQUEST));
            if (dir.exists()) {
                fc.setInitialDirectory(dir);
            }
        }

        File file = fc.showSaveDialog(submitButton.getScene().getWindow());

        if (file != null) {
            BugReportToFileTask bugReportToFileTask = new BugReportToFileTask(bugReportComposer, file);
            startAndShowBugReportTask(bugReportToFileTask);
        }

    }


    @FXML
    private void onSubmitAction() {
        if (checkValidity()) {
            BugReportToServerTask bugReportToServerTask = new BugReportToServerTask(bugReportComposer, fedmonWebApiClientConfig);
            startAndShowBugReportTask(bugReportToServerTask);
        }
    }

    private boolean checkValidity() {
        if (descriptionTextArea == null || descriptionTextArea.getText() == null || descriptionTextArea.getText().isEmpty()) {
            JFDialogs.create()
                    .owner(submitButton.getScene().getWindow())
                    .message("Please fill in a description to submit a bug report.")
                    .masthead("Description is a mandatory field")
                    .showError();
            return false;
        }

        if (emailTextField.getText() == null || emailTextField.getText().isEmpty()) {
            JFDialogs.create()
                    .owner(submitButton.getScene().getWindow())
                    .message("You must provide an email address on which we can contact you")
                    .masthead("Email is a mandatory field")
                    .showError();
            return false;
        }      else if(!isValidEmailAddress(emailTextField.getText())){
            JFDialogs.create()
                    .owner(submitButton.getScene().getWindow())
                    .message("Your email address is invalid. Please check your input!")
                    .masthead("Email is a mandatory field")
                    .showError();
            return false;
        }

        return true;
    }

    private static boolean isValidEmailAddress(String email) {
        String ePattern = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$";
        java.util.regex.Pattern p = java.util.regex.Pattern.compile(ePattern);
        java.util.regex.Matcher m = p.matcher(email);
        return m.matches();
    }

    private void startAndShowBugReportTask(final Task<Integer> bugReportTask) {
        this.reportTask = bugReportTask;
        submitting.set(true);

        progressLabel.textProperty().bind(bugReportTask.messageProperty());
        submitProgressIndicator.progressProperty().bind(bugReportTask.progressProperty());


        bugReportTask.setOnSucceeded(workerStateEvent -> {
            JFDialogs.create()
                    .owner(submitButton.getScene().getWindow())
                    .message("The bug report was saved with ID "+bugReportTask.getValue())
                    .masthead("Bug report result")
                    .title(null)
                    .showInformation();

            BugReportDialog.this.reportTask = null;
            close();
        });

        bugReportTask.setOnFailed(workerStateEvent -> {
            JFDialogs.create()
                    .owner(submitButton.getScene().getWindow())
                    .message("An error occurred while sending the bug report. Are you connected to the internet?")
                    .masthead("Error while sending bug report")
                    .title("Error while sending bug report")
                    .showException(bugReportTask.getException());

            submitting.set(false);
            progressLabel.textProperty().unbind();
            progressLabel.setText("Processing bug report failed");
            submitProgressIndicator.progressProperty().unbind();
            BugReportDialog.this.reportTask = null;
        });

        new Thread(bugReportTask).start();
    }

    private void close() {
        Stage stage = (Stage) cancelButton.getScene().getWindow();
        if (stage != null)
            stage.close();
    }

    @FXML
    private void onCancelAction() {
        if (reportTask != null) {
            reportTask.cancel();
        }
        close();
    }

    @Override
    public void registerScene(Scene scene) {
        scene.getAccelerators().put(new KeyCodeCombination(KeyCode.F10), () -> saveToFileButton.setVisible(!saveToFileButton.isVisible()));
    }

    @FXML
    private void fetchScreenshot() {
        if (systemClipboard.hasImage()) {
            screenshotImageView.setImage(systemClipboard.getImage());

        } else {
            String osSpecificHelpmessage = "";

            OSDetector.OS currentOS = OSDetector.findOS();
            if (currentOS == OSDetector.OS.WIN) {
                osSpecificHelpmessage =
                        "\t- Press 'Print Screen'/ 'Prnt Scrn' / 'PrtScn',\n" +
                                "\t- Press ALT+'Print Screen' if you only want to make a picture of the active window.";
            } else if (currentOS == OSDetector.OS.MAC) {
                osSpecificHelpmessage =
                        "\t- Press Command-Control-Shift-3 for the whole screen,\n" +
                                "\t- Press Command-Control-Shift-4 if you want to select a specific area of your screen first,\n" +
                                "\t- Press Command-Control-Shift-4 followed by Space if you want to select a specific window.";
            } else {
                osSpecificHelpmessage = "\t- On Ubuntu: press CTRL+'PrtScn'\n" +
                        "\t- On KDE: press 'PrtScn'\n" +
                        "\t- On Gnome: execute 'gnome-screenshot -a -c'";
            }


            JFDialogs.create()
                    .message("The clipboard doesn't contain an image at the moment. " +
                            "To include a screenshot in the bugreport, make a screenshot of the relevant windows, then press the button in the Bug Report dialog.\n\nTo make a screenshot and store it on the clipboard: \n" + osSpecificHelpmessage)
                    .masthead("Fetching screenshot from clipboard failed")
                    .showError();
        }


    }
}
