package be.iminds.ilabt.jfed.highlevel.model;

import be.iminds.ilabt.jfed.lowlevel.api_wrapper.StatusDetails;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.rspec.model.ManifestSliverFinder;
import be.iminds.ilabt.jfed.rspec.model.RspecLink;
import be.iminds.ilabt.jfed.rspec.model.RspecNode;
import be.iminds.ilabt.jfed.rspec.rspec_source.ManifestRspecSource;
import be.iminds.ilabt.jfed.rspec.rspec_source.RequestRspecSource;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.TextUtil;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.Instant;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * Sliver
 */
public class Sliver {

    /**
     * The sources are ordered according to importance
     */
    public enum Source  {
        REQUEST_RSPEC,
        SLICE_AUTHORITY,
        AGGREGATE_MANAGER_MANIFEST,
        AGGREGATE_MANAGER_STATUS
    }
    private static final Logger LOG = LoggerFactory.getLogger(Sliver.class);

    private final ObjectProperty<Source> source = new SimpleObjectProperty<>();

    private final StringProperty statusString = new SimpleStringProperty("<no status known>");
    private final ObjectProperty<StatusDetails> status =
            new SimpleObjectProperty<>(new StatusDetails(StatusDetails.SliverStatus.UNINITIALISED));

    @Nonnull
    private final Slice slice;
    private final GeniUrn urn;

    @Nonnull
    private final Server authority;
    private final ObjectProperty<RequestRspecSource> requestRspec = new SimpleObjectProperty<>();
    private final ObjectProperty<ManifestRspecSource> manifestRspec = new SimpleObjectProperty<>();


    private String allocationStatus;
    private String operationalStatus;
    private final ObjectProperty<Instant> expirationDate = new SimpleObjectProperty<>();

    private final SetProperty<ComponentInfo> componentInfo =
            new SimpleSetProperty<>(FXCollections.<ComponentInfo>observableSet());


    public Sliver(@Nonnull GeniUrn urn, @Nonnull Source source, @Nonnull Slice slice,
                  @Nullable RequestRspecSource requestRspec, @Nullable ManifestRspecSource manifestRspec,
                  @Nonnull Server authority) {
        this.urn = urn;
        this.source.set(source);
        this.slice = slice;

        this.requestRspec.set(requestRspec);
        this.manifestRspec.set(manifestRspec);
        this.authority = authority;
    }

    @Nonnull
    public Slice getSlice() {
        return slice;
    }

    @Nullable
    public ManifestRspecSource getManifestRspec() {
        return manifestRspec.get();
    }

    public void setManifestRspec(ManifestRspecSource manifestRspec) {
        this.manifestRspec.set(manifestRspec);
    }

    @Nullable
    public RequestRspecSource getRequestRspec() {
        return requestRspec.get();
    }

    public void setRequestRspec(RequestRspecSource requestRspec) {
        this.requestRspec.set(requestRspec);
    }

    public ObjectProperty<ManifestRspecSource> manifestRspecProperty() {
        return manifestRspec;
    }

    @Nonnull
    public Server getServer() {
        return authority;
    }

    public String getStatusString() {
        return statusString.get();
    }

    public void setStatusString(String statusString) {
        this.statusString.set(statusString);
    }

    public StringProperty statusStringProperty() {
        return statusString;
    }


    public StatusDetails getStatus() {
        return status.get();
    }

    public void setStatus(StatusDetails newStatus) {
        this.status.set(newStatus);
        if (LOG.isTraceEnabled())
            LOG.trace("New status of {} is {} ({})", getUrn(), this.status.get(), newStatus);
    }

    public ObjectProperty<StatusDetails> statusProperty() {
        return status;
    }

    public GeniUrn getUrn() {
        return urn;
    }

    public void setAllocationAndOperationalStatus(String allocationStatus, String operationalStatus, StatusDetails statusDetails) {
        this.allocationStatus = allocationStatus;
        this.operationalStatus = operationalStatus;

        this.status.set(statusDetails);
        this.statusString.set(statusDetails.getGlobalStatus().toString());
    }

    public void setExpirationDate(Instant expirationDate) {
        this.expirationDate.set(expirationDate);
    }

    @Nullable
    public Instant getExpirationDate() {
        return expirationDate.get();
    }

    public ObjectProperty<Instant> expirationDateProperty() {
        return expirationDate;
    }

    public String getAllocationStatus() {
        return allocationStatus;
    }

    public String getOperationalStatus() {
        return operationalStatus;
    }


    private static String toStringHelper(String res, String objectName, Object objectValue) {
        if (objectValue == null)
            return res;
        return res + TextUtil.indent(4, "\"" + objectName + "\" : \"" + objectValue + "\"") + "\n";
    }

    @Override
    public String toString() {
        String res = "\"Sliver\" : {\n";
        res = toStringHelper(res, "slice", slice.getUrn());
        res = toStringHelper(res, "source", source.get());
        res = toStringHelper(res, "urn", urn);
        res = toStringHelper(res, "expirationDate", expirationDate);
        res = toStringHelper(res, "auth", authority.getDefaultComponentManagerUrn());
        res = toStringHelper(res, "allocationStatus", allocationStatus);
        res = toStringHelper(res, "operationalStatus", operationalStatus);
        res = toStringHelper(res, "statusString", statusString.getValue() == null ? null : statusString.getValue());
        res = toStringHelper(res, "source", source.get() != null ? source.get().name() : null);
        res += "}\n";
        return res;
    }

    public boolean logExistsComponent(String clientId, String componentId) {
        ComponentInfo ci = new ComponentInfo(clientId, componentId);
        return componentInfo.add(ci);
    }

    public boolean containsComponent(String componentUrn) {
        for (ComponentInfo ci : componentInfo)
            if (Objects.equals(ci.getComponentId(), componentUrn))
                return true;
        return false;
    }

    public ObservableSet<ComponentInfo> getComponentInfo() {
        return componentInfo.get();
    }

    public SetProperty<ComponentInfo> componentInfoProperty() {
        return componentInfo;
    }

    public String getUrnString() {
        return urn.toString();
    }

    public Source getSource() {
        return source.get();
    }

    public ObjectProperty<Source> sourceProperty() {
        return source;
    }

    public void setSource(Source source) {
        this.source.set(source);
    }

    @Nullable
    public Collection<RspecNode> getNodes() {
        if (manifestRspec.get() == null)
            return null;
        if (manifestRspec.get().getModelRspec() == null)
            return null;

        return manifestRspec.get().getModelRspec().getNodes().stream()
                .filter(node -> Objects.equals(urn, node.getSliverId()))
                .collect(Collectors.toList());
    }


    public Collection<RspecLink> getLinks() {
        if (manifestRspec.get() == null)
            return null;
        if (manifestRspec.get().getModelRspec() == null)
            return null;

        return manifestRspec.get().getModelRspec().getLinks().stream()
                .filter(link -> Objects.equals(urn, link.getSliverId()))
                .collect(Collectors.toList());
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Sliver)) return false;

        Sliver sliver = (Sliver) o;
        if (!Objects.equals(urn, sliver.urn)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        return urn.hashCode();
    }

    public  boolean isFake(){
        return urn.getEncodedResourceName().startsWith(ManifestSliverFinder.IP_POOL_PREFIX);
    }
}
