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

import be.iminds.ilabt.jfed.experiment.*;
import be.iminds.ilabt.jfed.experiment.events.ExperimentPartsEvent;
import be.iminds.ilabt.jfed.experiment.tasks.ExperimentTaskStatus;
import be.iminds.ilabt.jfed.experiment.util.ExperimentUtils;
import be.iminds.ilabt.jfed.experiment.util.NextExperimentExpiration;
import be.iminds.ilabt.jfed.experimenter_gui.call_gui.TasksWindow;
import be.iminds.ilabt.jfed.experimenter_gui.config.JFedGuiConfig;
import be.iminds.ilabt.jfed.experimenter_gui.slice.ansible.AnsibleOutputTab;
import be.iminds.ilabt.jfed.experimenter_gui.slice.errors.ErrorDialogs;
import be.iminds.ilabt.jfed.experimenter_gui.slice.errors.ErrorsView;
import be.iminds.ilabt.jfed.experimenter_gui.slice.errors.ErrorsViewTab;
import be.iminds.ilabt.jfed.experimenter_gui.slice.events.RenewExperimentEvent;
import be.iminds.ilabt.jfed.experimenter_gui.slice.jobs.ExperimenterJobFactory;
import be.iminds.ilabt.jfed.experimenter_gui.slice.progress.ProgressItem;
import be.iminds.ilabt.jfed.experimenter_gui.slice.progress.ProgressView;
import be.iminds.ilabt.jfed.experimenter_gui.slice.progress.ProgressViewTab;
import be.iminds.ilabt.jfed.experimenter_gui.slice.raw.RawSliceView;
import be.iminds.ilabt.jfed.experimenter_gui.slice.scp.SCPTab;
import be.iminds.ilabt.jfed.experimenter_gui.slice.scp.SCPTask;
import be.iminds.ilabt.jfed.experimenter_gui.timeline.view.TimelineView;
import be.iminds.ilabt.jfed.experimenter_gui.ui.status.TaskStatusIndicator;
import be.iminds.ilabt.jfed.experimenter_gui.util.BrowserUtil;
import be.iminds.ilabt.jfed.highlevel.controller.Task;
import be.iminds.ilabt.jfed.highlevel.jobs.*;
import be.iminds.ilabt.jfed.highlevel.jobs.states.ExecuteAnsibleServicesState;
import be.iminds.ilabt.jfed.preferences.GuiPreferenceKey;
import be.iminds.ilabt.jfed.preferences.JFedGuiPreferences;
import be.iminds.ilabt.jfed.rspec.rspec_source.ManifestRspecSource;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
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.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.value.ObservableObjectValue;
import javafx.beans.value.ObservableStringValue;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.inject.Provider;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 1/30/2015
 * Time: 14:06
 */
public class ExperimentViewController extends SplitPane implements ExperimentChangeListener, ExperimentControllerListener, DetailsTabHost {
    private static final Logger LOG = LoggerFactory.getLogger(ExperimentViewController.class);

    private static final ListeningExecutorService jobExecutorService =
            MoreExecutors.listeningDecorator(Executors.newCachedThreadPool(
                    new ThreadFactoryBuilder().setNameFormat("ViewJobExecutor-%d").build()));

    private final TasksWindow tasksWindow;
    private final JFedGuiPreferences jFedPreferences;
    private final JFedGuiConfig jFedGuiConfig;
    private final EventBus eventBus;
    private final ProgressItemCustomDetailsViews progressItemCustomViews;
    private final BrowserUtil browserUtil;
    private final ExperimenterJobFactory experimenterJobFactory;

    /*
    The different main views
     */
    private final ModelSliceView modelSliceView;
    private final RawSliceView rawSliceView;
    private final TimelineView timelineView;

    /*
    Details TabPane + the default tabs
    */
    private final TabPane detailsTabPane;

    private final ProgressView progressView;
    private final ErrorsView errorsView;

    private final ErrorDialogs errorDialogs;

    private final ExperimentStatusProperty experimentStatusProperty;
    private final ExpirationNotifier expirationNotifier;

    private final ExperimentController experimentController;

    /*
    The variables which represent the state that must be visualised
     */
    private final Experiment experiment;
    /**
     * This is the request rspec that is used to visualise the experiment in the topology view.
     * This differs from the normal requestRspecSource in that it also contains all the nodes from the already active
     * slivers in the slice.
     */
    private final ManifestRspecSource canvasRspecSource;

    private SCPTab scpTab = null;


    private final InvalidationListener sceneInvalidationListener = new InvalidationListener() {
        @Override
        public void invalidated(Observable observable) {
            if (getScene() != null)
                experimentController.setParentWindow(getScene().getWindow());
            else
                experimentController.setParentWindow(null);
        }
    };

    public ExperimentViewController(ExperimentController experimentController, ExperimentViewsFactory experimentViewsFactory,
                                    Provider<ErrorsView> errorsViewProvider,
                                    ErrorDialogs errorDialogs,
                                    TasksWindow tasksWindow, JFedGuiPreferences jFedPreferences,
                                    JFedGuiConfig jFedGuiConfig,
                                    EventBus eventBus, ProgressItemCustomDetailsViews progressItemCustomViews,
                                    BrowserUtil browserUtil, ExperimenterJobFactory experimenterJobFactory) {
        this.experimentController = experimentController;
        this.progressItemCustomViews = progressItemCustomViews;
        this.browserUtil = browserUtil;
        this.experimenterJobFactory = experimenterJobFactory;
        this.experiment = experimentController.getExperiment();

        this.experimentStatusProperty = new ExperimentStatusProperty(experiment);
        this.expirationNotifier = new ExpirationNotifier(this, jFedPreferences, eventBus);
        this.eventBus = eventBus;

        this.errorDialogs = errorDialogs;
        this.tasksWindow = tasksWindow;
        this.jFedPreferences = jFedPreferences;
        this.jFedGuiConfig = jFedGuiConfig;

        eventBus.register(this);

        experimentController.addListener(this);
        experiment.addExperimentChangeListener(this);

        experimentController.addEventHandler(ExperimentPartsEvent.STOPPED, this::onExperimentPartsStoppedHandler);


        this.canvasRspecSource =
                ExperimentUtils.mergeNewRequestRspecWithExistingManifestRspec(experiment.getNewRequestRspecSource(), experiment.getSlice());

        sceneProperty().addListener(new WeakInvalidationListener(sceneInvalidationListener));

        //initialize detailsTabPane. This must be done BEFORE initializing the ControllerUI, as it adds tabs to this detailsPane
        detailsTabPane = new TabPane();
        detailsTabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.ALL_TABS);

        //add progressview and errorsview to detailsTabPane
        this.progressView = new ProgressView();
        this.errorsView = errorsViewProvider.get();
        detailsTabPane.getTabs().addAll(new ProgressViewTab(progressView), new ErrorsViewTab(errorsView));

        //initialize the views
        this.modelSliceView = experimentViewsFactory.createModelSliceView(experimentController, canvasRspecSource);
        this.rawSliceView = experimentViewsFactory.createRawSliceView(experiment, canvasRspecSource);
        this.timelineView = experimentViewsFactory.createControllerUi(experiment, this);

        ScrollPane detailsScrollPane = new ScrollPane();
        detailsScrollPane.setPrefHeight(100);
        detailsScrollPane.setContent(detailsTabPane);
        detailsScrollPane.setFitToHeight(true);
        detailsScrollPane.setFitToWidth(true);

        //configure SplitPane
        setOrientation(Orientation.VERTICAL);
        getItems().addAll(new Pane(), detailsScrollPane);
        setDividerPosition(0, 0.7);


        //now that the view has fully initialized and registered itself to the ExperimentController, we can start the latter
        experimentController.start();
    }

    private void onExperimentPartsStoppedHandler(ExperimentPartsEvent event) {


        //check if we have to ask for QOE reputation score
        List<SfaExperimentPart> sfaParts = event.getParts()
                .stream()
                .filter(part -> part instanceof SfaExperimentPart)
                .map(SfaExperimentPart.class::cast)
                .collect(Collectors.toList());

        if (!sfaParts.isEmpty() && jFedGuiConfig.isQOEReportingEnabled())
            submitJob(experimenterJobFactory.createQoeJob(experimentController.getExperiment(), sfaParts));
    }

    private <V> ListenableFuture<V> submitJob(Job<V> job) {
        onJobSubmitted(job);

        job.stateProperty().addListener(observable -> onJobStateChanged(job, job.getState()));

        return jobExecutorService.submit(() -> {
            try {
                V result = job.call();

                onJobFinished(job);

                return result;
            } catch (Exception ex) {
                LOG.error("Error while executing job '{}': {}", job.getName(), ex.getMessage(), ex);
                throw ex;
            }
        });
    }


    @Override
    public void onExperimentStateChange(ExperimentState newExperimentState) {
        if (newExperimentState == ExperimentState.CREATED) {
            Platform.runLater(this::checkAndWarnForExpiringResources);
        } else if (newExperimentState == ExperimentState.PROVISIONING) {
            Platform.runLater(this::showSliversInitializationDialog);
        } else if (newExperimentState == ExperimentState.FAILING) {
            Platform.runLater(this::showFailedDialog);
        }
    }

    @Override
    public void onExperimentPartAdded(ExperimentPart experimentPart) {

    }

    private void checkAndWarnForExpiringResources() {

        if (jFedPreferences.isRecoverExpirationWarningEnabled()
                &&         /*   only warn if we are recovering a slice! */
                experiment.getNewRequestRspecSource() == null) {

            NextExperimentExpiration nextExperimentExpiration = new NextExperimentExpiration(experiment);
            if (nextExperimentExpiration.getFirstExpirationTime() != null) {

                Duration durationToNextExpiration =
                        Duration.between(Instant.now(), nextExperimentExpiration.getFirstExpirationTime());
                if (durationToNextExpiration.toMinutes() < (long) jFedPreferences.getRecoverExpirationWarningTime()) {

                    LOG.debug("Showing warning dialog about imminent expiration of recovering experiment {}", experiment.getName());

                    ButtonType renewNewButtonType = new ButtonType("Renew now", ButtonBar.ButtonData.OK_DONE);
                    ButtonType ignoreButtonType = new ButtonType("Ignore", ButtonBar.ButtonData.CANCEL_CLOSE);


                    Alert alert =
                            new Alert(Alert.AlertType.WARNING,
                                    String.format("The experiment %s is expiring in less then %d minutes.\n" +
                                                    "Do you want to renew your experiment now?",
                                            experiment.getName(), durationToNextExpiration.toMinutes() + 1)
                                    , renewNewButtonType, ignoreButtonType);
                    alert.setHeaderText("Experiment is expiring soon");

                    alert.showAndWait().filter(buttonType -> buttonType == renewNewButtonType).ifPresent(buttonType -> eventBus.post(new RenewExperimentEvent(getScene().getWindow(), getExperimentController())));

                } else {

                    LOG.debug("Recovering experiment {} expires in {}, while treshold is on {} minutes. Not showing warning dialog.",
                            experiment.getName(), durationToNextExpiration.toMinutes(), jFedPreferences.getRecoverExpirationWarningTime());
                }
            }
        }
    }

    private void showFailedDialog() {
        LOG.debug("Showing failed dialog");


        DialogPane dialogPane = new DialogPane();
        dialogPane.setHeaderText("Experiment failed");
        dialogPane.setContentText("One or more resources in your experiment failed.\n\n" +
                "It is advised to delete all requested resources from your experiment now." +
                "\n\nDo you want do delete all your resources?");
        dialogPane.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.PLAY, Color.GREEN));

        dialogPane.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);


        Dialog<ButtonType> dialog = new Dialog<>();
        dialog.setTitle("Experiment run is initializing");
        dialog.setDialogPane(dialogPane);

        if (dialog.showAndWait().orElse(ButtonType.YES) == ButtonType.YES) {

            //terminate the experiment to actively delete all resources
            experimentController.stop();
        }

        experiment.setExperimentState(ExperimentState.FAILED);
    }


    @Override
    public void addDetailsTab(Tab tab) {
        detailsTabPane.getTabs().add(tab);
    }

    @Override
    public void removeDetailsTab(Tab tab) {
        detailsTabPane.getTabs().remove(tab);
    }

    @Override
    public boolean containsDetailsTab(Tab tab) {
        return detailsTabPane.getTabs().contains(tab);
    }

    @Override
    public void selectDetailsTab(Tab tab) {
        assert (detailsTabPane.getTabs().contains(tab));
        detailsTabPane.getSelectionModel().select(tab);
    }

    private Node getMainView() {
        return getItems().get(0);
    }

    public boolean switchToModelView() {
        setMainView(modelSliceView);
        return true;

    }

    public boolean switchToRawView() {
        setMainView(rawSliceView);
        return true;

    }

    public boolean switchToExperimentController() {
        setMainView(timelineView);
        return true;
    }

    /**
     * This method sets the element in the upper part of the splitpane
     *
     * @param node New item to be set as main view
     */
    private void setMainView(@Nonnull Node node) {
        Node oldNode = getItems().isEmpty() ? null : getItems().get(0);

        double[] oldDividerPostions = getDividerPositions();
        getItems().set(0, node);

        node.setVisible(true);
        if (oldNode != null)
            oldNode.setVisible(false);

        //FEDIBBTDEV-1006: force restoration of old  divider positions
        setDividerPositions(oldDividerPostions);
    }

    /*
    public ModelSliceView getModelSliceView() {
        return modelSliceView;
    }     */


    public TimelineView getTimelineView() {
        return timelineView;
    }

    public boolean isCanvasVisible() {
        return getMainView() == modelSliceView;
    }

    public boolean isControllerVisible() {
        return getMainView() == timelineView;
    }

    public boolean isRawRspecVisible() {
        return getMainView() == rawSliceView;
    }


    /**
     * Shows a mutable dialog with information about the slice initialization
     */
    public void showSliversInitializationDialog() {


        //show a (mutable) notification to mac-users that the terminal opens in the background
        if (jFedPreferences.getBoolean(GuiPreferenceKey.PREF_SHOW_SLICE_INITIALIZATION_DIALOG, true)) {
            LOG.debug("Showing slivers initialization dialog");

            VBox pane = new VBox();
            pane.setPrefWidth(400);
            pane.setMinHeight(300);
            pane.setSpacing(5);

            Label textLabel = new Label(
                    "It will take some time to start and initialize all nodes. " +
                            "You will be able to login by double-clicking or right-clicking on the node when it becomes green. " +
                            "Typically it takes about 3 minutes. " +
                            "Planetlab nodes take about 10-15 minutes before you can login through ssh (after they become green).\n\n" +
                            "If SSH-login does not work when the node is green, try again after some time.");
            textLabel.setMinHeight(200); //work around bug FEDIBBTDEV-1008
            textLabel.setWrapText(true);
            pane.getChildren().add(textLabel);
            final CheckBox checkBox = new CheckBox("Do not show this dialog again");
            pane.getChildren().add(checkBox);

            DialogPane dialogPane = new DialogPane();
            dialogPane.setHeaderText("Your experiment run is initializing...");
            dialogPane.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.PLAY, Color.GREEN));
            dialogPane.setContent(pane);
            dialogPane.getButtonTypes().setAll(ButtonType.OK);


            Dialog<ButtonType> dialog = new Dialog<>();
            dialog.setTitle("Experiment run is initializing");
            dialog.setDialogPane(dialogPane);

            dialog.showAndWait();

            jFedPreferences.setBoolean(GuiPreferenceKey.PREF_SHOW_SLICE_INITIALIZATION_DIALOG,
                    !checkBox.isSelected());
        } else {
            LOG.debug("Skipping slivers initializing dialog because user muted it before");
        }
    }


    public ExperimentController getExperimentController() {
        return experimentController;
    }


    @Override
    public void onJobSubmitted(Job<?> job) {
        if (job instanceof AbstractJob) {
            AbstractJob abstractJob = (AbstractJob) job;

            abstractJob.addTaskExecutionFinishedListener(errorsView);
            abstractJob.addTaskExecutionFinishedListener(errorDialogs);
        } else {
            LOG.warn("Unexpected job type: {}. Cannot listen for taskExecutions", job.getClass().getName());
        }
    }


    @Override
    public void onJobStateChanged(Job<?> job, State state) {

        LOG.debug("Received new State {} from job {} in ExperimentViewController of {}",
                state.getName(), job.getName(), experiment.getName());

        if ((state instanceof SlicedState)) {
            SlicedState<? extends StateSlice> slicedState = (SlicedState<? extends StateSlice>) state;

            for (StateSlice statefulThread : slicedState.getSlices()) {
                assert statefulThread.getState() == null : "Unexpected: Thread is already active!";
                statefulThread.stateProperty().addListener(observable -> onJobStateChanged(job, statefulThread.getState()));
            }
        } else {
            if (!(state instanceof AllocateExperimentJob.AllocatePartStateSlice.WaitForNextAllocateActionState)) {

                Platform.runLater(() -> progressView.registerProgressViewItem(new StateProgressItem(state)));
            }

        }

        if (state instanceof ExecuteAnsibleServicesState.ExecuteAnsibleServiceStateSlice.ExecuteAnsibleServiceState) {
            ExecuteAnsibleServicesState.ExecuteAnsibleServiceStateSlice.ExecuteAnsibleServiceState s =
                    (ExecuteAnsibleServicesState.ExecuteAnsibleServiceStateSlice.ExecuteAnsibleServiceState) state;
            Platform.runLater(() -> detailsTabPane.getTabs().add(new AnsibleOutputTab(s.getNode(), s.getAnsibleService(),
                    s.getGalaxyInputStream(), s.getGalaxyErrorStream(),
                    s.getPlaybookInputStream(), s.getPlaybookErrorStream())));

        }

    }

    private final class StateProgressItem extends ProgressItem {
        private final State state;

        private StateProgressItem(State state) {
            this.state = state;

            setText(state.getName());
            progressProperty().bind(new StatusBinding(state.statusProperty()));
            statusProperty().bind(state.messageProperty());

            final CustomStateDetailsView customDetailsView =
                    progressItemCustomViews.getCustomDetailsView(state);
            if (customDetailsView != null) {
                //noinspection unchecked
                setOnDetailsView(event -> customDetailsView.showCustomDetailsView(state));

            } else if (state instanceof SingleCallState) {
                SingleCallState singleCallState = (SingleCallState) state;
                setOnDetailsView(event -> {
                    if (singleCallState.getTaskExecution() != null) {
                        tasksWindow.showTaskExecution(singleCallState.getTaskExecution());
                    }
                });
            } else if (state instanceof MultipleCallState) {
                MultipleCallState<? extends Task> multipleCallState = (MultipleCallState<? extends Task>) state;
                setOnDetailsView(event -> multipleCallState.getTaskExecutions().values().stream()
                        .filter(Objects::nonNull)
                        .findFirst()
                        .ifPresent(tasksWindow::showTaskExecution));
            }
        }

    }

    private static class StatusBinding extends ObjectBinding<TaskStatusIndicator.Status> {

        private final ObservableObjectValue<ExperimentTaskStatus> statusProperty;

        private StatusBinding(ObservableObjectValue<ExperimentTaskStatus> statusProperty) {
            bind(statusProperty);
            this.statusProperty = statusProperty;
        }

        @Override
        protected TaskStatusIndicator.Status computeValue() {
            switch (statusProperty.get()) {
                case INACTIVE:
                    return TaskStatusIndicator.Status.INACTIVE;
                case BUSY:
                    return TaskStatusIndicator.Status.BUSY;
                case SUCCESS:
                    return TaskStatusIndicator.Status.SUCCESS;
                case FAILED:
                    return TaskStatusIndicator.Status.FAILED;
                case WARNING:
                    return TaskStatusIndicator.Status.WARNING;
            }
            throw new IllegalArgumentException("Unknown state");
        }
    }

    @Override
    public void onJobFinished(Job<?> job) {

        if (job instanceof OpenConsoleJob) {
            OpenConsoleJob openConsoleJob = (OpenConsoleJob) job;
            if (openConsoleJob.getResult() != null) {
                browserUtil.openUrlInBrowser(openConsoleJob.getResult());
            }
        }

        if (job instanceof AllocateExperimentJob) {
            AllocateExperimentJob allocateExperimentJob = (AllocateExperimentJob) job;

            if (allocateExperimentJob.getFailedResources() != null) {
                showReservationFailedDialog(allocateExperimentJob.getFailedResources());
            }
        }

        if (job instanceof RenewJob) {
            RenewJob renewJob = (RenewJob) job;

            if (renewJob.isRenewSliceFailed())
                showRenewSliceFailedDialog(experiment);
            else if (!renewJob.getFailedParts().isEmpty()) {
                showRenewSliversFailedDialog(renewJob.getExperiment(), renewJob.getFailedParts());
            }
        }

    }

    private void showRenewSliversFailedDialog(Experiment experiment, List<ExperimentPart> failedParts) {
        LOG.debug("Showing renew failed dialog");

        StringBuilder content = new StringBuilder();
        content.append("One or more resources in the experiment '").append(experiment.getName()).append("' could not be renewed on:\n");
        failedParts.forEach(geniUrn -> content.append("\t- ").append(geniUrn.toString()).append("\n"));
        content.append("\nWe strongly advise you to submit a bugreport, so that the testbed administrators can look into this issue.");


        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.setTitle("Renew failed");
        alert.setHeaderText("Renew failed");
        alert.setContentText(content.toString());
        alert.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.CLOCK_ALT, Color.ORANGERED));
        alert.showAndWait();
    }

    private void showRenewSliceFailedDialog(Experiment experiment) {
        LOG.debug("Showing renew slice failed dialog");

        String content = "The experiment container (slice) of '" + experiment.getName() + "' could not be renewed.\n" +
                "\nWe strongly advise you to submit a bugreport, so that the testbed administrators can look into this issue.";

        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.setTitle("Renew failed");
        alert.setHeaderText("Renew failed");
        alert.setContentText(content);
        alert.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.CLOCK_ALT, Color.ORANGERED));
        alert.showAndWait();
    }


    private void showReservationFailedDialog(Collection<GeniUrn> failedResources) {
        LOG.debug("Showing reservation failed dialog");


        StringBuilder content = new StringBuilder();
        content.append("The following resources could not be reserved:\n");
        failedResources.forEach(geniUrn -> content.append("\t- ").append(geniUrn.toString()).append("\n"));
        content.append("\nIt is advised to delete all requested resources from your experiment now.\n\n" +
                "Do you want do delete all your resources?");


        DialogPane dialogPane = new DialogPane();
        dialogPane.setHeaderText("Reservation failed");
        dialogPane.setContentText(content.toString());
        dialogPane.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.CALENDAR, Color.ORANGERED));

        dialogPane.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);


        Dialog<ButtonType> dialog = new Dialog<>();
        dialog.setTitle("Experiment run is initializing");
        dialog.setDialogPane(dialogPane);

        if (dialog.showAndWait().orElse(ButtonType.YES) == ButtonType.YES) {

            //terminate the experiment to actively delete all resources
            experimentController.stop();
        }

    }

    public Experiment getExperiment() {
        return experimentController.getExperiment();
    }

    public ObservableStringValue statusProperty() {
        return experimentStatusProperty;
    }

    public ModelSliceView getModelSliceView() {
        return modelSliceView;
    }

    public JFedGuiPreferences getjFedPreferences() {
        return jFedPreferences;
    }

    @Subscribe
    public void handleSCPTasks(SCPTask scpTask) {
        if (scpTask.getExperiment() != experiment)
            return;

        if (scpTab == null) {
            scpTab = new SCPTab();
            detailsTabPane.getTabs().add(scpTab);
            detailsTabPane.getSelectionModel().select(scpTab);
        }

        scpTab.registerSCPTask(scpTask);

        scpTask.setOnFailed(event ->
                JFDialogs.create()
                        .owner(this.getScene().getWindow())
                        .title("File transfer failed")
                        .masthead("Your file transfer encountered an error")
                        .message(event.getSource().getException().getMessage())
                        .showException(event.getSource().getException()));


    }
}
