package demo.layout.tree;

import demo.view.DemoBase;

import y.base.Edge;
import y.base.Node;
import y.layout.tree.DendrogramPlacer;
import y.layout.tree.GenericTreeLayouter;
import y.view.Arrow;
import y.view.BendList;
import y.view.CreateChildEdgeMode;
import y.view.EdgeRealizer;
import y.view.EditMode;
import y.view.GenericEdgePainter;
import y.view.GenericEdgeRealizer;
import y.view.Graph2D;
import y.view.HotSpotMode;
import y.view.NodeRealizer;
import y.view.PortAssignmentMoveSelectionMode;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.GeneralPath;
import java.util.Map;

/**
 * This demo is a modified variant of the {@link DendrogramLayouterDemo}. 
 * It shows how to disable anti-aliased rendering for edges only to avoid visual 
 * inconsistencies.
 * <br/>
 * In particular, when anti-aliased rendering is enabled, edges in edge groups that 
 * are routed in an orthogonal manner can appear thicker than they are in different 
 * zoom levels.
 */
public class NonAAEdgeRenderingDemo extends DemoBase {
  GenericTreeLayouter treeLayouter;
  Color[] layerColors = new Color[]{ Color.red, Color.orange, Color.yellow, Color.cyan, Color.green, Color.blue };

  public NonAAEdgeRenderingDemo() {
    treeLayouter = new GenericTreeLayouter();
    DendrogramPlacer dendrogramPlacer = new DendrogramPlacer();
    treeLayouter.setDefaultNodePlacer(dendrogramPlacer);
    treeLayouter.setDefaultChildComparator(dendrogramPlacer.createComparator());
    
    createSampleGraph(view.getGraph2D());
  }
  
  /**
   * Configures an edge realizer that explicitly turns off anti-alias rendering. 
   * The edge realizer is then set as the default edge realizer for the view's graph.
   */
  protected void configureDefaultRealizers() {
    super.configureDefaultRealizers();
    
    GenericEdgeRealizer.Factory factory = GenericEdgeRealizer.getFactory();
    Map cnfg = factory.createDefaultConfigurationMap();
    cnfg.put(GenericEdgeRealizer.Painter.class, new GenericEdgePainter() {
      protected void renderPath(EdgeRealizer context, Graphics2D gfx, GeneralPath path, boolean selected) {
        Object oldValue = gfx.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
        gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        
        super.renderPath(context, gfx, path, selected);
        
        if (oldValue == null) {
          gfx.getRenderingHints().remove(RenderingHints.KEY_ANTIALIASING);
        }
        else {
          gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldValue);
        }
      }
    
      public void paintSloppy(EdgeRealizer context, BendList bends, GeneralPath path, Graphics2D gfx, boolean selected) {
        Object oldValue = gfx.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
        gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        
        super.paintSloppy(context, bends, path, gfx, selected);
        
        if (oldValue == null) {
          gfx.getRenderingHints().remove(RenderingHints.KEY_ANTIALIASING);
        }
        else {
          gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldValue);
        }
      }
    });
    factory.addConfiguration("NoAntiAliasing", cnfg);

    GenericEdgeRealizer ger = new GenericEdgeRealizer("NoAntiAliasing");
    ger.setTargetArrow(Arrow.STANDARD);
    view.getGraph2D().setDefaultEdgeRealizer(ger);
  }
  
  public void calcLayout() {
    if (!view.getGraph2D().isEmpty()) {
      Cursor oldCursor = view.getCanvasComponent().getCursor();
      try {
        view.getCanvasComponent().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        view.applyLayoutAnimated(treeLayouter);
      }
      finally {
        view.getCanvasComponent().setCursor(oldCursor);
      }
    }
    view.fitContent();
    view.updateView();
  }

  protected void createSampleGraph(Graph2D graph) {
    graph.clear();
    Node root = graph.createNode();
    graph.getRealizer(root).setFillColor(layerColors[0]);
    createChildren(graph, root, 3, 1, 2);
    calcLayout();
  }

  protected void createChildren(Graph2D graph, Node root, int children, int layer, int layers) {
    if (graph.nodeCount() % 3 == 2) {
      // do not create nodes for every subtree
      return;
    }
    for (int i = 0; i < children; i++) {
      Node child = graph.createNode();
      graph.createEdge(root, child);
      graph.getRealizer(child).setFillColor(layerColors[layer % layerColors.length]);
      if (layers > 0) {
        createChildren(graph, child, children, layer + 1, layers - 1);
      }
    }
  }

  final class TreeCreateChildEdgeMode extends CreateChildEdgeMode {
    protected void edgeCreated(Edge e) {
      int depth = 1;
      for (Node n = e.source(); n.inDegree() > 0; n = n.firstInEdge().source()) {
        depth++;
      }
      Graph2D g = getGraph2D();
      g.getRealizer(e.target()).setFillColor(layerColors[depth % layerColors.length]);
      g.unselectAll();
      calcLayout();
      g.setSelected(e.target(), true);
    }

    protected NodeRealizer createChildNodeRealizer() {
      NodeRealizer retValue;
      retValue = super.createChildNodeRealizer();
      retValue.setLabelText("");
      return retValue;
    }
  }

  final class TreeHotSpotMode extends HotSpotMode {
    public void mouseReleasedLeft(double x, double y) {
      super.mouseReleasedLeft(x, y);
      calcLayout();
    }
  }
  final class TreeCreateEditMode extends EditMode {
    TreeCreateEditMode() {
      super();
      setMoveSelectionMode(new TreeMoveSelectionMode());
      setCreateEdgeMode(new TreeCreateChildEdgeMode());
      setHotSpotMode(new TreeHotSpotMode());
    }

    public boolean doAllowNodeCreation() {
      return getGraph2D().N() == 0;
    }
  }

  final class TreeMoveSelectionMode extends PortAssignmentMoveSelectionMode {
    TreeMoveSelectionMode() {
      super(null, null);
    }

    protected void selectionMovedAction(double dx, double dy, double x, double y) {
      super.selectionMovedAction(dx, dy, x, y);
      calcLayout();
    }
  }

  /** Launches this demo. */
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        initLnF();
        (new NonAAEdgeRenderingDemo()).start("Non Anti-Aliased Edge Rendering Demo");
      }
    });
  }
}
