package be.iminds.ilabt.jfed.experimenter_gui;

import be.iminds.ilabt.jfed.experiment.Experiment;
import be.iminds.ilabt.jfed.experiment.ExperimentState;
import be.iminds.ilabt.jfed.experimenter_gui.bugreporting.BugReportDialogFactory;
import be.iminds.ilabt.jfed.experimenter_gui.config.JFedGuiConfig;
import be.iminds.ilabt.jfed.experimenter_gui.dialogs.AboutDialogFactory;
import be.iminds.ilabt.jfed.experimenter_gui.dialogs.RecoverSliceDialog;
import be.iminds.ilabt.jfed.experimenter_gui.editor.ExperimentEditor;
import be.iminds.ilabt.jfed.experimenter_gui.editor.ExperimentEditorTab;
import be.iminds.ilabt.jfed.experimenter_gui.editor.ExperimentEditorTabFactory;
import be.iminds.ilabt.jfed.experimenter_gui.events.OpenRequestRspecFromFileEvent;
import be.iminds.ilabt.jfed.experimenter_gui.events.OpenRequestRspecFromUrlEvent;
import be.iminds.ilabt.jfed.experimenter_gui.events.OpenTutorialFromUrlEvent;
import be.iminds.ilabt.jfed.experimenter_gui.events.RecoverSliceEvent;
import be.iminds.ilabt.jfed.experimenter_gui.gts_editor.GTSEditorTabFactory;
import be.iminds.ilabt.jfed.experimenter_gui.preferences.PreferencesDialogFactory;
import be.iminds.ilabt.jfed.experimenter_gui.slice.ExperimentViewController;
import be.iminds.ilabt.jfed.experimenter_gui.slice.ExperimentViewControllerTab;
import be.iminds.ilabt.jfed.experimenter_gui.slice.events.TerminateExperimentEvent;
import be.iminds.ilabt.jfed.experimenter_gui.slice.events.UpdateExperimentEvent;
import be.iminds.ilabt.jfed.experimenter_gui.ui.ribbon.RibbonButton;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.TriggeredMessage;
import be.iminds.ilabt.jfed.highlevel.model.Slice;
import be.iminds.ilabt.jfed.lowlevel.ApiInfo;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.preferences.GuiPreferenceKey;
import be.iminds.ilabt.jfed.preferences.JFedGuiPreferences;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import be.iminds.ilabt.jfed.ui.javafx.util.BindingsUtil;
import com.google.common.eventbus.EventBus;
import javafx.application.HostServices;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import org.controlsfx.glyphfont.FontAwesome;
import org.reactfx.value.Val;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import java.io.File;
import java.util.List;
import java.util.Objects;

/**
 * User: twalcari
 * Date: 7/1/2014
 * Time: 15:24
 */
public class GeneralRibbonTabController {
    private static final Logger LOG = LoggerFactory.getLogger(GeneralRibbonTabController.class);

    //private final ExperimenterGUI experimenterGUI;

    @FXML
    private RibbonButton newExperimentButton;
    @FXML
    private RibbonButton openExperimentButton;
    @FXML
    private RibbonButton openExperimentURLButton;
    @FXML
    private RibbonButton saveExperimentButton;

    @FXML
    private RibbonButton preferencesButton;

    @FXML
    private RibbonButton startExperimentButton;
    @FXML
    private RibbonButton updateExperimentButton;
    @FXML
    private RibbonButton stopExperimentButton;
    @FXML
    private RibbonButton recoverExperimentButton;
    @FXML
    private RibbonButton openSharedExperimentButton;
    @FXML
    private RibbonButton openTutorialURLButton;


    private final GeniUserProvider geniUserProvider;
    private final BugReportDialogFactory bugReportDialogFactory;
    private final Provider<RecoverSliceDialog> recoverSliceDialogProvider;
    private final PreferencesDialogFactory preferencesDialogFactory;
    private final AboutDialogFactory aboutDialogFactory;
    private final Provider<HostServices> hostServicesProvider;
    @Named("primaryStage")
    private final Provider<Stage> primaryStageProvider;
    private final JFedGuiPreferences jFedPreferences;
    private final JFedGuiConfig guiConfig;
    private final ExperimentEditorTabFactory experimentEditorTabFactory;
    private final GTSEditorTabFactory gtsEditorTabFactory;
    private final EventBus eventBus;
    private final TriggeredMessageHandler triggeredMessageHandler;

    private final boolean isDemo = new JFedGuiPreferences().getBoolean(GuiPreferenceKey.PREF_IS_DEMO, false);

    private TabPane tabPane; //registered in registerExperimenterTabPane()

    @Inject
    GeneralRibbonTabController(GeniUserProvider geniUserProvider, BugReportDialogFactory bugReportDialogFactory,
                               Provider<RecoverSliceDialog> recoverSliceDialogProvider, PreferencesDialogFactory preferencesDialogFactory,
                               AboutDialogFactory aboutDialogFactory, Provider<HostServices> hostServicesProvider,
                               Provider<Stage> primaryStageProvider, JFedGuiPreferences jFedPreferences,
                               JFedGuiConfig guiConfig,
                               ExperimentEditorTabFactory experimentEditorTabFactory, GTSEditorTabFactory gtsEditorTabFactory,
                               EventBus eventBus, TriggeredMessageHandler triggeredMessageHandler) {
        this.geniUserProvider = geniUserProvider;
        this.bugReportDialogFactory = bugReportDialogFactory;
        this.recoverSliceDialogProvider = recoverSliceDialogProvider;
        this.preferencesDialogFactory = preferencesDialogFactory;
        this.aboutDialogFactory = aboutDialogFactory;
        this.hostServicesProvider = hostServicesProvider;
        this.primaryStageProvider = primaryStageProvider;
        this.jFedPreferences = jFedPreferences;
        this.guiConfig = guiConfig;
        this.experimentEditorTabFactory = experimentEditorTabFactory;
        this.gtsEditorTabFactory = gtsEditorTabFactory;
        this.eventBus = eventBus;
        this.triggeredMessageHandler = triggeredMessageHandler;
    }

    @FXML
    private void initialize() {

        //determine if "Open shared" button must be visible
        Server userAuth = geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer();
        boolean usersSeeSharedSlices = ApiInfo.findUrl(userAuth, ApiInfo.ApiName.GENI_CH_SA, 2) != null;

        openSharedExperimentButton.setVisible(!usersSeeSharedSlices);
        openSharedExperimentButton.setManaged(!usersSeeSharedSlices);

        preferencesButton.setDisable(isDemo);

        openTutorialURLButton.setVisible(guiConfig.isShowTutorialButtonEnabled());
    }

    /**
     * this method must be called by the GUI to bind the experiments TabPane (on the bottom of the GUI) to the buttons
     * of the GeneralRibbonTab.
     * <p/>
     * This cannot be done in initialize(), as the FXML of the ExperimenterGUI hasn't loaded at that point
     */
    void registerExperimentsTabPane(TabPane tabPane) {
        this.tabPane = tabPane;

        BooleanBinding isNoExperimentEditorTabSelectedBinding = Bindings.not(
                BindingsUtil.instanceOfBinding(tabPane.getSelectionModel().selectedItemProperty(),
                        ExperimentEditorTab.class)
        );

        saveExperimentButton.disableProperty().bind(isNoExperimentEditorTabSelectedBinding);
        startExperimentButton.disableProperty().bind(isNoExperimentEditorTabSelectedBinding);

        final Val<Experiment> experimentVal = Val.wrap(tabPane.getSelectionModel().selectedItemProperty())
                .filter(tab -> tab instanceof ExperimentViewControllerTab)
                .map(ExperimentViewControllerTab.class::cast)
                .map(ExperimentViewControllerTab::getExperimentViewController)
                .map(ExperimentViewController::getExperiment);

        final Val<Boolean> manifestUnavailableVal = experimentVal
                .flatMap(Experiment::sliceProperty)
                .flatMap(Slice::manifestRspecProperty)
                .map(Objects::isNull)
                .orElseConst(true);


        updateExperimentButton.disableProperty().bind(manifestUnavailableVal);

        if(!isDemo){
            final Val<Boolean> terminatedVal =
                    experimentVal.flatMap(Experiment::experimentStateProperty)
                            .map(ExperimentState::isTerminated);

            stopExperimentButton.disableProperty().bind(manifestUnavailableVal.orElse(terminatedVal));
        }else{
            stopExperimentButton.setDisable(true);
        }
    }

    @FXML
    public void onNewExperimentAction(ActionEvent actionEvent) {
        try {
            triggeredMessageHandler.onEvent(TriggeredMessage.GuiTrigger.DESIGN_EXPERIMENT);
        } catch (Exception e) {
            LOG.warn("Exception processing TriggeredMessage (will be ignored)", e);
        }

        ExperimentEditorTab newTab =
                experimentEditorTabFactory.createExperimentEditorTab();
        tabPane.getTabs().add(newTab);
        tabPane.getSelectionModel().select(newTab);
    }

    @FXML
    private void onOpenExperimentAction() {
        FileChooser fc = new FileChooser();
        fc.setTitle("Open Experiment Definition");
        // bvermeul, FEDIBBTDEV-666, MAC does not understand this it seems, so let's drop it
        //fc.getExtensionFilters().add(FileUtils.RSPEC_EXTENSION_FILTER);
        //fc.getExtensionFilters().add(FileUtils.ALL_EXTENSION_FILTER);

        //set the default open 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);
            }
        }


        List<File> files = fc.showOpenMultipleDialog(openExperimentButton.getScene().getWindow());

        if (files == null)
            return;

        //save folder in which files were opened for future file selector dialogs
        jFedPreferences.setString(GuiPreferenceKey.PREF_RSPECFOLDER_REQUEST,
                files.get(0).getParentFile().getAbsolutePath());

        for (final File file : files) {
            eventBus.post(new OpenRequestRspecFromFileEvent(file));
        }
    }

    @FXML
    public void onOpenExperimentFromURLAction() {
        //create a custom pane for entering the URL

        VBox vbox = new VBox(5);
        Label label = new Label("Please enter the URL:");

        TextField urlTextField = new TextField();
        urlTextField.setPrefColumnCount(40);
        vbox.getChildren().addAll(label, urlTextField);

        Dialog<String> dialog = new Dialog<>();
        DialogPane dialogPane = new DialogPane();
        dialogPane.setContent(vbox);
        dialogPane.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
        dialogPane.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.CLOUD_DOWNLOAD, Color.CHOCOLATE));

        dialog.initOwner(openExperimentURLButton.getScene().getWindow());
        dialog.setTitle("Open Experiment Definition");
        dialog.setDialogPane(dialogPane);
        dialog.setResultConverter(param -> {
            if (param == ButtonType.OK)
                return urlTextField.getText();
            else
                return null;
        });


        dialog.showAndWait().ifPresent(input -> eventBus.post(new OpenRequestRspecFromUrlEvent(input)));
            }

    @FXML
    public void onOpenTutorialFromURLAction() {
        //create a custom pane for entering the URL

        VBox vbox = new VBox(5);
        Label label = new Label("Please enter the URL:");

        TextField urlTextField = new TextField();
        urlTextField.setPrefColumnCount(40);
        String defaultTutorialUrl = guiConfig.getStringFlag("defaultTutorialUrl");
        if (defaultTutorialUrl != null)
            urlTextField.setText(defaultTutorialUrl);
        else
            urlTextField.setText("");
        vbox.getChildren().addAll(label, urlTextField);

        Dialog<String> dialog = new Dialog<>();
        DialogPane dialogPane = new DialogPane();
        dialogPane.setContent(vbox);
        dialogPane.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
        dialogPane.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.GRADUATION_CAP, Color.CHOCOLATE));

        dialog.initOwner(openExperimentURLButton.getScene().getWindow());
        dialog.setTitle("Open Tutorial");
        dialog.setDialogPane(dialogPane);
        dialog.setResultConverter(param -> {
            if (param == ButtonType.OK)
                return urlTextField.getText();
            else
                return null;
        });


        dialog.showAndWait().ifPresent(input -> eventBus.post(new OpenTutorialFromUrlEvent(input)));
    }

    @FXML
    private void onRecoverExperimentAction() {
        try {
            triggeredMessageHandler.onEvent(TriggeredMessage.GuiTrigger.RECOVER_EXPERIMENT);
        } catch (Exception e) {
            LOG.warn("Exception processing TriggeredMessage (will be ignored)", e);
        }

        //show the select slice dialog
        recoverSliceDialogProvider.get().show();
    }


    private ExperimentEditor getActiveExperimentEditor() {
        if (tabPane.getSelectionModel().getSelectedItem() instanceof ExperimentEditorTab) {
            return ((ExperimentEditorTab)
                    tabPane.getSelectionModel().getSelectedItem()).getExperimentEditor();
        } else
            return null;
    }

    @FXML
    private void onSaveExperimentAction() {
        ExperimentEditor activeExperimentEditor = getActiveExperimentEditor();
        assert (activeExperimentEditor != null);

        activeExperimentEditor.save();
    }

    private ExperimentViewController getActiveExperimentViewController() {
        if (tabPane.getSelectionModel().getSelectedItem() instanceof ExperimentViewControllerTab) {
            return ((ExperimentViewControllerTab)
                    tabPane.getSelectionModel().getSelectedItem()).getExperimentViewController();
        } else
            return null;
    }


    @FXML
    private void onUpdateExperimentAction() {
        assert getActiveExperimentViewController() != null;

        eventBus.post(new UpdateExperimentEvent(updateExperimentButton.getScene().getWindow(),
                getActiveExperimentViewController().getExperimentController()));
    }

    @FXML
    private void onStopExperimentAction() {
        assert getActiveExperimentViewController() != null;

        eventBus.post(new TerminateExperimentEvent(stopExperimentButton.getScene().getWindow(),
                getActiveExperimentViewController().getExperimentController()));
    }

    @FXML
    public void onPreferencesAction() {
        preferencesDialogFactory.showDialog();
    }

    @FXML
    private void onReportBugButtonAction() {
        bugReportDialogFactory.showDialog();
    }

    @FXML
    private void onAboutAction() {
        aboutDialogFactory.showDialog();
    }

    @FXML
    private void onRunExperimentAction(ActionEvent actionEvent) {
        ExperimentEditor activeExperimentEditor = getActiveExperimentEditor();
        assert (activeExperimentEditor != null);
        activeExperimentEditor.startExperiment();
    }

    @FXML
    private void onOpenSharedExperimentAction() {
        new OpenSharedSliceDialog().showDialog();
    }

    private class OpenSharedSliceDialog extends GridPane {
        private final TextField urnTextField;

        //private final Server homeAuthority = geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer();

        private final Dialog<String> textInputDialog = new Dialog<>();

        public OpenSharedSliceDialog() {
            urnTextField = new TextField();
            urnTextField.setText("urn:publicid:IDN+wall2.ilabt.iminds.be:PROJECT+slice+EXPERIMENT");
            urnTextField.setPrefColumnCount(40);
            add(urnTextField, 0, 1);

            textInputDialog.setTitle("Open shared experiment");

            DialogPane dialogPane = new DialogPane();
            dialogPane.setContent(this);
            dialogPane.setHeaderText("Open shared experiment");
            dialogPane.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.GROUP, Color.GREEN));
            dialogPane.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);


            textInputDialog.setResultConverter(param -> {
                if (param == ButtonType.OK) {
                    return urnTextField.getText();
                } else {
                    return null;
                }
            });

            textInputDialog.setDialogPane(dialogPane);
        }

        public void showDialog() {
            textInputDialog.showAndWait().ifPresent(urn -> eventBus.post(new RecoverSliceEvent(urn)));
        }
    }

    @FXML
    private void onOpenDocumentationAction() {
        hostServicesProvider.get().showDocument("http://doc.ilabt.iminds.be/jfed-documentation/");
    }


}
