package be.iminds.ilabt.jfed.lowlevel.api_wrapper.impl;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.*;
import be.iminds.ilabt.jfed.lowlevel.api.AbstractGeniAggregateManager;
import be.iminds.ilabt.jfed.lowlevel.api.user_spec.UserSpec;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.AggregateManagerWrapper;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.StatusDetails;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnectionProvider;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.util.GeniUrn;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.net.URL;
import java.util.*;

/**
 * AutomaticAggregateManagerWrapper
 */
public class AutomaticAggregateManagerWrapper extends AggregateManagerWrapper {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(AutomaticAggregateManagerWrapper.class);

    private static final String EMULAB_TYPE = "emulab";
    public static boolean preferAMv3forNonEmulab = true;  //TODO put this in a preferences system somehow
    public static boolean preferAMv3forEmulab = true;     //TODO put this in a preferences system somehow
    protected ApiInfo.Api api;
    protected ApiInfo.Api poa_api;
    protected AggregateManagerWrapper impl;
    protected AMv2Wrapper impl_amv2;
    protected AMv3Wrapper impl_amv3;
    protected AggregateManagerWrapper poa_impl; //seperate implementation which supports PerformOperationalAction
    protected ProtoGeniAMExtensionsWrapper protoGeniAMExtensionsWrapper;
    //seperate implementation for ProtoGeni Extensions


    protected AggregateManagerWrapper last_used_impl;

    private AutomaticAggregateManagerWrapper(@Nonnull Logger logger, @Nonnull GeniUserProvider geniUserProvider, @Nonnull JFedConnectionProvider connectionProvider,
                                             @Nonnull JFedPreferences jFedPreferences, @Nullable Server amServer) throws NoAmException {
        super(logger, geniUserProvider, connectionProvider, jFedPreferences, amServer);

        choose();
    }

    protected void choose() {
        //handle no authority given (occurs only when probe needs to check our methods).
        if (amServer == null)
            return;

        //first choose poa_impl
        choosePoaImpl();

        boolean preferAMv3;
        if (EMULAB_TYPE.equalsIgnoreCase(amServer.getServerType()))
            preferAMv3 = preferAMv3forEmulab;
        else
            preferAMv3 = preferAMv3forNonEmulab;

        boolean forceV2 = amServer.hasFlag(Server.Flag.workaroundForceAmv2All);
        boolean forceV3 = amServer.hasFlag(Server.Flag.workaroundForceAmv3All);

        if (ApiInfo.hasService(amServer, ApiInfo.ApiName.GENI_AM, 2)) {
            impl_amv2 = new AMv2Wrapper(logger, geniUserProvider, connectionProvider, jFedPreferences, amServer);
        }
        if (ApiInfo.hasService(amServer, ApiInfo.ApiName.GENI_AM, 3)) {
            impl_amv3 = new AMv3Wrapper(logger, geniUserProvider, connectionProvider, jFedPreferences, amServer);
        }

        if (impl_amv2 != null && (forceV2 || !preferAMv3 || impl_amv3 == null)) {
            api = new ApiInfo.Api(ApiInfo.ApiName.GENI_AM, 2);
            LOG.trace("Choosing AMv2: forceV2 =={} || !preferAMv3=={} || impl_amv3 == null =={}",
                    forceV2, !preferAMv3, impl_amv3 == null);
            impl = impl_amv2;
            setupProtoGeniAMExtensions();

        } else if (impl_amv3 != null && (forceV3 || preferAMv3 || impl_amv2 == null)) {
            api = new ApiInfo.Api(ApiInfo.ApiName.GENI_AM, 3);
            LOG.trace("Choosing AMv3: forceV3 =={} || preferAMv3=={} || impl_amv2 == null =={}",
                    forceV3, preferAMv3, impl_amv2 == null);
            impl = impl_amv3;
            setupProtoGeniAMExtensions();

        } else {
            assert (impl_amv2 == null && impl_amv3 == null) || (impl_amv2 != null && impl_amv3 != null) :
                    " impl_amv2=" + impl_amv2 + " impl_amv3=" + impl_amv3;

            throw new NoAmException("There is no (supported) Aggregate Manager known for \"" + amServer.getName() + "\". amServer urn: " + amServer.getDefaultComponentManagerUrn() + ". JSON:\n" + amServer);
        }
    }

    protected void choosePoaImpl() {
        //handle no authority given (occurs only when probe needs to check our methods).
        if (amServer == null)
            return;

        if (ApiInfo.hasService(amServer, ApiInfo.ApiName.GENI_AM, 3)) {
            poa_api = new ApiInfo.Api(ApiInfo.ApiName.GENI_AM, 3);
            poa_impl = new AMv3Wrapper(logger, geniUserProvider, connectionProvider, jFedPreferences, amServer);
        }
    }

    protected void setupProtoGeniAMExtensions() {
        //handle no authority given (occurs only when probe needs to check our methods).
        if (amServer == null)
            return;

        if (amServer.getServerType() == null)
            return;

        if ((amServer.isServerType("emulab") ||
                amServer.isServerType("protogeni") ||
                amServer.isServerType("instageni") ||
                amServer.hasFlag(Server.Flag.featurePoaExtDiskImageOps) //added for twist. might need update later (see mails and FEDIBBTDEV-2302)
        ) &&
                (
                        ApiInfo.hasService(amServer, ApiInfo.ApiName.GENI_AM, 2) //only works correctly for AMv2 !!!! see FEDIBBTDEV-726
                                || amServer.hasFlag(Server.Flag.featurePoaExtDiskImageOps) //added for twist. might need update later (see mails and FEDIBBTDEV-2302)
                )) {
            protoGeniAMExtensionsWrapper =
                    new ProtoGeniAMExtensionsWrapper(logger, geniUserProvider, connectionProvider, jFedPreferences, amServer,
                            ApiInfo.hasService(amServer, ApiInfo.ApiName.GENI_AM, 2)
                                    ? new ApiInfo.Api(ApiInfo.ApiName.GENI_AM, 2)
                                    : new ApiInfo.Api(ApiInfo.ApiName.GENI_AM, 3)
                    );
        }
    }

    protected AggregateManagerWrapper getImpl() {
        if (impl == null)
            choose();
        return impl;
    }

    @ApiMethod(order = 1, hint = "Call \"GetVersion\"")
    @Override
    public Map getVersion() throws JFedException {
        last_used_impl = impl;
        return impl.getVersion();
    }

    @ApiMethod(order = 2, hint = "List all Resources at the Aggregate (optionally list only available)")
    @Override
    public String listResources(
            @ApiMethodParameter(name = "userCredentials", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
            @Nonnull List<AnyCredential> userCredentials,
            @ApiMethodParameter(name = "available", parameterType = ApiMethodParameterType.BOOLEAN)
                    boolean available) throws JFedException {

        last_used_impl = impl;
        return impl.listResources(userCredentials, available);
    }

    @ApiMethod(order = 2, hint = "List all Resources at the Aggregate (optionally list only available)")
    @Override
    public String listResources(
            @ApiMethodParameter(name = "userCredentials", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
            @Nonnull List<AnyCredential> userCredentials,
            @ApiMethodParameter(name = "available", parameterType = ApiMethodParameterType.BOOLEAN)
                    boolean available,
            @ApiMethodParameter(name = "type", hint = "type of advertisement RSpec", parameterType = ApiMethodParameterType.STRING)
            @Nonnull String type,
            @ApiMethodParameter(name = "version", hint = "version of advertisement RSpec", parameterType = ApiMethodParameterType.STRING)
            @Nonnull String version) throws JFedException {

        last_used_impl = impl;
        return impl.listResources(userCredentials, available, type, version);
    }


    @ApiMethod(order = 3, hint = "Create a Sliver reservation")
    @Override
    public AllocateReply allocate(
            @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
            @Nonnull GeniUrn sliceUrn,
            @ApiMethodParameter(name = "sliceCredentials", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
            @Nonnull List<AnyCredential> sliceCredentials,
            @ApiMethodParameter(name = "rspec", parameterType = ApiMethodParameterType.RSPEC_STRING)
            @Nonnull String rspec,
            @ApiMethodParameter(name = "startDate", parameterType = ApiMethodParameterType.DATE)
                    Date startDate,
            @ApiMethodParameter(name = "endDate", parameterType = ApiMethodParameterType.DATE)
                    Date endDate) throws JFedException {
        last_used_impl = impl;
        return impl.allocate(sliceUrn, sliceCredentials, rspec, startDate, endDate);
    }

    @ApiMethod(order = 3, hint = "Create a Sliver (includes allocating, provisioning and starting)")
    @Override
    public String createSliver(
            @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
            @Nonnull GeniUrn sliceUrn,
            @ApiMethodParameter(name = "sliceCredentials", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
            @Nonnull List<AnyCredential> sliceCredentials,
            @ApiMethodParameter(name = "rspec", parameterType = ApiMethodParameterType.RSPEC_STRING)
            @Nonnull String rspec,
            @ApiMethodParameter(name = "userUrns", parameterType = ApiMethodParameterType.LIST_OF_USERSPEC)
                    List<UserSpec> userUrns,
            @ApiMethodParameter(name = "expirationDate", parameterType = ApiMethodParameterType.DATE, required = false)
                    Date expirationDate) throws JFedException {

        if (impl_amv2 != null && amServer.hasFlag(Server.Flag.featureReservationsNitos)) {
            LOG.debug("Forcing AMv2 for createSliver-call to {}", amServer.getName());
            last_used_impl = impl_amv2;
            return impl_amv2.createSliver(sliceUrn, sliceCredentials, rspec, userUrns, expirationDate);
        }
        last_used_impl = impl;
        return impl.createSliver(sliceUrn, sliceCredentials, rspec, userUrns, expirationDate);
    }

    @Override
    public boolean hasSeperateAllocateAndProvision() {
        return impl.hasSeperateAllocateAndProvision();
    }

    @Override
    public AllocateReply allocate(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull String rspec, Date expirationDate) throws JFedException {
        last_used_impl = impl;
        return impl.allocate(sliceUrn, sliceCredentials, rspec, expirationDate);
    }

    @Override
    public String provisionAndStart(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, List<UserSpec> userSpecs, Date expirationDate) throws JFedException {
        last_used_impl = impl;
        return impl.provisionAndStart(sliceUrn, sliceCredentials, userSpecs, expirationDate);
    }

    @ApiMethod(order = 4, hint = "Delete a Sliver")
    @Override
    public void deleteSliver(
            @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
            @Nonnull GeniUrn sliceUrn,
            @ApiMethodParameter(name = "sliceCredentials", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
            @Nonnull List<AnyCredential> sliceCredentials) throws JFedException {
        if (impl_amv2 != null && amServer != null && amServer.hasFlag(Server.Flag.workaroundForceAmv2Delete)) {
            last_used_impl = impl_amv2;
            impl_amv2.deleteSliver(sliceUrn, sliceCredentials);
        } else {
            last_used_impl = impl;
            impl.deleteSliver(sliceUrn, sliceCredentials);
        }
    }

    @ApiMethod(order = 5, hint = "Request the status of a sliver")
    @Override
    public StatusDetails status(
            @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
            @Nonnull GeniUrn sliceUrn,
            @ApiMethodParameter(name = "sliceCredentials", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
            @Nonnull List<AnyCredential> sliceCredentials) throws JFedException {
        LOG.debug("Calling AutomaticAggregateManagerWrapper status with " + sliceCredentials.size() + " credentials");
        if (impl_amv2 != null && amServer != null && amServer.hasFlag(Server.Flag.workaroundForceAmv2Status)) {
            LOG.debug("Forcing AMv2 for status-call to {}", amServer.getName());
            last_used_impl = impl_amv2;
            return impl_amv2.status(sliceUrn, sliceCredentials);
        }

        LOG.trace("Using implementation {} for status call", impl.getClass().getSimpleName());
        last_used_impl = impl;
        return impl.status(sliceUrn, sliceCredentials);
    }

    @ApiMethod(order = 6, hint = "Request the manifest RSpec of a sliver")
    @Override
    public String describe(
            @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
            @Nonnull GeniUrn sliceUrn,
            @ApiMethodParameter(name = "sliceCredentials", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
            @Nonnull List<AnyCredential> sliceCredentials) throws JFedException {
        if (impl_amv2 != null && amServer != null && amServer.hasFlag(Server.Flag.workaroundForceAmv2Describe)) {
            last_used_impl = impl_amv2;
            return impl_amv2.describe(sliceUrn, sliceCredentials);
        }
        last_used_impl = impl;
        return impl.describe(sliceUrn, sliceCredentials);
    }

    @ApiMethod(order = 7, hint = "Renew the expiration time of a sliver.")
    @Override
    public void renewSlivers(
            @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN, required = false)
                    GeniUrn sliceUrn,
            @ApiMethodParameter(name = "sliverUrns", parameterType = ApiMethodParameterType.LIST_OF_SLIVER_URN, required = false)
                    List<GeniUrn> sliverUrns,
            @ApiMethodParameter(name = "sliceCredentials", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
            @Nonnull List<AnyCredential> sliceCredentials,
            @ApiMethodParameter(name = "newExpire", parameterType = ApiMethodParameterType.DATE)
            @Nonnull Date newExpire) throws JFedException {
        last_used_impl = impl;
        impl.renewSlivers(sliceUrn, sliverUrns, sliceCredentials, newExpire);
    }

    //Also use last AM reply from impl
    @Nullable
    @Override
    public AbstractGeniAggregateManager.AggregateManagerReply getLastReply() {
        if (last_used_impl == null) return null;
        return last_used_impl.getLastReply();
    }

    @Override
    public boolean isExirationdateSetByCreateSliver() {
        last_used_impl = impl;
        return impl.isExirationdateSetByCreateSliver();
    }


    @ApiMethod(order = 10, hint = "Ask if the Share LAN option is supported")
    @Override
    public boolean isShareLanSupported() {
        if (amServer != null && !amServer.hasFlag(Server.Flag.featurePoaShareLan))
            return false;
        if (poa_impl != null) {
            last_used_impl = poa_impl;
            return poa_impl.isShareLanSupported();
        }
        return false;
    }

    @ApiMethod(order = 11, hint = "Ask if Restart is supported")
    @Override
    public boolean isRestartSupported() {
        if (amServer != null && !amServer.hasFlag(Server.Flag.featurePoaNodeRestart))
            return false;
        if (poa_impl != null) {
            last_used_impl = poa_impl;
            return poa_impl.isRestartSupported(); //always true
        }
        last_used_impl = impl;
        return impl.isRestartSupported(); //always false
    }

    @Override
    public boolean isReloadOSSupported() {
        if (amServer != null && !amServer.hasFlag(Server.Flag.featurePoaReloadOs))
            return false;
        if (poa_impl != null) {
            last_used_impl = poa_impl;
            return poa_impl.isRestartSupported(); //always true
        }
        last_used_impl = impl;
        return impl.isRestartSupported(); //always false
    }

    @Override
    public boolean isOpenConsoleSupported() {
        if (amServer != null && !amServer.hasFlag(Server.Flag.featurePoaOpenConsole))
            return false;
        if (poa_impl != null) {
            last_used_impl = poa_impl;
            return poa_impl.isOpenConsoleSupported(); //always true
        }
        last_used_impl = impl;
        return impl.isOpenConsoleSupported(); //always false
    }

    @ApiMethod(order = 12, hint = "Share a LAN between slices")
    @Override
    public void shareLan(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull String lanClientId, @Nonnull String newSharedLanName) throws JFedException {
        if (poa_impl != null && poa_impl.isShareLanSupported()) {
            last_used_impl = poa_impl;
            poa_impl.shareLan(sliceUrn, sliceCredentials, lanClientId, newSharedLanName);
        } else
            throw new RuntimeException("Aggregate Manager has no support for shared LAN");
    }

    @ApiMethod(order = 13, hint = "Unshare a shared LAN")
    @Override
    public void unshareLan(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull String lanClientId, @Nonnull String sharedLanName) throws JFedException {
        if (poa_impl != null && poa_impl.isShareLanSupported()) {
            last_used_impl = poa_impl;
            poa_impl.unshareLan(sliceUrn, sliceCredentials, lanClientId, sharedLanName);
        } else
            throw new RuntimeException("Aggregate Manager has no support for shared LAN");
    }

    @ApiMethod(order = 14, hint = "Restart all resources in a Slice")
    @Override
    public void restart(@Nonnull GeniUrn sliverUrn, @Nonnull List<AnyCredential> sliceCredentials) throws JFedException {
        if (poa_impl != null) {
            last_used_impl = poa_impl;
            poa_impl.restart(sliverUrn, sliceCredentials);
        } else
            throw new RuntimeException("Aggregate Manager has no support for PerformOperationAction, and thus not for restart.");
    }

    @Override
    public void reloadOS(@Nonnull GeniUrn sliverUrn, @Nonnull List<AnyCredential> sliceCredentials) throws JFedException {
        if (poa_impl != null) {
            last_used_impl = poa_impl;
            poa_impl.reloadOS(sliverUrn, sliceCredentials);
        } else
            throw new RuntimeException("Aggregate Manager has no support for PerformOperationAction, and thus not for reload.");
    }

    @Override
    public URL openConsole(@Nonnull GeniUrn sliverUrn, @Nonnull List<AnyCredential> sliceCredentials) throws JFedException {
        if (poa_impl != null) {
            last_used_impl = poa_impl;
            return poa_impl.openConsole(sliverUrn, sliceCredentials);
        } else
            throw new RuntimeException("Aggregate Manager has no support for PerformOperationAction, and thus not for open console.");
    }

    @ApiMethod(order = 15, hint = "Ask if SSH keys can be supported")
    @Override
    public boolean isEditSshKeysSupported() {
        if (amServer != null && !amServer.hasFlag(Server.Flag.featurePoaUpdateSshKeys))
            return false;
        if (poa_impl != null) {
            last_used_impl = poa_impl;
            return poa_impl.isEditSshKeysSupported(); //always true
        }
        last_used_impl = impl;
        return impl.isEditSshKeysSupported(); //always false
    }

    @ApiMethod(order = 16, hint = "Add users and SSH keys to nodes within a slice")
    @Override
    public void editSshKeys(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull Collection<UserSpec> userSpecs) throws JFedException {
        if (poa_impl != null) {
            last_used_impl = poa_impl;
            poa_impl.editSshKeys(sliceUrn, sliceCredentials, userSpecs);
        } else
            throw new RuntimeException("Aggregate Manager has no support for PerformOperationAction, and thus not for editSshKeys");
    }

    @ApiMethod(order = 17, hint = "Ask if Create Image is supported")
    @Override
    public boolean areUserDiskImageFunctionsSupported() {
        if (amServer != null && !amServer.hasFlag(Server.Flag.featurePoaExtDiskImageOps))
            return false;
        if (protoGeniAMExtensionsWrapper != null)
            return true;
        else {
            LOG.error("The server has the \"featurePoaExtDiskImageOps\" flag, but no protoGeniAMExtensionsWrapper was found!");
            return false;
        }
    }

    @ApiMethod(order = 18, hint = "Create a disk image from a node")
    @Override
    public ImageInfo createImage(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn componentUrn, @Nonnull String imageName,
                                 boolean global, boolean updatePrepare) throws JFedException {
        if (protoGeniAMExtensionsWrapper == null)
            throw new RuntimeException("operation is not supported");

        return protoGeniAMExtensionsWrapper.createImage(sliceUrn, sliceCredentials, componentUrn, imageName, global, updatePrepare);
    }

    @Nonnull
    @Override
    public List<ImageInfo> listImages(@Nonnull List<AnyCredential> userCredentials) throws JFedException {
        if (protoGeniAMExtensionsWrapper == null)
            throw new RuntimeException("operation is not supported");

        List<? extends ImageInfo> images = protoGeniAMExtensionsWrapper.listImages(userCredentials);
        return new ArrayList<>(images);
    }

    @Override
    public boolean deleteImage(@Nonnull List<AnyCredential> userCredentials, @Nonnull GeniUrn imageUrn) throws JFedException {
        if (protoGeniAMExtensionsWrapper == null)
            throw new RuntimeException("operation is not supported");

        boolean success = protoGeniAMExtensionsWrapper.deleteImage(userCredentials, imageUrn);

        if (!success)
            throw new JFedException("Did not successfully delete image");
        return true;
    }


    @Override
    public void setExtraOptionsForCallsWithCredential(Map<String, ?> extraOptionsForCallsWithCredential) {
        super.setExtraOptionsForCallsWithCredential(extraOptionsForCallsWithCredential);
        impl.setExtraOptionsForCallsWithCredential(extraOptionsForCallsWithCredential);

        if (poa_impl != null)
            poa_impl.setExtraOptionsForCallsWithCredential(extraOptionsForCallsWithCredential);
        if (impl_amv2 != null)
            impl_amv2.setExtraOptionsForCallsWithCredential(extraOptionsForCallsWithCredential);
        if (impl_amv3 != null)
            impl_amv3.setExtraOptionsForCallsWithCredential(extraOptionsForCallsWithCredential);
    }

    @Override
    public void setSpeaksFor(List<AnyCredential> speaksForCredentials, @Nullable GeniUrn speakingForUrn) {
        super.setSpeaksFor(speaksForCredentials, speakingForUrn);
        impl.setSpeaksFor(speaksForCredentials, speakingForUrn);

        if (poa_impl != null) poa_impl.setSpeaksFor(speaksForCredentials, speakingForUrn);
        if (impl_amv2 != null) impl_amv2.setSpeaksFor(speaksForCredentials, speakingForUrn);
        if (impl_amv3 != null) impl_amv3.setSpeaksFor(speaksForCredentials, speakingForUrn);
    }


    public interface AutomaticAggregateManagerWrapperFactory {
        AutomaticAggregateManagerWrapper create(Server amServer) throws NoAmException;

        AutomaticAggregateManagerWrapper create(Logger altLogger, Server amServer) throws NoAmException;

        void setSpeaksFor(@Nullable List<AnyCredential> speaksForCredentials, @Nullable GeniUrn speakingForUrn);

        boolean isSpeaksFor();

        @Nullable
        List<AnyCredential> getSpeaksForCredentials();

        @Nullable
        GeniUrn getSpeaksForUserUrn();
    }

    public static class BasicAutomaticAggregateManagerWrapperFactory implements AutomaticAggregateManagerWrapperFactory {
        private final Logger logger;
        private final GeniUserProvider geniUserProvider;
        private final JFedConnectionProvider connectionProvider;
        private final JFedPreferences jFedPreferences;

        @Nullable
        private List<AnyCredential> speaksForCredentials;
        @Nullable
        private GeniUrn speaksForUserUrn;

        @Inject
        public BasicAutomaticAggregateManagerWrapperFactory(Logger logger, GeniUserProvider geniUserProvider, JFedConnectionProvider connectionProvider, JFedPreferences jFedPreferences) {
            this.logger = logger;
            this.geniUserProvider = geniUserProvider;
            this.connectionProvider = connectionProvider;
            this.jFedPreferences = jFedPreferences;
        }

        public BasicAutomaticAggregateManagerWrapperFactory(Logger logger, @Nullable final GeniUser geniUser, JFedConnectionProvider connectionProvider, JFedPreferences jFedPreferences) {
            this(logger, new GeniUserProvider() {
                @Nullable
                public GeniUser getLoggedInGeniUser() {
                    return geniUser;
                }

                public boolean isUserLoggedIn() {
                    return geniUser != null;
                }
            }, connectionProvider, jFedPreferences);
        }

        @Nonnull
        public AutomaticAggregateManagerWrapper create(@Nonnull Server amServer) throws NoAmException {
            return this.create(logger, amServer);
        }

        @Nonnull
        public AutomaticAggregateManagerWrapper create(@Nonnull Logger altLogger, @Nonnull Server amServer) throws NoAmException {
            AutomaticAggregateManagerWrapper res =
                    new AutomaticAggregateManagerWrapper(altLogger, geniUserProvider, connectionProvider, jFedPreferences, amServer);
            if (geniUserProvider.isUserLoggedIn() && geniUserProvider.getLoggedInGeniUser() instanceof GeniUserWithSpeaksFor) {
                GeniUserWithSpeaksFor geniUserWithSpeaksFor =
                        (GeniUserWithSpeaksFor) geniUserProvider.getLoggedInGeniUser();
                setSpeaksFor(geniUserWithSpeaksFor.getSpeaksForCredentials(), geniUserWithSpeaksFor.getSpeaksForUrn());
            }
            if (speaksForCredentials != null || speaksForUserUrn != null) {
                res.setSpeaksFor(speaksForCredentials, speaksForUserUrn);
            }
            return res;
        }

        @Override
        public void setSpeaksFor(@Nullable List<AnyCredential> speaksForCredentials, @Nullable GeniUrn speakingForUrn) {
            this.speaksForCredentials = speaksForCredentials;
            this.speaksForUserUrn = speakingForUrn;
        }

        @Override
        public boolean isSpeaksFor() {
            return this.speaksForCredentials != null && !this.speaksForCredentials.isEmpty();
        }

        @Nullable
        @Override
        public List<AnyCredential> getSpeaksForCredentials() {
            return speaksForCredentials;
        }

        @Nullable
        @Override
        public GeniUrn getSpeaksForUserUrn() {
            return speaksForUserUrn;
        }
    }
}
