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.api_wrapper.AggregateManagerWrapper;
import be.iminds.ilabt.jfed.lowlevel.connection.SfaConnection;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;

/**
 * @author Wim Van de Meerssche
 * @see <a href="http://groups.geni.net/geni/wiki/GAPI_AM_API_V2">Geni Aggregate Manager API v2</a>
 */
public class ProtoGeniAMExtensions extends AbstractGeniAggregateManager {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ProtoGeniAMExtensions.class);

    /**
     * Construct a new ProtoGeniAMExtensions, with a certain logger.
     * <p/>
     * <p>This class stores no internal state, except for the logger used. It sends all results and debug info to this {@link be.iminds.ilabt.jfed.log.Logger} if it is non null.
     *
     * @param logger        the logger to use. May be null if no logger is used.
     * @param retrySettings the retrySettings to use, or null to use the static globalRetrySettings in AbstractApi
     */
    public ProtoGeniAMExtensions(@Nonnull Logger logger, @Nonnull RetrySettings retrySettings, @Nonnull JFedPreferences jFedPreferences) {
        super(logger, retrySettings, new ApiInfo.Api(ApiInfo.ApiName.GENI_AM, 2), jFedPreferences);
    }

    /**
     * Construct a new ProtoGeniAMExtensions, with a certain logger.
     * <p/>
     * <p>This class stores no internal state, except for the logger used. It sends all results and debug info to this {@link be.iminds.ilabt.jfed.log.Logger} if it is non null.
     * <p/>
     * <p>This constructor sets autoRetryBusy to true, so "busy" replies are retried.
     *
     * @param logger the logger to use. May be null if no logger is used.
     */
    public ProtoGeniAMExtensions(@Nonnull Logger logger, @Nonnull JFedPreferences jFedPreferences) {
        this(logger, jFedPreferences.getRetrySettings(), jFedPreferences);
    }

    /**
     * A human readable name for the implemented API
     *
     * @return "ProtoGeni Aggregate Manager API Extensions";
     */
    static public String getApiName() {
        return "ProtoGeni Aggregate Manager API Extensions";
    }

    /**
     * A human readable name for the implemented API
     *
     * @return "ProtoGeni Aggregate Manager API Extensions";
     */
    @Override
    public String getName() {
        return getApiName();
    }

    @Override
    @Nonnull
    public AggregateManagerReply<? extends AbstractVersionInfo> getVersion(@Nonnull SfaConnection con) throws JFedException {
        throw new RuntimeException("not implemented here");
    }

    /**
     * ListImages
     * <p/>
     * example return
     * "value": [
     * {
     * "urn": "urn:publicid:IDN+wall2.ilabt.iminds.be+image+wall2-ilabt-iminds-be:testImage",
     * "url": "https://www.wall2.ilabt.iminds.be/image_metadata.php?uuid=adf41cd4-c3dd-11e3-b407-001517becdc1"
     * }
     * ]
     */
    @ApiMethod(order = 1, hint = "ListImages call")
    @Nonnull
    public AggregateManagerReply<List<ImageInfo>> listImages(@Nonnull SfaConnection con,
                                                             @ApiMethodParameter(name = "credentialList", hint = "mandatory list of credentials. this must include a valid user credential.")
                                                             @Nonnull List<AnyCredential> credentialList,
                                                             @ApiMethodParameter(name = "userUrn", hint = "user to list images for", parameterType = ApiMethodParameterType.USER_URN)
                                                             @Nonnull String userUrn,
                                                             @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, "userUrn", userUrn, "extraOptions", extraOptions);

        Objects.requireNonNull(credentialList, "Illegal argument: " + "credentialList" + " is not allowed to be null");
        Objects.requireNonNull(userUrn, "Illegal argument: " + "userUrn" + " is not allowed to be null");

        List<Object> args = new ArrayList<>(3);
        args.add( userUrn);
        args.add( AggregateManager2.createCredentialsList(credentialList));
        args.add( createOptionsMap(extraOptions));

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "listImages", "ListImages",
                args, resultValueObject -> {
                    List<Map> v = apiSpecifiesListOfT(Map.class, resultValueObject);
                    List<ImageInfo> listImageResult = new ArrayList<>();
                    for (Map ht : v) {
                        Map<String, String> htss = apiSpecifiesMapStringToString(ht);
                        ImageInfo imageInfo = new ImageInfo(htss);
                        listImageResult.add(imageInfo);
                    }

                    return listImageResult;
                });
    }

    /**
     * DeleteImage
     * <p/>
     * example return:
     * "     value": true
     */
    @ApiMethod(order = 2, hint = "DeleteImage call")
    @Nonnull
    public AggregateManagerReply<Boolean> deleteImage(@Nonnull SfaConnection con,
                                                      @ApiMethodParameter(name = "credentialList", hint = "mandatory list of credentials. this must include a valid user credential.")
                                                      @Nonnull List<AnyCredential> credentialList,
                                                      @ApiMethodParameter(name = "imageUrn", hint = "image URN to delete", parameterType = ApiMethodParameterType.URN)
                                                      @Nonnull String imageUrn,
                                                      @ApiMethodParameter(name = "creatorUrn", required = false, hint = "Optional: creator user URN, needed if image not owned by calling user", parameterType = ApiMethodParameterType.USER_URN)
                                                      @Nullable String creatorUrn,
                                                      @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,
                        "imageUrn", imageUrn,
                        "creatorUrn", creatorUrn,
                        "extraOptions", extraOptions);

        Objects.requireNonNull(credentialList, "Illegal argument: " + "credentialList" + " is not allowed to be null");
        Objects.requireNonNull(imageUrn, "Illegal argument: " + "imageUrn" + " is not allowed to be null");

        List<String> credentialsList = AggregateManager2.createCredentialsList(credentialList);

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

        List<Object> args = new ArrayList<>(3);
        args.add( imageUrn);
        args.add( credentialsList);
        args.add( options);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "deleteImage", "DeleteImage",
                args, BOOLEAN_REPLY_CONVERTER);
    }

    /**
     * CreateImage
     */
    @ApiMethod(order = 3, hint = "CreateImage call")
    @Nonnull
    public AggregateManagerReply<ImageInfo> createImage(@Nonnull SfaConnection con,
                                                        @ApiMethodParameter(name = "credentialList", hint = "mandatory list of credentials.")
                                                        @Nonnull List<AnyCredential> credentialList,
                                                        @ApiMethodParameter(name = "sliceUrn", hint = "slice URN", parameterType = ApiMethodParameterType.SLICE_URN)
                                                        @Nonnull String sliceUrn,
                                                        @ApiMethodParameter(name = "imageName", hint = "image name", parameterType = ApiMethodParameterType.STRING)
                                                        @Nonnull String imageName,
                                                        @ApiMethodParameter(name = "sliverUrn", hint = "sliver URN (= URN of node)", parameterType = ApiMethodParameterType.URN)
                                                        @Nonnull String sliverUrn,
                                                        @ApiMethodParameter(name = "global", required = false, hint = "optional: global flag (if global is true, the image is public, otherwise, it is private)", parameterType = ApiMethodParameterType.BOOLEAN)
                                                        @Nullable Boolean global,
                                                        @ApiMethodParameter(name = "updatePrepare", required = false,
                                                                hint = "optional: update_prepare flag (if true, the prepare -M option " +
                                                                        "is used for image creation, which will cause system users to be saved in the image, default is false)",
                                                                parameterType = ApiMethodParameterType.BOOLEAN, guiDefault = "false")
                                                        @Nullable Boolean updatePrepare,
                                                        @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,
                "imageName", imageName,
                "sliverUrn", sliverUrn,
                "global", global,
                "updatePrepare", updatePrepare,
                "extraOptions", extraOptions);

        Objects.requireNonNull(credentialList, "Illegal argument: " + "credentialList" + " is not allowed to be null");
        Objects.requireNonNull(sliceUrn, "Illegal argument: " + "sliceUrn" + " is not allowed to be null");
        Objects.requireNonNull(imageName, "Illegal argument: " + "imageName" + " is not allowed to be null");
        Objects.requireNonNull(sliverUrn, "Illegal argument: " + "sliverUrn" + " is not allowed to be null");

        List<String> credentialsList = AggregateManager2.createCredentialsList(credentialList);

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

        List<Object> args = new ArrayList<>(5);
        args.add( sliceUrn);
        args.add( imageName);
        args.add( sliverUrn);
        args.add( credentialsList);
        args.add( options);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "createImage", "CreateImage",
                args, resultValueObject -> {
                    List<String> v = apiSpecifiesListOfString(resultValueObject);
                    if (v.size() < 2)
                        throw new BadReplyGeniException("returned array does not have exactly 2 items: " + v);

                    if (v.size() > 2) {
                        LOG.warn("returned array of CreateImage does not have exactly 2 items. It has {}. Will try to continue anyway.", v.size());
                    }
                    return new ImageInfo(v.get(0), v.get(1));
                });
    }

    public static class ImageInfo implements AggregateManagerWrapper.ImageInfo {
        private final String urn;
        private final String url;

        public ImageInfo(String urn, String url) {
            this.urn = urn;
            this.url = url;
        }

        public ImageInfo(Map<String, String> ht) {
            this.urn = ht.get("urn");
            this.url = ht.get("url");
        }

        public String getUrn() {
            return urn;
        }

        public String getUrl() {
            return url;
        }

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

}
