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.KeyUtil;
import be.iminds.ilabt.jfed.util.RFC3339Util;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.security.cert.X509Certificate;
import java.util.*;

/**
 * <p>This is a simple wrapper for the methods specified in the {@link <a href="http://svn.planet-lab.org/wiki/SfaRegistryInterface">SFA Registry Interface</a>}.
 * <p>Some functionality provided by the API is available in this implementation, and not much processing of results is done.
 * 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>All methods require a {@link be.iminds.ilabt.jfed.lowlevel.connection.SfaConnection} argument and return a {@link PlanetlabSfaRegistryInterface.SimpleApiCallReply}
 * which wraps the actual return value of the call, if any.
 *
 * @author Wim Van de Meerssche
 * @see <a href="http://svn.planet-lab.org/wiki/SfaRegistryInterface">SFA Registry Interface</a>
 */
public class PlanetlabSfaRegistryInterface extends AbstractApi {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(PlanetlabSfaRegistryInterface.class);

    public static GeniUrn sfaHrnToUrn(String sfaHrn, String type) {
        if (sfaHrn == null) return null;

        int lastDot = sfaHrn.lastIndexOf('.');
        if (lastDot == -1) return null;
        String name = sfaHrn.substring(lastDot + 1);
        String tld = sfaHrn.substring(0, lastDot).replace('.', ':');

        return GeniUrn.createGeniUrnFromEncodedParts(tld, type, name);
    }

    public static GeniUrn sfaHrnToUserUrn(String sfaHrn) {
        return sfaHrnToUrn(sfaHrn, "user");
    }

    public static String urnToSfaHrn(GeniUrn urn) {
        if (urn == null) return null;

        String first = urn.getEncodedTopLevelAuthority().replace(":", ".");
        String last = urn.getEncodedResourceName();

        return first + "." + last;
    }

    /**
     * Construct a new PlanterlabSfaRegistryInterface, 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 PlanetlabSfaRegistryInterface(@Nonnull Logger logger, RetrySettings retrySettings, @Nonnull JFedPreferences jFedPreferences) {
        super(logger, retrySettings, new ApiInfo.Api(ApiInfo.ApiName.PLANETLAB_SLICE_REGISTRY, 1), jFedPreferences);
    }

    /**
     * Construct a new PlanetlabSfaRegistryInterface, 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 PlanetlabSfaRegistryInterface(@Nonnull Logger logger, @Nonnull JFedPreferences jFedPreferences) {
        this(logger, jFedPreferences.getRetrySettings(), jFedPreferences);
    }

    /**
     * A human readable name for the implemented API
     *
     * @return "PlanetLab SFA Registry Interface"
     */
    static public String getApiName() {
        return "PlanetLab SFA Registry Interface";
    }

    /**
     * A human readable name for the implemented API
     *
     * @return "PlanetLab SFA Registry Interface"
     */
    @Override
    public String getName() {
        return getApiName();
    }

    @Override
    protected boolean isBusyReply(be.iminds.ilabt.jfed.lowlevel.XMLRPCCallDetails res) {
        //API has no special reply to indicate "busy"
        return false;
    }

    /**
     * add the target of the credential to its name
     */
    private void improveCredentialName(AnyCredential cred) {
        if (!(cred instanceof SfaCredential)) return;
        SfaCredential sfaCred = (SfaCredential) cred;
        GeniUrn target = GeniUrn.parse(sfaCred.getTargetUrn());
        if (target == null) return;
        String origname = cred.getName();
        if (Objects.equals(target.getEncodedResourceType(), "user")) {
            cred.setName(origname + " for user " + target.getEncodedResourceName());
            return;
        }
        if (Objects.equals(target.getEncodedResourceType(), "slice")) {
            cred.setName(origname + " for slice " + target.getEncodedResourceName());
            return;
        }
        if (target.getEncodedResourceType().startsWith("auth")) {
            cred.setName(origname + " for PI user (allows creating slices)");
            return;
        }

        cred.setName(origname + " target=" + target);
    }

    /**
     * GetVersion call
     *
     * @param con the {@link be.iminds.ilabt.jfed.lowlevel.connection.SfaConnection} to use for this call. Must be a connection to a SfaRegistry.
     * @return The version info, wrapped in a {@code PlanetlabSfaRegistryInterface.SimpleApiCallReply}
     * @throws be.iminds.ilabt.jfed.lowlevel.JFedException if something went wrong while performing the call: either a server-error or an unexpected server response
     */
    @ApiMethod(order = 1, hint = "GetVersion call: Get info about the version of the API supported at the server.")
    public SimpleApiCallReply<GetVersionReply> getVersion(SfaConnection con) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters();

        XMLRPCCallDetails res = executeXmlRpcCommand(con, "GetVersion", Collections.emptyList(), methodParams, false);
        GetVersionReply r;
        try {
            r = new GetVersionReply(res);
        } catch (Exception t) {
            LOG.error("Error parsing GetVersion reply", t);
            r = null;
        }
        SimpleApiCallReply<GetVersionReply> wrappedres = new SimpleApiCallReply<>(res, r);
        log(res, wrappedres, "getVersion", "GetVersion", con, methodParams);
        return wrappedres;
    }

    /**
     * GetSelfCredential call:  A degenerate version of GetCredential used by a client to get his initial credential when de doesn't have one.
     * <p/>
     * The registry ensures that the client is the principal that is named by (type, name) by comparing the public key in the record's GID to
     * the private key used to encrypt the client side of the HTTPS connection. Thus it is impossible for one principal to retrieve another
     * principal's credential without having the appropriate private key.
     *
     * @param con the {@code GeniConnection} to use for this call. Must be a connection to a SfaRegistry.
     * @return the {@code AnyCredential} for the current user, wrapped in a {@code PlanetlabSfaRegistryInterface.SimpleApiCallReply}
     * @throws be.iminds.ilabt.jfed.lowlevel.JFedException if something went wrong while performing the call: either a server-error or an unexpected server response
     */
    @ApiMethod(order = 2, hint = "GetSelfCredential call: Get the credential of the current user.")
    public SimpleApiCallReply<AnyCredential> getSelfCredential(SfaConnection con,
                                                               @ApiMethodParameter(name = "certificate", hint = "The client's certificate.", multiLineString = true)
                                                                       String certificate,
                                                               @ApiMethodParameter(name = "xrn", hint = "The client's URN or HRN.")
                                                                       String xrn,
                                                               @ApiMethodParameter(name = "type", hint = "The client's type (if HRN is specified).", required = false)
                                                                       String type) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("certificate", certificate, "xrn", xrn, "type", type);

        List<Object> args = new ArrayList<>(3);
        args.add(certificate);
        args.add(xrn);
        if (type != null)
            args.add(type);
        else
            args.add("");

        XMLRPCCallDetails res = executeXmlRpcCommand(con, "GetSelfCredential", args, methodParams, false);
        SimpleApiCallReply<AnyCredential> r;
        try {
            AnyCredential signedCredential =
                    AnyCredential.createSfa2("PlanetlabSfaReg GetSelfCredential", res.getResultValueObject().toString());
            improveCredentialName(signedCredential);
            r = new SimpleApiCallReply<>(res, signedCredential);
        } catch (Exception e) {
            log(res, null, "getSelfCredential", "GetSelfCredential", con, methodParams);
            throw new JFedException("Exception retrieving Credential for GetSelfCredential call.", e, res, null);
        }

        log(res, r, "getSelfCredential", "GetSelfCredential", con, methodParams);
        return r;

    }

    /**
     * GetSelfCredential call:  A degenerate version of GetCredential used by a client to get his initial credential when de doesn't have one.
     * <p/>
     * The registry ensures that the client is the principal that is named by (type, name) by comparing the public key in the record's GID to
     * the private key used to encrypt the client side of the HTTPS connection. Thus it is impossible for one principal to retrieve another
     * principal's credential without having the appropriate private key.
     *
     * @param con the {@code GeniConnection} to use for this call. Must be a connection to a SfaRegistry.
     * @return the {@code AnyCredential} for the current user, wrapped in a {@code PlanetlabSfaRegistryInterface.SimpleApiCallReply}
     * @throws be.iminds.ilabt.jfed.lowlevel.JFedException if something went wrong while performing the call: either a server-error or an unexpected server response
     */
    @ApiMethod(order = 3, hint = "GetSelfCredential call, with automatic argument: Get the credential of the current user.", convenienceMethodFor = "getSelfCredential")
    public SimpleApiCallReply<AnyCredential> getSelfCredential_AutomaticArguments(SfaConnection con, GeniUser geniUser) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("geniUserString", geniUser.getUserUrnString());

        List<X509Certificate> certChain = geniUser.getClientCertificateChain();
        String certificate = "";
        for (X509Certificate c : certChain)
            certificate += KeyUtil.x509certificateToPem(c);
        String xrn = geniUser.getUserUrnString();

        List<Object> args = new ArrayList<>(3);
        args.add(certificate);
        args.add(xrn);
        args.add("");

        XMLRPCCallDetails res = executeXmlRpcCommand(con, "GetSelfCredential", args, methodParams, false);
        SimpleApiCallReply<AnyCredential> r;
        try {
            AnyCredential signedCredential =
                    AnyCredential.createSfa2("PlanetlabSfaRegistry GetSelfCredential", res.getResultValueObject().toString());
            improveCredentialName(signedCredential);
            r = new SimpleApiCallReply<>(res, signedCredential);
        } catch (Exception e) {
            log(res, null, "getSelfCredential_AutomaticArguments", "GetSelfCredential", con, methodParams);
            throw new JFedException("Exception retrieving Credential for GetSelfCredential call.", e, res, null);
        }

        log(res, r, "getSelfCredential_AutomaticArguments", "GetSelfCredential", con, methodParams);
        return r;

    }

    /**
     * GetCredential call:  Retrieve a credential for an object.
     * <p/>
     * string GetCredential(string credentials[], string xrn, string type)
     *
     * @param credential A single credential string or an array of credentials.
     * @param xrn        The objects URN or HRN.
     * @param type       The objects type (if HRN is specified).
     *                   <p/>
     *                   Returns a string representation of a credential object.
     * @param con        the {@code GeniConnection} to use for this call. Must be a connection to a SfaRegistry.
     * @return the {@code AnyCredential} for the current user, wrapped in a {@code PlanetlabSfaRegistryInterface.SimpleApiCallReply}
     * @throws be.iminds.ilabt.jfed.lowlevel.JFedException if something went wrong while performing the call: either a server-error or an unexpected server response
     */
    @ApiMethod(order = 4, hint = "GetCredential call, without arguments: Get the credential of the current user.")
    public SimpleApiCallReply<AnyCredential> getCredential(SfaConnection con,
                                                           @ApiMethodParameter(name = "credential", hint = "A single credential string or an array of credentials (jFed currently only supports a single credential here)")
                                                                   AnyCredential credential,
                                                           @ApiMethodParameter(name = "xrn", hint = "The objects URN or HRN.")
                                                                   String xrn,
                                                           @ApiMethodParameter(name = "type", hint = "The objects type (if HRN is specified).", required = false)
                                                                   String type) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("credential", credential, "xrn", xrn, "type", type);

//        boolean isHrn = !xrn.startsWith("urn:");
        //let the user decide
//        if (isHrn == (type != null)) {
//            throw new GeniException("Error, an XRN that is a HRN, implies that type is specified. XRN is HRN (\""+xrn+"\"): "+isHrn+" type present: "+(type != null));
//        }

        List<Object> args = new ArrayList<>(3);
        args.add(credential.getCredentialXml());
        args.add(xrn);
        if (type != null)
            args.add(type);
        else
            args.add("");

        XMLRPCCallDetails res = executeXmlRpcCommand(con, "GetCredential", args, methodParams, false);
        SimpleApiCallReply<AnyCredential> r;
        try {
            AnyCredential signedCredential =
                    AnyCredential.createSfa2("PlanetlabSfaRegistry GetCredential", res.getResultValueObject().toString());
            improveCredentialName(signedCredential);
            r = new SimpleApiCallReply<>(res, signedCredential);
        } catch (Exception e) {
            log(res, null, "getCredential", "GetCredential", con, methodParams);
            throw new JFedException("Exception retrieving Credential for GetCredential call.", e, res, null);
        }

        log(res, r, "getCredential", "GetCredential", con, methodParams);
        return r;

    }

    /**
     * Resolve call:
     * <p/>
     * struct Resolve(string xrns[], string credentials[])
     *
     * @param credential A single credential string or an array of credentials.
     * @param xrn        A single object URN or HRN or an array of URNs/HRNs.
     * @param con        the {@code GeniConnection} to use for this call. Must be a connection to a SfaRegistry.
     * @return a struct packed as a Map (wrapped in a {@code PlanetlabSfaRegistryInterface.SimpleApiCallReply})
     * with at least the following attributes:
     * <p/>
     * {
     * string hrn,
     * string type,
     * string date_created,
     * string last_updated,
     * string authority,
     * string peer_authority,
     * struct gid
     * {
     * string hrn,
     * string urn,
     * string uuid
     * }
     * <p/>
     * }
     * @throws be.iminds.ilabt.jfed.lowlevel.JFedException NOTE: it seems that in practice, servers can wrap this struct in an array... so we'll handle that as well here.
     */
    @ApiMethod(order = 5, hint = "GetCredential call, without arguments: Get the credential of the current user.")
    public SimpleApiCallReply<Map> resolve(SfaConnection con,
                                                 @ApiMethodParameter(name = "credential", hint = "A single credential string or an array of credentials (jFed currently only supports a single credential here)")
                                                         AnyCredential credential,
                                                 @ApiMethodParameter(name = "xrn", hint = "A single object URN or HRN or an array of URNs/HRNs. (jFed currently only supports a single object here)")
                                                         String xrn) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("credential", credential, "xrn", xrn);

        List<Object> args = new ArrayList<>(2);
        args.add(xrn);
        args.add(credential.getCredentialXml());

        XMLRPCCallDetails res = executeXmlRpcCommand(con, "Resolve", args, methodParams, false);
        SimpleApiCallReply<Map> r;
        try {
            Object resVal = res.getResultValueObject();
            if (resVal instanceof Object[]) {
                Object[] wrappingList = (Object[]) resVal;
                if (wrappingList.length == 1)
                    resVal = wrappingList[0];
                else
                    throw new JFedException("Exception in Resolve call: returned array with " + wrappingList.length + " elements instead of struct or array with single struct inside", res, null);
            }

            Map table = (Map) resVal;
            r = new SimpleApiCallReply<>(res, table);
        } catch (Exception e) {
            log(res, null, "resolve", "Resolve", con, methodParams);
            throw new JFedException("Exception in Resolve call.", e, res, null);
        }

        log(res, r, "resolve", "Resolve", con, methodParams);
        return r;
    }

    /**
     * List call: List the records in an authority.
     * <p/>
     * struct[] List(string xrn, string credentials[])
     *
     * @param credential An array of credentials. At least one credential must be a valid slice credential for the slice specified in slice_urn.
     * @param xrn        The authority's URN or HRN.
     * @param con        the {@code GeniConnection} to use for this call. Must be a connection to a SfaRegistry.
     * @return Returns a list of structs where each struct represents a registry record. packed as a List of Map (wrapped in a {@code PlanetlabSfaRegistryInterface.SimpleApiCallReply})
     * @throws be.iminds.ilabt.jfed.lowlevel.JFedException if something went wrong while performing the call: either a server-error or an unexpected server response
     */
    @ApiMethod(order = 6, hint = "GetCredential call, without arguments: Get the credential of the current user.")
    public SimpleApiCallReply<List> list(SfaConnection con,
                                           @ApiMethodParameter(name = "credential", hint = "A single credential string or an array of credentials (jFed currently only supports a single credential here)")
                                                   AnyCredential credential,
                                           @ApiMethodParameter(name = "xrn", hint = "A single object URN or HRN or an array of URNs/HRNs. (jFed currently only supports a single object here)")
                                                   String xrn) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("credential", credential, "xrn", xrn);

        List<Object> args = new ArrayList<>(2);
        args.add(xrn);
        List<String> creds = Collections.singletonList(credential.getCredentialXml());
        args.add(creds);

        XMLRPCCallDetails res = executeXmlRpcCommand(con, "List", args, methodParams, false);
        SimpleApiCallReply<List> r;
        try {
            List list = (List) res.getResultValueObject();
            r = new SimpleApiCallReply<>(res, list);
        } catch (Exception e) {
            log(res, null, "list", "List", con, methodParams);
            throw new JFedException("Exception in List call.", e, res, null);
        }

        log(res, r, "list", "List", con, methodParams);
        return r;
    }

//TODO add "Remove" from "SFA Registry Interface"

    /**
     * Register call: Register an object with the registry.
     * <p/>
     * string Register(struct record, string credentials[])
     * <p/>
     * record: A struct containing the attributes required to register a particular object (depends on the object's type).
     * credentials: A single credential string or an array of credentials.
     * <p/>
     * Returns string representation of the object's GID if successful, faults otherwise.
     *
     * @param credential An array of credentials. At least one credential must be a valid slice credential for the slice specified in slice_urn.
     * @param con        the {@code GeniConnection} to use for this call. Must be a connection to a SfaRegistry.
     * @return Returns a list of structs where each struct represents a registry record. packed as a List of Map (wrapped in a {@code PlanetlabSfaRegistryInterface.SimpleApiCallReply})
     * @throws be.iminds.ilabt.jfed.lowlevel.JFedException if something went wrong while performing the call: either a server-error or an unexpected server response
     */
    @ApiMethod(order = 7, hint = "Register call, without Slice record as argument: Create a slice")
    public SimpleApiCallReply<String> registerSlice(SfaConnection con,
                                                    @ApiMethodParameter(name = "credential", hint = "A single credential string or an array of credentials (jFed currently only supports a single credential here)")
                                                            AnyCredential credential,
//               @ApiMethodParameter(name = "sliceName", hint="A name for the new slice")
//                    String sliceName,
                                                    @ApiMethodParameter(name = "hrn", hint = "hrn")
                                                            String hrn,
                                                    @ApiMethodParameter(name = "sliceUrl", hint = "sliceUrl", required = false, parameterType = ApiMethodParameterType.URL)
                                                            String sliceUrl,
                                                    @ApiMethodParameter(name = "expiresRRC3339", hint = "expires String in RFC3339 format", required = false, parameterType = ApiMethodParameterType.STRING_DATE_RFC3339)
                                                            String expiresRRC3339,
                                                    @ApiMethodParameter(name = "researchers", hint = "researchers", required = false, parameterType = ApiMethodParameterType.LIST_OF_STRING)
                                                            List<String> researchers,
                                                    @ApiMethodParameter(name = "pis", hint = "principle investigators", required = false, parameterType = ApiMethodParameterType.LIST_OF_STRING)
                                                            List<String> pis,
                                                    @ApiMethodParameter(name = "description", hint = "description", required = false, parameterType = ApiMethodParameterType.LIST_OF_STRING)
                                                            List<String> description) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters(
                        "credential", credential, "hrn", hrn, "sliceUrl", sliceUrl,
                        "expiresRRC3339", expiresRRC3339, "researchers",
                        researchers, "pis", pis, "description", description);

        Map<String, Object> sliceRecord = new HashMap<>();
//        sliceRecord.put("name", sliceName);
        sliceRecord.put("hrn", hrn);
        sliceRecord.put("type", "slice");
        if (sliceUrl != null) sliceRecord.put("url", sliceUrl);
//        if (expires != null) sliceRecord.put("expires", RFC3339Util.dateToRFC3339String(expires));
        if (expiresRRC3339 != null) sliceRecord.put("expires", expiresRRC3339);
        if (researchers != null) {
            List<String> researchersList = new ArrayList<>(researchers);
            sliceRecord.put("researchers", researchersList);
        }
        if (pis != null) {
            List<String> pisList = new ArrayList<>(pis);
            sliceRecord.put("pis", pisList);
        }
        if (description != null) {
            List<String> descriptionList = new ArrayList<>(description);
            sliceRecord.put("description", descriptionList);
        }

        List<Object> args = new ArrayList<>(2);
        args.add(sliceRecord);
        List<String> creds = Collections.singletonList(credential.getCredentialXml());
        args.add(creds);

        XMLRPCCallDetails res = executeXmlRpcCommand(con, "Register", args, methodParams, false);
        SimpleApiCallReply<String> r;
        try {
            String gid = (String) res.getResultValueObject();
            r = new SimpleApiCallReply<>(res, gid);
        } catch (Exception e) {
            log(res, null, "registerSlice", "Register", con, methodParams);
            throw new JFedException("Exception in Register call.", e, res, null);
        }

        log(res, r, "registerSlice", "Register", con, methodParams);
        return r;
    }

    //conveniance method not exposed to probe command line or GUI
    public SimpleApiCallReply<String> registerSlice(SfaConnection con,
                                                    AnyCredential credential,
                                                    String hrn,
                                                    String sliceUrl,
                                                    Date expires,
                                                    List<String> researchers,
                                                    List<String> pis,
                                                    List<String> description) throws JFedException {
        return registerSlice(con, credential, hrn, sliceUrl, RFC3339Util.dateToRFC3339String(expires, true, true, true), researchers, pis, description);
    }

/*
* info on register for slice record as found in record.py:
*
  class SliceRecord(SfaRecord):
    fields = {
        'name': Parameter(str, 'Slice name'),
        'url': Parameter(str, 'Slice url'),
        'expires': Parameter(int, 'Date and time this slice exipres'),
        'researcher': Parameter([str], 'List of users for this slice'),
        'PI': Parameter([str], 'List of PIs responsible for this slice'),
        'description': Parameter([str], 'Description of this slice'),
        }
    fields.update(SfaRecord.fields)
* */

    public static class SimpleApiCallReply<T> implements XmlRpcApiCallReply<T> {
        private final T val;
        private final Object rawResult;
        private final XMLRPCCallDetails xmlRpcDetails;

        public SimpleApiCallReply(XMLRPCCallDetails res, T val) {
            xmlRpcDetails = res;
            this.rawResult = res.getResult();
            this.val = val;
        }

        public Object getRawResult() {
            return rawResult;
        }

        public T getValue() {
            return val;
        }


        //not used for this API
        @Override
        public GeniAMResponseCode getGeniResponseCode() {
            return GeniAMResponseCode.GENIRESPONSE_SUCCESS;

        }

        @Override
        public String getOutput() {
            return "";
        }

        @Override
        public XMLRPCCallDetails getXMLRPCCallDetails() {
            return xmlRpcDetails;
        }


        @Nullable
        public Object getRawValue() {
            return getValue();
        }
    }

    class GetVersionReply {
        private int geniApi;
        private int sfa;
        private String codeUrl;
        private String codeTag;
        private String hrn;
        private String urn;
        private Map<String, String> peers;

        private Integer handleInteger(Map h, String key) {
            if (h == null) return null;
            Object o = h.get(key);
            if (o == null)
                return null;
            if (!(o instanceof Integer))
                return null;
            return (Integer) o;
        }

        private int handleInt(Map h, String key) {
            Integer res = handleInteger(h, key);
            if (res == null) return -1;
            return res;
        }

        private String handleString(Map h, String key) {
            if (h == null) return null;
            Object o = h.get(key);
            if (o == null)
                return null;
            return o + "";
        }

        private Map handleMap(Map h, String key) {
            if (h == null) return null;
            Object o = h.get(key);
            if (o == null)
                return null;
            if (!(o instanceof Map))
                return null;
            return (Map) o;
        }

        public GetVersionReply(XMLRPCCallDetails reply) {
            Map replyTable = (Map) reply.getResult();
            geniApi = handleInt(replyTable, "geni_api");
            sfa = handleInt(replyTable, "sfa");
            codeUrl = handleString(replyTable, "code_url");
            codeTag = handleString(replyTable, "code_tag");
            hrn = handleString(replyTable, "hrn");
            urn = handleString(replyTable, "urn");
            Map peersTable = handleMap(replyTable, "peers");
            peers = new HashMap<>();
            for (Object p : peersTable.keySet()) {
                String peer = (String) p;
                String peerUrl = handleString(peersTable, peer);
                peers.put(peer, peerUrl);
            }
        }

        public int getGeniApi() {
            return geniApi;
        }

        public int getSfa() {
            return sfa;
        }

        public String getCodeUrl() {
            return codeUrl;
        }

        public String getCodeTag() {
            return codeTag;
        }

        public String getHrn() {
            return hrn;
        }

        public String getUrn() {
            return urn;
        }

        public Map<String, String> getPeers() {
            return peers;
        }

        @Override
        public String toString() {
            return "GetVersionReply{" +
                    "geniApi=" + geniApi +
                    ", sfa=" + sfa +
                    ", codeUrl='" + codeUrl + '\'' +
                    ", codeTag='" + codeTag + '\'' +
                    ", hrn='" + hrn + '\'' +
                    ", urn='" + urn + '\'' +
                    ", peers=" + peers +
                    '}';
        }
    }


    //TODO add other methods from interface (register and update, but problem is they take a struct as argument)
}
