package be.iminds.ilabt.jfed.experimenter_gui.canvas.rspec;

import be.iminds.ilabt.jfed.experimenter_gui.canvas.CanvasLink;
import be.iminds.ilabt.jfed.experimenter_gui.canvas.ExperimentCanvas;
import be.iminds.ilabt.jfed.experimenter_gui.canvas.NewLinkTarget;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXRspecInterface;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXRspecLink;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import javafx.beans.InvalidationListener;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.paint.Color;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * Visualisation class for Rspec ifaceLinks.
 * <p>
 * These can have a connection to more than two nodes, and are thus a collection of
 * two OR MORE ifaceLinks to a central "link"-component
 * User: twalcari
 * Date: 11/13/13
 * Time: 9:22 AM
 */
public class RspecCanvasLink implements ListChangeListener<FXRspecInterface> {

    private static final Logger LOG = LoggerFactory.getLogger(RspecCanvasLink.class);

    private static final String LINK_CENTER_STYLECLASS = "link-center";
    private static final String IMPAIRED_LINK_CENTER_STYLECLASS = "impaired-link-center";
    private static final String SHARED_LINK_CENTER_STYLECLASS = "shared-link-center";
    private final FXRspecLink link;
    private final ModelRspecToCanvasBinding binding;
    private final ExperimentCanvas experimentCanvas;
    private final LinkCenter linkCenter;
    private final Map<FXRspecInterface, InterfaceLink> ifaceLinks = new HashMap<>();
    private final InvalidationListener layoutListener = observable -> updateLayout();

    private double centerX, centerY;

    public RspecCanvasLink(FXRspecLink link, ModelRspecToCanvasBinding binding) {
        this.link = link;
        this.binding = binding;
        this.experimentCanvas = binding.getExperimentCanvas();

        //create centernode
        this.linkCenter = new LinkCenter();
        experimentCanvas.addLinkCenter(linkCenter);


        //add ifaceLinks between center and FXRspecNode

        link.getInterfaces().forEach(this::addInterface);
        link.getInterfaces().addListener(this);

        //register to canvas for autolayouting support
        experimentCanvas.registerRspecCanvasLink(this);

    }

    private void addInterface(FXRspecInterface iface) {
        assert (!ifaceLinks.containsKey(iface));

        RspecCanvasNode rspecCanvasNode = binding.getRspecCanvasNode(iface.getNode());
        assert (rspecCanvasNode != null);

        rspecCanvasNode.layoutXProperty().addListener(layoutListener);
        rspecCanvasNode.layoutYProperty().addListener(layoutListener);
        rspecCanvasNode.boundsInLocalProperty().addListener(layoutListener);


        InterfaceLink canvasLink = new InterfaceLink(iface, rspecCanvasNode, linkCenter);

        ifaceLinks.put(iface, canvasLink);

        //add as first element to make sure that ifaceLinks are rendered first, and are thus overriden by nodes
        experimentCanvas.addLink(canvasLink);

        updateLayout();
    }

    private void removeInterface(FXRspecInterface iface) {
        InterfaceLink canvasLink = ifaceLinks.get(iface);
        RspecCanvasNode rspecCanvasNode = canvasLink.getNodeA();

        rspecCanvasNode.layoutXProperty().removeListener(layoutListener);
        rspecCanvasNode.layoutYProperty().removeListener(layoutListener);
        rspecCanvasNode.boundsInLocalProperty().removeListener(layoutListener);

        experimentCanvas.removeLink(canvasLink);
        ifaceLinks.remove(iface);
        updateLayout();
    }

    private void updateLayout() {
        if (ifaceLinks.isEmpty()) return;

        centerX = 0;
        centerY = 0;
        for (InterfaceLink ntlcLink : ifaceLinks.values()) {
            RspecCanvasNode node = ntlcLink.getNodeA();

            centerX += NewLinkTarget.getCenterX(node);
            centerY += NewLinkTarget.getCenterY(node);
        }
        centerX /= ifaceLinks.size();
        centerY /= ifaceLinks.size();

        NewLinkTarget.setCenterX(centerX, linkCenter);
        NewLinkTarget.setCenterY(centerY, linkCenter);
    }

    /**
     * Should be called when the RspecCanvasLink is removed from the canvas
     */
    public void remove() {
        //unregister from canvas (was for autolayouting support)
        experimentCanvas.unregisterRspecCanvasLink(this);

        experimentCanvas.removeLinkCenter(linkCenter);

        for (InterfaceLink ntlcLink : ifaceLinks.values()) {
            experimentCanvas.removeLink(ntlcLink);
        }

        link.getInterfaces().removeListener(this);
    }

    @Override
    public void onChanged(Change<? extends FXRspecInterface> change) {
        LOG.trace("Detected interface changes on link {}", link.getUniqueId());

        while (change.next()) {
            LOG.trace("Received interface change on link {}: {}", link.getUniqueId(), change);

            if (change.wasAdded()) {
                change.getAddedSubList().forEach(iface -> LOG.trace("iface {} was added to rspecCanvasLink {}", iface.getClientId(), getRspecLink().getClientId()));
                change.getAddedSubList().forEach(this::addInterface);
            }

            if (change.wasRemoved()) {
                change.getRemoved().forEach(iface -> LOG.trace("iface {} was removed to rspecCanvasLink {}", iface.getClientId(), getRspecLink().getClientId()));
                change.getRemoved().forEach(this::removeInterface);
            }
        }
    }

    public double getCenterX() {
        return centerX;
    }

    public double getCenterY() {
        return centerY;
    }

    public FXRspecLink getRspecLink() {
        return link;
    }

    public Button getLinkCenter() {
        return linkCenter;
    }

    public Collection<InterfaceLink> getInterfaceLinks() {
        return ifaceLinks.values();
    }

    public InterfaceLink getInterfaceLinkByRspecInterface(FXRspecInterface iface) {
        return ifaceLinks.get(iface);
    }


    public class LinkCenter extends Button implements NewLinkTarget {

        private boolean hasImpairedLook = false;
        private boolean hasAutoSharedLook = false;
        private boolean hasAddToSharedLook = false;
        private final Node createSharedGraphic;
        private final Node addTosharedGraphic;

        public LinkCenter() {
            super();
            createSharedGraphic = GlyphUtils.createSmallGlyph(FontAwesome.Glyph.USERS, Color.BLACK);
            addTosharedGraphic = GlyphUtils.createSmallGlyph(FontAwesome.Glyph.USER_PLUS, Color.BLACK);
            //other icons: PLUG  CLOUD  CLOUD_UPLOAD  cLOUD_DOWNLOAD

            textProperty().bind(link.clientIdProperty());

            getStyleClass().add(LINK_CENTER_STYLECLASS);
            setOnAction(actionEvent -> onLinkCenterClicked());
            layoutBoundsProperty().addListener(observable -> updateLayout());
            focusedProperty().addListener((observableValue, oldValue, newValue) -> {
                for (CanvasLink cl : ifaceLinks.values()) {
                    cl.setSelected(newValue);

                }
            });

            link.impairedProperty().addListener(observable -> {
                LOG.debug("Link is impaired? {}", link.isImpaired());
                if (link.isImpaired() != hasImpairedLook) {
                    if (link.isImpaired()) {
                        getStyleClass().add(IMPAIRED_LINK_CENTER_STYLECLASS);
                        hasImpairedLook = true;
                    } else {
                        getStyleClass().remove(IMPAIRED_LINK_CENTER_STYLECLASS);

                        //remove and add basis style to reset look
                        getStyleClass().remove(LINK_CENTER_STYLECLASS);
                        getStyleClass().add(LINK_CENTER_STYLECLASS);
                        hasImpairedLook = false;
                    }

                }
            });

            Runnable updateGraphic = () -> {
                if (hasAutoSharedLook)
                    setGraphic(createSharedGraphic);
                else {
                    if (hasAddToSharedLook)
                        setGraphic(addTosharedGraphic);
                    else
                        setGraphic(null);
                }
            };

            ListChangeListener<String> autoShareLanListener =  change -> {
                boolean shared = !link.getAutoShareLanNames().isEmpty();
                LOG.debug("Link is auto shared? {}", shared);
                if (shared != hasAutoSharedLook) {
                    if (shared) {
                        //getStyleClass().add(SHARED_LINK_CENTER_STYLECLASS);
                        hasAutoSharedLook = true;
                    } else {
                        //getStyleClass().remove(SHARED_LINK_CENTER_STYLECLASS);

                        //remove and add basis style to reset look
                        getStyleClass().remove(LINK_CENTER_STYLECLASS);
                        getStyleClass().add(LINK_CENTER_STYLECLASS);
                        hasAutoSharedLook = false;
                    }
                    updateGraphic.run();

                }
            };
            link.getAutoShareLanNames().addListener(autoShareLanListener);
            autoShareLanListener.onChanged(null);

            ChangeListener<String> addToShareLanListener =  (observable, oldValue, newValue) -> {
                boolean shared = link.getSharedLan() != null;
                LOG.debug("Link connects to shared? {}", shared);
                if (shared != hasAddToSharedLook) {
                    if (shared) {
                        //getStyleClass().add(SHARED_LINK_CENTER_STYLECLASS);
                        hasAddToSharedLook = true;
                    } else {
                        //getStyleClass().remove(SHARED_LINK_CENTER_STYLECLASS);

                        //remove and add basis style to reset look
                        getStyleClass().remove(LINK_CENTER_STYLECLASS);
                        getStyleClass().add(LINK_CENTER_STYLECLASS);
                        hasAddToSharedLook = false;
                    }
                    updateGraphic.run();
                }
            };
            link.sharedLanProperty().addListener(addToShareLanListener);
            addToShareLanListener.changed(null, null, null);
            updateGraphic.run();
        }

        public FXRspecLink getRspecLink() {
            return link;
        }

        private void onLinkCenterClicked() {
            experimentCanvas.getSelectionProvider().setSelectedObject(RspecCanvasLink.this);
        }

        public RspecCanvasLink getRspecCanvasLink() {
            return RspecCanvasLink.this;
        }

        @Override
        public Point2D getCenterPosition() {
            return new Point2D(centerX, centerY);
        }

        @Override
        public Bounds getCanvasBounds() {
            return ExperimentCanvas.calculateBoundsInCanvas(linkCenter);
        }
    }


    public class InterfaceLink extends CanvasLink {
        private final FXRspecInterface rspecInterface;

        public InterfaceLink(FXRspecInterface rspecInterface, RspecCanvasNode nodeA, Button nodeB) {
            super(nodeA, nodeB);
            this.rspecInterface = rspecInterface;
        }

        @Override
        public Button getNodeB() {
            return (Button) super.getNodeB();
        }

        @Override
        public RspecCanvasNode getNodeA() {
            return (RspecCanvasNode) super.getNodeA();
        }


        public FXRspecInterface getRspecInterface() {
            return rspecInterface;
        }


    }
}
