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

import be.iminds.ilabt.jfed.experiment.Experiment;
import be.iminds.ilabt.jfed.experiment.util.ExperimentRestoreInformation;
import be.iminds.ilabt.jfed.experimenter_gui.dialogs.SelectAuthoritiesDialog;
import be.iminds.ilabt.jfed.experimenter_gui.events.RecoverExperimentEvent;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.highlevel.controller.TaskExecution;
import be.iminds.ilabt.jfed.highlevel.controller.TaskExecutionFinishedCallback;
import be.iminds.ilabt.jfed.highlevel.controller.TaskFinishedCallback;
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.tasks.AggregatesForSliceTask;
import be.iminds.ilabt.jfed.highlevel.tasks.HighLevelTaskFactory;
import be.iminds.ilabt.jfed.highlevel.tasks.SliceManifestForAuthorityTask;
import be.iminds.ilabt.jfed.highlevel.tasks.UserAuthorityGetVersionTask;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.authority.AuthorityFinder;
import be.iminds.ilabt.jfed.preferences.CorePreferenceKey;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.rspec.model.ModelRspec;
import be.iminds.ilabt.jfed.rspec.model.ModelRspecType;
import be.iminds.ilabt.jfed.rspec.model.StringRspec;
import be.iminds.ilabt.jfed.rspec.rspec_source.RequestRspecSource;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import be.iminds.ilabt.jfed.util.GeniUrn;
import com.google.common.eventbus.EventBus;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Provider;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * Recovers an existing slice. The method to do this is chosen automatically.
 * <p/>
 * This method might immediately return, starting the recovery in the background.
 */
public final class RecoverSliceTask extends Task<Slice> {
    private static final Logger LOG = LoggerFactory.getLogger(RecoverSliceTask.class);

    private static final double RECOVER_PROGRESS_DETERMINE_METHOD = 0.1;
    private static final double RECOVER_PROGRESS_RETRIEVING_SLIVER_INFO = 0.5;
    private static final double RECOVER_PROGRESS_RETRIEVED_SLIVER_INFO = 0.9;
    private static final double RECOVER_PROGRESS_RETRIEVING_SRI = 0.6;
    private static final double RECOVER_PROGRESS_ASKING_USER = 0.4;
    private static final double RECOVER_PROGRESS_ASKED_USER = 0.6;
    private static final double RECOVER_PROGRESS_RETRIEVED_MANIFESTS = 0.9;

    //private final ExperimenterGUI experimenterGUI;
    private final Stage ownerStage = null;
    private final GeniUrn sliceUrn;
    private Slice recoverSlice = null;

    //this variable is a workaround. Should not be needed anymore after implementing support for geni_start_time
    private Instant recoverReservationStartTime = null;


    private final TestbedInfoSource testbedInfoSource;
    private final AuthorityFinder authorityFinder;
    private final JFedPreferences jFedPreferences;
    private final SfaModel sfaModel;
    private final HighLevelTaskFactory hltf;
    private final TaskThread tt;
    private final EventBus eventBus;


    RecoverSliceTask(GeniUrn sliceUrn, SfaModel sfaModel, HighLevelTaskFactory hltf,
                     TestbedInfoSource testbedInfoSource,
                     AuthorityFinder authorityFinder,
                     JFedPreferences jFedPreferences,
                     TaskThread tt, EventBus eventBus, Provider<SelectAuthoritiesDialog> selectAuthoritiesDialogProvider) {

        this.sliceUrn = sliceUrn;
        this.sfaModel = sfaModel;
        this.hltf = hltf;
        this.testbedInfoSource = testbedInfoSource;
        this.authorityFinder = authorityFinder;
        this.jFedPreferences = jFedPreferences;
        this.tt = tt;
        this.eventBus = eventBus;
        this.selectAuthoritiesDialogProvider = selectAuthoritiesDialogProvider;

        updateTitle("Recovering experiment '" + sliceUrn.getEncodedResourceName() + "'");

    }

    RecoverSliceTask(Slice recoverSlice, SfaModel sfaModel, HighLevelTaskFactory hltf,
                     TestbedInfoSource testbedInfoSource,
                     AuthorityFinder authorityFinder,
                     JFedPreferences jFedPreferences,
                     TaskThread tt, EventBus eventBus, Provider<SelectAuthoritiesDialog> selectAuthoritiesDialogProvider) {
        this.sfaModel = sfaModel;
        this.hltf = hltf;
        this.testbedInfoSource = testbedInfoSource;
        this.authorityFinder = authorityFinder;
        this.jFedPreferences = jFedPreferences;
        this.tt = tt;
        this.eventBus = eventBus;
        this.selectAuthoritiesDialogProvider = selectAuthoritiesDialogProvider;
        this.sliceUrn = recoverSlice.getUrn();
        this.recoverSlice = recoverSlice;


        updateTitle("Recovering experiment '" + recoverSlice.getUrn().getEncodedResourceName() + "'");
    }

    @Override
    protected Slice call() {
        try {
            //do we already have the slice-object?   If not, check if we can retrieve it from the SA!
            if (recoverSlice == null) {
                updateMessage("Retrieving information from slice authority");

                recoverSlice = sfaModel.logExistSlice(sliceUrn);

                AggregatesForSliceTask getAggregatesForSliceTask =
                        hltf.getAggregatesForSlice(recoverSlice);

                final TaskExecution taskExecution = hltf.submitTask(getAggregatesForSliceTask);

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

                if (taskExecution.getState() == TaskExecution.TaskState.SUCCESS) {
                    selectRecoveryMethod();
                } else {
                    recoverSlice = null;
                    Platform.runLater(() -> {
                        JFDialogs d = JFDialogs.create()
                                .owner(ownerStage)
                                .title("Could not retrieve shared experiment")
                                .masthead("Could not retrieve shared experiment")
                                .message("The given experiment could not be found on the server. Please check the provided URN!");
                        //noinspection ThrowableResultOfMethodCallIgnored
                        if (LOG.isDebugEnabled() && taskExecution.getException() != null)
                            d.showException(taskExecution.getException());
                        d.showError();
                    });
                }
            } else
                selectRecoveryMethod();

            return recoverSlice;
        } catch (InterruptedException | ExecutionException ex) {
            LOG.error("Got an error while doing slice recovery", ex);
            return null;
        }
    }

    /**
     * Figure out which type of recovery should be used.
     * We can choose between three options:
     * <p/>
     * 1. Use the information available via SLICE_INFO in the SA
     * 2. Use the locally stored slice information
     * 3. Ask the user for slice information
     */
    private void selectRecoveryMethod() throws InterruptedException, ExecutionException {
        updateProgress(0, 1);
        updateMessage("Determining recovery method");
        //Try to find slice info at the SA. Works if use authority supports SLIVER_INFO
        final UserAuthorityGetVersionTask getVersionTask =
                hltf.getUserAuthorityGetVersionTask();
        if (!getVersionTask.isKnown()) {
            LOG.info("Recover: getVersion not known. Requesting.");
            TaskExecution taskExecution = hltf.submitTask(getVersionTask);
            while (!taskExecution.isCompleted())
                Thread.sleep(100);

        }
        selectRecoveryMethod(getVersionTask);
    }


    private void selectRecoveryMethod(UserAuthorityGetVersionTask getVersionTask) throws InterruptedException, ExecutionException {
        assert getVersionTask.isKnown();

        updateProgress(RECOVER_PROGRESS_DETERMINE_METHOD, 1);

        if (getVersionTask.isSupportSliverRegistration()) {
            LOG.info("Recover: getVersion is known, SLIVER_INFO supported");
            recoverSliceFromSliverInfo();
        } else if (ExperimentRestoreInformation.exists(recoverSlice.getUrn())) {
            LOG.info("Recover: getVersion is known, SLIVER_INFO not supported, but local recovery information available");
            recoverSliceFromExperimentRestoreInformation();
        } else {
            LOG.info("Recover: getVersion is known, SLIVER_INFO not supported and no local recovery information available");
            recoverSliceWithoutAuthoritiesInfo();
        }

    }

    private void recoverSliceFromExperimentRestoreInformation() {
        updateProgress(RECOVER_PROGRESS_RETRIEVING_SRI, 1);
        if (ExperimentRestoreInformation.exists(recoverSlice.getUrn())) {
            ExperimentRestoreInformation sri = new ExperimentRestoreInformation(recoverSlice.getUrn());

            if (sri.getExpirationTime() != null && !sri.getExpirationTime().isBefore(Instant.now())
                    && sri.getRequestRspec() != null && !sri.getRequestRspec().isEmpty()) {
                //we add the information from SliceRestoreInformation to the Slice-object
                RequestRspecSource requestRspecSource =
                        new RequestRspecSource(new StringRspec(sri.getRequestRspec()), ModelRspecType.FX);

                recoverSlice.setRequestRspec(tryMakeModelBasedRspecSource(requestRspecSource));

                recoverReservationStartTime = sri.getReservationStartTime();

                return;
            } else {
                LOG.info("Could only find expired restore information: {} vs {}. We won't use it!", sri.getExpirationTime(), Instant.now());
            }
        }
        //fallback to asking the user
        recoverSliceWithoutAuthoritiesInfo();
    }


    /**
     * Recover using SLIVER_INFO
     */
    private void recoverSliceFromSliverInfo() throws InterruptedException, ExecutionException {
        final AggregatesForSliceTask getAggregatesForSliceTask =
                hltf.getAggregatesForSlice(recoverSlice);

        LOG.info("Recover: requesting SLIVER_INFO");
        updateProgress(RECOVER_PROGRESS_RETRIEVING_SLIVER_INFO, 1);
        updateMessage("Retrieving sliver information from slice authority");
        TaskExecution taskExecution = hltf.submitTask(getAggregatesForSliceTask);

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

        updateProgress(RECOVER_PROGRESS_RETRIEVED_SLIVER_INFO, 1);
        updateMessage("Parsing sliver information from slice authority");

        boolean usableSliverInfo = true;

        List<GeniUrn> aggregateUrns = getAggregatesForSliceTask.getAggregates();
        Set<Server> involvedAuthorities;
        if (aggregateUrns != null) {
            involvedAuthorities =
                    aggregateUrns.stream()
                            .map(testbedInfoSource::getByUrnExact)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toSet());
            List<GeniUrn> unknownAuthorities = aggregateUrns.stream()
                    .filter(urn -> testbedInfoSource.getByUrnExact(urn) == null)
                    .collect(Collectors.toList());

            LOG.info("Recover: got SLIVER_INFO: involvedAuthorities=" + involvedAuthorities);

            if (involvedAuthorities.isEmpty())
                usableSliverInfo = false;

            if (!unknownAuthorities.isEmpty()) {
                LOG.warn("Found unknown authorities while recovering {}: {}", recoverSlice.getName(), unknownAuthorities);

                FutureTask<Void> dialogTask = new FutureTask<>(() -> {
                    JFDialogs.create()
                            .owner(ownerStage)
                            .title("Unknown authorities detected")
                            .masthead("Unknown authorities detected")
                            .message(
                                    String.format("The slice '%s' that you are recovering contains one or more unknown authorities (%s).\n" +
                                                    "jFed cannot recover resources from authorities it does not know. The recovery can thus be incomplete.\n\nSubmit a bugreport to contact support.",
                                            recoverSlice.getName(),
                                            unknownAuthorities.stream().map(GeniUrn::toString).collect(Collectors.joining(", ")))
                            )
                            .showWarning();
                    return null;
                });

                Platform.runLater(dialogTask);

                while (!dialogTask.isDone()) {
                    Thread.sleep(100);
                }
            }
        } else {
            usableSliverInfo = false;
            involvedAuthorities = null;
        }

        if (!usableSliverInfo) {
            ButtonType selectManualButton = new ButtonType("Select manually", ButtonBar.ButtonData.YES);

            FutureTask<Optional<ButtonType>> dialogTask = new FutureTask<>(() -> {
                Alert alert = new Alert(Alert.AlertType.WARNING);
                alert.setTitle("No active resources found");
                alert.setHeaderText("No active resources found");
                alert.setContentText(String.format("There are currently no active resources registered at your authority for '%s'.\n" +
                                "If you are sure there are active resources, " +
                                "please manually select the involved testbeds in the next step.",
                        recoverSlice.getName()));
                alert.getButtonTypes().setAll(selectManualButton, ButtonType.CANCEL);

                return alert.showAndWait();
            });

            Platform.runLater(dialogTask);

            while (!dialogTask.isDone()) {
                Thread.sleep(100);
            }
            LOG.debug("User answer on slice recovery question: {}", dialogTask.get());

            if (dialogTask.get().orElse(ButtonType.CANCEL) == selectManualButton) {
                recoverSliceWithoutAuthoritiesInfo();
            } else {
                LOG.debug("User cancelled slice recovery");
                recoverSlice = null;
            }

            //recoverSliceFromExperimentRestoreInformation();
        } else {
            //no special exogeni handling here, just directly contact correct authority. SLIVER_INFO should correctly specify it.
            recoverSliceWithAuthoritiesInfo(involvedAuthorities, null, null, false/*userSelected*/);
        }
    }

    private final Provider<SelectAuthoritiesDialog> selectAuthoritiesDialogProvider;


    static RequestRspecSource tryMakeModelBasedRspecSource(RequestRspecSource requestRspecSource) {
        if (!requestRspecSource.isXmlBased())
            return requestRspecSource;

        ModelRspec modelRspec = requestRspecSource.getModelRspec(ModelRspecType.FX);

        if (requestRspecSource.isLosingData(ModelRspecType.FX))
            return requestRspecSource;

        return new RequestRspecSource(modelRspec);
    }

    private void recoverSliceWithoutAuthoritiesInfo() {
        LOG.info("Asking user for slice restore information.");
        updateProgress(RECOVER_PROGRESS_ASKING_USER, 1);
        updateMessage("Asking user for slice restore information");

        //could not recover yet, so as user for the authorities
        FutureTask<Optional<Set<Server>>> showSelectAuthoritiesDialogTask =
                new FutureTask<>(() -> {
                    try {
                        return selectAuthoritiesDialogProvider.get().showAndWait();
                    } catch (Exception | AssertionError t) {
                        LOG.error("Exception in showSelectAuthoritiesDialogTask", t);
                        return Optional.empty();
                    }
                });

        Platform.runLater(showSelectAuthoritiesDialogTask);

        try {
            LOG.debug("Waiting for select authorities-dialog to return");

            Optional<Set<Server>> selectedAuthorities = showSelectAuthoritiesDialogTask.get();

            if (selectedAuthorities.isPresent()) {
                LOG.debug("User selected authorities: " + selectedAuthorities.get().stream().map(Server::getDefaultComponentManagerUrn).collect(Collectors.toList()));

                Set<Server> involvedAuthorities;
                Set<Server> involvedExogeniAuthorities = null;
                Set<Server> backupInvolvedExogeniAuthorities = null;
                String recoverExoPref =
                        jFedPreferences.getString(CorePreferenceKey.PREF_TESTBEDSPECIFIC_EXOGENI_RECOVER_METHOD);
                assert recoverExoPref.equalsIgnoreCase("BOTH") || recoverExoPref.equalsIgnoreCase("SINGLE") :
                        "invalid preference value: \"" + recoverExoPref + "\"";

                if (recoverExoPref.equalsIgnoreCase("BOTH")) {
                    involvedAuthorities =
                            selectedAuthorities.get().stream().
                                    filter(Objects::nonNull).
                                    filter(auth -> !auth.hasFlag(Server.Flag.centralBrokerArchitectureBrokerauth) && !auth.hasFlag(Server.Flag.centralBrokerArchitectureSubauth)).
                                    collect(Collectors.toSet());
                    involvedExogeniAuthorities =
                            selectedAuthorities.get().stream().
                                    filter(Objects::nonNull).
                                    filter(auth -> auth.hasFlag(Server.Flag.centralBrokerArchitectureBrokerauth) || auth.hasFlag(Server.Flag.centralBrokerArchitectureSubauth)).
                                    map(auth -> {
                                        Server res =
                                                authorityFinder.findByAnyUrn(auth.getDefaultComponentManagerUrn(), AuthorityFinder.Purpose.RECOVER_BOTH_FIRST);
                                        LOG.debug("RECOVER_BOTH_FIRST for " + auth.getDefaultComponentManagerUrn() + " is " + res.getDefaultComponentManagerUrn());
                                        if (res == null)
                                            LOG.error("Could not find auth .getDefaultComponentManagerUrn()geniAuthorities: sliver_urn=" + auth.getDefaultComponentManagerUrn());
                                        return res;
                                    }).
                                    filter(Objects::nonNull).
                                    collect(Collectors.toSet());
                    backupInvolvedExogeniAuthorities =
                            selectedAuthorities.get().stream().
                                    filter(Objects::nonNull).
                                    filter(auth -> auth.hasFlag(Server.Flag.centralBrokerArchitectureBrokerauth) || auth.hasFlag(Server.Flag.centralBrokerArchitectureSubauth)).
                                    map(auth -> {
                                        Server res =
                                                authorityFinder.findByAnyUrn(auth.getDefaultComponentManagerUrn(), AuthorityFinder.Purpose.RECOVER_BOTH_SECOND);
                                        LOG.debug("RECOVER_BOTH_SECOND for " + auth.getDefaultComponentManagerUrn() + " is " + res.getDefaultComponentManagerUrn());
                                        if (res == null)
                                            LOG.error("Could not find auth for backupInvolvedExogeniAuthorities: sliver_urn=" + auth.getDefaultComponentManagerUrn());
                                        return res;
                                    }).
                                    filter(Objects::nonNull).
                                    collect(Collectors.toSet());
                } else {
                    //authorities are already resolved using preferred exoSM method
                    involvedAuthorities =
                            selectedAuthorities.get().stream().filter(Objects::nonNull).collect(Collectors.toSet());
                }

                LOG.debug("Select authorities-dialog returned {}", selectedAuthorities);
                updateProgress(RECOVER_PROGRESS_ASKED_USER, 1);

                recoverSliceWithAuthoritiesInfo(involvedAuthorities, involvedExogeniAuthorities, backupInvolvedExogeniAuthorities, true/*userSelected*/);
            } else {
                recoverSlice = null;
            }
        } catch (InterruptedException | ExecutionException e) {
            LOG.warn("Exception while showing SelectAuthoritiesDialog to user", e);
        }

    }


    private void recoverSliceWithAuthoritiesInfo(final Collection<Server> involvedAuthorities,
                                                 final Collection<Server> exoGeniAuthorities,
                                                 final Collection<Server> backupExoGeniAuthorities,
                                                 final boolean userSelected)
            throws InterruptedException {
        LOG.info("Recovering slice with restore information:" +
                " involvedAuthorities=" + involvedAuthorities +
                " exoGeniAuthorities=" + exoGeniAuthorities +
                " backupExoGeniAuthorities=" + backupExoGeniAuthorities);
        updateMessage(String.format("Recovering slice information from %d %s", involvedAuthorities.size(),
                involvedAuthorities.size() == 1 ? "authority" : "authorities"));


        final Set<Server> initialInvolvedAuthorities = new HashSet<>(involvedAuthorities);
        if (exoGeniAuthorities != null) initialInvolvedAuthorities.addAll(exoGeniAuthorities);

        final AtomicInteger finishedTaskExecutionsCount = new AtomicInteger(0);
        final AtomicInteger totalInvolvedAuthorities = new AtomicInteger(initialInvolvedAuthorities.size());

        TaskExecutionFinishedCallback<SliceManifestForAuthorityTask> incrementFinishedCounterCallback =
                (task, taskExecution, state) -> {
                    int finishedCount = finishedTaskExecutionsCount.incrementAndGet();


                    updateProgress(RECOVER_PROGRESS_ASKED_USER +
                                    (RECOVER_PROGRESS_RETRIEVED_MANIFESTS - RECOVER_PROGRESS_ASKED_USER) *
                                            ((1.0 * finishedTaskExecutionsCount.get()) / totalInvolvedAuthorities.get())
                            , 1);

                    LOG.debug("Manifest #{} received of slice  '{}'", finishedCount, recoverSlice.getUrn());
                    updateMessage(String.format("Recovering slice information from %d %s (%d ready)", totalInvolvedAuthorities.get(),
                            totalInvolvedAuthorities.get() == 1 ? "authority" : "authorities",
                            finishedCount));


                };

        final Set<Server> allInvolvedAuthorities = new HashSet<>(initialInvolvedAuthorities);
        final Set<Server> allActiveAuthorities = new HashSet<>();


        for (Server authority : initialInvolvedAuthorities) {
            SliceManifestForAuthorityTask recoverSliverManifest =
                    hltf.getSliceManifest(recoverSlice, authority);

            TaskFinishedCallback checkActiveCallback =
                    (TaskExecutionFinishedCallback) (task, taskExecution, state2) -> {
                        StringRspec stringRspec = recoverSliverManifest.getManifestStringRspec();
                        if (state2 == TaskExecution.TaskState.SUCCESS && stringRspec != null && !stringRspec.getBasicNodeInfo().isEmpty()) {
                            allActiveAuthorities.add(authority);
                        }
                    };

            recoverSliverManifest.addCallback(checkActiveCallback);
            recoverSliverManifest.addCallback((task, taskExecution, state) -> {
                StringRspec stringRspec = recoverSliverManifest.getManifestStringRspec();

                if (backupExoGeniAuthorities != null) {
                    synchronized (backupExoGeniAuthorities) {
                        if (!backupExoGeniAuthorities.isEmpty() && (state == TaskExecution.TaskState.FAILED || stringRspec == null || stringRspec.getBasicNodeInfo().isEmpty())) {
                            LOG.debug("exoSM recover failed, using fallback. " +
                                    "state=" + state +
                                    " stringRspec=" + (stringRspec == null ? "null" : "not null") +
                                    " nodecount=" + (stringRspec == null ? "null"
                                    : stringRspec.getBasicNodeInfo().size()));
                            backupExoGeniAuthorities.removeAll(initialInvolvedAuthorities); //remove all we already are checking  (should not be needed)
                            totalInvolvedAuthorities.addAndGet(backupExoGeniAuthorities.size());
                            allInvolvedAuthorities.addAll(backupExoGeniAuthorities);
                            for (Server backupExoGeniAuthority : backupExoGeniAuthorities) {
                                SliceManifestForAuthorityTask recoverSliverManifest2 =
                                        hltf.getSliceManifest(recoverSlice, backupExoGeniAuthority);

                                TaskFinishedCallback checkActiveCallback2 =
                                        (TaskExecutionFinishedCallback) (task1, taskExecution1, state1) -> {
                                            StringRspec stringRspec1 = recoverSliverManifest.getManifestStringRspec();
                                            if (state1 == TaskExecution.TaskState.SUCCESS && stringRspec1 != null && !stringRspec1.getBasicNodeInfo().isEmpty()) {
                                                allActiveAuthorities.add(backupExoGeniAuthority);
                                            }
                                        };
                                recoverSliverManifest2.addCallback(checkActiveCallback2);
                                tt.addTask(recoverSliverManifest2, incrementFinishedCounterCallback);
                                LOG.debug("Requesting manifest of slice  '{}' on '{}'", recoverSlice.getUrn(), backupExoGeniAuthority.getName());
                            }
                            backupExoGeniAuthorities.clear(); //add backups only once!
                        }
                    }
                }
            });

            tt.addTask(recoverSliverManifest, incrementFinishedCounterCallback);

            LOG.debug("Requesting manifest of slice  '{}' on '{}'", recoverSlice.getUrn(), authority.getName());
        }

        //wait for all tasks to finish
        while (finishedTaskExecutionsCount.get() < totalInvolvedAuthorities.get())
            Thread.sleep(100);


        updateMessage("Finishing up...");
        if (recoverSlice.getManifestRspec() != null) {

            //NOTE:
            //   getConnectAuthorities() is the main object used to pass the authorities to RestoreExperimentJob
            //   setRequestRspec is the fallback

            recoverSlice.setRequestRspec(tryMakeModelBasedRspecSource(
                    new RequestRspecSource(recoverSlice.getManifestRspec().getStringRspec(), ModelRspecType.FX)));
            recoverSlice.getConnectAuthorities().addAll(allActiveAuthorities);
            LOG.debug("Recover success: Manifest is not null, and there are " + allActiveAuthorities.size() + " involved authorities");
        } else {
            LOG.info("Recovering slice failed: no manifest found on involved testbeds. (userSelected=" + userSelected + ")");
            if (userSelected) {
                FutureTask<Void> dialogTask = new FutureTask<>(() -> JFDialogs.create()
                        .owner(ownerStage)
                        .message(String.format("Could not find information about %s on the selected testbeds." +
                                        " Please verify that you selected the correct testbeds and try again.",
                                recoverSlice.getName()))
                        .masthead("Could not retrieve shared experiment")
                        .title("Could not retrieve shared experiment")
                        .showWarning(), null);
                Platform.runLater(dialogTask);

                //only set recoverSlice to null if dialog has been shown
                try {
                    dialogTask.get();
                } catch (ExecutionException ex) {
                    LOG.error("Error while showing information dialog", ex);
                }
                recoverSlice = null;
            } else {
                Platform.runLater(() -> JFDialogs.create()
                        .owner(ownerStage)
                        .message(String.format("Could not find information about %s on the involved testbeds: %s" +
                                        " Please manually select testbeds and try again.",
                                recoverSlice.getName(),
                                allInvolvedAuthorities.stream().map(Server::getName).collect(Collectors.joining(", "))))
                        .masthead("Could not retrieve shared experiment")
                        .title("Could not retrieve shared experiment")
                        .showWarning());

                recoverSliceWithoutAuthoritiesInfo();
            }
        }
    }


    @Override
    protected void failed() {
        LOG.warn("An error occurred while recovering experiment '" + (recoverSlice == null ? "(unknown)"
                : recoverSlice.getUrn()) + "': " + getMessage(), getException());
        JFDialogs.create()
                .owner(ownerStage)
                .title("Recovering experiment failed")
                .masthead("Recovering experiment failed")
                .message("An error occurred while recovering experiment '" + recoverSlice.getName() + "': " + getMessage())
                .showException(getException());

        super.failed();
    }

    @Override
    protected void succeeded() {
        if (recoverSlice != null) {
            LOG.info("Successfully recovered experiment " + recoverSlice.getUrn());
            Experiment experiment = new Experiment(recoverSlice, recoverReservationStartTime);

            eventBus.post(new RecoverExperimentEvent(experiment));
        } else
            LOG.info("RecoverSliceTask succeeded()");
    }
}
