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

import be.iminds.ilabt.jfed.experimenter_gui.config.JFedGuiConfig;
import be.iminds.ilabt.jfed.experimenter_gui.editor.views.EditableExperimentCanvas;
import be.iminds.ilabt.jfed.experimenter_gui.util.InetAddressUtil;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Resource;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.ResourceClass;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.gts.GtsDslReader;
import be.iminds.ilabt.jfed.gts.model.GtsPort;
import be.iminds.ilabt.jfed.gts.model.impl.FXGtsExternalNetwork;
import be.iminds.ilabt.jfed.gts.model.impl.FXGtsNode;
import be.iminds.ilabt.jfed.gts.model.impl.FXGtsPort;
import be.iminds.ilabt.jfed.gts.model.impl.FXGtsType;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.rspec.model.*;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.*;
import com.google.common.base.Preconditions;
import javafx.geometry.Point2D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.StringReader;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class is used as a wrapper around an FXModelRspec-object, and contains convenience methods for
 * editing the FXModelRspec
 * <p/>
 * User: twalcari
 * Date: 6/30/2014
 * Time: 14:32
 */
public class ModelRspecEditor {
    public static final String DEFAULT_IPV4_NETMASK = "255.255.255.0";
    public static final String DEFAULT_IPV6_NETMASK = "64";
    private static final Logger LOG = LoggerFactory.getLogger(ModelRspecEditor.class);
    private static final long DEFAULT_LINK_CAPACITY = 10_000L;
    private static final Pattern IP_ADDRESS_PATTERN = Pattern.compile("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\.(\\d{1,3})");
    private static final FXRspecFactory rspecFactory = FXRspecFactory.getInstance();
    public static final int MAX_NODE_NAME_LENGTH = 10;
    public static final int MAX_IFACE_NAME_LENGTH = MAX_NODE_NAME_LENGTH + 4;
    private final Map<FXRspecNode, Resource> nodeDescriptionMap = new HashMap<>();
    private final Map<FXRspecLink, String> linkSubnetworks = new HashMap<>();
    private final FXModelRspec modelRspec;
    private final JFedGuiConfig config;
    private final TestbedInfoSource testbedInfoSource;
    private EditableExperimentCanvas editableExperimentCanvas = null;

    public ModelRspecEditor(FXModelRspec modelRspec, TestbedInfoSource testbedInfoSource, JFedGuiConfig config) {
        this.modelRspec = modelRspec;
        this.testbedInfoSource = testbedInfoSource;
        this.config = config;

        //try to find the subnetwork used in this link
        for (FXRspecLink link : modelRspec.getLinks()) {
            String subnetwork = getInet4SubnetworkFromLink(link);

            if (subnetwork != null)
                linkSubnetworks.put(link, subnetwork);
        }
    }

    public static String getInet4SubnetworkFromLink(FXRspecLink link) {
        //check if link-interfaces adhere to our restrictions
        for (FXRspecInterface iface : link.getInterfaces())
            for (FXRspecInterface.IpAddress addr : iface.getIpAddresses()) {
                //we only support IPv4-links
                if (!Objects.equals(RspecInterface.IpAddress.TYPE_IPV4, addr.getType()))
                    return null;

                //we don't support other netmasks besides DEFAULT_IPV4_NETMASK
                if (!Objects.equals(DEFAULT_IPV4_NETMASK, addr.getNetmask()))
                    return null;
            }

        Iterator<FXRspecInterface> it = link.getInterfaces().iterator();

        if (!it.hasNext())
            return null;

        FXRspecInterface iface = it.next();

        if (iface.getIpAddresses().size() != 1)
            return null;

        if (!InetAddressUtil.isValidInet4Address(iface.getIpAddresses().get(0).getAddress()))
            return null;

        String subnetwork =
                InetAddressUtil.getInet4Subnetwork(InetAddressUtil.simplifyInet4Address(iface.getIpAddresses().get(0).getAddress()));

        //check if subnetwork is consistent for all interfaces
        while (it.hasNext()) {
            iface = it.next();
            if (iface.getIpAddresses().size() != 1)
                return null;

            if (!InetAddressUtil.isValidInet4Address(iface.getIpAddresses().get(0).getAddress()) ||
                    !InetAddressUtil.simplifyInet4Address(iface.getIpAddresses().get(0).getAddress()).startsWith(subnetwork))
                return null;
        }

        return subnetwork;
    }

    public static String getInet6SubnetworkFromLink(FXRspecLink link) {
        //check if link-interfaces adhere to our restrictions
        for (FXRspecInterface iface : link.getInterfaces())
            for (FXRspecInterface.IpAddress addr : iface.getIpAddresses()) {
                //we only support IPv4-links
                if (!Objects.equals(RspecInterface.IpAddress.TYPE_IPV6, addr.getType()))
                    return null;

                //we don't support other netmasks besides DEFAULT_IPV4_NETMASK
                if (!Objects.equals(DEFAULT_IPV6_NETMASK, addr.getNetmask()))
                    return null;
            }

        Iterator<FXRspecInterface> it = link.getInterfaces().iterator();

        if (!it.hasNext())
            return null;

        FXRspecInterface iface = it.next();

        if (iface.getIpAddresses().size() != 1)
            return null;

        if (!InetAddressUtil.isValidInet6Address(iface.getIpAddresses().get(0).getAddress()))
            return null;

        String subnetwork =
                InetAddressUtil.getInet6Subnetwork(iface.getIpAddresses().get(0).getAddress());

        //check if subnetwork is consistent for all interfaces
        while (it.hasNext()) {
            iface = it.next();
            if (iface.getIpAddresses().size() != 1)
                return null;

            if (!iface.getIpAddresses().get(0).getAddress().startsWith(subnetwork))
                return null;
        }

        return subnetwork;
    }

    private static void clearInterface(FXRspecInterface iface) {
        iface.getIpAddresses().clear();
    }

    public void setEditableExperimentCanvas(EditableExperimentCanvas editableExperimentCanvas) {
        this.editableExperimentCanvas = editableExperimentCanvas;
    }

    public FXModelRspec getModelRspec() {
        return modelRspec;
    }

    /**
     * This method changes some settings of the FXRspecLink to make it a correct FXRspecLink
     */
    public void convertLinkToStitchedLink(FXRspecLink rspecLink) {
        rspecLink.getLinkTypes().remove("lan");

        //add default capacity
        for (FXRspecInterface iface1 : rspecLink.getInterfaces()) {
            for (FXRspecInterface iface2 : rspecLink.getInterfaces()) {
                if (iface1 == iface2)
                    continue;

                if (!rspecLink.getLinkSetting(iface1, iface2).isCapacitySet())
                    rspecLink.getLinkSetting(iface1, iface2).setCapacity_Kbps(DEFAULT_LINK_CAPACITY);
            }
        }
    }

    public void convertLinkToNormalLink(FXRspecLink rspecLink) {
        rspecLink.getLinkTypes().setAll("lan");

        //remove default capacity
        for (FXRspecInterface iface1 : rspecLink.getInterfaces()) {
            for (FXRspecInterface iface2 : rspecLink.getInterfaces()) {
                if (iface1 == iface2)
                    continue;

                if (rspecLink.getLinkSetting(iface1, iface2).getCapacity_Kbps() == DEFAULT_LINK_CAPACITY)
                    rspecLink.getLinkSetting(iface1, iface2).setCapacity_Kbps(0L);
            }
        }
    }

    /**
     * Gives the next available subnetwork in the 10.x.x-range.
     */
    public String getNextAvailableInet4Subnetwork() {

        int subnetwork = -1;
        String subnetworkString = null;
        boolean inUse = true;

        while (inUse && subnetwork < 255) { //for 192.168
            subnetwork++;
            subnetworkString = "192.168." + (subnetwork % 255);

            inUse = (linkSubnetworks.containsValue(subnetworkString));
        }

        if (subnetwork < 255) { //for 192.168
            return subnetworkString;
        } else
            return null;
    }

    private int getNextLastIpAddressDigits(String subNetwork) {

        boolean[] usedLastDigits = new boolean[254]; //from 1 to 254

        //multiple links can have the same subnet (ex. with delay bridges)
        for (Map.Entry<FXRspecLink, String> entry : linkSubnetworks.entrySet()) {
            if (!Objects.equals(subNetwork, entry.getValue()))
                continue;

            for (FXRspecInterface iface : entry.getKey().getInterfaces()) {
                if (!iface.getIpAddresses().isEmpty()) {
                    Matcher matcher = IP_ADDRESS_PATTERN.matcher(iface.getIpAddresses().get(0).getAddress());

                    if (matcher.matches()) {
                        if (!Objects.equals(subNetwork, matcher.group(1)))
                            return -1;

                        int lastDigit = Integer.parseInt(matcher.group(2));
                        if (lastDigit >= 1 && lastDigit <= 254)
                            usedLastDigits[lastDigit - 1] = true;
                        else
                            return -1; //encountered an illegal last digit
                    } else {
                        return -1; //found an interface with an invalid ip-address
                    }
                }
            }
        }

        int lastDigit = 1;
        while (lastDigit < 255 && usedLastDigits[lastDigit - 1]) {
            lastDigit++;
        }

        if (lastDigit == 255) return -1; //could not find usable last digit in subnetwork

        return lastDigit;
    }

    private void configureIfaceForSubnetwork(FXRspecInterface iface, String subnetwork) {
        configureIfaceForSubnetwork(iface, subnetwork, getNextLastIpAddressDigits(subnetwork));
    }

    private void configureIfaceForSubnetwork(FXRspecInterface iface, String subnetwork, int lastDigit) {
        Preconditions.checkNotNull(iface);
        Preconditions.checkNotNull(subnetwork);
        Preconditions.checkArgument(lastDigit > 0 && lastDigit < 255, "Got an illegal last digit");

        iface.getIpAddresses().add(new FXRspecInterface.IpAddress(subnetwork + "." + lastDigit, DEFAULT_IPV4_NETMASK,
                RspecInterface.IpAddress.TYPE_IPV4));
    }

    /**
     * Converts a normal FXRspecLink to a link with an impairment bridge
     */
    public DelayRspecNode convertLinkToImpairmentBridge(FXRspecLink rspecLink) {
        if (rspecLink.getInterfaces().size() > 2) {
            return null;
        }

        String impairmentBridgeSubnetwork = linkSubnetworks.containsKey(rspecLink) ?
                linkSubnetworks.get(rspecLink) : getNextAvailableInet4Subnetwork();

        if (impairmentBridgeSubnetwork == null) {
            return null;  //ran out of available sub-networks!
        }

        FXRspecInterface iface1 = rspecLink.getInterfaces().get(0);
        FXRspecNode node1 = iface1.getNode();
        String ifaceId1 = iface1.getClientId();

        FXRspecInterface iface2 = rspecLink.getInterfaces().get(1);
        FXRspecNode node2 = iface2.getNode();
        String ifaceId2 = iface2.getClientId();

        modelRspec.deleteLink(rspecLink);

        DelayRspecNode impairmentBridgeNode = new DelayRspecNode(modelRspec, getNextImpairmentBridgeName());
        impairmentBridgeNode.setEditorX((node1.getEditorX() + node2.getEditorX()) / 2);
        impairmentBridgeNode.setEditorY((node1.getEditorY() + node2.getEditorY()) / 2);
        impairmentBridgeNode.setExclusive(true);
        impairmentBridgeNode.setComponentManagerId(node1.getComponentManagerId());

        modelRspec.getNodes().add(impairmentBridgeNode);

        FXRspecLink newLink1 = rspecFactory.createLinkWithClientId(modelRspec, modelRspec.nextLinkName(), "lan");

        FXRspecInterface link1Iface1 = rspecFactory.createInterface(node1, newLink1, ifaceId1);
        configureIfaceForSubnetwork(link1Iface1, impairmentBridgeSubnetwork);
        rspecFactory.createInterface(impairmentBridgeNode, newLink1,
                impairmentBridgeNode.getClientId() + ":left");
        modelRspec.getLinks().add(newLink1);

        FXRspecLink newLink2 = rspecFactory.createLinkWithClientId(modelRspec, modelRspec.nextLinkName(), "lan");
        FXRspecInterface link2Iface1 = rspecFactory.createInterface(node2, newLink2, ifaceId2);
        configureIfaceForSubnetwork(link2Iface1, impairmentBridgeSubnetwork);
        rspecFactory.createInterface(impairmentBridgeNode, newLink2,
                impairmentBridgeNode.getClientId() + ":right");
        modelRspec.getLinks().add(newLink2);

        return impairmentBridgeNode;

    }

    private String getNextImpairmentBridgeName() {
        int nr = 0;

        while (modelRspec.getNodeByClientId("bridge" + nr) != null) {
            nr++;
        }

        return "bridge" + nr;
    }

    private boolean isInterfaceConfigurationAllowed(FXRspecNode node) {
        Resource resource = nodeDescriptionMap.get(node);

        return resource == null
                || resource.getServer() == null
                || resource.getServer().getTestbed() == null
                || resource.getServer().getTestbed().getAllowLinks() != Boolean.FALSE;
    }

    public FXRspecNode addNode(ResourceClass resourceClass, Point2D location) {
        assert resourceClass != null;
        assert resourceClass.getResources() != null;

        //TODO fedmon-v3 refactor: what was the use case for this? Does this need re-enable somehow?
        // at first sight, this was not used?

//        if (resourceClass.getRspecNodeDescriptions().size() > 1) {
//
//            CustomChoiceDialog<RspecNodeDescription> dialog =
//                    new CustomChoiceDialog<>(resourceClass.getRspecNodeDescriptions().get(0), resourceClass.getRspecNodeDescriptions());
//            dialog.setHeaderText("Multiple configurations possible");
//            dialog.setContentText("Please select your desired configuration:");
//            dialog.setStringConverter(new StringConverter<RspecNodeDescription>() {
//                @Override
//                public String toString(RspecNodeDescription object) {
//                    return object.getName();
//                }
//
//                @Override
//                public RspecNodeDescription fromString(String string) {
//                    throw new UnsupportedOperationException();
//                }
//            });
//
//
//            rnd = dialog.showAndWait().orElse(null);
//
//        } else {
//            rnd = resourceClass.getRspecNodeDescriptions().get(0);
//        }

//        if (rnd == null)
//            return null;

        //TODO: fedmon-v3 refactor: pick a random one, but always the same...
        Resource defaultResource = config.getDefaultResource(resourceClass);
        assert defaultResource != null : "There is no default resource for "+resourceClass.getId();

        //simple fallback, but won't always work
        if (defaultResource == null && !resourceClass.getResources().isEmpty())
            defaultResource = resourceClass.getResources().get(0);

        if (defaultResource == null)
            throw new RuntimeException("There is no resource for "+resourceClass.getId());

        FXRspecNode rspecNode = rspecFactory.createNodeWithClientId(modelRspec, modelRspec.nextNodeName());
        rspecNode.setResourceClassId(resourceClass.getId());

        rspecNode.setComponentManagerId(defaultResource.getServer().getDefaultComponentManagerAsGeniUrn());
        rspecNode.setExclusive(defaultResource.getDefaultExclusive());
        if (defaultResource.getAllowFixedNodeAssignment() != Boolean.FALSE)
            rspecNode.setComponentId(defaultResource.getComponentUrn());

        if (defaultResource.getSliverType() != null) {
            SliverTypeBuilder sliverTypeBuilder = new SliverTypeBuilder(defaultResource.getSliverType());
            if (defaultResource.getAllowDiskImage() != Boolean.FALSE && defaultResource.getDiskImage() != null)
                sliverTypeBuilder.addDiskImage(defaultResource.getDiskImage());
            rspecNode.setSliverType(sliverTypeBuilder.build());
        } else {
            if (defaultResource.getRequireDiskImage() != Boolean.FALSE) {
                LOG.error("Disk image requires a sliver type!");
            }
        }

        if (defaultResource.getAllowHardwareType() == Boolean.TRUE && defaultResource.getHardwareType() != null)
            rspecNode.getHardwareTypes().add(new HardwareType(defaultResource.getHardwareType()));

        rspecNode.setEditorX(location.getX());
        rspecNode.setEditorY(location.getY());

        modelRspec.getNodes().add(rspecNode);

        nodeDescriptionMap.put(rspecNode, defaultResource);

        return rspecNode;
    }

    /**
     * Creates a new link between two nodes
     */
    public FXRspecLink addLink(FXRspecNode node1, FXRspecNode node2) {
        LOG.trace("Adding link between {} and {}", node1.getClientId(), node2.getUniqueId());
        //we first need to create a new link
        FXRspecLink rspecLink = rspecFactory.createLinkWithClientId(modelRspec, modelRspec.nextLinkName(), "lan");
        linkSubnetworks.put(rspecLink, getNextAvailableInet4Subnetwork());

        addNodeToLink(node1, rspecLink);
        addNodeToLink(node2, rspecLink);

        modelRspec.getLinks().add(rspecLink);

        if (shouldBeStitchedLink(node1, node2)) {
            convertLinkToStitchedLink(rspecLink);
        }
        return rspecLink;

    }


    public FXRspecInterface addUnboundInterface(FXRspecNode node) {
        LOG.trace("Adding an unbound link to node {}", node.getUniqueId());

        return rspecFactory.createInterface(node, modelRspec.nextIfaceName(node));
    }

    /**
     * Creates a new FXRspecInterface to connect an FXRspecLink and an FXRspecNode.
     * Does additional configuration on this interface (or other interfaces on the link) if
     * required by the configuration-file.
     */
    private FXRspecInterface addNodeToLink(FXRspecNode node, FXRspecLink link) {
        FXRspecInterface iface = rspecFactory.createInterface(node,
                link,
                modelRspec.nextIfaceName(node));
        LOG.trace("Added node {} to link {} with iface {}", node.getUniqueId(), link.getUniqueId(), iface.getClientId());

        if (isInterfaceConfigurationAllowed(node)) {
            if (linkSubnetworks.containsKey(link)) {
                configureIfaceForSubnetwork(iface, linkSubnetworks.get(link));
            }
        } else {
            //don't add interfaces to fake authorities to prevent confusion. But also don't remove IP-addresses of other nodes on the link
            Server nodeServer =
                    testbedInfoSource.getByUrn(node.getComponentManagerId(),
                            TestbedInfoSource.SubAuthMatchAllowed.ALLOW_OTHER_SUBAUTHORITY,
                            TestbedInfoSource.SubAuthMatchPreference.PREFER_EXACT_SUBAUTHORITY);
            if (nodeServer == null || !nodeServer.isEdgeVlan()) {
                //remove all assigned ip-addresses from this link
                if (linkSubnetworks.containsKey(link)) {
                    linkSubnetworks.remove(link);
                }

                link.getInterfaces().forEach(be.iminds.ilabt.jfed.experimenter_gui.editor.ModelRspecEditor::clearInterface);
            }
        }

        return iface;
    }

    /**
     * Adds a new node to an existing link
     */
    public boolean addLink(FXRspecLink link, FXRspecNode node) {
        return addNodeToLink(node, link) != null;
    }

    public FXRspecChannel addChannel(Point2D location) {
        FXRspecChannel channel = (FXRspecChannel) rspecFactory.createChannel(modelRspec);
        channel.setEditorX(location.getX());
        channel.setEditorY(location.getY());

        modelRspec.addChannel(channel);

        return channel;
    }

    private boolean shouldBeStitchedLink(FXRspecNode node1, FXRspecNode node2) {

        return node1.getComponentManagerId() != null &&
                !Objects.equals(node1.getComponentManagerId(), node2.getComponentManagerId())
                || node2.getComponentManagerId() == null;
    }

//    public void autoLayout() {
//        autoLayout(600, 400);
//    }
//
//    /**
//     * This method arranges all the nodes on an ellipse of the given width and height
//     */
//    public void autoLayout(double width, double height) {
//        if (editableExperimentCanvas != null) {
//            editableExperimentCanvas.setZoom(1);
//        }
//        ModelRspecUtils.autoLayout(modelRspec, width, height);
//    }

    public FXGeantTestbedType addGeantTestbedType(Point2D location) {
        FXGeantTestbedType gtt = (FXGeantTestbedType) rspecFactory.createGeantTestbedType(nextGeantTestbedTypeName());
        gtt.setEditorX(location.getX());
        gtt.setEditorY(location.getY());

        modelRspec.addGeantTestbedType(gtt);

        return gtt;
    }

    public FXAddressPool addAddressPool(Point2D location) {
        FXAddressPool addressPool = (FXAddressPool) rspecFactory.createAddressPool(modelRspec);
        addressPool.setClientId(nextAddressPoolName());
        addressPool.setCount(1);
        addressPool.setType("any");
        addressPool.setEditorX(location.getX());
        addressPool.setEditorY(location.getY());

        modelRspec.addAddressPool(addressPool);

        return addressPool;
    }


    private String nextGeantTestbedTypeName() {

        final String base = "UntitledGts";
        String name = base;
        int number = 0;

        while (modelRspec.getGeantTestbedTypes().stream()
                .map(GeantTestbedType::getName)
                .anyMatch(name::equals)) {
            name = base + (++number);
        }

        return name;
    }

    private String nextAddressPoolName() {

        final String base = "pool";
        int number = 0;

        String name = base + number;
        while (modelRspec.getAddressPools().stream()
                .map(AddressPool::getClientId)
                .anyMatch(name::equals)) {
            name = base + (++number);
        }

        return name;
    }


    public void updateNodeClientId(FXRspecNode node, String newClientId) throws IllegalArgumentException {
        if (modelRspec.getNodeByClientId(newClientId) != null)
            throw new IllegalArgumentException("New clientId already exists in current Rspec");

        String oldClientId = node.getClientId();
        assert oldClientId != null;

        node.setClientId(newClientId);

        for (FXRspecInterface fxRspecInterface : node.getInterfaces()) {
            fxRspecInterface.setClientId(fxRspecInterface.getClientId().replace(oldClientId, newClientId));
        }
    }

    public static boolean isValidNodeName(String text) {
        if (text.isEmpty())
            return false;
        if (text.length() > MAX_NODE_NAME_LENGTH)
            return false;

        if(text.startsWith("-"))
            return false;

        for (char c : text.toCharArray()) {
            final boolean validChar = (c >= 'a' && c <= 'z')
                    || (c >= 'A' && c <= 'Z')
                    || (c >= '0' && c <= '9')
                    || c == '-';

            if (!validChar) {
                return false;
            }
        }

        return true;
    }


    public void checkForGtsToRspecVlans(FXGeantTestbedType gtt) {
        //try to extract the type from the dsl

        GtsDslReader reader = new GtsDslReader(new StringReader(gtt.getDsl()));
        FXGtsType type = (FXGtsType) reader.call();

        List<FXGeantTestbedType.ActiveExternalDomain> currentDomains = new ArrayList<>();

        for (FXGtsNode node : type.getNodes()) {
            if (node instanceof FXGtsExternalNetwork) {
                FXGtsExternalNetwork externalNetwork = (FXGtsExternalNetwork) node;
                if (externalNetwork.getPorts().isEmpty())
                    continue;
                GtsPort port = externalNetwork.getPorts().values().iterator().next();

                currentDomains.add(new FXGeantTestbedType.ActiveExternalDomain(node.getLocation(), port.getId()));

                FXGeantTestbedType.GtsVlan vlan = findGtsToRspecVlan(externalNetwork);

                if(vlan != null)
                    addExternalNetworkIfNecessary(gtt, vlan, currentDomains.size()-1);
            }
        }
        gtt.getActiveExternalDomains().setAll(currentDomains);
    }

    private boolean addExternalNetworkIfNecessary(FXGeantTestbedType gtt, FXGeantTestbedType.GtsVlan vlan, int count) {

        //check if external network exists
        for (FXRspecNode node : modelRspec.getNodes()) {
            if (Objects.equals(vlan.getVlanUrn(), node.getComponentManagerId()))
                return false; //it is already present!
        }

        FXRspecNode rspecNode = rspecFactory.createNodeWithClientId(modelRspec, vlan.getRspecNodeName());

        rspecNode.setComponentManagerId(vlan.getVlanUrn());
        rspecNode.setSliverTypeName("fake");

        double locationDegree = -.5 + (count);

        rspecNode.setEditorX(gtt.getEditorX() + Math.sin(locationDegree)*200);
        rspecNode.setEditorY(gtt.getEditorY() + Math.cos(locationDegree)*100);

        modelRspec.getNodes().add(rspecNode);

        return true;
    }

    private static FXGeantTestbedType.GtsVlan findGtsToRspecVlan(FXGtsExternalNetwork externalNetwork) {
        if (externalNetwork.getPorts().isEmpty())
            return null;
        GtsPort port = externalNetwork.getPorts().values().iterator().next();
        for (FXGeantTestbedType.GtsVlan gtsVlan : FXGeantTestbedType.GTS_TO_RSPEC_VLANS) {

            if (Objects.equals(gtsVlan.getGtsLocation(), externalNetwork.getLocation())
                    && Objects.equals(gtsVlan.getGtsPortId(), port.getId()))
                return gtsVlan;
        }

        return null;
    }

                 /*
    public boolean connectNodeToGts(FXRspecNode node, EditableGtsDsl gts) {
        List<AvailablePort> availablePorts =
                gts.getAvailablePorts().entrySet().stream().map(AvailablePort::new).collect(Collectors.toList());

        if (availablePorts.isEmpty()) {
            Alert alert =
                    new Alert(Alert.AlertType.WARNING, "The GTS DSL doesn't have any external connections to which a connection can be made.");

            alert.showAndWait();
            return false;
        } else {

            ChoiceDialog<AvailablePort> dialog = new ChoiceDialog<>(availablePorts.get(0), availablePorts);
            dialog.setTitle("Choose the GTS External Connection to connect to");
            dialog.setHeaderText("Choose the GTS External Connection to connect to");
            dialog.setContentText("Available connections:");


            boolean success = false;

            Optional<AvailablePort> chosenPort = dialog.showAndWait();

            if (chosenPort.isPresent()) {
                if (chosenPort.get().entry.getValue() != null) {
                    chosenPort.get().entry.getValue().delete();
                }

                FXRspecInterface unboundIface = addUnboundInterface(node);
                gts.getAvailablePorts().put(chosenPort.get().entry.getKey(), unboundIface);

                return true;
            } else
                return false;
        }
    }     */


    private static class AvailablePort implements Comparable<AvailablePort> {
        private final Map.Entry<FXGtsPort, FXRspecInterface> entry;

        private AvailablePort(Map.Entry<FXGtsPort, FXRspecInterface> entry) {
            this.entry = entry;
        }

        @Override
        public String toString() {
            return entry.getKey().getId();
        }

        @Override
        public int compareTo(@Nonnull AvailablePort o) {
            assert (entry.getKey().getId() != null);
            assert (o.entry.getKey().getId() != null);
            return entry.getKey().getId().compareTo(o.entry.getKey().getId());
        }
    }
}
