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

import be.iminds.ilabt.jfed.experimenter_gui.canvas.CanvasNode;
import be.iminds.ilabt.jfed.experimenter_gui.canvas.ExperimentCanvas;
import be.iminds.ilabt.jfed.gts.model.GtsAdjacency;
import be.iminds.ilabt.jfed.gts.model.GtsType;
import be.iminds.ilabt.jfed.gts.model.impl.FXGtsLink;
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 javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javafx.collections.WeakListChangeListener;
import javafx.collections.WeakMapChangeListener;
import javafx.scene.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * User: twalcari
 * Date: 2/9/2016
 * Time: 16:57
 */
public class GtsTypeToCanvasBinding {
    private static final Logger LOG = LoggerFactory.getLogger(GtsTypeToCanvasBinding.class);

    private final FXGtsType model;
    private final ExperimentCanvas canvas;

    private final Map<FXGtsNode, GtsCanvasNode> nodes = new HashMap<>();
    private final Map<FXGtsLink, GtsCanvasLink> links = new HashMap<>();
    private final Map<FXGtsPort, GtsCanvasPort> ports = new HashMap<>();
    private final Map<FXGtsType.AdjacencyInfo, GtsCanvasAdjacency> adjacencies = new HashMap<>();

    private final ListChangeListener<FXGtsNode> gtsNodeListChangeListener = change -> {
        while (change.next()) {
            if (change.wasAdded()) {
                change.getAddedSubList().forEach(this::addNodeToCanvas);
            }

            if (change.wasRemoved()) {
                change.getRemoved().forEach(this::removeNodeFromCanvas);
            }
        }
    };
    private final ListChangeListener<FXGtsLink> gtsLinkListChangeListener = change -> {
        while (change.next()) {
            if (change.wasAdded()) {
                change.getAddedSubList().forEach(this::addLinkToCanvas);
            }

            if (change.wasRemoved()) {
                change.getRemoved().forEach(this::removeLinkFromCanvas);
            }
        }
    };
    private final MapChangeListener<GtsType.Adjacency, FXGtsType.AdjacencyInfo> gtsAdjacencyInfoMapChangeListener =
            change -> {
                if(change.wasRemoved())
                    removeAdjacencyFromCanvas(change.getValueRemoved());
                if(change.wasAdded()){
                    addAdjacencyToCanvas(change.getValueAdded());
                }

            };


    private void removeAdjacencyFromCanvas(FXGtsType.AdjacencyInfo adjacencyInfo) {
        GtsCanvasAdjacency canvasAdjacency = adjacencies.remove(adjacencyInfo);
        canvas.removeLink(canvasAdjacency);
    }

    private void addAdjacencyToCanvas(FXGtsType.AdjacencyInfo adjacencyInfo) {
        GtsCanvasAdjacency canvasAdjacency = GtsCanvasAdjacency.createGtsCanvasAdjacency(adjacencyInfo, this);
        adjacencies.put(adjacencyInfo, canvasAdjacency);
        canvas.addLink(canvasAdjacency);

        if(adjacencyInfo instanceof FXGtsType.NodeToLinkAdjacencyInfo){
            FXGtsType.NodeToLinkAdjacencyInfo ntlAdjInfo = (FXGtsType.NodeToLinkAdjacencyInfo)adjacencyInfo;

            GtsCanvasLink canvasLink = getGtsCanvasLink(ntlAdjInfo.getLink());
            CanvasNode canvasNode = getGtsCanvasNode(ntlAdjInfo.getNode());

            canvasNode.layoutXProperty().addListener(canvasLink.getUpdatePositionListener());
            canvasNode.layoutYProperty().addListener(canvasLink.getUpdatePositionListener());
            canvasNode.boundsInParentProperty().addListener(canvasLink.getUpdatePositionListener());
        }
    }

    private final MapChangeListener<String, FXGtsPort> gtsPortMapChangeListener = change -> {
        if (change.wasAdded())
            addPortToCanvas(change.getValueAdded());
        if (change.wasRemoved())
            removePortFromCanvas(change.getValueRemoved());

    };

    public GtsTypeToCanvasBinding(FXGtsType model, ExperimentCanvas canvas) {
        this.model = model;
        this.canvas = canvas;

        model.getNodes().forEach(this::addNodeToCanvas);
        model.getLinks().forEach(this::addLinkToCanvas);
        model.getPorts().values().forEach(this::addPortToCanvas);

        //first create the Adjacency-Info
        model.getAdjacencyInfos().values().forEach(this::addAdjacencyToCanvas);

        model.getNodes().addListener(new WeakListChangeListener<>(gtsNodeListChangeListener));
        model.getLinks().addListener(new WeakListChangeListener<>(gtsLinkListChangeListener));
        model.getPorts().addListener(new WeakMapChangeListener<>(gtsPortMapChangeListener));
        model.getAdjacencyInfos().addListener(new WeakMapChangeListener<>(gtsAdjacencyInfoMapChangeListener));

    }

    private void addNodeToCanvas(FXGtsNode gtsNode) {
        GtsCanvasNode newNode = new GtsCanvasNode(gtsNode);
        nodes.put(gtsNode, newNode);
        canvas.addNode(newNode);

    }

    private void removeNodeFromCanvas(FXGtsNode gtsNode) {
        GtsCanvasNode canvasNode = nodes.remove(gtsNode);
        canvas.removeNode(canvasNode);
    }

    private void addLinkToCanvas(FXGtsLink gtsLink) {
        GtsCanvasLink newLink = new GtsCanvasLink(gtsLink, this);
        links.put(gtsLink, newLink);
        canvas.addLinkCenter(newLink);
    }

    private void removeLinkFromCanvas(FXGtsLink gtsLink) {
        GtsCanvasLink canvasLink = links.remove(gtsLink);
        canvas.removeLinkCenter(canvasLink);
    }

    private void addPortToCanvas(FXGtsPort gtsPort) {
        GtsCanvasPort newPort = new GtsCanvasPort(gtsPort);
        ports.put(gtsPort, newPort);
        canvas.addNode(newPort);

    }

    private void removePortFromCanvas(FXGtsPort gtsPort) {
        GtsCanvasPort canvasPort = ports.remove(gtsPort);
        canvas.removeNode(canvasPort);
    }

    public ExperimentCanvas getExperimentCanvas() {
        return canvas;
    }

    public GtsCanvasNode getGtsCanvasNode(FXGtsNode node) {
        return nodes.get(node);
    }

    public GtsCanvasPort getGtsCanvasPort(FXGtsPort port) {
        return ports.get(port);
    }

    public FXGtsType getModel() {
        return model;
    }


    public Node getNode(GtsAdjacency.PortId portId) {
        if (portId.getParentId() == null) {
            FXGtsPort port = model.getPortById(portId.getPortId());

            if (port == null)
                throw new IllegalArgumentException(String.format("Could not find a port with id '%s' in type '%s'", portId.getPortId(), model.getName()));

            return getGtsCanvasPort(port);
        }
        FXGtsNode node = model.getNodeById(portId.getParentId());
        if (node != null) {
            if (!node.getPorts().containsKey(portId.getPortId()))
                throw new IllegalArgumentException(String.format("Could not find a port with id '%s' in node '%s'", portId.getPortId(), node.getId()));
            return getGtsCanvasNode(node);
        }

        FXGtsLink link = model.getLinkById(portId.getParentId());
        if (link != null) {
            if (!link.getPorts().containsKey(portId.getPortId()))
                throw new IllegalArgumentException(String.format("Could not find a port with id '%s' in link '%s'", portId.getPortId(), link.getId()));
            return getGtsCanvasLink(link);
        }

        throw new IllegalArgumentException(String.format("Could not find a node or link with id '%s' in type '%s'", portId.getParentId(), model.getName()));

    }

    public GtsCanvasLink getGtsCanvasLink(FXGtsLink link) {
        return links.get(link);
    }



}
