package demo.view;

import java.awt.Color;
import java.awt.EventQueue;

import y.anim.AnimationObject;
import y.anim.AnimationPlayer;
import y.base.Edge;
import y.base.EdgeCursor;
import y.base.Node;
import y.base.NodeCursor;
import y.base.NodeList;
import y.base.NodeMap;
import y.base.YList;
import y.layout.BufferedLayouter;
import y.layout.hierarchic.IncrementalHierarchicLayouter;
import y.layout.hierarchic.incremental.SimplexNodePlacer;
import y.util.Maps;
import y.view.Arrow;
import y.view.Graph2D;
import y.view.Graph2DView;

public class GraphPresenterDemo extends ViewActionDemo {
  public GraphPresenterDemo() {
    createGraph();
    layoutGraph();

    view.fitRectangle(view.getGraph2D().getBoundingBox());
    view.getGraph2D().updateViews();

    presentGraph();
  }

  protected void createGraph() {
    Graph2D graph = view.getGraph2D();
    graph.getDefaultEdgeRealizer().setTargetArrow(Arrow.STANDARD);
    // Create some nodes.
    Node v1 = graph.createNode(0, 0, "Node 1");
    graph.getRealizer(v1).setFillColor(Color.RED);
    Node c1 = graph.createNode(0, 0, "Node 2");
    graph.getRealizer(c1).setFillColor(Color.GREEN);
    Node c2 = graph.createNode(0, 0, "Node 3");
    graph.getRealizer(c2).setFillColor(Color.BLUE);
    for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
      graph.setSize(nc.node(), 100, 50);
    }
    // Create some edges.
    Edge e1 = graph.createEdge(v1, c1);
    Edge e2 = graph.createEdge(v1, c2);
  }

  protected void layoutGraph() {
    // Perform a layout.
    IncrementalHierarchicLayouter layouter = new IncrementalHierarchicLayouter();
    ((SimplexNodePlacer) layouter.getNodePlacer()).setBaryCenterModeEnabled(true);
    layouter.setOrthogonallyRouted(true);
    new BufferedLayouter(layouter).doLayout(view.getGraph2D());
  }

  protected void presentGraph() {
    // Set up an AnimationPlayer.
    AnimationPlayer player = new AnimationPlayer();
    player.setBlocking(false);

    // Register the view as an animation listener.
    player.addAnimationListener(view);
    // Play the animation object.
    player.animate(new GraphPresenter(view));
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        initLnF();
        new GraphPresenterDemo().start("Graph Presenter Demo");
      }
    });
  }

  /**
   * Animation object that successively unhides nodes and edges of a graph.
   */
  public class GraphPresenter implements AnimationObject {
    Graph2D graph;
    YList order;
    Graph2DView view;
    int all;

    public GraphPresenter(Graph2DView view) {
      graph = view.getGraph2D();
      this.view = view;
      all = graph.N() + graph.E();
    }

    public void initAnimation() {
      // List of all graph elements.
      order = new YList();

      // Helper map to check if opposite node is already in element list.
      NodeMap markMap = Maps.createIndexNodeMap(new boolean[graph.N()]);
      NodeList nList = new NodeList(graph.nodes());

      while (!nList.isEmpty()) {
        Node v = nList.popNode();
        // Add node to element list.
        order.add(v);

        markMap.setBool(v, true);
        for (EdgeCursor ec = v.edges(); ec.ok(); ec.next()) {
          Edge e = ec.edge();
          // Add edge to element list if opposite node is already in it.
          if (markMap.getBool(e.opposite(v))) {
            order.add(e);
          }
        }
      }
      // Hide all nodes (and edges).
      while (!graph.isEmpty()) {
        graph.hide(graph.firstNode());
      }
    }

    /**
     * Calculates the animation frame for the given point in time.
     * Do not call this method directly.
     * It is used by the animation framework classes.
     */
    public void calcFrame(double time) {
      if (time >= ((double) (all + 1) - order.size()) / all) {
        if (order.first() instanceof Node) {
          graph.unhide((Node) order.pop());
        }
        else if (order.first() instanceof Edge) {
          graph.unhide((Edge) order.pop());
        }
      }
    }

    /**
     * Disposes the animation. Do not call this method directly.
     * It is used by the animation framework classes.
     */
    public void disposeAnimation() {
      order = null;
    }

    /**
     * Returns the animation's preferred duration in milliseconds.
     */
    public long preferredDuration() {
      return 5000;
    }
  }
}
