package be.iminds.ilabt.jfed.lowlevel;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.util.GeniUrn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URL;
import java.util.List;
import java.util.Objects;

/**
 * TestbedInfoSource replaces AuthorityListModel.
 * The method names and arguments are the same, but the return type is now a Server
 *
 * Note: this will always return Testbeds, Servers and Services that link to each other!
 *
 * Assumptions safe to make when using this:
 *   - Server.getTestbed refers to parent Testbed object (not minimized version)
 *   - Service.getServer refers to parent Server object (not minimized version)
 *   - Server.getScs(), Server.getDefaultScs(), Server.getUserAuthority(), Server.getDefaultUserAuthority()
 *     have at least the ID filled in.
 *   - The following fields are never null:
 *       - Testbed.getId()
 *       - Testbed.getLongName()
 *       - Testbed.getDefaultComponentManagerUrn()
 *       - Testbed.getServers()  (but may be empty)
 *
 *       - Server.getId()
 *       - Server.getName()
 *       - Server.getUrnTld()
 *       - Server.getDefaultComponentManagerUrn()
 *       - Server.getBaseUrl()   (but only if there is at least one service!)
 *       - Server.getServices()  (but may be empty)
 *
 *       - Service.getId()
 *       - Service.getUrn()
 *       - Service.getUrl()
 *       - Service.getApi()
 *       - Service.getApiVersion()
 *    - Any URN or URL that is present, is valid.
 *
 * Assumptions you should NOT make:
 *   - Server.getScs(), Server.getDefaultScs(), Server.getUserAuthority(), Server.getDefaultUserAuthority() do not point to actual non-minimized Service objects
 *
 * Assumptions to be decided/checked:
 *   - Testbed.getOrganisation refers to parent Organisation object (not minimized version)
 *     Is this the same object for all testbeds in the same organisation? Does it refer to all actual testbed objects or only their ID?
 *   - Testbed.getDefaultServer() (if not null) points to the actual Server object, which is also present in Testbed.getServers()
 *   - Testbed.getDefaultServer() is non null if there is at least one Server
 *   - URI references are not guaranteed -> getURI() might be null or a bad URI
 */
public abstract class TestbedInfoSource {
    private static final Logger LOG = LoggerFactory.getLogger(TestbedInfoSource.class);

    public abstract Service getServiceByUrl(String serviceUrlString);
    public abstract Service getServiceByUrl(URL serviceUrl);

    public abstract List<Server> getServers();
    public abstract List<Testbed> getTestbeds();
    public abstract int getServiceCount();


    public enum SubAuthMatchAllowed {
        ALLOW_ONLY_EXACT_SUBAUTHORITY, /*Sub authorities (or lack of sub authority) must be identical */
        ALLOW_TOPLEVEL, /* Sub authority either identical, or there exists an authority that is the same but without subauthority.
                                    (includes same matches as ALLOW_ONLY_EXACT_SUBAUTHORITY) */
        ALLOW_OTHER_SUBAUTHORITY /* match all with same authority, regardless of no or different sub authority.
                                    (includes same matches as ALLOW_OTHER_SUBAUTHORITY and ALLOW_ONLY_EXACT_SUBAUTHORITY) */
    }

    public enum SubAuthMatchPreference {
        PREFER_EXACT_SUBAUTHORITY /*Sub authorities (or lack of sub authority) must be identical */,
        PREFER_TOPLEVEL /* Returns authority without subauth, or if that is not found, the authority with sub authority will be returned if found. */,
        /* if ALLOW_OTHER_SUBAUTHORITY, an auth with other subauthority will only be returned when there is no matching toplevel or subauth athority. */
        /* if an urn without sub auth is given, both choices prefer the toplevel (as PREFER_EXACT_SUBAUTHORITY then prefers toplevel)*/
    }

    public abstract Service getServiceById(Integer id);
    public abstract Server getServerById(Integer id);
    public abstract Testbed getTestbedById(String id);

    public abstract Service getServiceByUrn(final GeniUrn urn, SubAuthMatchAllowed matchAllowed, SubAuthMatchPreference matchPreferred, boolean ignoreUrnTypeAndName,
                                            String api, String version);
    public abstract Testbed getTestbedByUrn(final GeniUrn urn, SubAuthMatchAllowed matchAllowed, SubAuthMatchPreference matchPreferred, boolean ignoreUrnTypeAndName);
    public abstract Server getByUrn(final GeniUrn urn, SubAuthMatchAllowed matchAllowed, SubAuthMatchPreference matchPreferred, boolean ignoreUrnTypeAndName);

    public Server getServerByUrn(final GeniUrn urn, SubAuthMatchAllowed matchAllowed, SubAuthMatchPreference matchPreferred, boolean ignoreUrnTypeAndName) {
        return getByUrn(urn, matchAllowed, matchPreferred, ignoreUrnTypeAndName);
    }

    public boolean matchUrn(String candidate, GeniUrn requested, SubAuthMatchAllowed matchAllowed, boolean ignoreUrnTypeAndName)
            throws GeniUrn.GeniUrnParseException {
        return matchUrn(new GeniUrn(candidate), requested, matchAllowed, ignoreUrnTypeAndName);
    }
    //TODO: test!
    public boolean matchUrn(GeniUrn candidate, GeniUrn requested, SubAuthMatchAllowed matchAllowed, boolean ignoreUrnTypeAndName) {
        if (!ignoreUrnTypeAndName) {
            if (!Objects.equals(candidate.getType(), requested.getType()))
                return false;
            if (!Objects.equals(candidate.getResourceName(), requested.getResourceName()))
                return false;
        }

        String candidateTldTop = candidate.getEncodedTopLevelAuthority_withoutSubAuth();
        String requestedTldTop = requested.getEncodedTopLevelAuthority_withoutSubAuth();
        if (!Objects.equals(candidateTldTop, requestedTldTop))
            return false;

        switch (matchAllowed) {
            case ALLOW_ONLY_EXACT_SUBAUTHORITY: {
                return Objects.equals(candidate.getEncodedTopLevelAuthority(), requested.getEncodedTopLevelAuthority());
            }
            case ALLOW_TOPLEVEL: {
                int reqcount = requested.getSubAuthCount();
                int cancount = candidate.getSubAuthCount();
                if (reqcount < cancount)
                    return false;
                for (int i = 0; i < cancount; i++) {
                    if (!Objects.equals(requested.getSubAuthName(i), candidate.getSubAuthName(i)))
                        return false;
                }
                return true;
            }
            case ALLOW_OTHER_SUBAUTHORITY: {
                //already checked enough
                return true;
            }
            default: throw new RuntimeException("case "+matchAllowed+" not supported");

        }
    }

    public Server getServerByUrl(String urlString) {
        Service service = getServiceByUrl(urlString);
        if (service == null)
            return null;
        assert service.getServer() != null;
        return service.getServer();
    }
    public Server getServerByUrl(URL url) {
        Service service = getServiceByUrl(url);
        if (service == null)
            return null;
        assert service.getServer() != null;
        return service.getServer();
    }

    public Server getByUrn(String urnString, SubAuthMatchAllowed matchAllowed, SubAuthMatchPreference matchPreferred, boolean ignoreUrnTypeAndName) {
        GeniUrn u = GeniUrn.parse(urnString);
        if (u == null) {
            LOG.warn("Tried to lookup Server using the invalid URN \"" + urnString + "\" -> solving problem by returning null");
            return null;
        }
        return getByUrn(u, matchAllowed, matchPreferred, ignoreUrnTypeAndName);
    }

    public Server getByUrn(String urnString, SubAuthMatchAllowed matchAllowed, SubAuthMatchPreference matchPreferred) {
        return getByUrn(urnString, matchAllowed, matchPreferred, false/*ignoreUrnTypeAndName*/);
    }

    public Server getByUrn(GeniUrn urn, SubAuthMatchAllowed matchAllowed, SubAuthMatchPreference matchPreferred) {
        return getByUrn(urn, matchAllowed, matchPreferred, false/*ignoreUrnTypeAndName*/);
    }

    public Server getByUrnExact(String urnString) {
        return getByUrn(urnString, SubAuthMatchAllowed.ALLOW_ONLY_EXACT_SUBAUTHORITY, SubAuthMatchPreference.PREFER_EXACT_SUBAUTHORITY, false/*ignoreUrnTypeAndName*/);
    }

    public Server getByUrnExact(GeniUrn urn) {
        return getByUrn(urn, SubAuthMatchAllowed.ALLOW_ONLY_EXACT_SUBAUTHORITY, SubAuthMatchPreference.PREFER_EXACT_SUBAUTHORITY, false/*ignoreUrnTypeAndName*/);
    }

    public Server getFromAnyUrn(String urnString, SubAuthMatchAllowed matchAllowed, SubAuthMatchPreference matchPreferred) {
        return getByUrn(urnString, matchAllowed, matchPreferred, true/*ignoreUrnTypeAndName*/);
    }

    public Server getFromAnyUrn(GeniUrn urn, SubAuthMatchAllowed matchAllowed, SubAuthMatchPreference matchPreferred) {
        return getByUrn(urn, matchAllowed, matchPreferred, true/*ignoreUrnTypeAndName*/);
    }

    public Server getFromAnyUrnExact(String urnString) {
        return getByUrn(urnString, SubAuthMatchAllowed.ALLOW_ONLY_EXACT_SUBAUTHORITY, SubAuthMatchPreference.PREFER_EXACT_SUBAUTHORITY, true/*ignoreUrnTypeAndName*/);
    }

    public Server getFromAnyUrnExact(GeniUrn urn) {
        return getByUrn(urn, SubAuthMatchAllowed.ALLOW_ONLY_EXACT_SUBAUTHORITY, SubAuthMatchPreference.PREFER_EXACT_SUBAUTHORITY, true/*ignoreUrnTypeAndName*/);
    }

    /**
     * returns true if this authority is authorative for the given URN.
     * This is not just a matter of comparing the URN, it's more complex due to sub authorities:
     *
     * (Most authorities are authorative over their sub authorities, but some special cases are not. (to be confoirmed this case exists))
     *
     * For some authorities, there is no top level, there are only sub authorities.
     * */
    public static boolean isAuthoritative(Server server, GeniUrn testedUrn) {
        String serverUrnTld = server.getUrnTld();
        String checkedUrnPart = testedUrn.getEncodedTopLevelAuthority();
        return checkedUrnPart.startsWith(serverUrnTld);
    }
}
