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

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.highlevel.controller.Task;
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.Sliver;
import be.iminds.ilabt.jfed.highlevel.util.AggregateManagerWrapperFactory;
import be.iminds.ilabt.jfed.highlevel.util.SliceRegistryUtil;
import be.iminds.ilabt.jfed.log.ResultListener;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.api.user_spec.UserSpec;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.UserAndSliceApiWrapper;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnectionProvider;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.rspec.rspec_source.RequestRspecSource;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.JavaFXLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

/**
 * User: twalcari
 * Date: 6/16/2015
 * Time: 15:25
 */
@Singleton
public class HighLevelTaskFactory implements SliceAuthorityTaskFactory {
    private static final Logger LOG = LoggerFactory.getLogger(HighLevelTaskFactory.class);

    protected final UserAndSliceApiWrapper userAndSliceApiWrapper;
    protected final AggregateManagerWrapperFactory aggregateManagerWrapperFactory;
    protected final JavaFXLogger logger;
    protected final GeniUserProvider geniUserProvider;
    protected final SfaModel sfaModel;
    protected final TaskThread tt;
    protected final JFedPreferences jFedPreferences;
    protected final TestbedInfoSource testbedInfoSource;
    protected final JFedConnectionProvider connectionProvider;

    private final SliceRegistryUtil sliceRegistryUtil;

    @Inject
    public HighLevelTaskFactory(UserAndSliceApiWrapper userAndSliceApiWrapper,
                                AggregateManagerWrapperFactory aggregateManagerWrapperFactory,
                                JavaFXLogger logger, GeniUserProvider geniUserProvider, SfaModel sfaModel,
                                TaskThread tt, JFedPreferences jFedPreferences, TestbedInfoSource testbedInfoSource,
                                JFedConnectionProvider connectionProvider, SliceRegistryUtil sliceRegistryUtil) {
        this.userAndSliceApiWrapper = userAndSliceApiWrapper;
        this.aggregateManagerWrapperFactory = aggregateManagerWrapperFactory;
        this.logger = logger;
        this.geniUserProvider = geniUserProvider;
        this.sfaModel = sfaModel;
        this.tt = tt;
        this.jFedPreferences = jFedPreferences;
        this.testbedInfoSource = testbedInfoSource;
        this.connectionProvider = connectionProvider;
        this.sliceRegistryUtil = sliceRegistryUtil;
    }


    private UserCredentialTask userCredentialTask = null;

    public synchronized UserCredentialTask getUserCredential() {
        if (userCredentialTask == null) {
            this.userCredentialTask = new UserCredentialTask(userAndSliceApiWrapper, logger);
        }
        return userCredentialTask;
    }

    private ListSlicesTask listSlicesTask = null;

    public synchronized ListSlicesTask listSlices() {
        if (this.listSlicesTask == null) {
            this.listSlicesTask =
                    new ListSlicesTask(userAndSliceApiWrapper, logger, geniUserProvider, HighLevelTaskFactory.this);
        }
        return this.listSlicesTask;
    }

    private ListSubAuthoritiesTask listSubAuthoritiesTask = null;


    public synchronized ListSubAuthoritiesTask listSubAuthorities() {
        if (this.listSubAuthoritiesTask == null) {
            this.listSubAuthoritiesTask =
                    new ListSubAuthoritiesTask(userAndSliceApiWrapper, logger, geniUserProvider, sfaModel, HighLevelTaskFactory.this);
        }
        return this.listSubAuthoritiesTask;
    }

    private ResolveUserAndSlicesTask resolveUserAndSlicesTask = null;

    public synchronized ResolveUserAndSlicesTask resolveUserAndSlices() {
        if (this.resolveUserAndSlicesTask == null) {
            this.resolveUserAndSlicesTask = new ResolveUserAndSlicesTask(userAndSliceApiWrapper, logger,
                    geniUserProvider, sfaModel, tt, HighLevelTaskFactory.this);
        }

        return this.resolveUserAndSlicesTask;
    }

    private UserAuthorityGetVersionTask userAuthorityGetVersionTask = null;

    public synchronized UserAuthorityGetVersionTask getUserAuthorityGetVersionTask() {
        if (this.userAuthorityGetVersionTask == null) {
            this.userAuthorityGetVersionTask =
                    new UserAuthorityGetVersionTask(geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer(), logger, geniUserProvider, connectionProvider, jFedPreferences);
        }
        return this.userAuthorityGetVersionTask;
    }

    private final CacheableTaskFactory<Server, GetVersionTask> getVersionTaskFactory =
            new CacheableTaskFactory<Server, GetVersionTask>() {
                @Override
                protected GetVersionTask createNewTask(Server auth) {
                    return new GetVersionTask(auth, aggregateManagerWrapperFactory);
                }
            };

    public GetVersionTask getVersion(Server auth) {
        return getVersionTaskFactory.getTask(auth);
    }

    public EmulabRegistryTask emulabRegistry(Server auth) {
        //no caching of this call!
        return new EmulabRegistryTask(auth, logger, geniUserProvider, jFedPreferences, connectionProvider);
    }

    private final CacheableTaskFactory<Server, GetAdvertisementTask> getAllAdvertisementTaskFactory =
            new CacheableTaskFactory<Server, GetAdvertisementTask>() {
                @Override
                protected GetAdvertisementTask createNewTask(Server auth) {
                    return GetAdvertisementTask.createFetchAllAdvertisementTask(auth, userAndSliceApiWrapper,
                            aggregateManagerWrapperFactory, HighLevelTaskFactory.this);
                }
            };

    public GetAdvertisementTask getAllAdvertisement(@Nonnull Server auth) {
        return getAllAdvertisementTaskFactory.getTask(auth);
    }

    private final CacheableTaskFactory<Server, GetAdvertisementTask> getAvailableAdvertisementTaskFactory =
            new CacheableTaskFactory<Server, GetAdvertisementTask>() {
                @Override
                protected GetAdvertisementTask createNewTask(Server auth) {
                    return GetAdvertisementTask.createFetchAvailableAdvertisementTask(auth, userAndSliceApiWrapper,
                            aggregateManagerWrapperFactory, HighLevelTaskFactory.this);
                }
            };

    public GetAdvertisementTask getAvailableAdvertisement(@Nonnull Server auth) {
        return getAvailableAdvertisementTaskFactory.getTask(auth);
    }

    private final CacheablePairTaskFactory<String, String, FedmonResultsTask> fedmonResultsTaskFactory =
            new CacheablePairTaskFactory<String, String, FedmonResultsTask>() {
                @Override
                protected FedmonResultsTask createNewTask(String testbedUrnFilter, String testDefinitionFilter) {
                    return new FedmonResultsTask(null/*testbedFilter*/, testbedUrnFilter, testDefinitionFilter, logger, geniUserProvider, jFedPreferences, testbedInfoSource, connectionProvider);
                }
            };

    public FedmonResultsTask getFedmonResults(String testbedUrnFilter, String testDefinitionFilter) {
        return fedmonResultsTaskFactory.getTask(testbedUrnFilter, testDefinitionFilter);
    }

    private final CacheableTaskFactory<Slice, SliceCredentialTask> sliceCredentialTaskFactory =
            new CacheableTaskFactory<Slice, SliceCredentialTask>() {
                @Override
                protected SliceCredentialTask createNewTask(Slice slice) {
                    return new SliceCredentialTask(slice, userAndSliceApiWrapper, logger, HighLevelTaskFactory.this);
                }
            };

    public SliceCredentialTask getSliceCredential(@Nonnull Slice slice) {
        return sliceCredentialTaskFactory.getTask(slice);
    }


    private final CacheablePairTaskFactory<Slice, Server, SliceManifestForAuthorityTask>
            sliceManifestForAuthorityFactory =
            new CacheablePairTaskFactory<Slice, Server, SliceManifestForAuthorityTask>() {
                @Override
                protected SliceManifestForAuthorityTask createNewTask(Slice slice, Server authority) {
                    return new SliceManifestForAuthorityTask(slice, authority, aggregateManagerWrapperFactory, HighLevelTaskFactory.this);
                }
            };

    public SliceManifestForAuthorityTask getSliceManifest(@Nonnull Slice slice, @Nonnull Server auth) {
        return sliceManifestForAuthorityFactory.getTask(slice, auth);
    }


    private final CacheableTaskFactory<Slice, AggregatesForSliceTask> aggregatesForSliceTaskFactory =
            new CacheableTaskFactory<Slice, AggregatesForSliceTask>() {
                @Override
                protected AggregatesForSliceTask createNewTask(Slice slice) {
                    return new AggregatesForSliceTask(slice, userAndSliceApiWrapper, logger, HighLevelTaskFactory.this);
                }
            };

    public AggregatesForSliceTask getAggregatesForSlice(@Nonnull Slice slice) {
        return aggregatesForSliceTaskFactory.getTask(slice);
    }

    private final CacheableTaskFactory<Sliver, SliverManifestTask> sliverManifestTaskFactory =
            new CacheableTaskFactory<Sliver, SliverManifestTask>() {
                @Override
                protected SliverManifestTask createNewTask(Sliver sliver) {
                    return new SliverManifestTask(sliver, aggregateManagerWrapperFactory, HighLevelTaskFactory.this);
                }
            };

    public SliverManifestTask getManifest(@Nonnull Sliver sliver) {
        return sliverManifestTaskFactory.getTask(sliver);
    }

    private final CacheableTaskFactory<Sliver, SliverStatusTask> sliverStatusTaskFactory =
            new CacheableTaskFactory<Sliver, SliverStatusTask>() {
                @Override
                protected SliverStatusTask createNewTask(Sliver sliver) {
                    return new SliverStatusTask(sliver, aggregateManagerWrapperFactory, HighLevelTaskFactory.this);
                }
            };

    public SliverStatusTask getStatus(@Nonnull Sliver sliver) {
        return sliverStatusTaskFactory.getTask(sliver);
    }

    private final CacheablePairTaskFactory<Slice, Server, StatusTask>
            statusFactory =
            new CacheablePairTaskFactory<Slice, Server, StatusTask>() {
                @Override
                protected StatusTask createNewTask(Slice slice, Server authority) {
                    return new StatusTask(authority, slice, aggregateManagerWrapperFactory, HighLevelTaskFactory.this);
                }
            };

    public StatusTask status(@Nonnull Slice slice, @Nonnull Server auth) {
        return statusFactory.getTask(slice,auth);
    }


    public RenewSliceTask renewSlice(Slice slice, Instant newExpirationDate) {
        return new RenewSliceTask(slice, newExpirationDate, userAndSliceApiWrapper, logger, this);
    }

    public RenewSliversTask renewSliversAtAuthority(Slice slice, Instant newExpirationDate, Server authority) {
        return new RenewSliversTask(slice, newExpirationDate, authority, null /*renew all slivers*/, false,
                userAndSliceApiWrapper, aggregateManagerWrapperFactory, logger, HighLevelTaskFactory.this);
    }

    public RenewSliversTask renewSliversAtAuthority(Slice slice, Instant newExpirationDate, Server authority,
                                                    Collection<Sliver> slivers, boolean onlyIfNeeded) {
        return new RenewSliversTask(slice, newExpirationDate, authority, slivers, onlyIfNeeded,
                userAndSliceApiWrapper, aggregateManagerWrapperFactory, logger, HighLevelTaskFactory.this);
    }

    /*
      Create tasks
     */

    /**
     * @param sliceName      name of the slice to be created. (not urn)
     * @param subAuthName    may be null if no subAuthority is needed
     * @param expirationDate time at which the slice should expire. Note that no call offers hard guarantees:
     *                       so slice might expire later or earlier than this time.
     */
    public CreateSliceTask createSlice(@Nonnull String sliceName, @Nullable String subAuthName, @Nullable Instant expirationDate) {
        assert sliceName != null;
        //always return a new task!
        return new CreateSliceTask(sliceName, subAuthName, expirationDate, userAndSliceApiWrapper, logger, sfaModel, HighLevelTaskFactory.this);
    }


    @Override
    public RegisterSliversTask registerSlivers(Slice slice, Server auth, Collection<GeniUrn> sliverUrns,
                                               Instant sliverExpirationDate, Task extraTaskToDependOn, ResultListener... extraResultListeners) {
        return new RegisterSliversTask(slice, auth, sliverUrns, sliverExpirationDate, extraTaskToDependOn,
                userAndSliceApiWrapper, logger, this,
                extraResultListeners);
    }

    public CreateSliverTask createSliver(Slice slice, Instant startTime,  Instant expirationTime, Server auth,
                                         List<UserSpec> userSpecs, @Nullable RequestRspecSource overridingRequestRspecSource) {
        return new CreateSliverTask(slice, startTime, expirationTime, auth, userSpecs, overridingRequestRspecSource,
                aggregateManagerWrapperFactory, sliceRegistryUtil, sfaModel, this);
    }

    public AllocateSliverTask allocateSliver(Slice slice,@Nullable Instant startTime, Instant expirationTime, List<UserSpec> userSpecs, Server auth,
                                             @Nullable RequestRspecSource overridingRequestRspecSource) {
        return new AllocateSliverTask(slice, startTime, expirationTime, userSpecs, auth, overridingRequestRspecSource,
                aggregateManagerWrapperFactory, sliceRegistryUtil, sfaModel, this);
    }


    public ProvisionSliverTask provisionSliver(Slice slice, Instant expirationTime, Server auth, List<UserSpec> userSpecs) {
        return new ProvisionSliverTask(slice, expirationTime, auth, userSpecs,
                aggregateManagerWrapperFactory, sfaModel, this, sliceRegistryUtil);
    }

    public ListSliceResourcesTask listSliceResources(Slice slice, Server auth) {
        return new ListSliceResourcesTask(auth, slice, aggregateManagerWrapperFactory, this);
    }

    /*
      Delete tasks
     */

    public DeleteSliversAtAuthorityTask deleteSliversAtAuthority(@Nonnull Slice slice, @Nonnull Server auth) {
        return new DeleteSliversAtAuthorityTask(slice, auth, aggregateManagerWrapperFactory, sliceRegistryUtil);
    }


    @Override
    public UnregisterSliversTask unregisterSlivers(@Nonnull Slice slice, @Nonnull Server auth) {
        return unregisterSlivers(slice, auth, null, null);
    }

    @Override
    public UnregisterSliversTask unregisterSlivers(@Nonnull Slice slice, @Nonnull Server auth,
                                                   @Nonnull Collection<GeniUrn> sliversToUnregister, Task extraDependsOnTask,
                                                   ResultListener... resultListeners) {
        return new UnregisterSliversTask(slice, auth, sliversToUnregister, extraDependsOnTask,
                userAndSliceApiWrapper, logger, this, resultListeners);
    }

    /*
       SSH-keys tasks
     */

    private UserSshKeysTask userSshKeysTask = null;

    public synchronized UserSshKeysTask getUserSSHKeys() {
        if (userSshKeysTask == null) {
            userSshKeysTask =
                    new UserSshKeysTask(userAndSliceApiWrapper, logger, geniUserProvider, HighLevelTaskFactory.this);
        }
        return userSshKeysTask;
    }

    private ProjectMemberSshKeysTask projectMemberSshKeysTask = null;

    public synchronized ProjectMemberSshKeysTask getAllMembersSSHKeys() {
        if (projectMemberSshKeysTask == null) {
            projectMemberSshKeysTask =
                    new ProjectMemberSshKeysTask(userAndSliceApiWrapper, logger, HighLevelTaskFactory.this);
        }
        return projectMemberSshKeysTask;
    }

    private final CacheableTaskFactory<String, ProjectMemberSshKeysTask> projectMemberSshKeysTaskFactory =
            new CacheableTaskFactory<String, ProjectMemberSshKeysTask>() {
                @Override
                protected ProjectMemberSshKeysTask createNewTask(String projectName) {
                    return new ProjectMemberSshKeysTask(projectName, userAndSliceApiWrapper, logger, HighLevelTaskFactory.this);
                }
            };

    public ProjectMemberSshKeysTask getProjectMembersSSHKeys(@Nonnull String projectName) {
        return projectMemberSshKeysTaskFactory.getTask(projectName);
    }

    private final CacheableTaskFactory<Slice, SliceMembersSshKeysTask> sliceMembersSshKeysTaskFactory =
            new CacheableTaskFactory<Slice, SliceMembersSshKeysTask>() {
                @Override
                protected SliceMembersSshKeysTask createNewTask(Slice slice) {
                    return new SliceMembersSshKeysTask(slice, userAndSliceApiWrapper, logger, HighLevelTaskFactory.this);
                }
            };

    public SliceMembersSshKeysTask getSliceMembersSSHKeys(@Nonnull Slice slice) {
        return sliceMembersSshKeysTaskFactory.getTask(slice);
    }


    private final HashMap<GeniUrn, OtherUserSshKeysTask> otherUserSshKeysTasks = new HashMap<>();

    public OtherUserSshKeysTask getOtherUserSshKeys(@Nonnull GeniUrn userUrn) {
        return getOtherUserSSHKey(userUrn, (List<Task>) null);
    }

    public OtherUserSshKeysTask getOtherUserSSHKey(@Nonnull GeniUrn userUrn, Task... extraTaskDeps) {
        return getOtherUserSSHKey(userUrn, Arrays.asList(extraTaskDeps));
    }

    public synchronized OtherUserSshKeysTask getOtherUserSSHKey(@Nonnull GeniUrn userUrn, @Nullable List<Task> extraTaskDeps) {
        return otherUserSshKeysTasks.computeIfAbsent(userUrn,
                urn -> new OtherUserSshKeysTask(userUrn, extraTaskDeps, userAndSliceApiWrapper, logger, HighLevelTaskFactory.this));

    }

    /*
    POA tasks
     */


    public ShareLanTask getShareLanTask(final Slice slice, final Server auth, final String lanClientId, final String newSharedLanName) {
        return new ShareLanTask(slice, auth, lanClientId, newSharedLanName, aggregateManagerWrapperFactory, this);
    }

    public UnshareLanTask getUnshareLanTask(final Slice slice, final Server auth, final String lanClientId, final String sharedLanName) {
        return new UnshareLanTask(slice, auth, lanClientId, sharedLanName, aggregateManagerWrapperFactory, this);
    }

    public RestartTask restart(@Nonnull Slice slice, @Nonnull GeniUrn restartTarget, @Nonnull Server auth) {
        return new RestartTask(slice, auth, restartTarget, aggregateManagerWrapperFactory, this);
    }

    public RestartTask restartSlice(final Slice slice, final Server auth) {
        assert (slice.getUrn() != null);
        return restart(slice, slice.getUrn(), auth);
    }

    public RestartTask restartSliver(@Nonnull Sliver sliver) {
        assert (sliver.getUrn() != null);
        return restart(sliver.getSlice(), sliver.getUrn(), sliver.getServer());
    }

    public ReloadOSTask reloadOS(@Nonnull Slice slice, @Nonnull GeniUrn reloadOSTarget, @Nonnull Server auth) {
        return new ReloadOSTask(slice, auth, reloadOSTarget, aggregateManagerWrapperFactory, this);
    }

    public ReloadOSTask reloadOS(final Sliver sliver) {
        assert (sliver.getUrn() != null);
        return reloadOS(sliver.getSlice(), sliver.getUrn(), sliver.getServer());
    }

    public CreateImageTask createImage(final Slice slice, final GeniUrn sliverUrn, String imageName, boolean global, boolean updatePrepare) {
        return new CreateImageTask(slice, sliverUrn, imageName, global, updatePrepare, aggregateManagerWrapperFactory, this);
    }

    public DeleteImageTask deleteImage(final Server auth, final GeniUrn imageUrn) {
        return new DeleteImageTask(auth, imageUrn, userAndSliceApiWrapper, aggregateManagerWrapperFactory, this);
    }


    public ListDiskImagesTask listDiskImages(final Server auth) {
        return new ListDiskImagesTask(auth, userAndSliceApiWrapper, aggregateManagerWrapperFactory, this);

    }

    public OpenConsoleTask openConsole(final Sliver sliver) {
        return new OpenConsoleTask(sliver, aggregateManagerWrapperFactory, this);
    }

    /**
     * Note: One task per user added
     */
    public ShareSliceTask getShareSliceTask(final Slice slice, final GeniUrn userUrn) {
        return new ShareSliceTask(slice, userUrn, userAndSliceApiWrapper, logger, this);
    }

    /**
     * @param userSpecs as in http://groups.geni.net/geni/wiki/GAPI_AM_API_DRAFT/Adopted#ChangeSetQ:Supportchangingusersandkeysonexistingcomputeslivers
     *                  - New users may be added
     *                  - Omitting a user means there will be no change to the keys installed for that user. It does not mean the user should be removed.
     *                  - Existing users will have all SSH keys replaced by the new set. Note that a user may have an empty list of SSH keys specified, effectively preventing the user from accessing the node.
     */
    public EditSSHKeysTask getEditSshKeysTask(final Slice slice, final Server auth, final Collection<UserSpec> userSpecs) {
        return new EditSSHKeysTask(slice, auth, userSpecs, aggregateManagerWrapperFactory, this);
    }


    //TODO these should be removed
    public TaskExecution submitTask(Task task) {
        return tt.addTask(task);
    }


    public ListSubAuthorityMembersTask listSubAuthorityMembers(String subAuth) {
        return new ListSubAuthorityMembersTask(subAuth, userAndSliceApiWrapper, logger);
    }

    public ListSliceMembersTask listSliceMembers(Slice slice) {
        return new ListSliceMembersTask(slice, userAndSliceApiWrapper, logger, this);
    }

    public RequestSlaUuidTask requestSlaUuid() {
        return new RequestSlaUuidTask(logger, jFedPreferences, testbedInfoSource, connectionProvider, geniUserProvider);
    }

    public RegisterSliversToSLACollectorTask registerSliversToSLACollector(@Nonnull Slice slice, @Nonnull Server auth,
                                                                           @Nonnull Collection<GeniUrn> createdSliverUrns) {
        return new RegisterSliversToSLACollectorTask(slice, auth, createdSliverUrns, logger, jFedPreferences, testbedInfoSource, connectionProvider, geniUserProvider);
    }

    public RegisterStopToSLACollectorTask registerStopToSLACollector(@Nonnull Slice slice, @Nonnull Server auth) {
        return new RegisterStopToSLACollectorTask(slice, auth, logger, jFedPreferences, testbedInfoSource, connectionProvider, geniUserProvider);
    }

    public SCSRequestTask createSCSRequestTask(Slice slice, RequestRspecSource requestRspecSource, Instant requestedEndTime, Server scsAuthority) {
        return new SCSRequestTask(slice, requestRspecSource, requestedEndTime, scsAuthority, geniUserProvider, logger, connectionProvider, jFedPreferences);
    }

    public ListPendingReputationQuestionnairesTask listPendingReputationQuestionnaires(@Nonnull Slice slice){
        return new ListPendingReputationQuestionnairesTask(slice, logger, jFedPreferences, testbedInfoSource, connectionProvider, geniUserProvider);
    }

    public RegisterReputationTask registerReputation(@Nonnull  Slice slice, @Nonnull List<GeniUrn> resources,
                                                     int overallScore, int qualityScore, int availabilityScore){
        return new RegisterReputationTask(slice, resources,overallScore, qualityScore, availabilityScore,
                logger, jFedPreferences, testbedInfoSource, connectionProvider, geniUserProvider);
    }
}
