package be.iminds.ilabt.jfed.rspec.model.javafx_impl;

import be.iminds.ilabt.jfed.rspec.model.*;
import be.iminds.ilabt.jfed.rspec.model.controller.ExperimentCommand;
import be.iminds.ilabt.jfed.rspec.model.impl.BasicRspecNode;
import be.iminds.ilabt.jfed.rspec.parser.EventListExtraXml;
import be.iminds.ilabt.jfed.rspec.parser.ExtraXml;
import be.iminds.ilabt.jfed.rspec.parser.RspecParseException;
import be.iminds.ilabt.jfed.util.GeniUrn;
import javafx.beans.InvalidationListener;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

import static be.iminds.ilabt.jfed.rspec.parser.RspecXmlConstants.Q_JFEDCOMMAND_EXP_COMMAND;
import static be.iminds.ilabt.jfed.rspec.parser.RspecXmlConstants.Q_JFEDCOMMAND_EXP_COMMAND_OLDERROR;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///// WARNING: very dirty cut and paste code reuse between setPropertiesFromGeni3RequestRspec and setPropertiesFromGeni3ManifestRspec and setPropertiesFromGeni3AdvertisementRspec ! /////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

public class FXRspecNode implements RspecNode {
    private static final Logger LOG = LoggerFactory.getLogger(FXRspecNode.class);

    protected final FXModelRspec modelRspec;

    protected final StringProperty clientId = new SimpleStringProperty();
    protected final ReadOnlyStringWrapper uniqueClientId = new ReadOnlyStringWrapper();
    protected final DoubleProperty editorX = new SimpleDoubleProperty(-1.0);
    protected final DoubleProperty editorY = new SimpleDoubleProperty(-1.0);
    protected final DoubleProperty flackEditorX = new SimpleDoubleProperty(-1.0);
    protected final DoubleProperty flackEditorY = new SimpleDoubleProperty(-1.0);
    protected final ObservableList<HardwareType> hardwareTypes = FXCollections.observableArrayList();
    protected final ObjectProperty<Boolean> exclusive = new SimpleObjectProperty<>(null);
    protected final BooleanProperty hasRoutableControlIp = new SimpleBooleanProperty(false);
    protected final ObjectProperty<Boolean> available = new SimpleObjectProperty<>(null);

    protected final ObservableList<SliverType> sliverTypes = FXCollections.observableArrayList();

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    protected final ObjectProperty<GeniUrn> componentManagerId = new SimpleObjectProperty<>();
    protected final ObjectProperty<GeniUrn> componentId = new SimpleObjectProperty<>();
    protected final StringProperty componentName = new SimpleStringProperty();

    protected final ObjectProperty<GeniUrn> sliverId = new SimpleObjectProperty<>();
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    protected final StringProperty hostName = new SimpleStringProperty();
    protected final ObservableList<FXExecuteService> executeServices = FXCollections.observableArrayList();
    protected final ObservableList<FXInstallService> installServices = FXCollections.observableArrayList();
    protected final ObservableList<LoginService> loginServices = FXCollections.observableArrayList();
    protected final ObservableList<ProxyService> proxyServices = FXCollections.observableArrayList();
    protected final ObservableList<AnsibleService> ansibleServices = FXCollections.observableArrayList();
    protected final ObservableList<String> ansibleGroups = FXCollections.observableArrayList();

    private final ObservableList<ExperimentCommand> commands = FXCollections.observableArrayList();
    private final ObjectProperty<NodeLocation> location = new SimpleObjectProperty<>();
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    protected final ObservableList<FXRspecInterface> interfaces = FXCollections.observableArrayList();
    protected final StringProperty resourceClassId = new SimpleStringProperty();
    protected final ObservableList<String> leaseIdRefs = FXCollections.observableArrayList();

    protected final List<ExtraXml> extraXml = new ArrayList<>();
    protected final List<ExtraXml> extraServicesXmls = new ArrayList<>();

    private final Set<EmptyTag> emptyTags = new HashSet<>();

    {
        this.componentManagerIdProperty().addListener(observable -> {
            for (FXRspecInterface iface : interfaces)
                if (iface.isLinkBound()) {
                    iface.getLink().updateComponentManagerUrns();
                }
        });
    }

    /**
     * constuctor without initializing ID is not public.
     */
    private FXRspecNode() {
        this.modelRspec = null;
    }

    FXRspecNode(FXModelRspec modelRspec, String clientId) {
        this.clientId.setValue(clientId);
        this.modelRspec = modelRspec;
    }

    /**
     * Copy Constructor
     *
     * @param rspecNode Node to be copied
     */
    FXRspecNode(FXModelRspec newModelRspec, RspecNode rspecNode, InterfaceCopyMethod interfaceCopyMethod) {
        this.modelRspec = newModelRspec;
        this.clientId.set(rspecNode.getClientId());
        this.editorX.set(rspecNode.getEditorX());
        this.editorY.set(rspecNode.getEditorY());
        this.flackEditorX.set(rspecNode.getFlackEditorX());
        this.flackEditorY.set(rspecNode.getFlackEditorY());
        this.componentId.set(rspecNode.getComponentId());
        this.componentManagerId.set(rspecNode.getComponentManagerId());
        this.componentName.set(rspecNode.getComponentName());
        this.sliverId.set(rspecNode.getSliverId());
        this.exclusive.set(rspecNode.getExclusive());
        this.available.set(rspecNode.getAvailable());
        this.hardwareTypes.addAll(rspecNode.getHardwareTypes());
        this.hostName.set(rspecNode.getHostName());
        this.leaseIdRefs.setAll(rspecNode.getLeaseIdRefs());

        this.location.set(rspecNode.getLocation());

        this.loginServices.addAll(rspecNode.getLoginServices());
        this.proxyServices.addAll(rspecNode.getProxyServices());
        this.ansibleServices.addAll(rspecNode.getAnsibleServices());
        this.ansibleGroups.addAll(rspecNode.getAnsibleGroups());
        this.sliverTypes.setAll(rspecNode.getSliverTypes());
        this.hasRoutableControlIp.set(rspecNode.getRoutableControlIp());

        this.emptyTags.addAll(rspecNode.getEmptyTags());

        if (rspecNode instanceof FXRspecNode) {
            this.extraServicesXmls.addAll(rspecNode.getExtraServicesXml());
            this.extraXml.addAll(rspecNode.getExtraXml());
            for (ExtraXml extraXmlEl : rspecNode.getExtraXml()) {
                assert !Objects.equals(Q_JFEDCOMMAND_EXP_COMMAND, extraXmlEl.getStartElementName());
                assert !Objects.equals(Q_JFEDCOMMAND_EXP_COMMAND_OLDERROR, extraXmlEl.getStartElementName());
            }

            FXRspecNode fxRspecNode = (FXRspecNode) rspecNode;
            for (ExperimentCommand c : fxRspecNode.commands) {
                ExperimentCommand n = new ExperimentCommand(c);
                n.getNodeIDProperty().bind(this.uniqueIdProperty());
                this.commands.add(n);
            }
            assert fxRspecNode.commands.size() == this.commands.size();
            this.executeServices.addAll(fxRspecNode.getExecuteServices());
            this.installServices.addAll(fxRspecNode.getInstallServices());
            this.resourceClassId.set(fxRspecNode.getResourceClassId());
        } else {
            FXRspecFactory fxRspecFactory =
                    (FXRspecFactory) RspecFactoryFactory.getRspecFactoryInstance(ModelRspecType.FX);
            for (ExtraXml exml : rspecNode.getExtraXml()) {
                boolean noStoreNeeded;
                try {
                    noStoreNeeded = fxRspecFactory.processExtraNodeXml(this, new EventListExtraXml(exml));
                } catch (RspecParseException e) {
                    LOG.error("RspecParseException while processing extra node XML: Extra node XML will be ignored", e);
                    noStoreNeeded = false;
                }
                if (!noStoreNeeded)
                    this.extraXml.add(exml);
            }
            this.extraServicesXmls.addAll(rspecNode.getExtraServicesXml());
            for (ExecuteService e : rspecNode.getExecuteServices())
                this.executeServices.add(new FXExecuteService(e));
            for (InstallService i : rspecNode.getInstallServices())
                this.installServices.add(new FXInstallService(i));
            //resourceClassId may be ignored in this type of copy. It is only used by the editor, which only uses FXRspecModel/FXRspecNode
        }

        if (Objects.equals(interfaceCopyMethod, InterfaceCopyMethod.CREATE_NEW_INTERFACE)) {
            copyInterfaces(rspecNode);
        }
        if (Objects.equals(interfaceCopyMethod, InterfaceCopyMethod.STEAL_INTERFACE)) {
            stealInterfaces(rspecNode);
        }

    }

    @Override
    public void overwritePropertiesWith(RspecNode otherNode, boolean overwriteWithNull) {
        assert Objects.equals(this.getUniqueId(), otherNode.getUniqueId());

        if (overwriteWithNull) {
            this.componentId.set(otherNode.getComponentId());
            this.componentManagerId.set(otherNode.getComponentManagerId());
            this.editorX.set(otherNode.getEditorX());
            this.editorY.set(otherNode.getEditorY());
            this.flackEditorX.set(otherNode.getFlackEditorX());
            this.flackEditorY.set(otherNode.getFlackEditorY());
            this.componentName.set(otherNode.getComponentName());
            this.sliverId.set(otherNode.getSliverId());
            this.exclusive.set(otherNode.getExclusive());
            this.available.set(otherNode.getAvailable());
            this.hostName.set(otherNode.getHostName());
            this.location.set(otherNode.getLocation());
            this.leaseIdRefs.setAll(otherNode.getLeaseIdRefs());

            setExecuteServices(otherNode.getExecuteServices());
            this.hardwareTypes.setAll(otherNode.getHardwareTypes());
            setInstallServices(otherNode.getInstallServices());
            this.loginServices.setAll(otherNode.getLoginServices());
            this.proxyServices.setAll(otherNode.getProxyServices());
            this.ansibleServices.setAll(otherNode.getAnsibleServices());
            this.ansibleGroups.setAll(otherNode.getAnsibleGroups());
            this.sliverTypes.setAll(otherNode.getSliverTypes());

            this.extraXml.clear();
            this.extraXml.addAll(otherNode.getExtraXml());

            this.extraServicesXmls.clear();
            this.extraServicesXmls.addAll(otherNode.getExtraServicesXml());
        } else {
            if (otherNode.getComponentId() != null) this.componentId.set(otherNode.getComponentId());
            if (otherNode.getComponentManagerId() != null)
                this.componentManagerId.set(otherNode.getComponentManagerId());
            if (otherNode.getEditorX() >= 0) this.editorX.set(otherNode.getEditorX());
            if (otherNode.getEditorY() >= 0) this.editorY.set(otherNode.getEditorY());
            if (otherNode.getFlackEditorX() >= 0) this.flackEditorX.set(otherNode.getFlackEditorX());
            if (otherNode.getFlackEditorY() >= 0) this.flackEditorY.set(otherNode.getFlackEditorY());
            if (otherNode.getComponentName() != null) this.componentName.set(otherNode.getComponentName());
            if (otherNode.getSliverId() != null) this.sliverId.set(otherNode.getSliverId());
            if (otherNode.getExclusive() != null) this.exclusive.set(otherNode.getExclusive());
            if (otherNode.getAvailable() != null) this.available.set(otherNode.getAvailable());
            if (otherNode.getHostName() != null) this.hostName.set(otherNode.getHostName());
            if (otherNode.getLocation() != null) this.location.set(otherNode.getLocation());

            addMissingExecuteServices(this.executeServices, otherNode.getExecuteServices());
            addMissing(this.hardwareTypes, otherNode.getHardwareTypes());
            addMissingInstallServices(this.installServices, otherNode.getInstallServices());
            addMissing(this.loginServices, otherNode.getLoginServices());
            addMissing(this.proxyServices, otherNode.getProxyServices());
            addMissing(this.ansibleServices, otherNode.getAnsibleServices());
            addMissing(this.sliverTypes, otherNode.getSliverTypes());
            addMissing(this.extraXml, otherNode.getExtraXml()); //works due to custom comparison in EventListExtraXml
            addMissing(this.extraServicesXmls, otherNode.getExtraServicesXml()); //works due to custom comparison in EventListExtraXml
        }
    }

    private static <T> void addMissing(List<T> target, List<? extends T> other) {
        for (T o : other) {
            if (!target.contains(o))
                target.add(o);
        }
    }

    @SuppressWarnings("SuspiciousMethodCalls")
    private static void addMissingExecuteServices(List<FXExecuteService> target, List<? extends ExecuteService> other) {
        for (ExecuteService o : other) {
            if (!target.contains(o)) {
                if (o instanceof FXExecuteService)
                    target.add((FXExecuteService) o);
                else
                    target.add(new FXExecuteService(o));
            }
        }
    }

    @SuppressWarnings("SuspiciousMethodCalls")
    private static void addMissingInstallServices(List<FXInstallService> target, List<? extends InstallService> other) {
        for (InstallService o : other) {
            if (!target.contains(o)) {
                if (o instanceof FXInstallService)
                    target.add((FXInstallService) o);
                else
                    target.add(new FXInstallService(o));
            }
        }
    }

    /**
     * creates a new interfaceId, using any info found in rspecInterface as a basis.
     * It will not use the same id as any existing interface on the node or link bound to rspecInterface,
     * or as rspecInterface if it already has one.
     */
    static String generateNewInterfaceId(RspecInterface rspecInterface) {
        return BasicRspecNode.generateNewInterfaceId(rspecInterface);
    }

    //TODO: is this method still used for copying interfaces in the same rspec, or should it be cleaned up?
    public void copyInterfaces(RspecNode rspecNode) {
        for (RspecInterface rspecInterface : rspecNode.getInterfaces()) {
            //clone interface
            FXRspecInterface res;
            if (rspecNode.getRspec() == getRspec()) {
                if (rspecInterface.isLinkBound()) {
                    res = new FXRspecInterface(this, (FXRspecLink) rspecInterface.getLink(),
                            generateNewInterfaceId(rspecInterface));
                } else {
                    res = new FXRspecInterface(this, generateNewInterfaceId(rspecInterface));
                }
            } else {
                //since we copy to new model, just copy interface name
                res = new FXRspecInterface(this, rspecInterface.getClientId());
            }
            //overwrite, but do not overwrite clientId
            String clientId = res.getClientId();
            res.overwritePropertiesWith(rspecInterface, true);
            res.setClientId(clientId);
        }
    }

    public void stealInterfaces(RspecNode rspecNode) {
        List<RspecInterface> origIfaces = new ArrayList<>(rspecNode.getInterfaces());
        for (RspecInterface rspecInterface : origIfaces) {
            rspecInterface.moveToNode(this);
        }
    }

    @Override
    public FXModelRspec getRspec() {
        return modelRspec;
    }

    @Override
    public String getUniqueId() {
        return BasicStringRspec.makeUniqueNodeId(
                clientId.get(),
                componentId.get() == null ? null : componentId.get().getValue(),
                componentManagerId.get() == null ? null : componentManagerId.get().getValue()
        );
    }

    public ReadOnlyStringProperty uniqueIdProperty() {
        return uniqueClientId.getReadOnlyProperty();
    }

    {
        InvalidationListener l = observable -> uniqueClientId.setValue(getUniqueId());
        clientId.addListener(l);
        componentId.addListener(l);
        componentManagerId.addListener(l);
    }

    @Override
    @Nullable
    public String getClientId() {
        return clientId.get();
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public void setClientId(String value) {
        clientId.set(value);
    }

    public StringProperty clientIdProperty() {
        return clientId;
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public double getEditorX() {
        return editorX.get();
    }

    @Override
    public void setEditorX(double value) {
        editorX.set(value);
    }


    public DoubleProperty editorXProperty() {
        return editorX;
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public double getEditorY() {
        return editorY.get();
    }

    @Override
    public void setEditorY(double value) {
        editorY.set(value);
    }

    public DoubleProperty editorYProperty() {
        return editorY;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public double getFlackEditorX() {
        return flackEditorX.get();
    }

    @Override
    public void setFlackEditorX(double flackEditorX) {
        this.flackEditorX.set(flackEditorX);
    }

    public DoubleProperty flackEditorXProperty() {
        return flackEditorX;
    }

    @Override
    public double getFlackEditorY() {
        return flackEditorY.get();
    }

    @Override
    public void setFlackEditorY(double flackEditorY) {
        this.flackEditorY.set(flackEditorY);
    }

    public DoubleProperty flackEditorYProperty() {
        return flackEditorY;
    }


    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public ObservableList<HardwareType> getHardwareTypes() {
        return hardwareTypes;
    }

    @Override
    @Nullable
    public Boolean getExclusive() {
        return exclusive.get();
    }

    @Override
    public void setExclusive(Boolean value) {
        exclusive.set(value);
    }

    public ObjectProperty<Boolean> exclusiveProperty() {
        return exclusive;
    }

    @Override
    @Nullable
    public Boolean getAvailable() {
        return available.get();
    }

    public ObjectProperty<Boolean> availableProperty() {
        return available;
    }

    @Override
    public void setAvailable(Boolean available) {
        this.available.set(available);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // handy accessors for single sliver type (manifest or request)

    @Override
    public void setSliverTypeName(String name) {
        assert sliverTypes.size() <= 1;
        //by using a builder, we keep all other sliver type settings, such as diskimage and xen options

        SliverTypeBuilder builder;
        if (sliverTypes.size() == 1)
            builder = new SliverTypeBuilder(sliverTypes.get(0)).name(name);
        else
            builder = new SliverTypeBuilder(name);

        sliverTypes.setAll(builder.build());
    }

    @Override
    public void setSliverTypeDiskImage(@Nullable String diskImageName) {
        if (sliverTypes.size() > 1)
            throw new RuntimeException("The caller did expected this RSpec to have exactly one sliver types. (probably because expected this to be a request rspec). But this has " + sliverTypes.size() + " sliver types.");
        assert sliverTypes.size() == 1;

        SliverTypeBuilder builder = new SliverTypeBuilder(sliverTypes.get(0));
        List<DiskImage> diskImageList = new ArrayList<>();
        if (diskImageName != null)
            diskImageList.add(new DiskImage(diskImageName));
        builder.setDiskImages(diskImageList);
        sliverTypes.setAll(builder.build());
    }

    @Override
    @Nullable
    public String getSliverTypeName() {
        if (sliverTypes.size() > 1)
            throw new RuntimeException("The caller did not expect this RSpec to have multiple sliver types. (it probably expected this to be a request rspec, which does indeed not have multiple sliver_type's)");

        if (sliverTypes.isEmpty()) return null;
        return sliverTypes.get(0).getName();
    }

    @Override
    public SliverType getSliverType() {
        if (sliverTypes.isEmpty())
            return null;
        if (sliverTypes.size() > 1)
            throw new RuntimeException("The caller did not expect this RSpec to have multiple sliver types. (it probably expected this to be a request rspec, which does indeed not have multiple sliver_type's)");
        return sliverTypes.get(0);
    }

    @Override
    public void setSliverType(@Nullable SliverType singleType) {
        if (singleType == null)
            sliverTypes.clear();
        else
            sliverTypes.setAll(singleType);
    }

    @Override
    @Nullable
    public String getOsImageUrn() {
        if (sliverTypes.size() > 1)
            throw new RuntimeException("The getOsImageUrn convenience method does not support multiple sliver_types");
        if (sliverTypes.isEmpty())
            return null;
        SliverType sliverType = sliverTypes.get(0);
        if (sliverType.getDiskImages().size() > 1)
            throw new RuntimeException("The getOsImageUrn convenience method does not support multiple disk_images in the sliver_type");
        if (sliverType.getDiskImages().isEmpty())
            return null;
        return sliverType.getDiskImages().get(0).getName();
    }

    //accessors for multiple sliver types (advertisement)

    @Override
    public ObservableList<SliverType> getSliverTypes() {
        return sliverTypes;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    @Nullable
    public NodeLocation getLocation() {
        return location.get();
    }

    @Override
    public void setLocation(NodeLocation value) {
        location.set(value);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public ObjectProperty<NodeLocation> locationProperty() {
        return location;
    }

//    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//    @Override
//    public String getOsImageUrl() {
//        return getOsImageUrn();
//    }
//
//    @Override
//    public void setOsImageUrl(String value) {
//        setOsImageUrn(value);
//    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    @Nullable
    public GeniUrn getComponentManagerId() {
        return componentManagerId.get();
    }

    @Override
    public void setComponentManagerId(GeniUrn value) {
        componentManagerId.set(value);
    }

    @Override
    public void setComponentManagerId(String value) {
        if (value == null) {
            this.componentManagerId.set(null);
            return;
        }

        GeniUrn geniUrn = GeniUrn.parse(value);

        if (geniUrn != null) {
            this.componentManagerId.set(geniUrn);
        } else {
            LOG.warn("Could not set componentManagerId to {} as it is not a valid URN!", value);
            this.componentManagerId.set(null);
        }
    }


    public ObjectProperty<GeniUrn> componentManagerIdProperty() {
        return componentManagerId;
    }

    @Override
    @Nullable
    public GeniUrn getComponentId() {
        return componentId.get();
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public void setComponentId(GeniUrn value) {
        componentId.set(value);
    }

    @Override
    public void setComponentId(String value) {
        if (value == null) {
            this.componentId.set(null);
            return;
        }

        GeniUrn geniUrn = GeniUrn.parse(value);

        if (geniUrn != null) {
            this.componentId.set(geniUrn);
        } else {
            LOG.warn("Could not set componentId to {} as it is not a valid URN!", value);
            this.componentId.set(null);
        }
    }

    public ObjectProperty<GeniUrn> componentIdProperty() {
        return componentId;
    }

    @Override
    @Nullable
    public String getComponentName() {
        return componentName.get();
    }

    @Override
    public void setComponentName(String value) {
        componentName.set(value);
    }

    @Override
    @Nullable
    public GeniUrn getSliverId() {
        return sliverId.get();
    }

    @Override
    public void setSliverId(String sliverId) {
        if (sliverId == null) {
            this.sliverId.set(null);
            return;
        }

        GeniUrn geniUrn = GeniUrn.parse(sliverId);

        if (geniUrn != null) {
            this.sliverId.set(geniUrn);
        } else {
            LOG.warn("Could not set sliverId to {} as it is not a valid URN!", sliverId);
            this.sliverId.set(null);
        }
    }

    @Override
    public void setSliverId(GeniUrn sliverId) {
        this.sliverId.set(sliverId);
    }

    public ObjectProperty<GeniUrn> sliverIdProperty() {
        return sliverId;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public boolean getRoutableControlIp() {
        return hasRoutableControlIp.getValue();
    }

    @Override
    public void setRoutableControlIp(boolean hasRoutableControlIp) {
        this.hasRoutableControlIp.setValue(hasRoutableControlIp);
    }

    public BooleanProperty hasRoutableControlIpProperty() {
        return hasRoutableControlIp;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public StringProperty componentNameProperty() {
        return componentName;
    }

    @Override
    @Nullable
    public String getHostName() {
        return hostName.get();
    }

    @Override
    public void setHostName(String value) {
        hostName.set(value);
    }

    public StringProperty hostNameProperty() {
        return hostName;
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public String getResourceClassId() {
        return resourceClassId.get();
    }

    public StringProperty resourceClassIdProperty() {
        return resourceClassId;
    }

    public void setResourceClassId(String resourceClassId) {
        this.resourceClassId.set(resourceClassId);
    }


    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    @Nonnull
    public List<FXExecuteService> getExecuteServices() {
        return executeServices;
    }

    @Override
    public void setExecuteServices(@Nonnull List<? extends ExecuteService> value) {
        executeServices.clear();
        for (ExecuteService executeService : value) {
            executeServices.add(new FXExecuteService(executeService));
        }
    }

    @Override
    @Nonnull
    public List<FXInstallService> getInstallServices() {
        return installServices;
    }

    @Override
    public void setInstallServices(@Nonnull List<? extends InstallService> value) {
        installServices.clear();
        for (InstallService installService : value) {
            installServices.add(new FXInstallService(installService));
        }
    }

    @Override
    public void addExecuteService(ExecuteService executeService) {
        if (executeService instanceof FXExecuteService) {
            executeServices.add((FXExecuteService) executeService);
        } else {
            executeServices.add(new FXExecuteService(executeService));
        }
    }

    @Override
    public void addInstallService(InstallService installService) {
        if (installService instanceof FXInstallService) {
            installServices.add((FXInstallService) installService);
        } else {
            installServices.add(new FXInstallService(installService));
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    @Override
    @Nonnull
    public ObservableList<LoginService> getLoginServices() {
        return loginServices;
    }

    @Override
    public void setLoginServices(List<LoginService> value) {
        loginServices.setAll(value);
    }


    @Override
    @Nonnull
    public ObservableList<ProxyService> getProxyServices() {
        return proxyServices;
    }

    @Override
    public void setProxyServices(List<ProxyService> value) {
        proxyServices.setAll(value);
    }


    @Override
    public List<? extends AnsibleService> getAnsibleServices() {
        return ansibleServices;
    }

    @Override
    public void setAnsibleServices(List<? extends AnsibleService> value) {
        this.ansibleServices.setAll(value);
    }

    @Override
    public void addAnsibleService(AnsibleService as) {
        ansibleServices.add(as);
    }


    @Override
    public ObservableList<String> getAnsibleGroups() {
        return ansibleGroups;
    }

    @Override
    public ObservableList<FXRspecInterface> getInterfaces() {
        return /*FXCollections.unmodifiableObservableList(*/interfaces/*)*/;
    }

    @Override
    @Nullable
    public FXRspecInterface getInterfaceByClientId(String clientId) {
        for (FXRspecInterface iface : interfaces) {
            if (Objects.equals(iface.getClientId(), clientId))
                return iface;
        }
        return null;
    }

    @Override
    @Nullable
    public FXRspecInterface getInterfaceByUniqueId(String uniqueId) {
        for (FXRspecInterface iface : interfaces) {
            if (Objects.equals(iface.getUniqueId(), uniqueId))
                return iface;
        }
        return null;
    }

    protected boolean addInterface(RspecInterface iface) {
        assert iface instanceof FXRspecInterface;
        return interfaces.add((FXRspecInterface) iface);
    }

    protected boolean removeInterface(RspecInterface iface) {
        assert iface instanceof FXRspecInterface;
        return interfaces.remove(iface);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    @Nonnull
    public List<FXRspecLink> getLinks() {
        List<FXRspecLink> res = new ArrayList<>();

        for (FXRspecInterface iface : interfaces)
            if (iface.isLinkBound())
                res.add(iface.getLink());

        return Collections.unmodifiableList(res);
    }

    public ObservableList<ExperimentCommand> getCommands() {
        return commands;
    }

    @Override
    public List<String> getLeaseIdRefs() {
        return leaseIdRefs;
    }

    @Override
    public void addLeaseIdRef(String leaseIdRef) {
        this.leaseIdRefs.add(leaseIdRef);
    }

    @Override
    @Nonnull
    public List<ExtraXml> getExtraXml() {
        return extraXml;
    }

    @Override
    @Nonnull
    public List<ExtraXml> getExtraServicesXml() {
        return extraServicesXmls;
    }

    @Override
    public void addEmptyTag(EmptyTag e) {
        emptyTags.add(e);
    }

    @Override
    public boolean hasEmptyTag(EmptyTag e) {
        return emptyTags.contains(e);
    }

    @Override
    public Collection<EmptyTag> getEmptyTags() {
        return emptyTags;
    }


    @Override
    public String toString() {
        return "RspecNode{" +
                "id=" + clientId +
                ", editorX=" + editorX +
                ", editorY=" + editorY +
                ", flackEditorX=" + flackEditorX +
                ", flackEditorY=" + flackEditorY +
                ", hardwareTypes=" + hardwareTypes +
                ", exclusive=" + exclusive +
                ", available=" + available +
                ", sliverTypes=" + sliverTypes +
                ", componentManagerId=" + componentManagerId +
                ", componentId=" + componentId +
                ", componentName=" + componentName +
                ", executeServices=" + executeServices +
                ", installServices=" + installServices +
                ", interfaces=" + interfaces +
                '}';
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

}
