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 org.slf4j.LoggerFactory;

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

/**
 * AbstractGeniClearingHouseApi
 */
public abstract class AbstractFederationApi1 extends AbstractFederationApi {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(AbstractFederationApi1.class);

    public AbstractFederationApi1(Logger logger, RetrySettings retrySettings, ApiInfo.Api api, JFedPreferences jFedPreferences) {
        super(logger, retrySettings, api, jFedPreferences);
    }

    public abstract String getMethodObject(Method method);

    @Nonnull
    protected <T> FederationApiReply<LookupResult<T>> genericLookup(Map<String, Object> methodParams,
                                                            @Nonnull SfaConnection con,
                                                            @Nonnull String methodJavaName,
                                                            @Nonnull String methodGeniName,
                                                            @Nullable List<AnyCredential> credentialList,
                                                            @Nullable Map<String, ?> match,
                                                            @Nullable List<String> filter,
                                                            @Nullable Map<String, Object> extraOptions,
                                                            @Nullable List<Object> extraArguments,
                                                            @Nonnull LookupResultConverter<T> lookupResultConverter) throws JFedException {
        List<Object> args = new ArrayList<>();
        if (extraArguments != null)
            extraArguments.forEach(args::add);

        if (credentialList != null) {
            args.add(createCredentialsListWithTypeAndVersion(credentialList));
        }


        Map<String, Object> options = new HashMap<>();
        if (match != null)
            options.put("match",match);
        if (filter != null/* && !filter.isEmpty()*/)
            options.put("filter", filter);
        if (extraOptions != null)
            options.putAll(extraOptions);
        args.add(options);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, methodJavaName, methodGeniName, args,
                resultValueObject -> new LookupResult<>(resultValueObject, lookupResultConverter));
    }

    @Nonnull
    protected <R> FederationApiReply<R> internalLookup(@Nullable Map<String, Object> methodParams,
                                                       @Nonnull SfaConnection con,
                                                       @Nonnull String methodJavaName,
                                                       @Nonnull String methodGeniName,
                                                       @Nullable List<AnyCredential> credentialList,
                                                       @Nullable Map<String, ?> match,
                                                       @Nullable List<String> filter,
                                                       @Nullable Map<String, Object> extraOptions,
                                                       @Nullable List<Object> extraArguments /* extraArguments are added in front of call*/,
                                                       @Nonnull ReplyConverter<R> replyConverter) throws JFedException {
        if (methodParams != null) methodParams.put("type", methodGeniName);

        List<Object> args = new ArrayList<>();
        if (extraArguments != null)
            extraArguments.forEach(args::add);

        if (credentialList != null) {
            args.add(createCredentialsListWithTypeAndVersion(credentialList));
        }

        Map<String, Object> options = createOptionsMap(extraOptions);
        if (match != null)
            options.put("match", match);
        if (filter != null)
            options.put("filter", filter);
        args.add(options);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, methodJavaName, methodGeniName, args,
                replyConverter);
    }

    /**
     * normally returns nothing at all. Anything that might be returned anyway is converted to string, but normally a null Object will be returned.
     */
    @Nonnull
    public FederationApiReply<String> genericUpdateCall(Map<String, Object> methodParams,
                                                        SfaConnection con,
                                                        @Nonnull String methodJavaName,
                                                        @Nonnull String methodGeniName,
                                                        @Nonnull List<AnyCredential> credentialList,
                                                        @Nonnull String urn,
                                                        @Nullable String urnType,
                                                        @Nonnull Map<String, String> fields,
                                                        @Nullable Map<String, Object> extraOptions) throws JFedException {
        return genericUpdateCall(methodParams, con, methodJavaName, methodGeniName, credentialList, urn, urnType, fields, extraOptions, null);
    }

    @Nonnull
    public FederationApiReply<String> genericUpdateCall(Map<String, Object> methodParams,
                                                        SfaConnection con,
                                                        @Nonnull String methodJavaName,
                                                        @Nonnull String methodGeniName,
                                                        @Nonnull List<AnyCredential> credentialList,
                                                        @Nonnull String urn,
                                                        @Nullable String urnType,
                                                        @Nonnull Map<String, String> fields,
                                                        @Nullable Map<String, Object> extraOptions,
                                                        @Nullable List<Object> extraArguments /* extraArguments are added in front of call*/) throws JFedException {
        assert !fields.isEmpty();

        GeniUrn geniUrn = GeniUrn.parse(urn);
        if (geniUrn == null || (urnType != null && !Objects.equals(geniUrn.getEncodedResourceType(), urnType)))
            LOG.error("WARNING: URN argument to " + methodJavaName + " is not a valid " + (urnType == null ? ""
                    : urnType) + " urn: \"" + urn + "\" (will be used anyway)");

        List<Object> args = new ArrayList<>();
        if (extraArguments != null)
            extraArguments.forEach(args::add);

        args.add(urn);
        args.add(createCredentialsListWithTypeAndVersion(credentialList));

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

        args.add(options);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, methodJavaName, methodGeniName, args, STRING_REPLY_CONVERTER);
    }
}
