package y.layout.hierarchic;

import y.base.DataProvider;
import y.base.Node;
import y.base.NodeList;
import y.base.NodeMap;
import y.geom.YRectangle;
import y.layout.LayoutGraph;
import y.layout.NodeLabelLayout;
import y.layout.NodeLayout;

/**
 * This class can be used to wrap {@link Drawer} implementations.
 * It modifies the {@link Drawer.NODE_BORDER_LEFT} and 
 * {@link Drawer.NODE_BORDER_RIGHT} DataProvider instances and delegates the
 * actual drawing to the inner drawer.
 * Actual space requirements are calculated in {@link #getHaloSpace(Node, boolean)}.
 * This implementation uses the maximum left and right label overlaps as the halo values.
 * Note that this will only work with Drawer implementations that respect the values
 * provided through the DataProviders, e.g. MedianLinearSegmentsDrawer and SimplexDrawer.
 * <br/>
 * Here is some example set up code.
 * <pre>
 * <code>
 *   // create the Layouter
 *   HierarchicLayouter hl = new HierarchicLayouter();
 *   // set a suitable Drawer
 *   hl.setDrawer(new SimplexDrawer());
 *   // wrap the Drawer
 *   hl.setDrawer(new NodeLabelSpaceDrawer(hl.getDrawer()));
 * </code>
 * </pre>
 */
public class NodeLabelSpaceDrawer implements Drawer
{
  private Drawer inner;
  private LayoutGraph graph;
  
  /** Creates a new instance of NodeLabelSpaceDrawer */
  public NodeLabelSpaceDrawer(Drawer inner)
  {
    this.inner = inner;
  }
  
  public void assignCoordinates(LayoutGraph g, NodeList[] layerLists, DataProvider layerID)
  {
    DataProvider oldLeft;
    DataProvider oldRight;
    DataProvider left = new NodeSpaceDataProvider(oldLeft = g.getDataProvider(Drawer.NODE_BORDER_LEFT), true);
    DataProvider right = new NodeSpaceDataProvider(oldRight = g.getDataProvider(Drawer.NODE_BORDER_RIGHT), false);
    this.graph = g;
    g.addDataProvider(Drawer.NODE_BORDER_LEFT, left);
    g.addDataProvider(Drawer.NODE_BORDER_RIGHT, right);
    try {
      inner.assignCoordinates(g, layerLists, layerID);
    } finally {
      this.graph = null;
      g.addDataProvider(Drawer.NODE_BORDER_LEFT, oldLeft);
      g.addDataProvider(Drawer.NODE_BORDER_RIGHT, oldRight);
    }
  }
  
  protected double getHaloSpace(Node node, boolean left){
    NodeLayout nl = graph.getNodeLayout(node);
    NodeLabelLayout[] labels = graph.getLabelLayout(node);
    if (labels != null && labels.length > 0){
      double max = 0.0d;
      for (int i = 0; i < labels.length; i++){
        NodeLabelLayout nll = labels[i];
        YRectangle box = nll.getBox();
        if (left){
          max = Math.max(max, nl.getX() - box.getX());
        } else {
          max = Math.max(max, box.getX() + box.getWidth() - (nl.getX() + nl.getWidth()));
        }
      }
      return max;
    } else {
      return 0.0d;
    }
  }
  
  public void setDummyMap(NodeMap dummy)
  {
    inner.setDummyMap(dummy);
  }
  
  public void setMinimalEdgeDistance(double d)
  {
    inner.setMinimalEdgeDistance(d);
  }
  
  public void setMinimalLayerDistance(double d)
  {
    inner.setMinimalLayerDistance(d);
  }
  
  public void setMinimalMultiEdgeDistance(double d)
  {
    inner.setMinimalMultiEdgeDistance(d);
  }
  
  public void setMinimalNodeDistance(double d)
  {
    inner.setMinimalNodeDistance(d);
  }

  /**
   * Wraps a node border DataProvider and assures a minimum halo around nodes.
   */
  final class NodeSpaceDataProvider implements DataProvider {
    DataProvider inner;
    boolean left;
    
    NodeSpaceDataProvider(DataProvider inner, boolean left){
      this.inner = inner;
      this.left = left;
    }
    
    public Object get(Object dataHolder)
    {
      return inner.get(dataHolder);
    }
    
    public boolean getBool(Object dataHolder)
    {
      return inner.getBool(dataHolder);
    }
    
    public double getDouble(Object dataHolder)
    {
      
      return Math.max(inner.getDouble(dataHolder), getHaloSpace((Node) dataHolder, left));
    }
    
    public int getInt(Object dataHolder)
    {
      return inner.getInt(dataHolder);
    }
  }
}
