package be.iminds.ilabt.jfed.experiment;

import be.iminds.ilabt.jfed.highlevel.model.Sliver;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.StatusDetails;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.util.GeniUrn;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 1/12/2016
 * Time: 10:36
 */
public class SfaExperimentPart extends ExperimentPart {

    private final Server connectAuthority;
    private final Server componentManagerAuthority;
    private final boolean acceptTopAuthSlivers;
    private final boolean acceptSubAuthSliver;
    private final boolean acceptConnectAuth;
    private final ObservableList<Sliver> slivers = FXCollections.observableArrayList();

    public SfaExperimentPart(Experiment experiment, Server connectAuthority, Server componentManagerAuthority) {
        super(experiment);
        this.connectAuthority = connectAuthority;
        this.componentManagerAuthority = componentManagerAuthority;

        //never any reason to accept these
        this.acceptTopAuthSlivers = false;

        //central broker should not claim sub auth slivers (they are for broker subauths). For all other cases, not a problem to accept them as ours.
        this.acceptSubAuthSliver = !connectAuthority.hasFlag(Server.Flag.centralBrokerArchitectureBrokerauth);

        this.acceptConnectAuth = connectAuthority.hasFlag(Server.Flag.centralBrokerArchitectureBrokerauth);

        assert experiment.getSlice() != null;

        slivers.addListener((ListChangeListener<Sliver>) c -> {
            while (c.next()) {
                for (Sliver sliver : c.getAddedSubList()) {
                    sliver.expirationDateProperty().addListener(observable -> updateExpirationTime());
                }
            }
            updateExpirationTime();
        });

        experiment.getSlice().getSlivers().addListener((ListChangeListener<Sliver>) c -> {
            while (c.next()) {
                //TODO: replace by AuthorityFinder ?
                slivers.addAll(c.getAddedSubList().stream()
                        .filter(this::doesSliverBelongToThis)
                        .collect(Collectors.toList()));
                c.getRemoved().forEach(slivers::remove);
            }
        });

        //add existing slivers (which will be non-empty in case of restoring)
        slivers.addAll(experiment.getSlice().findSlivers(componentManagerAuthority)); //suspicious match


    }

    private boolean doesSliverBelongToThis(Sliver sliver) {
        GeniUrn sliverUrn = sliver.getServer().getDefaultComponentManagerAsGeniUrn();

        if (acceptConnectAuth &&
                Objects.equals(sliverUrn.getEncodedTopLevelAuthority_withoutSubAuth(), componentManagerAuthority.getDefaultComponentManagerAsGeniUrn().getEncodedTopLevelAuthority_withoutSubAuth()))
            return true;

        if (!sliverUrn.hasCompatibleAuthority(componentManagerAuthority.getDefaultComponentManagerAsGeniUrn()))
            return false;
        //top level authority and any same level sub authorities match

        if (acceptTopAuthSlivers && acceptSubAuthSliver) {
            return true;
        }

        if (acceptTopAuthSlivers) {
            return sliverUrn.getSubAuthCount() <=
                    componentManagerAuthority.getDefaultComponentManagerAsGeniUrn().getSubAuthCount();
        }

        if (acceptSubAuthSliver) {
            return sliverUrn.getSubAuthCount() >=
                    componentManagerAuthority.getDefaultComponentManagerAsGeniUrn().getSubAuthCount();
        }

        //only exact match allowed
        return Objects.equals(sliverUrn.getEncodedTopLevelAuthority(), componentManagerAuthority.getDefaultComponentManagerAsGeniUrn().getEncodedTopLevelAuthority());
    }

    public String getName() {
        return getConnectSfaAuthority().getName();
    }


    /**
     * Note that  getConnectSfaAuthority() and getComponentManagerSfaAuthority() return the same authority,
     * except for some special cases such as exogeni.
     * @return the authority used as componentManager
     */
    public Server getComponentManagerSfaAuthority() {
        return componentManagerAuthority;
    }

    /**
     * Note that  getConnectSfaAuthority() and getComponentManagerSfaAuthority() return the same authority,
     * except for some special cases such as exogeni.
     * @return The authority used to connect to
     */
    public Server getConnectSfaAuthority() {
        return connectAuthority;
    }

    private void updateExpirationTime() {
        expirationTime.set(
                slivers.stream()
                        .map(Sliver::getExpirationDate)
                        .filter(Objects::nonNull)
                        .min(Comparator.naturalOrder()).orElse(null));
    }

    @Nonnull
    public Collection<Sliver> getSlivers() {
        assert experiment.getSlice() != null;
        return experiment.getSlice().findSlivers(getConnectSfaAuthority());
    }

    public StatusDetails getStatusDetails() {
        boolean hasStatus = false;
        StatusDetails statusDetails = new StatusDetails(StatusDetails.SliverStatus.READY);
        for (Sliver sliver : getSlivers()) {
            if (sliver.getStatus() != null) {
                statusDetails.add(sliver.getStatus());
                hasStatus = true;
            }
        }

        if (hasStatus)
            return statusDetails;
        else
            return new StatusDetails(StatusDetails.SliverStatus.UNKNOWN);
    }
}
