package be.iminds.ilabt.jfed.experimenter_gui.slice;

import be.iminds.ilabt.jfed.experiment.*;
import be.iminds.ilabt.jfed.experimenter_gui.canvas.rspec.AddressPoolCanvasNode;
import be.iminds.ilabt.jfed.experimenter_gui.canvas.rspec.RspecCanvasLink;
import be.iminds.ilabt.jfed.experimenter_gui.canvas.rspec.RspecCanvasNode;
import be.iminds.ilabt.jfed.experimenter_gui.canvas.rspec.RspecGTSNode;
import be.iminds.ilabt.jfed.experimenter_gui.util.BrowserUtil;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.highlevel.model.InternalState;
import be.iminds.ilabt.jfed.highlevel.model.Slice;
import be.iminds.ilabt.jfed.highlevel.model.Sliver;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.StatusDetails;
import be.iminds.ilabt.jfed.rspec.model.GeantTestbedType;
import be.iminds.ilabt.jfed.rspec.model.ModelRspec;
import be.iminds.ilabt.jfed.rspec.model.ModelRspecType;
import be.iminds.ilabt.jfed.rspec.model.RspecNode;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.*;
import be.iminds.ilabt.jfed.rspec.rspec_source.ManifestRspecSource;
import be.iminds.ilabt.jfed.util.ExecuteOnNotNull;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.jfed.gts_highlevel.jobs.gts_parts.GtsExperimentPart;
import be.iminds.jfed.gts_highlevel.model.GtsXmlReservation;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.binding.ObjectBinding;
import javafx.collections.ListChangeListener;
import javafx.collections.WeakListChangeListener;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This controller is used when the Rspec can be parsed into a FXModelRspec
 * <p/>
 * User: twalcari
 * Date: 2/5/14
 * Time: 5:45 PM
 */
public class ModelSliceView extends BorderPane implements ExperimentChangeListener {
    private static final Logger LOG = LoggerFactory.getLogger(ModelSliceView.class);


    private final ExperimentController experimentController;
    private final Experiment experiment;
    private final TestbedInfoSource testbedInfoSource;
    private final SliceExperimentCanvasFactory sliceExperimentCanvasFactory;
    private final BrowserUtil browserUtil;

    private final SliceExperimentCanvas experimentCanvas;

    private Slice slice;
    private final FXModelRspec canvasModel;

    private final Map<GeantTestbedType, GtsReservationStatusBinding> gtsStatusBindings = new HashMap<>();

    private final ListChangeListener<ExperimentPart> experimentPartsListener = change -> {
        while (change.next()) {
            for (ExperimentPart experimentPart : change.getAddedSubList()) {
                if (experimentPart instanceof GtsExperimentPart)
                    registerGtsExperimentPart((GtsExperimentPart) experimentPart);
            }
        }
    };

    ModelSliceView(ExperimentController experimentController,
                   ManifestRspecSource canvasRspecSource,
                   TestbedInfoSource testbedInfoSource,
                   SliceExperimentCanvasFactory sliceExperimentCanvasFactory, BrowserUtil browserUtil) {
        this.experimentController = experimentController;
        this.browserUtil = browserUtil;
        this.experiment = experimentController.getExperiment();

        this.testbedInfoSource = testbedInfoSource;
        this.sliceExperimentCanvasFactory = sliceExperimentCanvasFactory;
        //initialize canvasModel

        canvasModel = canvasRspecSource.getModelRspec(ModelRspecType.FX);
        assert canvasModel != null;

        //initialize GUI
        this.experimentCanvas =
                sliceExperimentCanvasFactory.createSliceExperimentCanvas(experimentController, canvasModel);

        //rearrange nodes on circle if not all positions are defined
        if (!areAllNodePositionsDefined(canvasModel)) {
            experimentCanvas.autoLayout();
        }

        setCenter(experimentCanvas);

        //update gui
        for (FXRspecNode node : canvasModel.getNodes()) {
            //fake nodes are immediately ready, otherwise the status is unknown
            if (isFakeAuthority(node.getComponentManagerId()))
                setRspecNodeStatus(node, StatusDetails.SliverStatus.READY, false, false);
            else
                setRspecNodeStatus(node, StatusDetails.SliverStatus.UNKNOWN, false, false);
        }

        canvasModel.getGeantTestbedTypes().forEach(gtt -> setGeantTestbedTypeStatus(gtt, SliceExperimentCanvas.NodeStatus.UNKNOWN));

        canvasModel.getAddressPools().forEach(addressPool -> setAddressPoolStatus(addressPool, StatusDetails.SliverStatus.UNKNOWN));


        experiment.addExperimentChangeListener(this);
        if (experiment.getSlice() != null) {
            initializeSlice(experiment.getSlice());
        }

        //install tooltips on GTS nodes

        canvasModel.getGeantTestbedTypes().forEach(this::installGTSTooltip);


        experiment.getParts().addListener(new WeakListChangeListener<>(experimentPartsListener));

        experiment.getParts().stream()
                .filter(experimentPart -> experimentPart instanceof GtsExperimentPart)
                .forEach(experimentPart -> registerGtsExperimentPart((GtsExperimentPart) experimentPart));
    }


    public static boolean areAllNodePositionsDefined(ModelRspec model) {
        for (RspecNode node : model.getNodes()) {
            if (node.getEditorX() == -1 && node.getEditorY() == -1)
                return false;
        }
        return true;

    }

    private void registerGtsExperimentPart(GtsExperimentPart experimentPart) {

        experimentPart.stateProperty().addListener(observable -> {
            if (experimentPart.getState() == InternalState.UNALLOCATED)
                setGeantTestbedTypeStatus(experimentPart.getGeantTestbedType(),
                        SliceExperimentCanvas.NodeStatus.UNINITIALISED);
        });

        new ExecuteOnNotNull<GtsXmlReservation>(experimentPart.reservationProperty()) {
            @Override
            public void run() {
                Platform.runLater(() -> {
                    assert experimentPart.getReservation() != null;
                    initializeGTSStatus(experimentPart);

                    final GtsReservationStatusBinding gtsReservationStatusBinding =
                            new GtsReservationStatusBinding(experimentPart.getReservation());

                    gtsStatusBindings.put(experimentPart.getGeantTestbedType(), gtsReservationStatusBinding);

                    setGeantTestbedTypeStatus(experimentPart.getGeantTestbedType(), gtsReservationStatusBinding.computeValue());   //initial update
                    gtsReservationStatusBinding.addListener((observable, oldValue, newValue) -> {
                        LOG.trace("Updating status of GTS testbed {} to {}", experimentPart.getGeantTestbedType().getName(), newValue);
                        setGeantTestbedTypeStatus(experimentPart.getGeantTestbedType(), newValue);
                    });
                });

            }
        };


    }

    private void installGTSTooltip(FXGeantTestbedType gtt) {
        Tooltip tooltip = new Tooltip();
        tooltip.setGraphic(new Label("No information available"));
        tooltip.setAutoHide(true);
        tooltips.put(gtt, tooltip);

        final RspecGTSNode gtsNode = getCanvas().getRspecGTSNode(gtt);

        gtsNode.setOnMouseEntered(mouseEvent -> tooltip.show(gtsNode, mouseEvent.getScreenX(), mouseEvent.getScreenY()));
    }

    private final Map<FXGeantTestbedType, Tooltip> tooltips = new HashMap<>();

    private void initializeGTSStatus(GtsExperimentPart experimentPart) {
        assert experimentPart.getReservation() != null;

        Tooltip tooltip = tooltips.get(experimentPart.getGeantTestbedType());
        assert tooltip != null;

        LOG.info("Initializing GTSDetailsView for {}", experimentPart.getName());
        tooltip.setGraphic(new GTSDetailsView(experimentPart, browserUtil));
    }

    private final InvalidationListener sliceStatusListener = observable -> {
        if (Platform.isFxApplicationThread()) updateStatus();
        else
            Platform.runLater(this::updateStatus);
    };

    private void initializeSlice(Slice slice) {
        assert (this.slice == null) : "Slice can only be assigned once!";

        this.slice = slice;
        slice.statusProperty().addListener(new WeakInvalidationListener(sliceStatusListener));
        slice.manifestRspecProperty().addListener(new WeakInvalidationListener(sliceStatusListener));
    }


    /**
     * This function must support two scenarios:
     * <p/>
     * A) nodes, interfaces and links have a sliverId assigned to them, which can be used to distinguish which resource belongs to which sliver
     * <p/>
     * B) No sliverIds are assigned. We have to reason ourselves over which resources belong to which sliver.
     * Typically, there will be only one sliver for all resources managed by one CM. We use this knowledge to
     * update the right resources.
     */
    public void updateStatus() {
        assert (slice != null);

        //we try to get an available manifest FXModelRspec.

        FXModelRspec modelRspec = null;

        if (slice.getManifestRspec() != null) {
            modelRspec = slice.getManifestRspec().getModelRspec(ModelRspecType.FX);
        }

        //revert to request-Rspec
        if (modelRspec == null && slice.getRequestRspec() != null) {
            modelRspec = slice.getRequestRspec().getModelRspec(ModelRspecType.FX);
        }

        if (modelRspec == null) {
            LOG.error("Cannot update sliverStatus, as there is no modelRspec available.");
            return;
        }

        if (LOG.isTraceEnabled())
            LOG.trace("Updating ModelSliceView status with {}", slice.getStatus().detailedToString());


        //            //Scenario A: skip this node if it has a sliverId that does not match the sliver that we're processing
//            if (manifestRspecNode.getSliverId() != null
//                    && !manifestRspecNode.getSliverId().equals(sliver.getUrn()))
//                continue;
//
//            //Scenario B: skip this node if the componentManager and the sliver-authority do not match
//            if (!sliver.getAuthority().getDefaultComponentManagerAsGeniUrn().equals(manifestRspecNode.getComponentManagerId()))
//                continue;
        modelRspec.getNodes().forEach(this::updateRspecNodeStatus);


        //            //Scenario A: skip this link if it has a sliverId that does not match the sliver that we're processing
//            if (manifestRspecLink.getSliverId() != null
//                    && !manifestRspecLink.getSliverId().equals(sliver.getUrn()))
//                continue;
//            //Scenario B: skip this link if the sliver-authority doesn't appear in the list of componentManagers responsible for the link
//            if (!manifestRspecLink.getComponentManagerUrns().contains(sliver.getAuthority().getDefaultComponentManagerAsGeniUrn()))
//                continue;
        modelRspec.getLinks().forEach(this::updateRspecLinkStatus);

        modelRspec.getAddressPools().forEach(this::updateAddressPoolStatus);
    }

    private void updateAddressPoolStatus(FXAddressPool manifestAddressPool) {
        assert slice != null;

        FXAddressPool canvasAddressPool =
                canvasModel.getAddressPoolByClientId(manifestAddressPool.getClientId());

        if (canvasAddressPool == null) {
            LOG.warn("Could not find canvasAddressPool for addresspool {}", manifestAddressPool.getClientId());
            return;
        }

        setAddressPoolStatus(canvasAddressPool, slice.getStatus().getGlobalStatus());
    }

    private void setAddressPoolStatus(FXAddressPool canvasAddressPool, StatusDetails.SliverStatus status) {
        assert canvasAddressPool != null;
        LOG.debug("Canvas: Setting status of addresspool {} to {}", canvasAddressPool.getClientId(),
                status != null ? status.name() : "null");

        AddressPoolCanvasNode addressPoolCanvasNode = experimentCanvas.getAddressPoolCanvasNode(canvasAddressPool);

        //remove all old styleclasses
        for (StatusDetails.SliverStatus s : StatusDetails.SliverStatus.values()) {
            addressPoolCanvasNode.getStyleClass().remove(SliceExperimentCanvas.statusToAddressPoolStyleClass(s));
        }

        //apply new styleclasses
        addressPoolCanvasNode.getStyleClass().add(SliceExperimentCanvas.statusToAddressPoolStyleClass(status));
    }

    private boolean isFakeAuthority(GeniUrn authUrn) {
        Server auth = testbedInfoSource.getByUrn(authUrn,
                TestbedInfoSource.SubAuthMatchAllowed.ALLOW_TOPLEVEL, TestbedInfoSource.SubAuthMatchPreference.PREFER_EXACT_SUBAUTHORITY);

        if (auth == null)
            LOG.warn("Could not retrieve an authority for urn '{}'", authUrn);

        return auth != null && auth.isEdgeVlan();
    }


    private void updateRspecNodeStatus(FXRspecNode manifestRspecNode) {
        assert (slice != null);

        //get the matching FXRspecNode of the FXModelRspec that was used to build the canvas-view
        FXRspecNode canvasNode = canvasModel.getNodeByUniqueId(manifestRspecNode.getUniqueId());

        if (canvasNode == null) {
            canvasNode = canvasModel.getNodeByClientId(manifestRspecNode.getClientId());

            LOG.debug("Could not find a matching node in canvas-view for node {} while using uniqueId," +
                            " doing fallback to clientId. Selected node is: {}",
                    manifestRspecNode.getUniqueId(),
                    canvasNode != null ? canvasNode.getUniqueId() : "<NO NODE FOUND>");
        }

        if (canvasNode == null) {
            LOG.warn("Could not find matching node AT ALL in canvas-view for node {}", manifestRspecNode.getUniqueId());
            return;
        }

        StatusDetails status = slice.getStatus();

        //determine which status should be given to this rspecNode
        StatusDetails.SliverStatus nodeStatus;

        if (isFakeAuthority(manifestRspecNode.getComponentManagerId())) {
            if (status.getGlobalStatus() == StatusDetails.SliverStatus.UNALLOCATED) {
                nodeStatus = StatusDetails.SliverStatus.UNALLOCATED;
            } else {
                nodeStatus = StatusDetails.SliverStatus.READY;
            }
        }

        //first try to fetch the component-specific status
        else if (status.hasComponentStatus(manifestRspecNode.getComponentId())) {

            nodeStatus = status.getNodeStatusByComponentUrn(manifestRspecNode.getComponentId());
            if (LOG.isTraceEnabled())
                LOG.trace("Status node {} is {} according to nodeStatusByComponentUrn", manifestRspecNode.getUniqueId(), nodeStatus);
        } //fallback to status of the sliverStatus, but only if it is mentioned in the {@link FXRspecNode#sliverId} (Scenario A)
        else if (manifestRspecNode.getSliverId() != null && status.hasSliverStatus(manifestRspecNode.getSliverId())) {
            nodeStatus = status.getStatusBySliverUrn(manifestRspecNode.getSliverId());
            if (LOG.isTraceEnabled())
                LOG.trace("Status node {} is {} according to getStatusBySliverUrn", manifestRspecNode.getUniqueId(), nodeStatus);
        } else {
            //we assume status B, and try to fetch the status of the sliver associated with the node componentManagerId
            if (LOG.isTraceEnabled())
                LOG.trace("Current status: {}", status.detailedToString());
            List<Sliver> slivers = slice.findSlivers(manifestRspecNode.getComponentManagerId());
            StatusDetails.SliverStatus sliversStatus = StatusDetails.SliverStatus.READY;
            boolean validStatus = false;
            for (Sliver sliver : slivers) {
                if (status.hasSliverStatus(sliver.getUrn())) {
                    sliversStatus =
                            StatusDetails.SliverStatus.merge(sliversStatus, status.getStatusBySliverUrn(sliver.getUrn()));
                    validStatus = true;
                }
            }

            if (validStatus) {
                nodeStatus = sliversStatus;

                if (LOG.isTraceEnabled())
                    LOG.trace("Status node {} is {} according to getStatusBySliverUrn VIA AUTHORITY", manifestRspecNode.getUniqueId(), nodeStatus);
            } else {
                nodeStatus = status.getGlobalStatus();
                if (LOG.isTraceEnabled())
                    LOG.trace("Status node {} is {} according to slice.globalStatus because no slivers available", manifestRspecNode.getUniqueId(), nodeStatus);

            }
        }


        //FEDIBBTDEV-1280 : global failed + sliver "notready" (which is interpreted as CHANGING) should be handled as failed
        if (nodeStatus == StatusDetails.SliverStatus.CHANGING && status.getGlobalStatus() == StatusDetails.SliverStatus.FAIL) {
            nodeStatus = StatusDetails.SliverStatus.FAIL;

            if (LOG.isDebugEnabled())
                LOG.debug("Status node {} was changed to FAILED because global status is FAIL", manifestRspecNode.getUniqueId());
        }

        if (LOG.isTraceEnabled())
            LOG.trace("Setting {} to status {} with loginServices available = {}",
                    canvasNode.getUniqueId(), nodeStatus, !manifestRspecNode.getLoginServices().isEmpty());

        setRspecNodeStatus(canvasNode, nodeStatus, !manifestRspecNode.getLoginServices().isEmpty(),
                //TODO fix reservation experiment.getFailedReservationResources().contains(manifestRspecNode.getComponentId())
                false);
    }

    private void updateRspecLinkStatus(FXRspecLink manifestRspecLink) {
        assert (slice != null);

        StatusDetails status = slice.getStatus();
        //we determine the current overall link-status
        StatusDetails.SliverStatus linkStatus = StatusDetails.SliverStatus.READY;
        //Scenario A: the link is not stitched, and has a specific sliverId
        if (!manifestRspecLink.isStitched(testbedInfoSource)
                && manifestRspecLink.getSliverId() != null
                && status.hasSliverStatus(manifestRspecLink.getSliverId())) {
            linkStatus = status.getStatusBySliverUrn(manifestRspecLink.getSliverId());
        } //Scenario B: we compose a linkstatus ourself by iterating over the componentMangers involved in the link
        else if (manifestRspecLink.getComponentManagerUrns() != null && !manifestRspecLink.getComponentManagerUrns().isEmpty()) {
            for (GeniUrn cmUrn : manifestRspecLink.getComponentManagerUrns()) {
                if (isFakeAuthority(cmUrn))
                    continue;

                List<Sliver> slivers = slice.findSlivers(cmUrn);
                if (!slivers.isEmpty()) {

                    //TODO see FEDIBBTDEV-1460
//TODO                    Set<Sliver> associatedSlivers = new HashSet<>();
//TODO                    for (Sliver sliver : slivers) {
//TODO                        for (Sliver.ComponentInfo ci : sliver.getComponentInfo()) {
//TODO                            if (ci.getClientId() != null && manifestRspecLink.getClientId() != null && ci.getClientId().equals(manifestRspecLink.getClientId())) {
//TODO                                associatedSlivers.add(sliver);
//TODO                            }
//TODO                            if (ci.getComponentId() != null  ???) {
//TODO                                associatedSlivers.add(sliver);
//TODO                            }
//TODO                        }
//TODO                    }


                    /*
                    As there is currently no status-information returned on the slivers from "stitched" interfaces on
                    emulab-testbeds, and as we don't know the names of the slivers responsible in  the intermediary
                    authorities (eg. ION), we use all the slivers we can find to determine the status of the link.

                     If more than one link goes trough an intermediary authority, we have no possibility to find out
                     which sliver is responsible for which link!

                     */
                    for (Sliver sliver : slivers) {
                        //we only take slivers into account with a node or link associated to it. Otherwise we risk to take unused slivers into account
                        boolean hasNodes = sliver.getNodes() != null && !sliver.getNodes().isEmpty();
                        boolean hasLinks = sliver.getLinks() != null && !sliver.getLinks().isEmpty();
                        if (!hasNodes && hasLinks)
                            continue;

                        if (LOG.isTraceEnabled()) {
                            LOG.trace("Updating status of link {} from {} to {} because of sliver {} with status {}",
                                    manifestRspecLink.getUniqueId(), linkStatus, StatusDetails.SliverStatus.merge(linkStatus, sliver.getStatus().getGlobalStatus()),
                                    sliver.getUrn(), sliver.getStatus().getGlobalStatus()
                            );
                        }
                        linkStatus =
                                StatusDetails.SliverStatus.merge(linkStatus, sliver.getStatus().getGlobalStatus());
                    }
                } else {
                    linkStatus =
                            StatusDetails.SliverStatus.merge(linkStatus, StatusDetails.SliverStatus.UNINITIALISED);
                    if (LOG.isTraceEnabled())
                        LOG.trace("Setting statusByCMs to UNINITIALISED because no slivers for {} could be found", cmUrn);
                }
            }
        }

        FXRspecLink canvasLink =
                canvasModel.getLinkByUniqueId(manifestRspecLink.getUniqueId());

        if (canvasLink == null) {
            canvasLink =
                    canvasModel.getLinkByUniqueId(manifestRspecLink.getPreStitchingUniqueId());
        }

        if (canvasLink == null) {
            LOG.trace("Could not match link with uniqueId {}, reverting to clientId", manifestRspecLink.getUniqueId());
            canvasLink = canvasModel.getLinkByClientId(manifestRspecLink.getClientId());
        }


        if (canvasLink != null) {
            setRspecLinkStatus(canvasLink, linkStatus);
        } else {
            LOG.warn("Could not find matching canvas link for {}", manifestRspecLink.getUniqueId());
        }


        for (FXRspecInterface manifestRspecIface : manifestRspecLink.getInterfaces()) {
            FXRspecInterface canvasIface =
                    canvasModel.getInterfaceByUniqueId(manifestRspecIface.getUniqueId());

            if (canvasIface == null) {
                LOG.warn("Could not find matching interface in canvas-view for iface {}, reverting to clientId", manifestRspecIface.getUniqueId());
                canvasIface = canvasModel.getInterfaceByClientId(manifestRspecIface.getClientId());
            }

            if (canvasIface == null) {
                LOG.warn("Could not find matching interface in canvas-view for clientId {}", manifestRspecIface.getClientId());
                continue;
            }

            //Determine which status should be given to this rspecInterface
            StatusDetails.SliverStatus ifaceStatus;

//            //Scenario A: first try to use the status of the sliver mentioned in {@link RSpecInterface#sliverId}
//            if (manifestRspecIface.getSliverId() != null
//                    && status.hasSliverStatus(manifestRspecIface.getSliverId())) {
//                ifaceStatus = status.getStatusBySliverUrn(manifestRspecIface.getSliverId());
//                LOG.trace("Status iface {} is {} according to getStatusBySliverUrn with FXRspecInterface#sliverId", manifestRspecIface.getId(), ifaceStatus);
//            }
//            //Scenario A: otherwise use the status of the link, if it has a {@link RSpecLink#sliverId}
//            else if (manifestRspecLink.getSliverId() != null
//                    && status.hasSliverStatus(manifestRspecLink.getSliverId())) {
//                ifaceStatus = status.getStatusBySliverUrn(manifestRspecLink.getSliverId());
//                LOG.trace("Status iface {} is {} according to getStatusBySliverUrn with FXRspecLink#sliverId", manifestRspecIface.getId(), ifaceStatus);
//
//            } else {
            // Scenario B: fallback to the combined status of the associated componentManagers.
            ifaceStatus = linkStatus;
            if (LOG.isTraceEnabled())
                LOG.trace("Status iface {} is {} according fallback to link-status", manifestRspecIface.getUniqueId(), ifaceStatus);

//            }

            setRspecIfaceStatus(canvasIface, ifaceStatus);
        }
    }


    private void setRspecNodeStatus(FXRspecNode canvasRspecNode, StatusDetails.SliverStatus status, boolean loginServiceAvailable, boolean reservationFailed) {
        if (LOG.isTraceEnabled())
            LOG.trace("setRspecNodeStatus: Setting status of node {} to {} with loginServices available = {}, reservationFailed = {}", canvasRspecNode.getUniqueId(),
                    status != null ? status.name() : "null",
                    loginServiceAvailable, reservationFailed);
        RspecCanvasNode canvasNode = experimentCanvas.getRspecCanvasNode(canvasRspecNode);

        if (canvasNode == null) {
            LOG.warn("setRspecNodeStatus: Could not find a RspecCanvasNode for rspecNode with UniqueID {}. Ignoring status-update.", canvasRspecNode.getUniqueId());
            return;
        }

        //remove all old styleclasses
        for (SliceExperimentCanvas.NodeStatus s : SliceExperimentCanvas.NodeStatus.values()) {
            canvasNode.getStyleClass().remove(SliceExperimentCanvas.statusToNodeStyleClass(s));
        }

        //apply new styleclasses
        canvasNode.getStyleClass().add(SliceExperimentCanvas.statusToNodeStyleClass(
                SliceExperimentCanvas.NodeStatus.fromSliverStatus(status, loginServiceAvailable, reservationFailed)));
    }


    private void setRspecIfaceStatus(FXRspecInterface rspecIface, StatusDetails.SliverStatus status) {
        if (LOG.isTraceEnabled())
            LOG.trace("Canvas: Setting status of iface {} to {}", rspecIface.getClientId(),
                    status != null ? status.name() : "null");

        if (rspecIface.isLinkBound()) {
            RspecCanvasLink canvasLink = experimentCanvas.getRspecCanvasLink(rspecIface.getLink());
            //get the right interface-link
            RspecCanvasLink.InterfaceLink ifaceLink = canvasLink.getInterfaceLinkByRspecInterface(rspecIface);


            //remove all old styleclasses
            for (StatusDetails.SliverStatus s : StatusDetails.SliverStatus.values()) {
                ifaceLink.getStyleClass().remove(SliceExperimentCanvas.statusToInterfaceStyleClass(s));
            }

            //apply new styleclasses
            ifaceLink.getStyleClass().add(SliceExperimentCanvas.statusToInterfaceStyleClass(status));
        }
    }

    private void setRspecLinkStatus(FXRspecLink rspecLink, StatusDetails.SliverStatus status) {
        assert rspecLink != null;
        LOG.debug("Canvas: Setting status of link {} to {}", rspecLink.getUniqueId(),
                status != null ? status.name() : "null");

        RspecCanvasLink canvasLink = experimentCanvas.getRspecCanvasLink(rspecLink);

        //remove all old styleclasses
        for (StatusDetails.SliverStatus s : StatusDetails.SliverStatus.values()) {
            canvasLink.getLinkCenter().getStyleClass().remove(SliceExperimentCanvas.statusToLinkCenterStyleClass(s));
        }

        //apply new styleclasses
        canvasLink.getLinkCenter().getStyleClass().add(SliceExperimentCanvas.statusToLinkCenterStyleClass(status));
    }

    private void setGeantTestbedTypeStatus(FXGeantTestbedType gtt, SliceExperimentCanvas.NodeStatus status) {
        assert gtt != null;
        LOG.debug("Canvas: Setting status of GTT {} to {}", gtt.getName(),
                status != null ? status.name() : "null");

        RspecGTSNode rspecGTSNode = experimentCanvas.getRspecGTSNode(gtt);

        //remove all old styleclasses
        for (SliceExperimentCanvas.NodeStatus s : SliceExperimentCanvas.NodeStatus.values()) {
            rspecGTSNode.getStyleClass().remove(SliceExperimentCanvas.statusToNodeStyleClass(s));
        }

        //apply new styleclasses
        rspecGTSNode.getStyleClass().add(SliceExperimentCanvas.statusToNodeStyleClass(status));
    }


    public SliceExperimentCanvas getCanvas() {
        return experimentCanvas;
    }


    @Override
    public void onExperimentStateChange(ExperimentState newExperimentState) {
        if (slice == null && newExperimentState != ExperimentState.PENDING) {
            assert (experiment.getSlice() != null);
            initializeSlice(experiment.getSlice());
        }

        if (slice != null) {
            Platform.runLater(this::updateStatus);
        }
    }

    @Override
    public void onExperimentPartAdded(ExperimentPart experimentPart) {

    }

    private static class GtsReservationStatusBinding extends ObjectBinding<SliceExperimentCanvas.NodeStatus> {
        private final GtsXmlReservation reservation;

        private GtsReservationStatusBinding(GtsXmlReservation reservation) {
            this.reservation = reservation;
            bind(reservation.statusProperty());
        }

        @Override
        protected SliceExperimentCanvas.NodeStatus computeValue() {

            switch (reservation.getStatus()) {
                case ACTIVATING:
                case DEACTIVATING:
                    return SliceExperimentCanvas.NodeStatus.CHANGING;
                case ACTIVE:
                    return SliceExperimentCanvas.NodeStatus.READY_WITH_LOGINSERVICES;
                case DEACTIVED:
                    return SliceExperimentCanvas.NodeStatus.UNALLOCATED;
                case FAILURE:
                    return SliceExperimentCanvas.NodeStatus.FAIL;
                default:
                    LOG.warn("Unexpected reservation state: {}", reservation.getStatus());
                    return SliceExperimentCanvas.NodeStatus.UNKNOWN;
            }
        }
    }
}
