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

import be.iminds.ilabt.jfed.util.GeniUrn;

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

/**
 * Status of a sliver of a slice
 * <p/>
 * This is a generic status, independent of any AM API. The real status might be more fine-grained.
 * <p/>
 * This is the aggregated status of multiple slivers. Usually on the same, but possibly on multiple authorities.
 * <p/>
 * Components:
 * - There is always a "global" status. This might be directly reported or automatically derived
 * - There might be a status per node
 * - There might be a Status per sliver
 * - There is a list of sliver URNs
 * - There might be a list of node URNs.
 * <p/>
 * This status can be created by updated by adding node and or sliver states, and by adding other states like it.
 * When created this way, the aggregated status is automatically created.
 * <p/>
 * Note that this can thus contain:
 * - the status of a single sliver, with details of multiple nodes
 * - the status of a single sliver and a single node
 * - the status of multiple slivers, each with a single node (or without data per node)
 * - the status of multiple slivers on the same AM, each with multiple nodes
 * - the status of multiple slivers on the different AM, each with multiple nodes
 * Which of the above will depend on the context it is used in. Note that there is ALWAYS a global status available.
 * <p/>
 * Note: Do not compare StatusDetails,
 */
public class StatusDetails {
    private SliverStatus globalStatus;

    private final List<ErrorDetails> allErrors = new ArrayList<>();

    private final Map<GeniUrn, SliverStatus> statusByComponentUrn = new HashMap<>();
    private final Map<GeniUrn, SliverStatus> statusBySliverUrn = new HashMap<>();

    private Date earliestKnownExpires = null;
    private Date latestKnownExpires = null;

    public StatusDetails(@Nonnull SliverStatus globalStatus) {
        this.globalStatus = globalStatus;
    }

    public StatusDetails(@Nonnull SliverStatus globalStatus, GeniUrn forSliverUrn) {
        this.globalStatus = globalStatus;
        if (forSliverUrn != null)
            addSliverStatus(forSliverUrn, globalStatus);
    }

    public StatusDetails(@Nonnull String status) {
        this(SliverStatus.parse(status));
    }

    public StatusDetails(StatusDetails o) {
        this.globalStatus = o.globalStatus;
        this.allErrors.addAll(o.allErrors);

        this.statusByComponentUrn.putAll(o.statusByComponentUrn);
        this.statusBySliverUrn.putAll(o.statusBySliverUrn);
    }

    public SliverStatus getGlobalStatus() {
        return globalStatus;
    }

    /**
     * return empty list if no errors. Never returns null.
     */
    public List<ErrorDetails> getAllErrors() {
        return allErrors;
    }

    public Collection<GeniUrn> getAllSliverUrns() {
        return statusBySliverUrn.keySet();
    }

    public Collection<GeniUrn> getAllComponentUrns() {
        return statusByComponentUrn.keySet();
    }

    public boolean hasComponentStatus(GeniUrn geniUrn) {
        return statusByComponentUrn.containsKey(geniUrn);
    }

    public boolean hasSliverStatus(GeniUrn geniUrn) {
        return statusBySliverUrn.containsKey(geniUrn);
    }

    public SliverStatus getNodeStatusByComponentUrn(GeniUrn componentUrn) {
        return statusByComponentUrn.get(componentUrn);
    }

    public SliverStatus getStatusBySliverUrn(GeniUrn sliverUrn) {
        return statusBySliverUrn.get(sliverUrn);
    }

    public void addError(ErrorDetails errorDetails) {
        allErrors.add(errorDetails);
    }

    public void addComponentStatus(String componentUrn, String status) {
        GeniUrn u = GeniUrn.parse(componentUrn);
        if (u == null) return;
        addComponentStatus(u, status);
    }

    public void addComponentStatus(GeniUrn componentUrn, String status) {
        statusByComponentUrn.put(componentUrn, SliverStatus.parse(status));
    }

    public void addSliverStatus(String sliverUrn, String status) {
        GeniUrn u = GeniUrn.parse(sliverUrn);
        if (u == null) return;
        addSliverStatus(u, SliverStatus.parse(status));
    }

    public void addSliverStatus(String sliverUrn, SliverStatus status) {
        GeniUrn u = GeniUrn.parse(sliverUrn);
        if (u == null) return;
        addSliverStatus(u, status);
    }

    public void addSliverStatus(GeniUrn sliverUrn, String status) {
        addSliverStatus(sliverUrn, SliverStatus.parse(status));
    }

    public void addSliverStatus(GeniUrn sliverUrn, SliverStatus status) {
        statusBySliverUrn.put(sliverUrn, status);
    }

    public void mergeGlobalStatus(SliverStatus globalStatus) {
        this.globalStatus = SliverStatus.merge(this.globalStatus, globalStatus);
    }

    public Date getEarliestKnownExpires() {
        return earliestKnownExpires;
    }

    public Date getLatestKnownExpires() {
        return latestKnownExpires;
    }

    public void addKnownExpires(Date knownExpires) {
        if (knownExpires == null) return;
        if (earliestKnownExpires == null || knownExpires.before(earliestKnownExpires))
            this.earliestKnownExpires = knownExpires;
        if (latestKnownExpires == null || knownExpires.after(latestKnownExpires))
            this.latestKnownExpires = knownExpires;
    }

    public void add(StatusDetails otherStatus) {
        mergeGlobalStatus(otherStatus.globalStatus);

        allErrors.addAll(otherStatus.allErrors);

        statusByComponentUrn.putAll(otherStatus.statusByComponentUrn);
        statusBySliverUrn.putAll(otherStatus.statusBySliverUrn);

        addKnownExpires(otherStatus.getEarliestKnownExpires());
    }

    public Map<GeniUrn, SliverStatus> getStatusByComponentUrn() {
        return Collections.unmodifiableMap(statusByComponentUrn);
    }

    public Map<GeniUrn, SliverStatus> getStatusBySliverUrn() {
        return Collections.unmodifiableMap(statusBySliverUrn);
    }

    public enum SliverStatus {
        UNINITIALISED, /* nothing known about status*/
        READY,
        UNALLOCATED,   /* known not to exist */
        UNKNOWN,       /* known to exist, but no status known. probably it is changing. */
        CHANGING,
        FAIL;

        public static SliverStatus parse(@Nonnull String s) {
            //remove prefixes if any
            if (s.startsWith(":")) s = s.substring(1);
            if (s.startsWith("geni_")) s = s.substring(5);

            //these are the official status:
            if (s.equalsIgnoreCase("configuring")) return CHANGING;
            if (s.equalsIgnoreCase("ready")) return READY;
            if (s.equalsIgnoreCase("failed")) return FAIL;
            if (s.equalsIgnoreCase("unknown")) return UNKNOWN;

            //these are UNOFFICIAL statusses, but they happen anyway:
            if (s.equalsIgnoreCase("changing")) return CHANGING;
            if (s.equalsIgnoreCase("pending_allocation")) return CHANGING;

            if (s.equalsIgnoreCase("notready")) return CHANGING; // This state happens between Provision and PAO "geni_start" in AMv3.
            if (s.equalsIgnoreCase("error")) return FAIL;
            if (s.toLowerCase().startsWith("fail")) return FAIL;    //catchall

            if (s.equalsIgnoreCase("uninitialised")) return UNINITIALISED;
            if (s.equalsIgnoreCase("unallocated")) return UNALLOCATED;

            return UNKNOWN;
        }

        private static final SliverStatus[] PRECEDENCE_ORDER =
                {SliverStatus.FAIL, SliverStatus.UNKNOWN, SliverStatus.UNALLOCATED, SliverStatus.UNINITIALISED,
                 SliverStatus.CHANGING, SliverStatus.READY};

        public static SliverStatus merge(SliverStatus ss1, SliverStatus ss2) {
            for (SliverStatus ss : PRECEDENCE_ORDER) {
                if (ss1 == ss || ss2 == ss)
                    return ss;
            }
            //this should never happen
            throw new IllegalStateException("Could not establish precedence!");
        }
    }

    @Override
    public String toString() {
        return globalStatus != null ? globalStatus.name() : "NULL";
    }

    public String detailedToString() {
        return "StatusDetails{" +
                "globalStatus=" + globalStatus +
                ", allErrors=" + allErrors +
                ", statusByComponentUrn=" + statusByComponentUrn +
                ", statusBySliverUrn=" + statusBySliverUrn +
                '}';
    }
}
