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

import be.iminds.ilabt.jfed.experimenter_gui.config.JFedGuiConfig;
import be.iminds.ilabt.jfed.experimenter_gui.canvas.AbstractEditableCanvas;
import be.iminds.ilabt.jfed.experimenter_gui.canvas.CanvasNode;
import be.iminds.ilabt.jfed.experimenter_gui.canvas.NewCanvasLink;
import be.iminds.ilabt.jfed.experimenter_gui.canvas.rspec.*;
import be.iminds.ilabt.jfed.experimenter_gui.editor.ModelRspecEditor;
import be.iminds.ilabt.jfed.experimenter_gui.editor.ToolboxItem;
import be.iminds.ilabt.jfed.experimenter_gui.editor.addresspool_properties.AddressPoolPropertiesDialogFactory;
import be.iminds.ilabt.jfed.experimenter_gui.editor.channel_properties.ChannelPropertiesDialogFactory;
import be.iminds.ilabt.jfed.experimenter_gui.editor.events.ShowLinkPropertiesEvent;
import be.iminds.ilabt.jfed.experimenter_gui.editor.events.ShowNodePropertiesEvent;
import be.iminds.ilabt.jfed.experimenter_gui.editor.impl.AddressPoolToolboxItem;
import be.iminds.ilabt.jfed.experimenter_gui.editor.impl.ChannelToolboxItem;
import be.iminds.ilabt.jfed.experimenter_gui.editor.impl.GeantTestbedToolboxItem;
import be.iminds.ilabt.jfed.experimenter_gui.editor.impl.NodeResourceClassToolboxItem;
import be.iminds.ilabt.jfed.experimenter_gui.gts_editor.EditableGtsDsl;
import be.iminds.ilabt.jfed.experimenter_gui.gts_editor.events.ShowGTSEditorEvent;

import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.authority.AuthorityFinder;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.rspec.model.RspecNode;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.*;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import be.iminds.ilabt.jfed.util.GeniUrn;
import com.google.common.eventbus.EventBus;
import javafx.geometry.Point2D;
import javafx.scene.control.*;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * User: twalcari
 * Date: 11/18/13
 * Time: 5:11 PM
 */
public class EditableExperimentCanvas extends AbstractEditableCanvas {
    private static final Logger LOG = LoggerFactory.getLogger(EditableExperimentCanvas.class);

    private final ModelRspecEditor modelRspecEditor;
    private final TestbedInfoSource testbedInfoSource;
    private final AuthorityFinder authorityFinder;
    private final JFedGuiConfig config;

    private final FXModelRspec model;
    private final ModelRspecToCanvasBinding binding;

    private final ContextMenu nodeContextMenu;
    private final ContextMenu linkContextMenu;
    private final ContextMenu linkInterfaceContextMenu;
    private final ContextMenu channelContextMenu;
    private final ContextMenu gtsContextMenu;
    // private final ContextMenu gtsLinkContextMenu;
    private final ContextMenu addressPoolContextMenu;


    private final FXRspecFactory rspecFactory = FXRspecFactory.getInstance();
    private final ChannelPropertiesDialogFactory channelPropertiesDialogFactory;
    private final AddressPoolPropertiesDialogFactory addressPoolPropertiesDialogFactory;

    private final EventBus eventBus;

    public EditableExperimentCanvas(DraggedItemProvider draggedItemProvider, final ModelRspecEditor modelRspecEditor,
                                    JFedGuiConfig config, CanvasNodeFactory canvasNodeFactory, TestbedInfoSource testbedInfoSource, AuthorityFinder authorityFinder,
                                    ChannelPropertiesDialogFactory channelPropertiesDialogFactory, AddressPoolPropertiesDialogFactory addressPoolPropertiesDialogFactory, EventBus eventBus) {
        super(draggedItemProvider);
        this.modelRspecEditor = modelRspecEditor;
        this.testbedInfoSource = testbedInfoSource;
        this.authorityFinder = authorityFinder;
        this.config = config;

        this.model = modelRspecEditor.getModelRspec();
        this.binding = new ModelRspecToCanvasBinding(modelRspecEditor.getModelRspec(), this, config, canvasNodeFactory);

        this.channelPropertiesDialogFactory = channelPropertiesDialogFactory;
        this.addressPoolPropertiesDialogFactory = addressPoolPropertiesDialogFactory;
        this.eventBus = eventBus;

        // create context menus
        {
            this.nodeContextMenu = new ContextMenu();

            MenuItem configureItem =
                    new MenuItem("Configure node", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.PENCIL));
            configureItem.setOnAction(actionEvent -> {
                assert (getSelectionProvider().getSelectedObject() instanceof RspecCanvasNode);
                RspecCanvasNode rspecCanvasNode = (RspecCanvasNode) getSelectionProvider().getSelectedObject();

                onConfigureNodePressed(rspecCanvasNode);
            });
            nodeContextMenu.getItems().add(configureItem);

            MenuItem deleteItem = new MenuItem("Delete", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.TIMES));
            deleteItem.setOnAction(actionEvent -> {
                assert (getSelectionProvider().getSelectedObject() instanceof RspecCanvasNode);
                RspecCanvasNode rspecCanvasNode = (RspecCanvasNode) getSelectionProvider().getSelectedObject();

                onDeleteNodePressed(rspecCanvasNode);
            });
            nodeContextMenu.getItems().add(deleteItem);
        }

        //contextMenu for links
        {
            this.linkContextMenu = new ContextMenu();


            // was disabled as FXRspecLink.LinkSetting is currently not supported in practice, but it seems to work for VM's now
            MenuItem configureLinkItem =
                    new MenuItem("Configure link", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.PENCIL));
            configureLinkItem.setOnAction(actionEvent -> {
                assert getSelectionProvider().getSelectedObject() instanceof RspecCanvasLink;
                RspecCanvasLink rspecCanvasLink = (RspecCanvasLink) getSelectionProvider().getSelectedObject();

                onConfigureLinkPressed(rspecCanvasLink.getRspecLink());
            });
            linkContextMenu.getItems().add(configureLinkItem);

            MenuItem impairLinkItem =
                    new MenuItem("Add impairment bridge to link", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.EXPAND));
            impairLinkItem.setOnAction(actionEvent -> {
                assert getSelectionProvider().getSelectedObject() instanceof RspecCanvasLink;
                RspecCanvasLink rspecCanvasLink = (RspecCanvasLink) getSelectionProvider().getSelectedObject();

                convertLinkToImpairmentBridge(rspecCanvasLink.getRspecLink());
            });
            linkContextMenu.getItems().add(impairLinkItem);

            MenuItem linkDeleteItem = new MenuItem("Delete", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.TIMES));
            linkDeleteItem.setOnAction(actionEvent -> {
                assert getSelectionProvider().getSelectedObject() instanceof RspecCanvasLink;
                RspecCanvasLink rspecCanvasLink = (RspecCanvasLink) getSelectionProvider().getSelectedObject();

                onDeleteLinkPressed(rspecCanvasLink);
            });
            linkContextMenu.getItems().add(linkDeleteItem);
        }


        {
            //contextMenu for interfaces
            this.linkInterfaceContextMenu = new ContextMenu();
            MenuItem deleteItem =
                    new MenuItem("Delete", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.TIMES));
            deleteItem.setOnAction(actionEvent -> {
                RspecCanvasLink.InterfaceLink ifaceLink = getSelectionProvider().getSelectedInterfaceLink();
                assert (ifaceLink != null);

                onDeleteLinkInterfacePressed(ifaceLink);
            });
            linkInterfaceContextMenu.getItems().add(deleteItem);
        }

        {
            channelContextMenu = new ContextMenu();
            MenuItem configureChannelMenuItem =
                    new MenuItem("Configure channel", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.PENCIL));
            configureChannelMenuItem.setOnAction(actionEvent -> {
                RspecChannelCanvasNode rspecChannelCanvasNode = getSelectionProvider().getSelectedChannel();

                onConfigureChannelPressed(rspecChannelCanvasNode.getChannel());
            });
            channelContextMenu.getItems().add(configureChannelMenuItem);

            MenuItem deleteItem = new MenuItem("Delete", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.TIMES));
            deleteItem.setOnAction(actionEvent -> {
                RspecChannelCanvasNode rspecChannelCanvasNode = getSelectionProvider().getSelectedChannel();

                onDeleteChannelPressed(rspecChannelCanvasNode);
            });
            channelContextMenu.getItems().add(deleteItem);
        }

        {
            gtsContextMenu = new ContextMenu();
            MenuItem editGtsMenuItem =
                    new MenuItem("Edit", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.PENCIL));
            editGtsMenuItem.setOnAction(actionEvent -> {
                RspecGTSNode rspecGTSNode = getSelectionProvider().getSelectedGTSNode();

                onEditGTSNodePressed(rspecGTSNode);
            });
            gtsContextMenu.getItems().add(editGtsMenuItem);

            MenuItem deleteItem = new MenuItem("Delete", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.TIMES));
            deleteItem.setOnAction(actionEvent -> {
                RspecGTSNode rspecChannelCanvasNode = getSelectionProvider().getSelectedGTSNode();

                onDeleteGTSNodePressed(rspecChannelCanvasNode);
            });
            gtsContextMenu.getItems().add(deleteItem);
        }

//        {
//            gtsLinkContextMenu = new ContextMenu();
//
//            MenuItem gtsLinkDeleteItem = new MenuItem("Delete", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.TIMES));
//            gtsLinkDeleteItem.setOnAction(actionEvent -> {
//                GtsToRspecCanvasLink selectedLink = ((GtsToRspecCanvasLink) getSelectionProvider().getSelectedObject());
//
//                onDeleteGTSLinkPressed(selectedLink);
//            });
//            gtsLinkContextMenu.getItems().add(gtsLinkDeleteItem);
//        }

        {
            addressPoolContextMenu = new ContextMenu();
            MenuItem configureMenuItem =
                    new MenuItem("Configure address pool", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.PENCIL));
            configureMenuItem.setOnAction(actionEvent -> {
                AddressPoolCanvasNode addressPoolCanvasNode = getSelectionProvider().getSelectedAddressPool();

                onConfigureAddressPoolPressed(addressPoolCanvasNode.getAddressPool());
            });
            addressPoolContextMenu.getItems().add(configureMenuItem);

            MenuItem deleteItem = new MenuItem("Delete", GlyphUtils.createSmallGlyph(FontAwesome.Glyph.TIMES));
            deleteItem.setOnAction(actionEvent -> {
                AddressPoolCanvasNode addressPoolCanvasNode = getSelectionProvider().getSelectedAddressPool();

                onDeleteAddressPoolPressed(addressPoolCanvasNode);
            });
            addressPoolContextMenu.getItems().add(deleteItem);
        }
    }

    private void onEditGTSNodePressed(RspecGTSNode rspecGTSNode) {
        final FXGeantTestbedType gtt = rspecGTSNode.getGeantTestbedType();

        EditableGtsDsl editableGtsDsl = new EditableGtsDsl(gtt.getName(), gtt.getDsl()) {

            @Override
            protected void save(String name, String dsl) {
                super.save(name, dsl);
                gtt.setName(name);
                gtt.setDsl(dsl);

                modelRspecEditor.checkForGtsToRspecVlans(gtt);
            }
        };

        eventBus.post(new ShowGTSEditorEvent(editableGtsDsl));
    }

    private void onDeleteGTSNodePressed(RspecGTSNode rspecGTSNode) {
        model.deleteGeantTestbedType(rspecGTSNode.getGeantTestbedType());
    }

    private void onDeleteChannelPressed(RspecChannelCanvasNode rspecChannelCanvasNode) {
        modelRspecEditor.getModelRspec().deleteChannel(rspecChannelCanvasNode.getChannel());
        if (getSelectionProvider().getSelectedObject() == rspecChannelCanvasNode) {
            getSelectionProvider().clearAllSelections();
        }
    }

    private void onConfigureChannelPressed(FXRspecChannel channel) {
        channelPropertiesDialogFactory.showChannelPropertiesDialog(getScene().getWindow(), channel, model);

    }

    private void onConfigureAddressPoolPressed(FXAddressPool addressPool) {
        addressPoolPropertiesDialogFactory.showAddressPoolPropertiesDialog(getScene().getWindow(), addressPool, model);
    }

    private void onDeleteAddressPoolPressed(AddressPoolCanvasNode addressPoolCanvasNode) {
        model.deleteAddressPool(addressPoolCanvasNode.getAddressPool());
        if (getSelectionProvider().getSelectedObject() == addressPoolCanvasNode) {
            getSelectionProvider().clearAllSelections();
        }
    }


    @Override
    protected void deleteSelectedItem() {
        if (getSelectionProvider().getSelectedObject() instanceof RspecCanvasNode) {
            onDeleteNodePressed((RspecCanvasNode) getSelectionProvider().getSelectedObject());
        } else if (getSelectionProvider().getSelectedObject() instanceof RspecCanvasLink) {
            onDeleteLinkPressed((RspecCanvasLink) getSelectionProvider().getSelectedObject());
        } else if (getSelectionProvider().getSelectedObject() instanceof RspecCanvasLink.InterfaceLink) {
            onDeleteLinkInterfacePressed((RspecCanvasLink.InterfaceLink) getSelectionProvider().getSelectedObject());
        } else if (getSelectionProvider().getSelectedObject() instanceof RspecChannelCanvasNode) {
            onDeleteChannelPressed((RspecChannelCanvasNode) getSelectionProvider().getSelectedObject());
        } else if (getSelectionProvider().getSelectedObject() instanceof RspecGTSNode) {
            onDeleteGTSNodePressed((RspecGTSNode) getSelectionProvider().getSelectedObject());
        } else if (getSelectionProvider().getSelectedObject() != null) {
            LOG.warn("I don't know how to remove {}", getSelectionProvider().getSelectedObject());
        }
    }

    private void convertLinkToImpairmentBridge(FXRspecLink rspecLink) {
        //check if convertion is possible
        if (rspecLink.getInterfaces().size() > 2) {
            JFDialogs.create()
                    .owner(this)
                    .message("An impairment bridge can only be added on a link between two nodes!")
                    .masthead("Unsupported operation")
                    .showWarning();
            return;
        }


        DelayRspecNode delayRspecNode = modelRspecEditor.convertLinkToImpairmentBridge(rspecLink);

        //show configuration-window for impairmentBridgeNode now
        eventBus.post(new ShowNodePropertiesEvent(delayRspecNode, modelRspecEditor, getScene().getWindow()));
    }


    private void onDeleteLinkInterfacePressed(RspecCanvasLink.InterfaceLink ifaceLink) {

        if (ifaceLink.getRspecInterface().getLink().getInterfaces().size() <= 2) {
            model.deleteLink(ifaceLink.getRspecInterface().getLink());
        } else
            ifaceLink.getRspecInterface().delete();

        if (getSelectionProvider().getSelectedObject() == ifaceLink) {
            getSelectionProvider().clearAllSelections();
        }
    }

    private void onConfigureNodePressed(RspecCanvasNode rspecCanvasNode) {
        eventBus.post(new ShowNodePropertiesEvent(rspecCanvasNode.getRspecNode(), modelRspecEditor, getScene().getWindow()));
    }

    private void onConfigureLinkPressed(FXRspecLink rspecLink) {
        eventBus.post(new ShowLinkPropertiesEvent(rspecLink, modelRspecEditor, getScene().getWindow()));
    }


    private void onDeleteNodePressed(RspecCanvasNode rspecCanvasNode) {
        model.deleteNode(rspecCanvasNode.getRspecNode());
        if (getSelectionProvider().getSelectedObject() == rspecCanvasNode) {
            getSelectionProvider().clearAllSelections();
        }
    }

    private void onDeleteLinkPressed(RspecCanvasLink rspecCanvasLink) {
        model.deleteLink(rspecCanvasLink.getRspecLink());
        if (getSelectionProvider().getSelectedObject() == rspecCanvasLink) {
            getSelectionProvider().clearAllSelections();
        }
    }

    @Override
    public void canvasItemOnMouseClicked(MouseEvent mouseEvent) {
        super.canvasItemOnMouseClicked(mouseEvent);

        if (mouseEvent.getSource() instanceof RspecCanvasNode) {
            RspecCanvasNode node = (RspecCanvasNode) mouseEvent.getSource();
            getSelectionProvider().setSelectedObject(node);

            if (mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.getClickCount() == 2) {
                onConfigureNodePressed(node);

            } else if (mouseEvent.getButton() != MouseButton.PRIMARY || mouseEvent.isControlDown()) {
                //show context menu
                nodeContextMenu.show(node, mouseEvent.getScreenX(), mouseEvent.getScreenY());

            }
        }

        if (mouseEvent.getSource() instanceof RspecCanvasLink.LinkCenter) {
            RspecCanvasLink link =
                    ((RspecCanvasLink.LinkCenter) mouseEvent.getSource()).getRspecCanvasLink();

            getSelectionProvider().setSelectedObject(link);

            if (mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.getClickCount() == 2) {
                onConfigureLinkPressed(link.getRspecLink());
            } else if (mouseEvent.getButton() != MouseButton.PRIMARY || mouseEvent.isControlDown()) {
                //show context menu
                linkContextMenu.show(link.getLinkCenter(), mouseEvent.getScreenX(), mouseEvent.getScreenY());
            }
        }

        if (mouseEvent.getSource() instanceof RspecCanvasLink.InterfaceLink) {
            if (mouseEvent.getButton() != MouseButton.PRIMARY || mouseEvent.isControlDown()) {

                final RspecCanvasLink.InterfaceLink interfaceLink =
                        (RspecCanvasLink.InterfaceLink) mouseEvent.getSource();
                getSelectionProvider().setSelectedObject(interfaceLink);

                //show context menu
                linkInterfaceContextMenu.show(interfaceLink, mouseEvent.getScreenX(), mouseEvent.getScreenY());
            }
        }

        if (mouseEvent.getSource() instanceof RspecChannelCanvasNode) {
            final RspecChannelCanvasNode channelCanvasNode = (RspecChannelCanvasNode) mouseEvent.getSource();
            getSelectionProvider().setSelectedObject(channelCanvasNode);

            if (mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.getClickCount() == 2) {
                onConfigureChannelPressed(channelCanvasNode.getChannel());
            } else if (mouseEvent.getButton() != MouseButton.PRIMARY || mouseEvent.isControlDown()) {
                //show context menu
                channelContextMenu.show(channelCanvasNode, mouseEvent.getScreenX(), mouseEvent.getScreenY());
            }
        }

        if (mouseEvent.getSource() instanceof RspecGTSNode) {
            final RspecGTSNode gtsNode = (RspecGTSNode) mouseEvent.getSource();
            getSelectionProvider().setSelectedObject(gtsNode);

            if (mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.getClickCount() == 2) {
                onEditGTSNodePressed(gtsNode);
            } else if (mouseEvent.getButton() != MouseButton.PRIMARY || mouseEvent.isControlDown()) {
                //show context menu
                gtsContextMenu.show(gtsNode, mouseEvent.getScreenX(), mouseEvent.getScreenY());
            }
        }

        if (mouseEvent.getSource() instanceof AddressPoolCanvasNode) {
            final AddressPoolCanvasNode addressPoolCanvasNode = (AddressPoolCanvasNode) mouseEvent.getSource();
            getSelectionProvider().setSelectedObject(addressPoolCanvasNode);

            if (mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.getClickCount() == 2) {
                onConfigureAddressPoolPressed(addressPoolCanvasNode.getAddressPool());
            } else if (mouseEvent.getButton() != MouseButton.PRIMARY || mouseEvent.isControlDown()) {
                //show context menu
                addressPoolContextMenu.show(addressPoolCanvasNode, mouseEvent.getScreenX(), mouseEvent.getScreenY());
            }
        }
    }

    private boolean testForTestbedLinkSupport(FXRspecNode node) {
        if (node.getComponentManagerId() == null) {
            LOG.warn("ComponentManagerId is null for node '{}'", node.getUniqueId());
            return true;
        }

        Server server = authorityFinder.findByUrn(node.getComponentManagerId(), AuthorityFinder.Purpose.CREATE_SLIVER);

        if (server != null && server.getTestbed().getAllowLinks() == Boolean.FALSE) {
            return false;
        } else
            return true;
    }

    @Override
    protected boolean processNewLinkRequest(NewCanvasLink newCanvasLink) {
        LOG.trace("Received new link request between {} AND {}", newCanvasLink.getOrigin(),
                newCanvasLink.getTarget());

        if (newCanvasLink.getOrigin() instanceof RspecCanvasNode) {
            if (!processNodeOfNewLinkRequest((RspecCanvasNode) newCanvasLink.getOrigin(), isStitchedLink(newCanvasLink))) {
                return false;
            }
        }


        if (newCanvasLink.getTarget() instanceof RspecCanvasNode) {
            if (!processNodeOfNewLinkRequest((RspecCanvasNode) newCanvasLink.getTarget(), isStitchedLink(newCanvasLink))) {
                return false;
            }
        }

        //warn for stitched link if necessary

        if (isStitchedLink(newCanvasLink)) {
            Optional<ButtonType> reply = JFDialogs.create()
                    .title("Experimental functionality detected.")
                    .message("Stitched links between testbeds are currently experimental. Are you sure you want to continue?")
                    .buttonTypes(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL)
                    .showConfirm();

            if (!reply.isPresent() || reply.get() != ButtonType.YES) {
                LOG.trace("User cancelled new link because it is stitched");
                return false;
            }
        }

        //delegate the real link-creation to ModelRspecEditor
        LOG.trace("Received link request is OK!");

        if (newCanvasLink.getOrigin() instanceof RspecCanvasNode) {
            final FXRspecNode originNode = ((RspecCanvasNode) newCanvasLink.getOrigin()).getRspecNode();
            if (newCanvasLink.getTarget() instanceof RspecCanvasNode) {
                final FXRspecNode targetNode = ((RspecCanvasNode) newCanvasLink.getTarget()).getRspecNode();
                FXRspecLink newLink =
                        modelRspecEditor.addLink(originNode, targetNode);

                setStatus("Created a new link '" + newLink.getClientId() + "'");
            } else if (newCanvasLink.getTarget() instanceof RspecCanvasLink.LinkCenter) {
                final FXRspecLink targetLink = ((RspecCanvasLink.LinkCenter) newCanvasLink.getTarget()).getRspecLink();

                boolean success = modelRspecEditor.addLink(targetLink, originNode);

                if (success) {
                    setStatus(String.format("Added node %s to existing link %s",
                            originNode.getClientId(), targetLink.getClientId()));
                }
            } else if (newCanvasLink.getTarget() instanceof RspecGTSNode) {
             /*   EditableGtsDsl targetGts = ((RspecGTSNode) newCanvasLink.getTarget()).getEditableGtsDsl();
                boolean success = modelRspecEditor.connectNodeToGts(originNode, targetGts);

                if (success) {
                    setStatus(String.format("Connected node %s to port on GTS DSL %s",
                            originNode.getClientId(), targetGts.getName()));
                }   */

                //TODO: reimplement connecting to GTS
                return false;

            } else {
                throw new RuntimeException("Unexpected type of target node: " + newCanvasLink.getTarget().getClass().getName());
            }
        } else if (newCanvasLink.getOrigin() instanceof RspecCanvasLink.LinkCenter) {
            FXRspecLink originLink = ((RspecCanvasLink.LinkCenter) newCanvasLink.getOrigin()).getRspecLink();

            if (newCanvasLink.getTarget() instanceof RspecCanvasNode) {
                final FXRspecNode targetNode = ((RspecCanvasNode) newCanvasLink.getTarget()).getRspecNode();

                boolean success = modelRspecEditor.addLink(originLink, targetNode);

                if (success) {
                    setStatus(String.format("Added node %s to existing link %s",
                            targetNode.getClientId(), originLink.getClientId()));
                }
            } else {
                LOG.debug("Unsupported combination of items");
                return false;
            }
        } else if (newCanvasLink.getOrigin() instanceof RspecGTSNode) {
            /*EditableGtsDsl originGts = ((RspecGTSNode) newCanvasLink.getOrigin()).getEditableGtsDsl();

            if (newCanvasLink.getTarget() instanceof RspecCanvasNode) {
                final FXRspecNode targetNode = ((RspecCanvasNode) newCanvasLink.getTarget()).getRspecNode();
                boolean success =
                        modelRspecEditor.connectNodeToGts(targetNode, originGts);

                if (success) {
                    setStatus(String.format("Connected node %s to port on GTS DSL %s",
                            targetNode.getClientId(), originGts.getName()));
                }
            } else {
                LOG.debug("Unsupported combination of items");
                return false;
            }    */


            //TODO: reimplement connecting to GTS
            return false;


        } else {
            throw new RuntimeException("Unexpected type of origin node: " + newCanvasLink.getOrigin().getClass().getName());
        }

        return false;
    }

    private boolean processNodeOfNewLinkRequest(RspecCanvasNode canvasNode, boolean isStitchedLink) {
        if (!testForTestbedLinkSupport(canvasNode.getRspecNode())) {

            String testbedName =
                    authorityFinder.findByUrn(canvasNode.getRspecNode().getComponentManagerId(),
                            AuthorityFinder.Purpose.STITCHING).getName();

            final Alert alert =
                    new Alert(Alert.AlertType.WARNING, String.format("'%s' does not support links.", testbedName));
            alert.setHeaderText("Cannot add link");
            alert.setTitle("Cannot add link");
            alert.show();

            setStatus(String.format("Cannot add link. '%s' does not support links.", testbedName));
            return false;
        }

        if (isStitchedLink && !supportsStitching(canvasNode)) {
            String testbedName =
                    authorityFinder.findByUrn(canvasNode.getRspecNode().getComponentManagerId(),
                            AuthorityFinder.Purpose.STITCHING).getName();

            ButtonType doNotAdd = new ButtonType("Don't Add Link", ButtonBar.ButtonData.NO);
            ButtonType addAnyWay = new ButtonType("Add Link Anyway", ButtonBar.ButtonData.YES);

            final Alert alert =
                    new Alert(Alert.AlertType.CONFIRMATION, String.format("'%s' does not support stitched links. " +
                            "It is best not to add this link. If you know what you are doing you can go further.", testbedName), doNotAdd, addAnyWay);
            alert.setHeaderText("Cannot add link");
            alert.setTitle("Cannot add link");

            //FIXME: workaround for bug on Linux 8u40
            alert.setResizable(true);
            alert.getDialogPane().setPrefSize(480, 200);

            Optional<ButtonType> result = alert.showAndWait();
            if (result.isPresent() && result.get() == addAnyWay) {
                setStatus(String.format("Will add link despite the fact that '%s' does not support stitched links.", testbedName));
                return true;
            }
            assert !result.isPresent() || result.get() == doNotAdd;
            setStatus(String.format("Cannot add link. '%s' does not support stitched links.", testbedName));
            return false;
        }

        return true;
    }


    private boolean supportsStitching(RspecCanvasNode node) {
        Server auth =
                authorityFinder.findByUrn(node.getRspecNode().getComponentManagerId(), AuthorityFinder.Purpose.STITCHING);

        assert auth != null;

        return auth.hasFlag(Server.Flag.featureStitching);
    }

    protected void addCanvasNodeFromToolboxItem(ToolboxItem item, Point2D location) {
        if (item instanceof NodeResourceClassToolboxItem) {
            NodeResourceClassToolboxItem ndti = (NodeResourceClassToolboxItem) item;
            FXRspecNode rspecNode = modelRspecEditor.addNode(ndti.getResourceClass(), location);

            if (rspecNode != null) {

                LOG.debug("Added node {} to canvas", rspecNode.getClientId());
                setStatus("Added node " + rspecNode.getClientId() + " to the canvas");

            } else {
                setStatus("Node was not properly configured!");
            }

        } else if (item instanceof ChannelToolboxItem) {
            ChannelToolboxItem cti = (ChannelToolboxItem) item;

            FXRspecChannel channel = modelRspecEditor.addChannel(location);
            boolean success =
                    channelPropertiesDialogFactory.showChannelPropertiesDialog(getScene().getWindow(), channel, model);

            if (success) {
                LOG.debug("Adding channel {} to canvas", channel.getComponentName());
                setStatus("Added channel " + channel.getComponentName() + " to the canvas");

            } else {
                LOG.warn("User did not properly configure channel. Removing it again!");

                model.deleteChannel(channel);
                setStatus("Channel was not properly configured!");
            }
        } else if (item instanceof GeantTestbedToolboxItem) {
            FXGeantTestbedType gtt = modelRspecEditor.addGeantTestbedType(location);

        } else if (item instanceof AddressPoolToolboxItem) {
            FXAddressPool addressPool = modelRspecEditor.addAddressPool(location);

            //try to set testbed automatically
            List<Server> serversWithAddressPool = getAddressPoolEnabledServers();

            if (serversWithAddressPool != null && !serversWithAddressPool.isEmpty()) {
                addressPool.setComponentManagerId(serversWithAddressPool.get(0).getDefaultComponentManagerAsGeniUrn());
            } else {
                boolean success =
                        addressPoolPropertiesDialogFactory.showAddressPoolPropertiesDialog(getScene().getWindow(), addressPool, model);

                if (success) {
                    LOG.debug("Adding address pool {} to canvas", addressPool.getClientId());
                    setStatus("Added channel " + addressPool.getClientId() + " to the canvas");

                } else {
                    LOG.warn("User did not properly configure address pool. Removing it again!");

                    model.deleteAddressPool(addressPool);
                    setStatus("Address pool was not properly configured!");
                }
            }


        } else {
            LOG.error("Could not instantiate CanvasNode from ToolboxItem '{}'", item);
            throw new RuntimeException("Unexpected dragged item type: " + item.getClass().getName());
        }
    }

    @Nullable
    private List<Server> getAddressPoolEnabledServers() {
        List<Server> res = new ArrayList<>();
        for (Server server : testbedInfoSource.getServers()) {
            if (server.hasFlag(Server.Flag.featureIPv4AddressPool)) {
                res.add(server);
            }
        }
        return res;
    }

    @Override
    protected CanvasNode addCopyOfNodeToCanvas(CanvasNode originalCanvasNode) {
        if (!(originalCanvasNode instanceof RspecCanvasNode))
            return null;
        RspecCanvasNode originalRspecCanvasNode = (RspecCanvasNode) originalCanvasNode;

        FXRspecNode newRspecNode =
                rspecFactory.copyNode(model, originalRspecCanvasNode.getRspecNode(), RspecNode.InterfaceCopyMethod.NO_COPY);
        modelRspecEditor.updateNodeClientId(newRspecNode, model.nextNodeName());

        newRspecNode.setEditorX(newRspecNode.getEditorX() + EDITOR_LOCATION_SHIFT);
        newRspecNode.setEditorY(newRspecNode.getEditorY() + EDITOR_LOCATION_SHIFT);

        model.getNodes().add(newRspecNode);

        // Now also copy the interface of the orignal node.
        // We do this using the ModelRspecEditor to assure that IP-addresses etc. are correctly changed.

        for (FXRspecInterface originalIface : originalRspecCanvasNode.getRspecNode().getInterfaces()) {
            if (originalIface.isLinkBound()) {
                LOG.info("Duplicating interface {} of {} to {} into new node {}",
                        originalIface.getUniqueId(),
                        originalRspecCanvasNode.getRspecNode().getUniqueId(),
                        originalIface.getLink().getUniqueId(),
                        newRspecNode.getUniqueId());
                modelRspecEditor.addLink(originalIface.getLink(), newRspecNode);
            } else {
                LOG.info("Duplicating unbound interface {} of {} into new node {}",
                        originalIface.getUniqueId(),
                        originalRspecCanvasNode.getRspecNode().getUniqueId(),
                        newRspecNode.getUniqueId());
                modelRspecEditor.addUnboundInterface(newRspecNode);
            }
        }

        return binding.getRspecCanvasNode(newRspecNode);
    }

    @Override
    public boolean isLegalLink(NewCanvasLink newCanvasLink) {

        if (!newCanvasLink.isLinkComplete()) {
            LOG.trace("Requested link is not complete");
            return false;
        }

        if (newCanvasLink.getOrigin() instanceof RspecCanvasNode) {
            final FXRspecNode originNode = ((RspecCanvasNode) newCanvasLink.getOrigin()).getRspecNode();

            if (newCanvasLink.getTarget() instanceof RspecCanvasNode) {
                final FXRspecNode targetNode = ((RspecCanvasNode) newCanvasLink.getTarget()).getRspecNode();

                if (originNode == targetNode) {
                    LOG.trace("Origin and target-node cannot be identical.");
                    return false;
                } else {
                    return true;
                }
            } else if (newCanvasLink.getTarget() instanceof RspecCanvasLink.LinkCenter) {
                final FXRspecLink targetLink = ((RspecCanvasLink.LinkCenter) newCanvasLink.getTarget()).getRspecLink();

                //do not allow more than two interfaces on a stitched link
                if (targetLink.isStitched(testbedInfoSource)) {
                    LOG.trace("Cannot add node to a stitched link");
                    return false;
                }
                //prevent a normal link from becoming a stitched link
                if (isStitchedLink(newCanvasLink)) {
                    LOG.trace("Cannot add node from different AM to existing link");
                    return false;
                }

                //do not allow multiple interfaces to the same node to be added to a link
                if (targetLink.getInterfaceForNode(originNode) != null) {
                    LOG.trace("Node already has an interface on the requested link.");
                    return false;
                }
                return true;
            } else {
                assert newCanvasLink.getTarget() instanceof RspecGTSNode;
                return true;
            }
        } else if (newCanvasLink.getOrigin() instanceof RspecCanvasLink.LinkCenter) {
            FXRspecLink originLink = ((RspecCanvasLink) newCanvasLink.getOrigin()).getRspecLink();
            //do not allow more than two interface on a stitched link
            if (originLink.isStitched(testbedInfoSource)) {
                LOG.trace("Cannot add node to a stitched link");
                return false;
            }

            if (newCanvasLink.getTarget() instanceof RspecCanvasNode) {
                final FXRspecNode targetNode = ((RspecCanvasNode) newCanvasLink.getTarget()).getRspecNode();
                //prevent a normal link from becoming a stitched link
                if (isStitchedLink(newCanvasLink)) {
                    LOG.trace("Cannot add node from different AM to existing link");
                    return false;
                }

                //do not allow multiple interfaces to the same node to be added to a link
                if (originLink.getInterfaceForNode(targetNode) != null) {
                    LOG.trace("Node already has an interface on the requested link.");
                    return false;
                }

                return true;
            } else if (newCanvasLink.getTarget() instanceof RspecCanvasLink.LinkCenter) {
                //do not allow links between two RspecLinks
                LOG.trace("Cannot link two links together!");
                return false;
            } else {
                assert newCanvasLink.getTarget() instanceof RspecGTSNode;
                LOG.trace("Cannot connect a link and a GTS port");
                return false;
            }
        } else {
            assert newCanvasLink.getOrigin() instanceof RspecGTSNode;
           /* EditableGtsDsl originGts = ((RspecGTSNode) newCanvasLink.getOrigin()).getEditableGtsDsl();

            if (newCanvasLink.getTarget() instanceof RspecCanvasNode) {
                return true;
            } else {
                LOG.trace("Cannot connect anything else then a node to a GTS port");
                return false;
            }   */
            return false;
        }
    }

    public boolean isStitchedLink(NewCanvasLink newCanvasLink) {
        if (!newCanvasLink.isLinkComplete())
            return false;

        GeniUrn originComponentManagerId;
        if (newCanvasLink.getOrigin() instanceof RspecCanvasNode) {
            final FXRspecNode originNode = ((RspecCanvasNode) newCanvasLink.getOrigin()).getRspecNode();
            originComponentManagerId = originNode.getComponentManagerId();
        } else if (newCanvasLink.getOrigin() instanceof RspecCanvasLink.LinkCenter) {
            FXRspecLink originLink = ((RspecCanvasLink.LinkCenter) newCanvasLink.getOrigin()).getRspecLink();
            assert originLink.getInterfaces().size() > 0;
            originComponentManagerId = originLink.getInterfaces().get(0).getNode().getComponentManagerId();

            //check if link type is "lan" (required for stitching)
            if (originLink.getLinkTypes().size() > 1 ||
                    (originLink.getLinkTypes().size() == 1 &&
                            !(originLink.getLinkTypes().get(0).equalsIgnoreCase("lan") || originLink.getLinkTypes().get(0).equalsIgnoreCase("vlan")))
                    )
                return false;
        } else {
            assert newCanvasLink.getOrigin() instanceof RspecGTSNode;
            return false;
        }

        GeniUrn targetComponentManagerId;
        if (newCanvasLink.getTarget() instanceof RspecCanvasNode) {
            final FXRspecNode targetNode = ((RspecCanvasNode) newCanvasLink.getTarget()).getRspecNode();
            targetComponentManagerId = targetNode.getComponentManagerId();
        } else if (newCanvasLink.getTarget() instanceof RspecCanvasLink.LinkCenter) {
            FXRspecLink targetLink = ((RspecCanvasLink.LinkCenter) newCanvasLink.getTarget()).getRspecLink();
            assert targetLink.getInterfaces().size() > 0;
            targetComponentManagerId = targetLink.getInterfaces().get(0).getNode().getComponentManagerId();

            //check if link type is "lan" (required for stitching)
            if (targetLink.getLinkTypes().size() > 1 ||
                    (targetLink.getLinkTypes().size() == 1 &&
                            !(targetLink.getLinkTypes().get(0).equalsIgnoreCase("lan")
                                    || targetLink.getLinkTypes().get(0).equalsIgnoreCase("vlan"))))
                return false;
        } else {
            assert newCanvasLink.getTarget() instanceof RspecGTSNode;
            return false;
        }


        assert originComponentManagerId != null;
        assert targetComponentManagerId != null;
        //return !originComponentManagerId.equals(targetComponentManagerId);

        //we need to check if the authority used in CreateSliver is the same. The URN might be different. See Server.SpecialCase.PREFER_TOPLEVEL
        Server originAuth =
                authorityFinder.findByUrn(originComponentManagerId, AuthorityFinder.Purpose.CREATE_SLIVER);
        Server targetAuth =
                authorityFinder.findByUrn(targetComponentManagerId, AuthorityFinder.Purpose.CREATE_SLIVER);

        if (originAuth == null || targetAuth == null) {
            if (originAuth == null)
                LOG.warn("Could not find authority for " + originComponentManagerId + " -> falling back to comparing URNs");
            if (targetAuth == null)
                LOG.warn("Could not find authority for " + targetComponentManagerId + " -> falling back to comparing URNs");

            //fallback
            return !Objects.equals(originComponentManagerId, targetComponentManagerId);
        }

        return !Objects.equals(originAuth.getDefaultComponentManagerAsGeniUrn(), targetAuth.getDefaultComponentManagerAsGeniUrn());
    }
}
