/// Evolver Copyright 2000, Morgan McGuire
/// matrix@alum.mit.edu   http://graphics3d.com/matrix
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;

public class ELExpressionGraph extends JPanel
{
  private ELExpression expression = null;
  private int iconWidth = 32;
  private int iconHeight = 32;

  public ELExpressionGraph()
  {
    setBackground(Color.white);
    setBorder(new BevelBorder(BevelBorder.LOWERED, Color.white, Color.gray, Color.white, Color.gray));
  }

  public void setExpression(ELExpression expression) 
  {
    this.expression = expression;
    repaint();
  }

  public void paintComponent(Graphics graphics)
  {
    super.paintComponent(graphics);

    if (expression != null) {
      Rectangle bounds = getBounds();
      int maxWidth = bounds.width;
      int maxHeight = bounds.height;

      if (expression instanceof ELCall) {
        // Tree rendering algorithm

        ELCall call = (ELCall)expression;

        int totalDepth = call.depth() + 1;
        
        int verticalPadding = 20;
        int horizontalPadding = 20;

        //  verticalSpacing * (totalDepth - 1) + iconHeight * totalDepth + padding = height
        //  verticalSpacing = (height - iconHeight * totalDepth - padding) / (totalDepth - 1) 
        int verticalSpacing;
        if (totalDepth == 1) {
          verticalSpacing = maxHeight / 2;
        } else {
          verticalSpacing = (maxHeight - iconHeight - verticalPadding * 2) / (totalDepth - 1);
        }
        
        // Draw the tree
        drawSubtree(graphics, expression, 1, totalDepth, verticalSpacing,
                    maxWidth - horizontalPadding * 2 - iconWidth,
                    maxWidth / 2, 
                    iconHeight / 2 + verticalPadding);
        
      } else {
        // Draw the expression
        paintIcon(expression, graphics, maxWidth / 2 + bounds.x, maxHeight / 2 + bounds.y);
      }
    }
  }

  private void drawSubtree(Graphics graphics, ELExpression expression,
                           int depth, int totalDepth,
                           int vSpacing, int maxWidth, int x, int y)
  {
    if ((expression instanceof ELCall) && (((ELCall)expression).child.length > 0)){
      ELCall call = (ELCall) expression;

      // This call makes the algorithm O(n^2), but that isn't a
      // problem for small graphs.  For larger graphs, draw bottom up
      // and return the previous horizontal spacing to get O(n)
      // performance.
      int hSpacing = horizontalSpacing(depth + 1, totalDepth, maxWidth);

      int left = x - (hSpacing * (call.child.length - 1)) / 2;

      for(int c = 0; c < call.child.length; c++) {
        
        int xx = left + hSpacing * c;
        int yy = y + vSpacing;

        // Draw lines between nodes
        graphics.setColor(Color.gray);
        graphics.drawLine(x, y, xx, yy);

        drawSubtree(graphics, call.child[c], depth + 1, totalDepth, 
                    vSpacing, maxWidth, xx, yy);
      }
    }

    // Draw the icon
    paintIcon(expression, graphics, x, y);
  }
  
  /**
   * Returns the inter-element spacing for a row of the graph.
   *
   * @param totalDepth Depth of expression.  A single node expression has depth 1.
   * @param depth Depth of this row.
   * @param maxWidth Width of the total graph in pixels.
   */
  private int horizontalSpacing(int depth, int totalDepth, int maxWidth)
  {
    if (depth > totalDepth) {
      throw new Error("internal consistency error");
    }

    if (depth == totalDepth) {
      int elementsInRow = (int) Math.pow(2, totalDepth - 1);
      // horizontalSpacing * (elementsInRow - 1) + iconWidth * elementsInRow = maxWidth
      return maxWidth / (elementsInRow - 1);
    } else {
      // Each icon fits in the space occupied by two on the previous row
      return horizontalSpacing(depth + 1, totalDepth, maxWidth) * 2;
    }
  }

  /**
   * Pixel indent from left edge to first element.
   */
  private int indent(int depth, int totalDepth, int maxWidth)
  {
    // integer division
    return horizontalSpacing(depth, totalDepth, maxWidth) / 2;
  }

  /**
   * Renders an icon for the root of an expression.
   */
  private void paintIcon(ELExpression expression, Graphics graphics, int x, int y) 
  {
    if (expression instanceof ELCall) {
      ELCall call = (ELCall) expression;
      ELOperator operator = call.operator;

      operator.paintIcon((Graphics2D)graphics, x - iconWidth / 2, y - iconWidth / 2, iconWidth, iconHeight);

    } else if (expression instanceof ELScalar) {
      //        ELScalar scalar = (ELScalar)unparse();
      //        String string = scalar.unparse();
      //        drawCenteredString(string, graphics, x, y);
      
      drawCenteredString("#", graphics, x, y);

    } else if (expression instanceof ELVector) {
      drawCenteredString("{}", graphics, x, y);
    } else if (expression instanceof ELMorphInterpolator) {
      drawCenteredString("morph", graphics, x, y);
    }
  }

  private void drawCenteredString(String string, Graphics graphics, int x, int y)
  {
    FontMetrics fontMetrics = graphics.getFontMetrics();
    graphics.drawString(string, x - fontMetrics.stringWidth(string) / 2, y - fontMetrics.getHeight() / 2);
  }
}
