package be.iminds.ilabt.jfed.experimenter_gui;

import be.iminds.ilabt.jfed.experiment.Experiment;
import be.iminds.ilabt.jfed.experiment.util.ExperimentRestoreInformation;
import be.iminds.ilabt.jfed.experimenter_gui.call_gui.TasksWindow;
import be.iminds.ilabt.jfed.experimenter_gui.connectivity_tester.ConnectivityTesterDialog;
import be.iminds.ilabt.jfed.experimenter_gui.editor.ExperimentEditorEventHandler;
import be.iminds.ilabt.jfed.experimenter_gui.editor.ExperimentEditorTab;
import be.iminds.ilabt.jfed.experimenter_gui.editor.ribbon_tabs.EditorRibbonTab;
import be.iminds.ilabt.jfed.experimenter_gui.events.*;
import be.iminds.ilabt.jfed.experimenter_gui.gts_editor.GTSEditorEventHandler;
import be.iminds.ilabt.jfed.experimenter_gui.gts_editor.GTSEditorTab;
import be.iminds.ilabt.jfed.experimenter_gui.gts_editor.ribbon_tabs.GTSEditorRibbonTab;
import be.iminds.ilabt.jfed.experimenter_gui.slice.*;
import be.iminds.ilabt.jfed.experimenter_gui.slice.ribbon_tabs.SliceRibbonTab;
import be.iminds.ilabt.jfed.experimenter_gui.tabs.AskOnCloseRequest;
import be.iminds.ilabt.jfed.experimenter_gui.tabs.RibbonEnabled;
import be.iminds.ilabt.jfed.experimenter_gui.tabs.StatusEnabled;
import be.iminds.ilabt.jfed.experimenter_gui.tasks.OpenRequestRspecTask;
import be.iminds.ilabt.jfed.experimenter_gui.tasks.OpenTutorialTask;
import be.iminds.ilabt.jfed.experimenter_gui.tasks.RecoverSliceTask;
import be.iminds.ilabt.jfed.experimenter_gui.tasks.TasksFactory;
import be.iminds.ilabt.jfed.experimenter_gui.ui.ExperimentEditorTabHost;
import be.iminds.ilabt.jfed.experimenter_gui.ui.GTSEditorTabHost;
import be.iminds.ilabt.jfed.experimenter_gui.ui.StatusUpdateListener;
import be.iminds.ilabt.jfed.experimenter_gui.ui.progressbar.TaskService;
import be.iminds.ilabt.jfed.experimenter_gui.ui.progressbar.TasksProgressIndicator;
import be.iminds.ilabt.jfed.experimenter_gui.ui.ribbon.RibbonBar;
import be.iminds.ilabt.jfed.experimenter_gui.ui.status.TaskStatusIndicator;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.highlevel.controller.JavaFXTaskThread;
import be.iminds.ilabt.jfed.highlevel.controller.TaskExecution;
import be.iminds.ilabt.jfed.highlevel.controller.TaskThread;
import be.iminds.ilabt.jfed.highlevel.model.SfaModel;
import be.iminds.ilabt.jfed.highlevel.model.Slice;
import be.iminds.ilabt.jfed.highlevel.model.UserSpecHelper;
import be.iminds.ilabt.jfed.highlevel.tasks.HighLevelTaskFactory;
import be.iminds.ilabt.jfed.highlevel.tasks.ListSlicesTask;
import be.iminds.ilabt.jfed.highlevel.tasks.ProjectMemberSshKeysTask;
import be.iminds.ilabt.jfed.highlevel.tasks.UserAuthorityGetVersionTask;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.api.user_spec.UserSpec;
import be.iminds.ilabt.jfed.preferences.CorePreferenceKey;
import be.iminds.ilabt.jfed.preferences.GuiPreferenceKey;
import be.iminds.ilabt.jfed.preferences.JFedGuiPreferences;
import be.iminds.ilabt.jfed.preferences.ProxyPreferencesManager;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import be.iminds.ilabt.jfed.util.GeniUrn;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import javafx.application.Platform;
import javafx.collections.*;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Window;
import org.controlsfx.glyphfont.Glyph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 10/18/13
 * Time: 4:21 PM
 */
@Singleton
public class ExperimenterGUI implements StatusUpdateListener, TaskService, ExperimentEditorTabHost, GTSEditorTabHost {
    private static final Logger LOG = LoggerFactory.getLogger(ExperimenterGUI.class);

    private final ExecutorService executorService = Executors.newCachedThreadPool(
            new ThreadFactoryBuilder().setNameFormat("ExperimenterGUI-%d").build());

    private final UserAuthorityGetVersionTask getVersionTask;

    private final ObservableMap<GeniUrn, ExperimentViewController> experimentViewControllers =
            FXCollections.observableHashMap();
    private final Provider<ConnectivityTesterDialog> connectivityTesterDialogProvider;
    private final TaskThread taskThread;
    private final JavaFXTaskThread javaFXTaskThread;

    private final HighLevelTaskFactory hltf;
    private final GeniUserProvider geniUserProvider;
    private final SfaModel sfaModel;
    private final TasksFactory tasksFactory;
    private final Provider<TasksWindow> tasksWindowProvider;
    private final JFedGuiPreferences jFedPreferences;
    private final SliceRibbonTabs sliceRibbonTabs;
    private final TriggeredMessageHandler triggeredMessageHandler;
    private final ExperimentViewControllerFactory experimentViewControllerFactory;

    @SuppressWarnings("FieldCanBeLocal")
    private final EventBus eventBus;

    //these dependencies are put here to make sure that it is loaded when ExperimenterGUI is created
    @SuppressWarnings({"unused", "FieldCanBeLocal"})
    private final ExperimentEventHandler experimentEventHandler;
    @SuppressWarnings({"unused", "FieldCanBeLocal"})
    private final ExperimentEditorEventHandler experimentEditorEventHandler;
    @SuppressWarnings({"unused", "FieldCanBeLocal"})
    private final GTSEditorEventHandler gtsEditorEventHandler;

    @Inject
    public ExperimenterGUI(Provider<ConnectivityTesterDialog> connectivityTesterDialogProvider, TaskThread taskThread, SfaModel sfaModel,
                           HighLevelTaskFactory hltf, JavaFXTaskThread javaFXTaskThread,
                           TasksFactory tasksFactory,
                           GeniUserProvider geniUserProvider, Provider<TasksWindow> tasksWindowProvider, JFedGuiPreferences jFedPreferences,
                           SliceRibbonTabs sliceRibbonTabs,
                           EventBus eventBus, ExperimentEventHandler experimentEventHandler,
                           ExperimentEditorEventHandler experimentEditorEventHandler, GTSEditorEventHandler gtsEditorEventHandler,
                           TriggeredMessageHandler triggeredMessageHandler, ExperimentViewControllerFactory experimentViewControllerFactory) {
        this.connectivityTesterDialogProvider = connectivityTesterDialogProvider;
        this.taskThread = taskThread;
        this.sfaModel = sfaModel;
        this.hltf = hltf;
        this.javaFXTaskThread = javaFXTaskThread;
        this.tasksFactory = tasksFactory;
        this.geniUserProvider = geniUserProvider;
        this.tasksWindowProvider = tasksWindowProvider;
        this.jFedPreferences = jFedPreferences;
        this.sliceRibbonTabs = sliceRibbonTabs;
        this.eventBus = eventBus;
        this.experimentEventHandler = experimentEventHandler;
        this.experimentEditorEventHandler = experimentEditorEventHandler;
        this.gtsEditorEventHandler = gtsEditorEventHandler;
        this.triggeredMessageHandler = triggeredMessageHandler;
        this.experimentViewControllerFactory = experimentViewControllerFactory;

        this.eventBus.register(this);

        //start a getVersionTask if necessary, this will allow us to check if we need to overwrite existing recovery information or not
        this.getVersionTask = this.hltf.getUserAuthorityGetVersionTask();
        if (!getVersionTask.isKnown()) {
            LOG.debug("CreateSliceDialog: getVersion not known. Requesting.");
            this.taskThread.addTask(getVersionTask);
        }

    }

    @FXML
    private BorderPane root;
    @FXML
    private RibbonBar ribbonBar;
    @FXML
    private VBox noTabsOpenBox;
    @FXML
    private TabPane tabPane;
    @FXML
    private Label leftStatus;
    @FXML
    private Label rightStatus;
    @FXML
    private Label connectivityLabel;
    @FXML
    private Glyph connectivityLabelIcon;
    @FXML
    private Label pageantLabel;
    @FXML
    private TaskStatusIndicator callsStatusIndicator;

    @FXML
    private TasksProgressIndicator tasksProgressIndicator;

    @FXML
    private GeneralRibbonTabController generalRibbonTabController;

    @FXML
    private void initialize() {
        //add and initialize the general ribbon tab
        generalRibbonTabController.registerExperimentsTabPane(tabPane);

        experimentViewControllers.addListener((MapChangeListener<GeniUrn, ExperimentViewController>) change -> {
            if (change.wasRemoved()) {
                ExperimentViewController removedSliceController = change.getValueRemoved();
                ExperimentViewControllerTab experimentViewControllerTab = null;
                for (Tab tab : tabPane.getTabs()) {
                    if (tab instanceof ExperimentViewControllerTab &&
                            ((ExperimentViewControllerTab) tab).getExperimentViewController() == removedSliceController) {
                        experimentViewControllerTab = (ExperimentViewControllerTab) tab;
                        break;
                    }
                }
                if (experimentViewControllerTab != null) {
                    tabPane.getTabs().remove(experimentViewControllerTab);
                }
            }
            if (change.wasAdded()) {
                ExperimentViewControllerTab experimentViewControllerTab =
                        new ExperimentViewControllerTab(change.getValueAdded(), experimentViewControllers, sliceRibbonTabs);
                tabPane.getTabs().add(experimentViewControllerTab);
                tabPane.getSelectionModel().select(experimentViewControllerTab);
            }
        });


        // hide/show "no tabs" notification when a tab is opened/all tabas are closed
        tabPane.getTabs().addListener((ListChangeListener<Tab>) change -> noTabsOpenBox.setVisible(tabPane.getTabs().size() == 0));

        //register AskOnCloseRequest-tabs
        tabPane.getTabs().addListener((ListChangeListener<Tab>) c -> {
            while (c.next()) {
                c.getAddedSubList().stream()
                        .filter(tab -> tab instanceof AskOnCloseRequest)
                        .forEach(tab -> tab.setOnCloseRequest(event -> {
                            if (!((AskOnCloseRequest) tab).onTabCloseRequest())
                                event.consume();
                        }));
            }
        });


        tabPane.getSelectionModel().selectedItemProperty().addListener(
                (observableValue, oldValue, newValue) -> {
                    //bind to status-property of tab if it is available

                    if (oldValue != null && oldValue instanceof StatusEnabled)
                        leftStatus.textProperty().unbind();

                    if (newValue != null && newValue instanceof StatusEnabled)
                        leftStatus.textProperty().bind(((StatusEnabled) newValue).statusProperty());
                    else {
                        leftStatus.setText("");
                    }

                    //add and/or remove ribbontabs if necessary
                    if (oldValue != null && oldValue instanceof RibbonEnabled) {
                        ribbonBar.getTabs().removeAll(((RibbonEnabled) oldValue).getRibbonTabs());
                    }

                    if (newValue != null && newValue instanceof RibbonEnabled) {

                        //register ExperimentEditor in EditorRibbonTab's
                        if (newValue instanceof ExperimentEditorTab) {
                            ((ExperimentEditorTab) newValue).getRibbonTabs().stream()
                                    .filter(rt -> rt instanceof EditorRibbonTab)
                                    .forEach(rt -> ((EditorRibbonTab) rt).setActiveExperimentEditor(
                                            ((ExperimentEditorTab) newValue).getExperimentEditor()));
                        }

                        //register GTSEditor in GTSEditorRibbonTab's
                        if (newValue instanceof GTSEditorTab) {
                            ((GTSEditorTab) newValue).getRibbonTabs().stream()
                                    .filter(rt -> rt instanceof GTSEditorRibbonTab)
                                    .forEach(rt -> ((GTSEditorRibbonTab) rt).setActiveGtsEditor(
                                            ((GTSEditorTab) newValue).getGtsEditor()));
                        }


                        //register SliceController in SliceRibbonTab's
                        if (newValue instanceof ExperimentViewControllerTab) {
                            ((ExperimentViewControllerTab) newValue).getRibbonTabs().stream()
                                    .filter(rt -> rt instanceof SliceRibbonTab)
                                    .forEach(rt -> ((SliceRibbonTab) rt).setActiveExperimentViewController(
                                            ((ExperimentViewControllerTab) newValue).getExperimentViewController()));
                        }

                        ribbonBar.getSelectionModel().selectFirst();
                        ribbonBar.getTabs().addAll(((RibbonEnabled) newValue).getRibbonTabs());

                        Tab tabToActivate = ((RibbonEnabled) newValue).getInitialTabToActivate();
                        ribbonBar.getSelectionModel().select(tabToActivate);


                    } else {
                        ribbonBar.getSelectionModel().select(0); //select general tab
                    }

                    fixRibbonBar();
                }
        );

        //initialize progress indicator
        javaFXTaskThread.busyProperty().addListener((observableValue, oldValue, newValue) -> callsStatusIndicator.setStatus(
                newValue ? TaskStatusIndicator.Status.BUSY : TaskStatusIndicator.Status.SUCCESS));
        callsStatusIndicator.setOnMouseClicked(mouseEvent -> onOpenCallOverviewAction());

        //request only slices but don't resolve all of them yet
        final ListSlicesTask listSlicesTask = hltf.listSlices();

        listSlicesTask.addCallback((task, taskExecution, state) -> {
            if (taskExecution.getState() == TaskExecution.TaskState.FAILED) {
                Platform.runLater(() -> JFDialogs.create()
                        .masthead("Information call failed")
                        .message("jFed could not successfully execute a basic information call to your slice authority. " +
                                "Other calls will fail most likely too.\n\n" +
                                "This probably indicates an authentication problem. Has your certificate been revoked? Certain actions " +
                                "(like changing your password at your authority) will invalidate all issued certificates.\n\n" +
                                "Review the call log for more information.")
                        .showError());
            }
        });

        taskThread.addTask(listSlicesTask);

        //If emulab-user: contact emulab-registry

        if (Objects.equals(geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer().getDefaultComponentManagerUrn(), "urn:publicid:IDN+emulab.net+authority+cm")) {
            //HACK to treat emulab different
            be.iminds.ilabt.jfed.highlevel.controller.Task t =
                    hltf.emulabRegistry(geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer());
            taskThread.addTask(t);
        }

        updateConnectivityStatusLabel();
    }

    private Window getDialogOwnerWindow() {
        if (getRoot().getScene() != null)
            return getRoot().getScene().getWindow();
        else
            return null;
    }

    private void fetchAndRecoverSlice(String sliceUrnParam) {
        final GeniUrn sliceUrn = GeniUrn.parse(sliceUrnParam);
        if (sliceUrn == null) {
            LOG.error("Could not parse '{}' into a valid slice", sliceUrnParam);


            JFDialogs.create()
                    .owner(getDialogOwnerWindow())
                    .message(String.format("jFed was unable to recover experiment '%s' because the URN cannot be parsed properly. Please verify your input.", sliceUrnParam))
                    .masthead("Experiment recovery failed").showError();
            return;

        } else if (!Objects.equals("slice", sliceUrn.getEncodedResourceType())) {
            LOG.error("The given URN '{}' does not have the resource type 'slice'!", sliceUrn.toString());
            JFDialogs.create()
                    .owner(getDialogOwnerWindow())
                    .message(String.format("jFed was unable to recover experiment '%s' because the URN has an incorrect resource type: only the resource type 'slice' is supported! Please verify your input.", sliceUrnParam))
                    .masthead("Experiment recovery failed").showError();
            return;
        } else if (!Objects.equals(sliceUrn.getEncodedTopLevelAuthority_withoutSubAuth(), geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer().getDefaultComponentManagerAsGeniUrn().getEncodedTopLevelAuthority_withoutSubAuth())) {

            LOG.error("Invalid slice {} to recover: authority doesn't match user authority", sliceUrn.toString());
            JFDialogs.create()
                    .owner(getDialogOwnerWindow())
                    .message(String.format("You can only open experiments that were shared on your own authority '%s'",
                            geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer().getName()))
                    .masthead("Error while recovering shared experiment")
                    .title("Error while recovering shared experiment")
                    .showError();
            return;
        }


        Task<Slice> fetchAndRecoverSliceTask = new Task<Slice>() {
            @Override
            protected Slice call() throws Exception {
                updateProgress(-1, 0);
                updateMessage("Fetching available slices");

                final ListSlicesTask refreshSlicesTask = hltf.listSlices();

                TaskExecution taskExecution = taskThread.addTask(refreshSlicesTask);

                while (!taskExecution.isCompleted()) {
                    Thread.sleep(100);
                }

                return sfaModel.getSlice(sliceUrn);
            }

            @Override
            protected void succeeded() {
                Slice slice = getValue();

                if (slice == null) {
                    LOG.warn("Could not retrieve '{}' as one of the slices of the current user!", sliceUrn);


                    Alert alert = new Alert(Alert.AlertType.WARNING);
                    alert.initOwner(getDialogOwnerWindow());
                    alert.setTitle("Slice recovery error");
                    alert.setHeaderText("Could not retrieve slice");
                    alert.setContentText(String.format("jFed was unable to recover slice '%s'. Please check if the slice exists, hasn't expired, and if you have access to this slice.", sliceUrn));

                    ButtonType tryAnywayButtonType = new ButtonType("Try anyway", ButtonBar.ButtonData.OTHER);

                    alert.getButtonTypes().setAll(ButtonType.OK, tryAnywayButtonType);

                    Optional<ButtonType> result = alert.showAndWait();
                    if (!result.isPresent() || result.get() != tryAnywayButtonType)
                        return;
                }

                RecoverSliceTask recoverSliceTask = tasksFactory.createRecoverSliceTask(slice);

                submitTask(recoverSliceTask);
            }
        };

        submitTask(fetchAndRecoverSliceTask);
    }


    public void setSecondaryStatus(String status) {
        rightStatus.setText(status);
    }

    @FXML
    private void onOpenCallOverviewAction() {
        tasksWindowProvider.get().showTasks();
    }

    @FXML
    public void onOpenConnectivityTestAction() {
        connectivityTesterDialogProvider.get().showDialog();
    }


    public void updateConnectivityStatusLabel() {
        if (connectivityLabel == null) //the GUI hasn't been injected yet by JavaFX. We can safely ignore this update request
            return;

        assert connectivityLabelIcon != null;
        assert pageantLabel != null;

        String jFedProxy = jFedPreferences.getString(CorePreferenceKey.PREF_SSHPROXY_USE_FOR_JFED);
        String sshProxy = jFedPreferences.getString(CorePreferenceKey.PREF_SSHPROXY_USE_FOR_SSH);
        if (jFedProxy == null)
            jFedProxy = ProxyPreferencesManager.NEVER_USE_PROXY;
        if (sshProxy == null)
            sshProxy = ProxyPreferencesManager.NEVER_USE_PROXY;


        String statusText;
        Color color;

        if (Objects.equals(jFedProxy, ProxyPreferencesManager.NEVER_USE_PROXY) &&
                Objects.equals(sshProxy, ProxyPreferencesManager.NEVER_USE_PROXY)) {
            statusText = "Direct connection";
            color = Color.LAWNGREEN;
        } else if (Objects.equals(jFedProxy, ProxyPreferencesManager.ALWAYS_USE_PROXY) &&
                Objects.equals(sshProxy, ProxyPreferencesManager.ALWAYS_USE_PROXY)) {
            statusText = "Proxy fully enabled";
            color = Color.AQUA;
        } else if (Objects.equals(jFedProxy, ProxyPreferencesManager.ALWAYS_USE_PROXY) &&
                Objects.equals(sshProxy, ProxyPreferencesManager.NEVER_USE_PROXY)) {
            statusText = "Proxy enabled for jFed only";
            color = Color.DARKORANGE;
        } else {
            statusText = "Proxy enabled for SSH-sessions only";
            color = Color.YELLOW;
        }

        connectivityLabel.setText(statusText);
        connectivityLabelIcon.setColor(color);

        pageantLabel.setVisible(jFedPreferences.getBoolean(GuiPreferenceKey.PREF_SSHAGENT_USE, false));

    }


    @Override
    @Subscribe
    public void submitTask(Task<?> task) {
        tasksProgressIndicator.followTaskProgress(task);
        executorService.submit(task);
    }


    /**
     * Works around bug in Java8u20. See   FEDIBBTDEV-1046
     */
    private void fixRibbonBar() {
        List<Tab> tabs = new ArrayList<>(ribbonBar.getTabs());
        Tab activeTab = ribbonBar.getSelectionModel().getSelectedItem();
        ribbonBar.getTabs().clear();

        RibbonBar newRibbonBar = new RibbonBar();
        newRibbonBar.getTabs().addAll(tabs);
        newRibbonBar.getSelectionModel().select(activeTab);

        root.setTop(newRibbonBar);
        this.ribbonBar = newRibbonBar;


    }

    public BorderPane getRoot() {
        return root;
    }

    @Override
    public void setStatus(String status) {
        setSecondaryStatus(status);
    }

    @Override
    public void addExperimentEditorTab(ExperimentEditorTab experimentEditorTab) {
        tabPane.getTabs().add(experimentEditorTab);
    }

    @Override
    public void showExperimentEditorTab(ExperimentEditorTab experimentEditorTab) {
        tabPane.getSelectionModel().select(experimentEditorTab);
    }

    @Override
    public List<ExperimentEditorTab> getExperimentEditorTabs() {
        return tabPane.getTabs().stream()
                .filter(tab -> tab instanceof ExperimentEditorTab)
                .map(tab -> (ExperimentEditorTab) tab)
                .collect(Collectors.toList());
    }

    @Override
    public void addGTSEditorTab(GTSEditorTab gtsEditorTab) {
        tabPane.getTabs().add(gtsEditorTab);
    }

    @Override
    public void showGTSEditorTab(GTSEditorTab gtsEditorTab) {
        tabPane.getSelectionModel().select(gtsEditorTab);
    }

    @Override
    public List<GTSEditorTab> getGTSEditorTabs() {
        return tabPane.getTabs().stream()
                .filter(tab -> tab instanceof GTSEditorTab)
                .map(tab -> (GTSEditorTab) tab)
                .collect(Collectors.toList());
    }

    public ObservableList<Tab> getTabs() {
        return tabPane.getTabs();
    }

    @Subscribe
    public void startExperiment(StartExperimentEvent event) {
        submitTask(new CreateExperimentTask(event));
    }


    private static final double FETCH_USERSPEC_PROGRESS = 0.7;
    private static final double FETCH_GETVERSION_PROGRESS = 0.85;

    public class CreateExperimentTask extends Task<Experiment> {
        private final StartExperimentEvent event;
        private GeniUrn sliceUrn;

        private CreateExperimentTask(StartExperimentEvent event) {
            this.event = event;

            updateTitle("Starting experiment '" + event.getSliceName() + "'");
        }


        @Override
        protected Experiment call() throws Exception {
            List<UserSpec> userSpecs = null;
            if (event.isIncludeSshKeys() && event.getSubAuthName() != null) {
                ProjectMemberSshKeysTask task = hltf.getProjectMembersSSHKeys(event.getSubAuthName());

                LOG.debug("CreateExperimentTask.call: ProjectMemberSshKeysTask.newExecutionNeeded()="+task.newExecutionNeeded());
                if (task.newExecutionNeeded()) {
                    LOG.info("Requesting project member info before continuing");
                    updateMessage("Requesting project members info");

                    TaskExecution<ProjectMemberSshKeysTask> taskExecution = taskThread.addTask(task);

                    while (!taskExecution.isCompleted()) {
                        if (task.getTotalMembersCount() != -1) {
                            updateMessage(String.format("Requesting project members info (%d/%d)",
                                    task.getMemberInfoRequested(), task.getTotalMembersCount()));
                            updateProgress((FETCH_USERSPEC_PROGRESS * task.getMemberInfoRequested()) / task.getTotalMembersCount(), 1.0);
                        }

                        Thread.sleep(250);
                    }
                }

                LOG.debug("CreateExperimentTask.call:" +
                        " ProjectMemberSshKeysTask.getTotalMembersCount()="+task.getTotalMembersCount()+
                        " ProjectMemberSshKeysTask.getTotalKeyCount()="+task.getTotalKeyCount());

                userSpecs = UserSpecHelper.getProjectMemberUserSpecs(event.getSubAuthName(), sfaModel);

                LOG.debug("CreateExperimentTask.call: userSpecs.size()={}", userSpecs.size());
            }
            updateMessage("Processing");

            LOG.debug("Starting experiment {} in sub-auth {} (expiration {})", event.getSliceName(), event.getSubAuthName(),
                    ZonedDateTime.ofInstant(event.getExpirationTime(), ZoneId.systemDefault()));


            final Server userAuthorityServer = geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer();
            assert userAuthorityServer != null;
            String topLevelAuthority =
                    userAuthorityServer.getDefaultComponentManagerAsGeniUrn().getEncodedTopLevelAuthority();
            if (event.getSubAuthName() != null) {
                topLevelAuthority += ":" + event.getSubAuthName();
            }
            //derive the expected sliceUrn
            sliceUrn = GeniUrn.createGeniUrnFromEncodedParts(topLevelAuthority, "slice", event.getSliceName());

            Slice slice = sfaModel.getSlice(sliceUrn);

            if (slice == null) {
                //slice does not exist yet

                //check if we are overwriting an existing backup, and warn the user

                //a taskexecution for this task has already been requested in the constructor. It should finish soon!
                while (!getVersionTask.isKnown()) {
                    updateMessage("Retrieving slice authority information");
                    updateProgress(FETCH_GETVERSION_PROGRESS, 1.0);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException ignored) {
                    }
                }

                if (!getVersionTask.isSupportSliverRegistration() && ExperimentRestoreInformation.exists(sliceUrn)) {
                    ExperimentRestoreInformation sri = new ExperimentRestoreInformation(sliceUrn);
                    if (sri.getExpirationTime() != null && sri.getExpirationTime().isAfter(Instant.now())) {

                        FutureTask<Boolean> warnForRecoveryFileOverwriteTask = new FutureTask<>(() -> {
                            Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
                            alert.setContentText(String.format("There currently exists a recovery file for slice %s. " +
                                    "By re-using this slice, this backup will be overwritten. " +
                                    "Are you sure you want to continue?", event.getSliceName()));
                            alert.setHeaderText("Slice Recovery Data");
                            alert.setTitle("Slice Recovery Data");
                            return alert.showAndWait().filter(r -> r == ButtonType.YES).isPresent();
                        }
                        );
                        Platform.runLater(warnForRecoveryFileOverwriteTask);
                        if (warnForRecoveryFileOverwriteTask.get())
                            return null;
                    }
                }

                return new Experiment(event.getSliceName(), event.getSubAuthName(), event.getRequestRspecSource(),
                        event.getStartTime(), event.getExpirationTime(), userSpecs, event.isRequestSla());
            } else {
                //first reset the slice to prevent old state from seeping through
                slice = sfaModel.resetSlice(sliceUrn);
                return new Experiment(slice, event.getRequestRspecSource(), event.getStartTime(),
                        event.getExpirationTime(), userSpecs, null, event.isRequestSla());
            }
        }

        @Override
        protected void succeeded() {
            final Experiment experiment = getValue();

            if (experiment != null) {
                final ExperimentViewController experimentViewController =
                        experimentViewControllerFactory.createExperimentViewController(experiment);

                experimentViewControllers.put(sliceUrn, experimentViewController);
            } else {
                LOG.debug("No experiment to visualize");
            }
        }

        public <T extends be.iminds.ilabt.jfed.highlevel.controller.Task> TaskExecution<T> submitTaskAndWait(T task) throws InterruptedException {
            CountDownLatch latch = new CountDownLatch(1);

            TaskExecution<T> taskExecution = taskThread.addTask(task, (t, te, state) -> latch.countDown());

            latch.await();

            return taskExecution;
        }
    }

    @Subscribe
    public void fetchAndRecoverSlice(RecoverSliceEvent event) {
        fetchAndRecoverSlice(event.getSliceUrn());
    }

    @Subscribe
    public void recoverExperiment(RecoverExperimentEvent event) {
        assert event.getExperiment().getSlice() != null;
        experimentViewControllers.put(event.getExperiment().getSlice().getUrn(),
                experimentViewControllerFactory.createExperimentViewController(event.getExperiment()));
    }

    @Subscribe
    public void openRequestRspec(OpenRequestRspecFromUrlEvent event) {
        try {
            OpenRequestRspecTask openRequestRspecTask =
                    tasksFactory.createOpenRequestRspecTask(getDialogOwnerWindow(), event.getUrlString());
            submitTask(openRequestRspecTask);
        } catch (IOException e) {
            LOG.error("Could not open request rspec '{}'", event.getUrlString(), e);
            new Alert(Alert.AlertType.ERROR, "The RSpec '" + event.getUrlString() + "' could not be opened.").showAndWait();

        }
    }

    @Subscribe
    public void openRequestRspec(OpenRequestRspecFromFileEvent event) {
        try {
            OpenRequestRspecTask readFileTask =
                    tasksFactory.createOpenRequestRspecTask(getDialogOwnerWindow(), event.getFile());
            submitTask(readFileTask);
        } catch (FileNotFoundException e) {
            new Alert(Alert.AlertType.ERROR, "The RSpec-file '" + event.getFile().getAbsolutePath() + "' could not be opened.").showAndWait();
        }
    }

    @Subscribe
    public void openRequestRspec(OpenRequestRspecFromInputStreamEvent event) {
        OpenRequestRspecTask readFileTask =
                tasksFactory.createOpenRequestRspecTask(getDialogOwnerWindow(), event.getInputStream(), event.getLength(), event.getFileName(), null);
        submitTask(readFileTask);
    }

    @Subscribe
    public void openTutorial(OpenTutorialFromUrlEvent event) {
        try {
            OpenTutorialTask openTutorialTask =
                    tasksFactory.createOpenTutorialTask(getDialogOwnerWindow(), event.getUrlString());
            submitTask(openTutorialTask);
        } catch (IOException ex) {
            LOG.error("Could not open tutorial '{}'", event.getUrlString(), ex);
            new Alert(Alert.AlertType.ERROR, "The tutorial '" + event.getUrlString() + "' could not be opened.").showAndWait();
        }
    }

    /**
     * Give open tabs the opportunity to show warning dialogs.
     *
     * @return true if stage may be closed, false if it should be cancelled.
     */
    public boolean onStageCloseRequest() {
        List<Tab> tabsCopy = new ArrayList<>(tabPane.getTabs());

        for (Tab tab : tabsCopy) {
            if (tab instanceof AskOnCloseRequest) {
                tabPane.getSelectionModel().select(tab);

                if (!((AskOnCloseRequest) tab).onTabCloseRequest()) {
                    return false;
                }
            }
        }
        return true;
    }

    public TriggeredMessageHandler getTriggeredMessageHandler() {
        return triggeredMessageHandler;
    }
}
