////////////////////////////////////////////////////////////////////////////
// "Tree.java" - Demonstrates various types of minimum-length
// interconnection trees (minimum spanning tree, Steiner tree, etc.)
//
// Author: Joe Ganley, ganley@cadence.com
////////////////////////////////////////////////////////////////////////////



import java.lang.*;
import java.awt.*;


public class Tree extends java.applet.Applet {

private final int	maxN = 30;	// the max number of terminals
private int		n = 10;		// the current number of terminals
private final int	r = 4;		// the radius of a terminal
private Point		p[];		// the terminals
private Point		current;	// the current one and its old location
private boolean		m[][];		// the minimum spanning tree edges
private Rectangle	border, inner;	// applet borders
private Scrollbar	sb;		// scrollbar for changing n
private Image		buffer;		// buffer for double-buffering
private Graphics	bufg;		// buffer's Graphics


public void init() {
  p = new Point[maxN];
  current = null;
  m = new boolean[maxN][maxN];

  border = new Rectangle(0, 0, size().width - 1, size().height - 1);
  inner = new Rectangle(r + 1, r + 1,
			size().width - 2 * r - 3, size().height - 2 * r - 23);

  // initialize the terminals to random locations
  for (int i = 0; i < maxN; i++) {
    p[i] = new Point((int) Math.round(Math.random()
				  * (size().width - 2 * r - 2) + r + 1),
		     (int) Math.round(Math.random()
				  * (size().height - 20 - 2 * r - 2) + r + 1));
    for (int j = 0; j < maxN; j++) {
      m[i][j] = false;
    }
  }
  mst();

  setBackground(Color.white);

  setLayout(new BorderLayout());
  sb = new Scrollbar(Scrollbar.HORIZONTAL, n, 5, 2, maxN);
  add("South", sb);

  buffer = createImage(size().width, size().height);
  bufg = buffer.getGraphics();
  bufg.setFont(getFont());
} // init()


public void update(Graphics g) {
  bufg.setColor(getBackground());
  bufg.fillRect(border.x, border.y, border.width, border.height);
  bufg.setColor(Color.black);
  bufg.drawRect(border.x, border.y, border.width, border.height);

  // first do the MST edges
  for (int i = 0; i < n; i++) {
    for (int j = (i + 1); j < n; j++) {
      if (m[i][j]) {
	bufg.setColor(Color.red);
	bufg.drawLine(p[i].x, p[i].y, p[j].x, p[j].y);
      }
    }
  }
  
  // redraw the terminals
  for (int i = 0; i < n; i++) {
    bufg.setColor(Color.green);
    bufg.fillOval(p[i].x - r, p[i].y - r, 2 * r, 2 * r);
    bufg.setColor(Color.black);
    bufg.drawOval(p[i].x - r, p[i].y - r, 2 * r, 2 * r);
  }

  // draw the current one in cyan
  if (current != null) {
    bufg.setColor(Color.cyan);
    bufg.fillOval(current.x - r, current.y - r, 2 * r, 2 * r);
    bufg.setColor(Color.black);
    bufg.drawOval(current.x - r, current.y - r, 2 * r, 2 * r);
  } 

  g.drawImage(buffer, 0, 0, null);
} // update(Graphics)


public void paint(Graphics g) {
  update(g);
} // paint(Graphics)


public boolean handleEvent(Event evt) {
  switch (evt.id) {

  case Event.MOUSE_DOWN: {
    Rectangle rect = new Rectangle();

    current = null;
    for (int i = 0; (i < n) && (current == null); i++) {
      rect.reshape(p[i].x - r, p[i].y - r, 2 * r, 2 * r);
      if (rect.inside(evt.x, evt.y)) {
	current = p[i];
      }
    }
    break;
  }

  case Event.MOUSE_UP: {
    current = null;
    repaint();
    break;
  }

  case Event.MOUSE_DRAG: {
    if (current != null) {
      if (inner.inside(evt.x, evt.y)) {
	current.move(evt.x, evt.y);
      }
      else {
	current.move(Math.max(Math.min(evt.x, inner.x + inner.width), inner.x),
		   Math.max(Math.min(evt.y, inner.y + inner.height), inner.y));
      }
      mst();
      repaint();
    }
    break;
  }

  case Event.SCROLL_LINE_UP: case Event.SCROLL_LINE_DOWN:
  case Event.SCROLL_PAGE_UP: case Event.SCROLL_PAGE_DOWN:
  case Event.SCROLL_ABSOLUTE: {
    n = sb.getValue();
    mst();
    repaint();
    break;
  }

  default: {
    break;
  }

  } // switch

  return(true);
} // handleEvent(Event)


// Euclidean distance between two points (x1,y1) and (x2,y2)
private int distance(int x1, int y1, int x2, int y2) {
  return((int) Math.round(Math.sqrt(
		     (double) (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))));
} // distance(int,int,int,int)


// mst - compute a minimum spanning tree using Prim's algorithm with "dumb"
// heaps.  This is the fastest graph algorithm for complete graphs, though
// we could do better geometrically - but with 10 terminals, why bother?
private void mst() {
  int dist[], neigh[], closest, minDist, d;

  dist = new int[n];
  neigh = new int[n];

  // initialize data structures
  for (int i = 0; i < n; i++) {
    dist[i] = distance(p[0].x, p[0].y, p[i].x, p[i].y);
    neigh[i] = 0;
    for (int j = 0; j < n; j++) {
      m[i][j] = false;
    }
  }

  // find terminal closest to current partial tree
  for (int i = 1; i < n; i++) {
    closest = -1;
    minDist = Integer.MAX_VALUE;
    for (int j = 1; j < n; j++) {
      if ((dist[j] != 0) && (dist[j] < minDist)) {
	closest = j;
	minDist = dist[j];
      }
    }
    
    // set an edge from it to its nearest neighbor
    m[neigh[closest]][closest] = true;
    m[closest][neigh[closest]] = true;

    // update nearest distances to current partial tree
    for (int j = 1; j < n; j++) {
      d = distance(p[j].x, p[j].y, p[closest].x, p[closest].y);
      if (d < dist[j]) {
	dist[j] = d;
	neigh[j] = closest;
      }
    }
  }
} // mst()

} // class Tree extends java.applet.Applet
