package be.iminds.ilabt.jfed.lowlevel.api;

import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.*;
import be.iminds.ilabt.jfed.lowlevel.connection.SfaConnection;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.TextUtil;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * FederationSliceAuthorityApi1
 */
public class FederationSliceAuthorityApi1 extends AbstractFederationApi1 {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(FederationSliceAuthorityApi1.class);
    protected static final String TYPE_SLICE = "SLICE";
    public static final String TYPE_PROJECT = "PROJECT";
    protected static final String FIELD_SLIVER_INFO_SLICE_URN = "SLIVER_INFO_SLICE_URN";
    protected static final String FIELD_SLIVER_INFO_URN = "SLIVER_INFO_URN";
    protected static final String FIELD_SLIVER_INFO_AGGREGATE_URN = "SLIVER_INFO_AGGREGATE_URN";
    protected static final String FIELD_SLIVER_INFO_CREATOR_URN = "SLIVER_INFO_CREATOR_URN";

    /**
     * A human readable name for the implemented API
     *
     * @return "Uniform Federation Slice Authority API v1";
     */
    static public String getApiName() {
        return "Uniform Federation Slice Authority API v1";
    }

    /**
     * A human readable name for the implemented API
     *
     * @return "Uniform Federation Slice Authority API v1";
     */
    @Override
    public String getName() {
        return getApiName();
    }

    public FederationSliceAuthorityApi1(@Nonnull Logger logger, RetrySettings retrySettings, @Nonnull JFedPreferences jFedPreferences) {
        super(logger, retrySettings, new ApiInfo.Api(ApiInfo.ApiName.GENI_CH_SA, 1), jFedPreferences);
    }

    public FederationSliceAuthorityApi1(@Nonnull Logger logger, @Nonnull JFedPreferences jFedPreferences) {
        this(logger, jFedPreferences.getRetrySettings(), jFedPreferences);
    }

    public static class GetVersionSAResult extends GetVersionResult {
        private final List<String> services;//only for Slice Authority
        private final List<String> roles;//only for Slice Authority

        public GetVersionSAResult(Object resultValueObject) throws BadReplyGeniException {
            super(resultValueObject, false/*v2*/);
            Map<String, Object> versionInfo = apiSpecifiesMapStringToObject(resultValueObject);
            services = new ArrayList<>(apiSpecifiesListOfString(versionInfo.get("SERVICES")));

            //roles is optional
            if (versionInfo.get("ROLES") != null)
                roles = new ArrayList<>(apiSpecifiesListOfString(versionInfo.get("ROLES")));
            else
                roles = Collections.emptyList();
        }

        public List<String> getServices() {
            return services;
        }

        public List<String> getRoles() {
            return roles;
        }

        @Override
        public String toString() {
            return "GetVersionSAResult{" +
                    "version='" + version + '\'' +
                    ", supportedCredentialTypes=" + supportedCredentialTypes +
                    ", fieldInfos=" + fieldInfos +
                    ", services=" + services +
                    ", roles=" + roles +
                    '}';
        }
    }

    @ApiMethod(order = 1, hint = "get_version call: Provide a structure detailing the version information as well as details of accepted options s for CH API calls.", unprotected = true)
    @Nonnull
    public FederationApiReply<GetVersionSAResult> getVersion(@Nonnull SfaConnection con) throws JFedException {
        return executeAndLogXmlRpcCommandGeni(null, con, "getVersion", "get_version",
                new ArrayList<>(), GetVersionSAResult::new);
    }

    public String getMethodObject(Method method) {
        switch (method.getName()) {
            case "createSlice":
            case "lookupSlices":
            case "updateSlice":
                return TYPE_SLICE;

            case "createProject":
            case "lookupProjects":
            case "updateProject":
                return TYPE_PROJECT;
            default:
                throw new IllegalArgumentException("The given method '" + method.getName() + "' was not expected");
        }
    }

    private static final List<String> API_OBJECTS = Collections.unmodifiableList(Arrays.asList(TYPE_SLICE, TYPE_PROJECT));

    @Override
    public List<String> getApiObjects() {
        return API_OBJECTS;
    }

    @Override
    public List<String> getRequiredApiServices() {
        return Collections.singletonList(TYPE_SLICE);
    }

    private static final List<String> OPTIONAL_API_SERVICES = Collections.unmodifiableList(Arrays.asList(
            TYPE_PROJECT,
            "SLICE_MEMBER",
            "PROJECT_MEMBER",
            "SLIVER_INFO"
    ));

    @Override
    public List<String> getOptionalApiServices() {
        return OPTIONAL_API_SERVICES;
    }

    protected static final String FIELD_SLICE_NAME = "SLICE_NAME";
    private static final List<GetVersionResult.FieldInfo> MINIMUM_SLICE_FIELDS =
            Collections.unmodifiableList(Arrays.asList(
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_URN", GetVersionResult.FieldInfo.FieldType.URN, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_UID", GetVersionResult.FieldInfo.FieldType.UID, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_CREATION", GetVersionResult.FieldInfo.FieldType.DATETIME, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, false, false),
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_EXPIRATION", GetVersionResult.FieldInfo.FieldType.DATETIME, GetVersionResult.FieldInfo.CreationAllowed.ALLOWED, false, true),
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_EXPIRED", GetVersionResult.FieldInfo.FieldType.BOOLEAN, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_SLICE, FIELD_SLICE_NAME, GetVersionResult.FieldInfo.FieldType.STRING, GetVersionResult.FieldInfo.CreationAllowed.REQUIRED, false, false),
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_DESCRIPTION", GetVersionResult.FieldInfo.FieldType.STRING, GetVersionResult.FieldInfo.CreationAllowed.ALLOWED, false, true),

                    //WARNING: special field: SLICE_PROJECT_URN only exists when the server supports the PROJECT service!
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_PROJECT_URN", GetVersionResult.FieldInfo.FieldType.URN, GetVersionResult.FieldInfo.CreationAllowed.REQUIRED, true, false)
            ));

    private static final List<GetVersionResult.FieldInfo> MINIMUM_PROJECT_FIELDS =
            Collections.unmodifiableList(Arrays.asList(
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_URN", GetVersionResult.FieldInfo.FieldType.URN, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_UID", GetVersionResult.FieldInfo.FieldType.UID, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_CREATION", GetVersionResult.FieldInfo.FieldType.DATETIME, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, false, false),
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_EXPIRATION", GetVersionResult.FieldInfo.FieldType.DATETIME, GetVersionResult.FieldInfo.CreationAllowed.ALLOWED, false, true),
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_EXPIRED", GetVersionResult.FieldInfo.FieldType.BOOLEAN, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_NAME", GetVersionResult.FieldInfo.FieldType.STRING, GetVersionResult.FieldInfo.CreationAllowed.REQUIRED, false, false),
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_DESCRIPTION", GetVersionResult.FieldInfo.FieldType.STRING, GetVersionResult.FieldInfo.CreationAllowed.ALLOWED, false, true)

            ));

    @Nonnull
    @Override
    public List<GetVersionResult.FieldInfo> getMinimumFields(String objectName) {
        if (objectName.equalsIgnoreCase(TYPE_SLICE)) {
            return MINIMUM_SLICE_FIELDS;
        } else if (objectName.equalsIgnoreCase(TYPE_PROJECT)) {
            return MINIMUM_PROJECT_FIELDS;
        } else
            throw new IllegalArgumentException(objectName + " is invalid");
    }


    @ApiMethod(order = 2, hint = "create_slice call:")
    @Nonnull
    public FederationApiReply<SliceInfo> createSlice(@Nonnull SfaConnection con,
                                                                     @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                     @Nonnull List<AnyCredential> credentialList,
                                                                     @ApiMethodParameter(name = "sliceName", parameterType = ApiMethodParameterType.STRING)
                                                                     @Nonnull String sliceName,
                                                                     @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API1_FIELDS)
                                                                     @Nonnull Map<String, String> fields,
                                                                     @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                     @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "sliceName", sliceName,
                        "fields", fields,
                        "extraOptions", extraOptions);

        List<Object> args = new ArrayList<>(2);
        args.add(createCredentialsListWithTypeAndVersion(credentialList));

        Map<String, Object> options = new HashMap<>();
        Map<String, Object> allFields = new HashMap<>(fields);
        assert !allFields.containsKey(FIELD_SLICE_NAME) :
                "Add SLICE_NAME with the sliceName option, not directly in the \"fields\" argument";
        allFields.put(FIELD_SLICE_NAME, sliceName);
        options.put("fields", allFields);
        if (extraOptions != null)
            options.putAll(extraOptions);
        args.add(options);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "createSlice", "create_slice", args,
                SliceInfo::new);
    }

    @ApiMethod(order = 3, hint = "lookup_slices call:")
    @Nonnull
    public FederationApiReply<SliceInfoList> lookupSlices(@Nonnull SfaConnection con,
                                                           @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                         @Nonnull List<AnyCredential> credentialList,
                                                           @ApiMethodParameter(name = "match", parameterType = ApiMethodParameterType.CH_API1_MATCH)
                                                         @Nullable Map<String, ?> match,
                                                           @ApiMethodParameter(name = "filter", parameterType = ApiMethodParameterType.CH_API1_FILTER)
                                                         @Nullable List<String> filter,
                                                           @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                         @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "match", match,
                        "filter", filter,
                        "extraOptions", extraOptions);
        return internalLookup(methodParams, con, "lookupSlices", "lookup_slices", credentialList, match, filter,
                extraOptions, null, SliceInfoList::new);
    }

    /**
     * normally returns nothing at all. Anything that might be returned anyway is converted to string, but normally a null Object will be returned.
     */
    @ApiMethod(order = 4, hint = "update_slice call:")
    @Nonnull
    public FederationApiReply<String> updateSlice(@Nonnull SfaConnection con,
                                                  @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                  @Nonnull List<AnyCredential> credentialList,
                                                  @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
                                                  @Nonnull GeniUrn sliceUrn,
                                                  @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API1_FIELDS)
                                                  @Nonnull Map<String, String> fields,
                                                  @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                  @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "sliceUrn", sliceUrn,
                        "fields", fields,
                        "extraOptions", extraOptions);
        return genericUpdateCall(methodParams, con, "updateSlice", "update_slice", credentialList, sliceUrn.toString(), "slice", fields, extraOptions);
    }

    @ApiMethod(order = 5, hint = "get_credentials call:")
    @Nonnull
    public FederationApiReply<List<AnyCredential>> getSliceCredentials(@Nonnull SfaConnection con,
                                                                       @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                       @Nonnull List<AnyCredential> credentialList,
                                                                       @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
                                                                       @Nonnull GeniUrn sliceUrn,
                                                                       @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                       @Nullable Map<String, String> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList, "sliceUrn", sliceUrn, "extraOptions", extraOptions);

        if (!Objects.equals(sliceUrn.getEncodedResourceType(), "slice"))
            LOG.warn("slice URN argument to getSliceCredentials is not a valid slice urn: \"" + sliceUrn + "\" (will be used anyway)");

        List<Object> args = new ArrayList<>(3);
        args.add(sliceUrn.toString());
        args.add(createCredentialsListWithTypeAndVersion(credentialList));
        args.add(createOptionsMap(extraOptions));

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "getSliceCredentials", "get_credentials", args,
                resultValueObject -> apiSpecifiesListOfCredentials("FederationSliceAuthorityApi1 getSliceCredentials", resultValueObject));

    }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// Slice Member Service Methods /////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /*
    Modify slice membership, adding, removing and changing roles of members with respect to given slice
    Arguments:
      Slice_urn: URN of slice for which to modify membership
      members_to_add: List of member_urn/role tuples for members to add to slice
      members_to_remove: List of member_urn of members to remove from slice
      members_to_change: List of member_urn/role tuples for members whose role should change as specified for given slice
    Return:
       None

    function modify_slice_membership (slice_urn, members_to_add, members_to_remove, members_to_modify, credentials, options)
    */

    public static class UrnRoleTuple {
        @Nonnull
        public final GeniUrn urn;
        @Nonnull
        public final String role;
        @Nullable
        public final Map dictRaw;

        public UrnRoleTuple(@Nonnull GeniUrn urn, @Nonnull String role) {
            this.dictRaw = null;
            this.urn = urn;
            this.role = role;
        }

        public UrnRoleTuple(@Nullable Map dictRaw, @Nonnull String urnKeyName, @Nonnull String roleKeyName) throws BadReplyGeniException {
            Map<String, Object> dict = apiSpecifiesMapStringToObject(dictRaw);
            String urnString = (String) dict.get(urnKeyName);
            if (urnString == null)
                throw new BadReplyGeniException("Dictionary should contain keys '" + urnKeyName + "' and '" + roleKeyName + ". It contains only: " + dict.keySet());

            try {
                this.urn = new GeniUrn(urnString);
            } catch (GeniUrn.GeniUrnParseException e) {
                throw new BadReplyGeniException("Could not parse URN '" + urnString + "'", e);
            }
            this.role = (String) dict.get(roleKeyName);
            if (role == null)
                throw new BadReplyGeniException("Dictionary should contain keys '" + urnKeyName + "' and '" + roleKeyName + ". It contains only: " + dict.keySet());
            this.dictRaw = dictRaw;
        }

        public List toList() {
            return Arrays.asList(urn.toString(), role);
        }

        @Nonnull
        public GeniUrn getUrn() {
            return urn;
        }

        @Nonnull
        public String getRole() {
            return role;
        }

        public boolean isExpired() {
            return dictRaw != null && dictRaw.containsKey("EXPIRED") && TextUtil.objectToBoolean(dictRaw.get("EXPIRED"));
        }

        @Override
        public String toString() {
            return "UrnRoleTuple{" +
                    "urn=\"" + urn +
                    "\", role=\"" + role + "\"" +
                    '}';
        }
    }

    @ApiMethod(order = 6, hint = "modify_slice_membership call:")
    @Nonnull
    public FederationApiReply<String> modifySliceMembership(@Nonnull SfaConnection con,
                                                            @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
                                                            @Nonnull GeniUrn sliceUrn,
                                                            @ApiMethodParameter(name = "membersToAdd", parameterType = ApiMethodParameterType.CH_API_LIST_MEMBER_TUPLES)
                                                            @Nonnull List<UrnRoleTuple> membersToAdd,
                                                            @ApiMethodParameter(name = "membersToRemove", parameterType = ApiMethodParameterType.LIST_OF_URN_STRING)
                                                            @Nonnull List<GeniUrn> membersToRemove,
                                                            @ApiMethodParameter(name = "membersToChange", parameterType = ApiMethodParameterType.CH_API_LIST_MEMBER_TUPLES)
                                                            @Nonnull List<UrnRoleTuple> membersToChange,
                                                            @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                            @Nonnull List<AnyCredential> credentialList,
                                                            @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                            @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("sliceUrn", sliceUrn,
                "membersToAdd", membersToAdd,
                "membersToRemove", membersToRemove,
                "membersToChange", membersToChange,
                "credentialList", credentialList,
                "extraOptions", extraOptions);

        List<Object> args = new ArrayList<>(6);
        args.add(sliceUrn.getValue());

        List membersToAddList =
                membersToAdd.stream().map(UrnRoleTuple::toList).collect(Collectors.toList());
        args.add(membersToAddList);

        List membersToRemoveList =
                membersToRemove.stream().map(GeniUrn::toString).collect(Collectors.toList());
        args.add(membersToRemoveList);

        List membersToChangeList =
                membersToChange.stream().map(UrnRoleTuple::toList).collect(Collectors.toList());
        args.add(membersToChangeList);

        List credentials = createCredentialsListWithTypeAndVersion(credentialList);
        args.add(credentials);

        Map<String, Object> options = createOptionsMap(extraOptions);
        args.add(options);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "modifySliceMembership", "modify_slice_membership",
                args, STRING_REPLY_CONVERTER);
    }

    /*
   Lookup members of given slice and their roles within that slice
   Arguments:
     slice_urn: URN of slice for which to provide current members and roles
   Return:
      Dictionary of member_urn/role pairs {‘SLICE_MEMBER’: member_urn, ‘SLICE_ROLE’: role } where ‘role’ is a string of the role name

    function lookup_slice_members (slice_urn, credentials, options)
    */
    @ApiMethod(order = 7, hint = "lookup_slice_members call:")
    @Nonnull
    public FederationApiReply<List<UrnRoleTuple>> lookupSliceMembers(@Nonnull SfaConnection con,
                                                                     @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
                                                                     @Nonnull GeniUrn sliceUrn,
                                                                     @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                     @Nonnull List<AnyCredential> credentialList,
                                                                     @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("credentialList", credentialList,
                "sliceUrn", sliceUrn,
                "extraOptions", extraOptions);

        List<Object> args = new ArrayList<>();
        args.add(sliceUrn.toString());
        args.add(createCredentialsListWithTypeAndVersion(credentialList));
        if (extraOptions != null)
            args.add(extraOptions);
        else
            args.add(Collections.emptyMap());


        return executeAndLogXmlRpcCommandGeni(methodParams, con, "lookupSliceMembers", "lookup_slice_members", args,
                resultValueObject -> processUrnRoleTuplesReply(resultValueObject, "SLICE_MEMBER", "SLICE_ROLE"));
    }

    @Nonnull
    private static List<UrnRoleTuple> processUrnRoleTuplesReply(Object resultValueObject, String urnKeyName, String roleKeyName) throws BadReplyGeniException {
        List<Map> resDicts = apiSpecifiesListOfT(Map.class, resultValueObject);
        List<UrnRoleTuple> resList = new ArrayList<>();
        for (Map dict : resDicts) {
            resList.add(new UrnRoleTuple(dict, urnKeyName, roleKeyName));
        }
        return resList;
    }

    /*
    Lookup slices for which the given member belongs
    Arguments:
      Member_urn: The member for whom to find slices to which it belongs
    Return:
       Dictionary of slice_urn/role pairs (‘SLICE_URN’ : slice_urn, ‘SLICE_ROLE’ : role} where role is a string of the role name

       function lookup_slices_for_member(member_urn, credentials, options)
    */
    @ApiMethod(order = 8, hint = "lookup_slices_for_member call:")
    @Nonnull
    public FederationApiReply<List<UrnRoleTuple>> lookupSlicesForMember(@Nonnull SfaConnection con,
                                                                        @ApiMethodParameter(name = "memberUrn", parameterType = ApiMethodParameterType.USER_URN)
                                                                        @Nonnull GeniUrn memberUrn,
                                                                        @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                        @Nonnull List<AnyCredential> credentialList,
                                                                        @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                        @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "memberUrn", memberUrn,
                        "extraOptions", extraOptions);


        List<Object> args = new ArrayList<>();
        args.add(memberUrn.toString());
        args.add(createCredentialsListWithTypeAndVersion(credentialList));
        if (extraOptions != null)
            args.add(extraOptions);
        else
            args.add(Collections.emptyMap());

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "lookupSlicesForMember", "lookup_slices_for_member", args,
                resultValueObject -> processUrnRoleTuplesReply(resultValueObject, "SLICE_URN", "SLICE_ROLE"));
    }


    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////// Sliver Info Service API /////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /*
        # Create a record of a sliver creation
        #
        # Arguments:
        #   options: 'fields' containing the fields for the sliver info  being registered at SA
        #
        # Return:
        # Dictionary of name/value pairs for created sliver_info record
    */
    @ApiMethod(order = 9, hint = "register_aggregate call:")
    @Nonnull
    public FederationApiReply<SliverInfo> createSliverInfo(@Nonnull SfaConnection con,
                                                                          @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                          @Nonnull List<AnyCredential> credentialList,
                                                                          @ApiMethodParameter(name = "sliceUrn", hint = "convenience argument: SLIVER_INFO_SLICE_URN to add to fields option", parameterType = ApiMethodParameterType.SLICE_URN)
                                                                          @Nullable String sliceUrn,
                                                                          @ApiMethodParameter(name = "sliverUrn", hint = "convenience argument: SLIVER_INFO_URN to add to fields option", parameterType = ApiMethodParameterType.URN)
                                                                          @Nullable String sliverUrn,
                                                                          @ApiMethodParameter(name = "aggregateUrn", hint = "convenience argument: SLIVER_INFO_AGGREGATE_URN to add to fields option", parameterType = ApiMethodParameterType.URN)
                                                                          @Nullable String aggregateUrn,
                                                                          @ApiMethodParameter(name = "creatorUrn", hint = "convenience argument: SLIVER_INFO_CREATOR_URN to add to fields option", parameterType = ApiMethodParameterType.URN)
                                                                          @Nullable String creatorUrn,
                                                                          @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API1_FIELDS)
                                                                          @Nonnull Map<String, String> fields,
                                                                          @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                          @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("credentialList", credentialList, "sliceUrn", sliceUrn,
                "sliverUrn", sliverUrn, "aggregateUrn", aggregateUrn, "creatorUrn", creatorUrn, "fields", fields, "extraOptions", extraOptions);

        List<Object> args = new ArrayList<>(2);
        args.add(createCredentialsListWithTypeAndVersion(credentialList));

        assert fields.containsKey(FIELD_SLIVER_INFO_SLICE_URN) == (sliceUrn == null) :
                "Either specify sliceUrn (non-null) or add \"SLIVER_INFO_SLICE_URN\" to \"fields\". (exactly 1 is required)";
        assert fields.containsKey(FIELD_SLIVER_INFO_URN) == (sliverUrn == null) :
                "Either specify sliverUrn (non-null) or add \"SLIVER_INFO_URN\" to \"fields\". (exactly 1 is required)";
        assert fields.containsKey(FIELD_SLIVER_INFO_AGGREGATE_URN) == (aggregateUrn == null) :
                "Either specify aggregateUrn (non-null) or add \"SLIVER_INFO_AGGREGATE_URN\" to \"fields\". (exactly 1 is required)";
        assert fields.containsKey(FIELD_SLIVER_INFO_CREATOR_URN) == (creatorUrn == null) :
                "Either specify creatorUrn (non-null) or add \"SLIVER_INFO_CREATOR_URN\" to \"fields\". (exactly 1 is required)";

        if (sliceUrn != null)
            fields.put(FIELD_SLIVER_INFO_SLICE_URN, sliceUrn);
        if (sliverUrn != null)
            fields.put(FIELD_SLIVER_INFO_URN, sliverUrn);
        if (aggregateUrn != null)
            fields.put(FIELD_SLIVER_INFO_AGGREGATE_URN, aggregateUrn);
        if (creatorUrn != null)
            fields.put(FIELD_SLIVER_INFO_CREATOR_URN, creatorUrn);

        Map<String, Object> optionsMap = new HashMap<>();
        optionsMap.put("fields", fields);
        if (extraOptions != null)
            optionsMap.putAll(extraOptions);
        args.add(optionsMap);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "createSliverInfo", "create_sliver_info",
                args, SliverInfo::new);
    }

    /*
         # Delete a sliver_info record
         #
         # Arguments:
         #    sliver_urn: urn of sliver whose record is to be deleted
         #
         # Return:
         #   True if succeeded
         #
         #Should return ARGUMENT_ERROR if no such sliver urn is registered
         def delete_sliver_info(sliver_urn, credentials, options)
    */
    @ApiMethod(order = 10, hint = "delete_sliver_info call:")
    @Nonnull
    public FederationApiReply<String> deleteSliverInfo(@Nonnull SfaConnection con,
                                                       @ApiMethodParameter(name = "sliverUrn", parameterType = ApiMethodParameterType.URN)
                                                       @Nonnull GeniUrn sliverUrn,
                                                       @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                       @Nonnull List<AnyCredential> credentialList,
                                                       @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                       @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "sliverUrn", sliverUrn,
                        "extraOptions", extraOptions);

        List<Object> args = new ArrayList<>(3);
        args.add(sliverUrn);
        args.add(createCredentialsListWithTypeAndVersion(credentialList));
        args.add(createOptionsMap(extraOptions));

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "deleteSliverInfo", "delete_sliver_info",
                args, STRING_REPLY_CONVERTER);
    }


    /*
        # Lookup sliver_info for given match criteria return fields in given filter criteria
        #
        # Arguments:
        #   options: 'match' for query match criteria, 'filter' for fields to be returned
        #
        # Return:
        #    Dictionary (indexed by sliver_urn) of dictionaries containing name/value pairs for all sliver_infos registered at this SA matching given criteria.
        def lookup_sliver_info(credentials, options)
    */
    @ApiMethod(order = 11, hint = "lookup_sliver_info call:")
    @Nonnull
    public FederationApiReply<SliverInfoList> lookupSliverInfo(@Nonnull SfaConnection con,
                                                               @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                             @Nonnull List<AnyCredential> credentialList,
                                                               @ApiMethodParameter(name = "sliceUrn", hint = "convenience argument: SLIVER_INFO_SLICE_URN to add to match argument", parameterType = ApiMethodParameterType.SLICE_URN)
                                                             @Nullable GeniUrn sliceUrn,
                                                               @ApiMethodParameter(name = "match", parameterType = ApiMethodParameterType.CH_API1_MATCH)
                                                             @Nullable Map<String, ?> match,
                                                               @ApiMethodParameter(name = "filter", parameterType = ApiMethodParameterType.CH_API1_FILTER)
                                                             @Nullable List<String> filter,
                                                               @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                             @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "sliceUrn", sliceUrn,
                        "match", match,
                        "filter", filter,
                        "extraOptions", extraOptions);
        assert match != null || sliceUrn != null;

        Map<String, Object> matchMap;
        if (match == null)
            matchMap = new HashMap<>();
        else
            matchMap = new HashMap<>(match);

        if (sliceUrn != null)
            matchMap.put(FIELD_SLIVER_INFO_SLICE_URN, sliceUrn.getValue());

        return internalLookup(methodParams, con, "lookupSliverInfo", "lookup_sliver_info",
                credentialList, matchMap, filter, extraOptions, null, SliverInfoList::new);
    }

    /*
        # Update the details of a sliver_info record
        #
        #Arguments:
        #   sliver_urn: urn of sliver for which to update
        #   options: 'fields' containing fields for sliver_infos that are permitted for update
        #
        # Return:
        #   True if succeeded
        #
        # Should return ARGUMENT_ERROR if no such sliver_urn is found
        def update_sliver_info(sliver_urn, credentials, options)
    */
    @ApiMethod(order = 12, hint = "lookup_sliver_info call:")
    @Nonnull
    public FederationApiReply<String> updateSliverInfo(@Nonnull SfaConnection con,
                                                       @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                       @Nonnull List<AnyCredential> credentialList,
                                                       @ApiMethodParameter(name = "sliceUrn", hint = "URN of sliver to update", parameterType = ApiMethodParameterType.SLICE_URN)
                                                       @Nonnull GeniUrn sliceUrn,
                                                       @ApiMethodParameter(name = "fields", hint = "fields top update", parameterType = ApiMethodParameterType.CH_API1_FIELDS)
                                                       @Nonnull Map<String, String> fields,
                                                       @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                       @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "sliceUrn", sliceUrn,
                        "fields", fields,
                        "extraOptions", extraOptions);
        return genericUpdateCall(methodParams, con, "updateSliverInfo", "update_sliver_info",
                credentialList, sliceUrn.getValue(), "slice", fields, extraOptions, null);
    }


    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////// Project Service Methods /////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /*
        Create project with given details. See generic create_* description above.
        Arguments:
           Options: Dictionary of name/value pairs for newly created project.
        Return:
           Dictionary of name/value pairs of newly created project including urn

        function create_project(credentials, options)
    */
    @ApiMethod(order = 13, hint = "create_project call:")
    @Nonnull
    public FederationApiReply<ProjectInfo> createProject(@Nonnull SfaConnection con,
                                                                       @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                       @Nonnull List<AnyCredential> credentialList,
                                                                       @ApiMethodParameter(name = "projectName", required = false, parameterType = ApiMethodParameterType.STRING)
                                                                       @Nullable String projectName,
                                                                       @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API1_FIELDS)
                                                                       @Nonnull Map<String, String> fields,
                                                                       @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                       @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("credentialList", credentialList,
                "projectName", projectName,
                "fields", fields,
                "extraOptions", extraOptions);


        List<Object> args = new ArrayList<>(2);
        args.add(createCredentialsListWithTypeAndVersion(credentialList));

        assert fields.containsKey("PROJECT_NAME") == (projectName == null) :
                "Either specify projectName (non-null) or add \"PROJECT_NAME\" to \"fields\". (exactly 1 is required)";
        if (projectName != null)
            fields.put("PROJECT_NAME", projectName);
        Map<String, Object> optionsMap = new HashMap<>();
        optionsMap.put("fields", fields);
        if (extraOptions != null)
            optionsMap.putAll(extraOptions);
        args.add(optionsMap);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "createProject", "create_project",
                args, ProjectInfo::new);
    }



/*
Lookup project detail for projects matching ‘match options.
‘filter options indicate what detail to provide.
Arguments:
options: What details to provide (filter options) for which members (match options)
Return: Dictionary of name/value pairs from ‘filter’ options for each project matching ‘match’ option criteria.

function lookup_projects (credentials, options)
*/

    @ApiMethod(order = 14, hint = "lookup_projects call:")
    @Nonnull
    public FederationApiReply<ProjectInfoList> lookupProjects(@Nonnull SfaConnection con,
                                                             @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                           @Nonnull List<AnyCredential> credentialList,
                                                             @ApiMethodParameter(name = "match", parameterType = ApiMethodParameterType.CH_API1_MATCH)
                                                           @Nullable Map<String, ?> match,
                                                             @ApiMethodParameter(name = "filter", parameterType = ApiMethodParameterType.CH_API1_FILTER)
                                                           @Nullable List<String> filter,
                                                             @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                           @Nullable Map<String, Object> extraOptions) throws JFedException {

        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "match", match,
                        "filter", filter,
                        "extraOptions", extraOptions);
        return internalLookup(methodParams, con, "lookupProjects", "lookup_projects",
                credentialList, match, filter, extraOptions, null, ProjectInfoList::new);
    }

    /*
    Update fields in given project object, as allowed in Get_version advertisement. See generic update_* description above.
    Arguments:
    project_urn: URN of project to update
    Options: What details to update (key/value pairs)
    Return: None
    function update_project(project_urn, credentials, options)
    */

    /**
     * @return Normally nothing at all. Anything that might be returned anyway is converted to string, but normally a null Object will be returned.
     */
    @ApiMethod(order = 15, hint = "update_project call:")
    @Nonnull
    public FederationApiReply<String> updateProject(@Nonnull SfaConnection con,
                                                    @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                    @Nonnull List<AnyCredential> credentialList,
                                                    @ApiMethodParameter(name = "projectUrn", parameterType = ApiMethodParameterType.URN)
                                                    @Nonnull GeniUrn projectUrn,
                                                    @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API1_FIELDS)
                                                    @Nonnull Map<String, String> fields,
                                                    @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false,
                                                            parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                    @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "projectUrn", projectUrn,
                        "fields", fields,
                        "extraOptions", extraOptions);
        return genericUpdateCall(methodParams, con, "updateProject", "update_project", credentialList, projectUrn.getValue(), "project", fields, extraOptions);
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// Project Member Service Methods /////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /*
    Modify project membership, adding, removing and changing roles of members with respect to given project
    Arguments:
      project_urn: Name of project for which to modify membership
      members_to_add: List of member_urn/role tuples for members to add to project
      members_to_remove: List of member_urn of members to remove from project
      members_to_change: List of member_urn/role tuples for members whose role should change as specified for given project
    Return:
       None

        function modify_project_membership (project_urn, members_to_add, members_to_remove, members_to_modify, credentials, options)
    */
    @ApiMethod(order = 16, hint = "modify_project_membership call:")
    @Nonnull
    public FederationApiReply<String> modifyProjectMembership(@Nonnull SfaConnection con,
                                                              @ApiMethodParameter(name = "projectUrn", parameterType = ApiMethodParameterType.URN)
                                                              @Nonnull GeniUrn projectUrn,
                                                              @ApiMethodParameter(name = "membersToAdd", parameterType = ApiMethodParameterType.CH_API_LIST_MEMBER_TUPLES)
                                                              @Nonnull List<UrnRoleTuple> membersToAdd,
                                                              @ApiMethodParameter(name = "membersToRemove", parameterType = ApiMethodParameterType.LIST_OF_URN_STRING)
                                                              @Nonnull List<GeniUrn> membersToRemove,
                                                              @ApiMethodParameter(name = "membersToChange", parameterType = ApiMethodParameterType.CH_API_LIST_MEMBER_TUPLES)
                                                              @Nonnull List<UrnRoleTuple> membersToChange,
                                                              @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                              @Nonnull List<AnyCredential> credentialList,
                                                              @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                              @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("projectUrn", projectUrn,
                "membersToAdd", membersToAdd,
                "membersToRemove", membersToRemove,
                "membersToChange", membersToChange,
                "credentialList", credentialList,
                "extraOptions", extraOptions);

        List<Object> args = new ArrayList<>(6);
        args.add(projectUrn.getValue());

        List membersToAddList =
                membersToAdd.stream().map(UrnRoleTuple::toList).collect(Collectors.toList());
        args.add(membersToAddList);

        List membersToRemoveList =
                membersToRemove.stream().map(GeniUrn::toString).collect(Collectors.toList());
        args.add(membersToRemoveList);

        List membersToChangeList =
                membersToChange.stream().map(UrnRoleTuple::toList).collect(Collectors.toList());
        args.add(membersToChangeList);

        args.add(createCredentialsListWithTypeAndVersion(credentialList));
        args.add(createOptionsMap(extraOptions));

        //should return nothing at all, but if there is anything, we convert it to string and return it
        return executeAndLogXmlRpcCommandGeni(methodParams, con, "modifyProjectMembership", "modify_project_membership",
                args, STRING_REPLY_CONVERTER);
    }

    /*
    Lookup members of given project and their roles within that project
    Arguments:
      project_urn: project_urn for which to provide current members and roles
    Return:
       Dictionary of member_urn/role pairs

        function lookup_project_members (project_urn, credentials, options)
    */
    @ApiMethod(order = 17, hint = "lookup_project_members call: Lookup members of given project and their roles within that project")
    @Nonnull
    public FederationApiReply<List<UrnRoleTuple>> lookupProjectMembers(@Nonnull SfaConnection con,
                                                                       @ApiMethodParameter(name = "projectUrn", hint = "project_urn for which to provide current members and roles", parameterType = ApiMethodParameterType.URN)
                                                                       @Nonnull GeniUrn projectUrn,
                                                                       @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                       @Nonnull List<AnyCredential> credentialList,
                                                                       @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                       @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "projectUrn", projectUrn,
                        "extraOptions", extraOptions);

        List<Object> args = new ArrayList<>();
        args.add(projectUrn.getValue());
        args.add(createCredentialsListWithTypeAndVersion(credentialList));
        args.add(createOptionsMap(extraOptions));

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "lookupProjectMembers", "lookup_project_members",
                args, resultValueObject -> processUrnRoleTuplesReply(resultValueObject, "PROJECT_MEMBER", "PROJECT_ROLE"));
    }


    @Nonnull
    public FederationApiReply<List<UrnRoleTuple>> lookupProjectsForMember(@Nonnull SfaConnection con,
                                                                          @ApiMethodParameter(name = "memberUrn", parameterType = ApiMethodParameterType.URN)
                                                                          @Nonnull GeniUrn memberUrn,
                                                                          @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                          @Nonnull List<AnyCredential> credentialList,
                                                                          @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false,
                                                                                  parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                          @Nullable Map<String, Object> extraOptions) throws JFedException {
        return lookupProjectsForMember(con, memberUrn, credentialList, null, null, extraOptions);
    }

    /*
    Lookup projects for which the given member belongs
    Arguments:
      Member_urn: The member for whom to find projects to which it belongs
    Return:
       Dictionary of project_urn/role pairs (‘PROJECT_URN’ : project_urn, ‘PROJECT_ROLE’ : role} where role is a string of the role name

       function lookup_projects_for_member(member_urn, credentials, options)
    */
    @ApiMethod(order = 18, hint = "lookup_projects_for_member call  (doesn't actually support match or filter, but you can provide them anyway):")
    @Nonnull
    public FederationApiReply<List<UrnRoleTuple>> lookupProjectsForMember(@Nonnull SfaConnection con,
                                                                          @ApiMethodParameter(name = "memberUrn", parameterType = ApiMethodParameterType.URN)
                                                                          @Nonnull GeniUrn memberUrn,
                                                                          @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                          @Nonnull List<AnyCredential> credentialList,
                                                                          @ApiMethodParameter(name = "match", parameterType = ApiMethodParameterType.CH_API1_MATCH)
                                                                          @Nullable Map<String, ?> match,
                                                                          @ApiMethodParameter(name = "filter", parameterType = ApiMethodParameterType.CH_API1_FILTER)
                                                                          @Nullable List<String> filter,
                                                                          @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                          @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "memberUrn", memberUrn,
                        "extraOptions", extraOptions);

        List<Object> args = new ArrayList<>();
        args.add(memberUrn.toString());
        args.add(createCredentialsListWithTypeAndVersion(credentialList));
        Map<String, Object> options = new HashMap<>();
        if (match != null) {
            Map<String, Object> newMatch = new HashMap<>(match);
            if (match.containsKey("EXPIRED")) {
                Object t = match.get("EXPIRED");
                newMatch.put("EXPIRED", TextUtil.objectToBoolean(t));
            }
            options.put("match", newMatch);
        }
        if (filter != null) {
            options.put("filter", filter);
        }
        if (extraOptions != null)
            options.putAll(extraOptions);
        args.add(options);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "lookupProjectsForMember", "lookup_projects_for_member",
                args, resultValueObject -> processUrnRoleTuplesReply(resultValueObject, "PROJECT_URN", "PROJECT_ROLE"));
    }


}
