package demo.view.advanced;

import demo.view.DemoBase;
import y.base.Edge;
import y.base.EdgeCursor;
import y.base.EdgeList;
import y.base.NodeList;
import y.base.Node;
import y.layout.LayoutTool;
import y.layout.ParallelEdgeLayouter;
import y.util.GraphHider;
import y.view.Arrow;
import y.view.EditMode;
import y.view.MoveSelectionMode;
import y.view.CreateEdgeMode;
import y.view.Graph2D;

import javax.swing.JToolBar;
import javax.swing.AbstractAction;
import java.awt.event.ActionEvent;

/**
 * This class creates a simple graph editor with its own MoveSelectionMode and CreateEdgeMode. Whenever nodes are moved
 * all adjacent edges that have the same source and target nodes are routed parallel using {@link ParallelEdgeLayouter}.
 * Whenever an edge is created all edges connecting from/to the same target/source node of this edge will be routed
 * parallel using {@link ParallelEdgeLayouter}.
 */
public class ParallelEdgeLayouterDemo extends DemoBase {
  private ParallelEdgeLayouter parallelEdeLayouter;
  private final int LINE_DISTANCE = 5;

  public ParallelEdgeLayouterDemo() {
    Graph2D graph = view.getGraph2D();
    graph.getDefaultEdgeRealizer().setTargetArrow(Arrow.STANDARD);

    //create parallel edge layouter
    parallelEdeLayouter = new ParallelEdgeLayouter();
    //set a line distance between parallel edges
    parallelEdeLayouter.setLineDistance(LINE_DISTANCE);
    // join end points of parallel edges
    parallelEdeLayouter.setJoinEndsEnabled(true);

    fillGraphInitially(graph);
  }

  private void fillGraphInitially(Graph2D graph) {
    //create some nodes
    Node node1 = graph.createNode(100, 100, "1");
    Node node2 = graph.createNode(100, 200, "2");
    Node node3 = graph.createNode(200, 200, "3");

    //create some parallel edges
    for (int i = 0; i < 4; i++) {
      if (i % 2 == 0) {
        graph.createEdge(node1, node2);
        graph.createEdge(node2, node3);
      } else {
        graph.createEdge(node2, node1);
        graph.createEdge(node3, node2);
      }
    }
    view.fitContent();
    view.updateView();
  }

  protected JToolBar createToolBar() {
    JToolBar toolbar = super.createToolBar();
    //add an action to reset all edge paths.
    toolbar.add(new AbstractAction("Reset Edge Paths") {
      public void actionPerformed(ActionEvent e) {
        LayoutTool.resetPaths(view.getGraph2D());
        view.updateView();
      }
    });
    return toolbar;
  }

  protected EditMode createEditMode() {
    EditMode editMode = super.createEditMode();
    //set our own MoveSelectionMode and CreateEdgeMode
    editMode.setCreateEdgeMode(new ParallelEdgeCreateEdgeMode());
    editMode.setMoveSelectionMode(new ParallelEdgeMoveSelectionMode());
    return editMode;
  }

  /**
   * Routes the edges of the given list using {@link y.layout.ParallelEdgeLayouter}.
   *
   * @param affectedEdges a list of edges that shall be routed in a parallel fashion
   */
  private void routeParallelEdges(EdgeList affectedEdges) {
    Graph2D graph = view.getGraph2D();

    //GraphHider is needed to hide all edges that shall not be routed by ParallelEdgeLayouter
    GraphHider hider = new GraphHider(graph);
    for (EdgeCursor edgeCursor = graph.edges(); edgeCursor.ok(); edgeCursor.next()) {
      Edge edge = edgeCursor.edge();
      if (affectedEdges.contains(edge)) {
        //remove bends from affected edges
        LayoutTool.resetPath(graph, edge);
      } else {
        //hide unaffected edges from layouter
        hider.hide(edge);
      }
    }
    //do the layout
    parallelEdeLayouter.doLayout(graph);
    //unhide unaffected edges
    hider.unhideAll();
  }

  /**
   * A ViewMode that handles edge creations. It will reroute all edges that have the same source and target node as the
   * newly created edge using {@link y.layout.ParallelEdgeLayouter}
   */
  class ParallelEdgeCreateEdgeMode extends CreateEdgeMode {
    private EdgeList affectedEdges;

    public ParallelEdgeCreateEdgeMode() {
      affectedEdges = new EdgeList();
    }

    protected void edgeCreated(Edge edge) {
      super.edgeCreated(edge);

      //collect all edges from the created edge's source to the created edge's target node, these must be rerouted
      for (EdgeCursor edgeCursor = edge.source().outEdges(); edgeCursor.ok(); edgeCursor.next()) {
        Edge outEdge = edgeCursor.edge();
        if (outEdge.target() == edge.target()) {
          affectedEdges.add(outEdge);
        }
      }

      //collect all edges from the created edge's target to the created edge's source node, these must be rerouted
      for (EdgeCursor edgeCursor = edge.source().inEdges(); edgeCursor.ok(); edgeCursor.next()) {
        Edge inEdge = edgeCursor.edge();
        if (inEdge.source() == edge.target()) {
          affectedEdges.add(inEdge);
        }
      }

      routeParallelEdges(affectedEdges);
      affectedEdges.clear();
    }
  }

  /**
   * A ViewMode that handles selection movement. It will reroute edges between selected and unselected nodes using
   * {@link y.layout.ParallelEdgeLayouter} when the selected nodes are moved.
   */
  class ParallelEdgeMoveSelectionMode extends MoveSelectionMode {
    private EdgeList affectedEdges;

    public ParallelEdgeMoveSelectionMode() {
      affectedEdges = new EdgeList();
    }

    protected void selectionOnMove(double dx, double dy, double x, double y) {
      super.selectionOnMove(dx, dy, x, y);
      routeParallelEdges(affectedEdges);
    }

    protected void selectionMoveStarted(double x, double y) {
      super.selectionMoveStarted(x, y);

      //fill list with edges that shall be routed
      for (EdgeCursor edgeCursor = getGraph2D().edges(); edgeCursor.ok(); edgeCursor.next()) {
        Edge edge = edgeCursor.edge();
        NodeList movedNodes = getNodesToBeMoved();
        //if the source or the target of an edge is moved and the counterpart is not moved, we want to reroute the edge
        //if the source *and* target is moved, we do *not* want to reroute the edge
        if (movedNodes.contains(edge.source()) != movedNodes.contains(edge.target())) {
          affectedEdges.add(edge);
        }
      }
    }

    protected void selectionMovedAction(double dx, double dy, double x, double y) {
      super.selectionMovedAction(dx, dy, x, y);
      routeParallelEdges(affectedEdges);
      affectedEdges.clear();
    }
  }

  public static void main(String[] args) {
    initLnF();
    ParallelEdgeLayouterDemo demo = new ParallelEdgeLayouterDemo();
    demo.start("ParallelEdgelayouter Demo");
  }
}
