
import java.io.*;
import java.awt.*;
import java.applet.*;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Enumeration;

/** This program implement's the "Left Edge Algorithm" for channel
    routing.  It includes vertical constraints but cannot deal with cyclic
    vertical constraints.

*/

public class ChannelRouterApplet extends Applet {

  private TextField[] upperFields, lowerFields;    
  private Netlist n;
  ChannelRouterCanvas cv;
  
  public void init() {
    System.out.println("starting applet!!!");
    n = new Netlist();
    setLayout(new BorderLayout());
    Panel up = new Panel();
    Panel lp = new Panel();
    Panel gp = new Panel();
    Panel bp = new Panel();
    cv = new ChannelRouterCanvas(n);
    setLayout(new BorderLayout());
    up.setLayout(new GridLayout(1, Netlist.NUM_COLS));
    lp.setLayout(new GridLayout(1,Netlist.NUM_COLS));
    gp.setLayout(new BorderLayout());
    bp.setLayout(new FlowLayout());
    Button cb = new Button("Clear Nets");
    Button rb = new Button("Route Nets");

    upperFields = new TextField[Netlist.NUM_COLS];
    lowerFields = new TextField[Netlist.NUM_COLS];
    for (int i = 0; i < Netlist.NUM_COLS; i++) {
      TextField tf;
      upperFields[i] = tf = new TextField("", 2);
      up.add(tf);
      lowerFields[i] = tf = new TextField("", 2);
      lp.add("Center", tf);
    }
    gp.add("North", up);
    gp.add("South", lp);
    gp.add("Center", cv);
    bp.add(cb);
    bp.add(rb);
    add("South", bp);
    add("Center", gp);

  }
  
  /** Clear all net fields */
  private void clearNetFields() {
    for (int i = 0; i < Netlist.NUM_COLS; i++) {
      upperFields[i].setText("");
      lowerFields[i].setText("");
    }
  }
  
  /** Scan the terminal fields for each column and build up the netlist data structure */
  private void scanFieldsAndRoute() {
    n.clear();
    for (int i = 0; i < Netlist.NUM_COLS; i++) {
      String uname = upperFields[i].getText().trim();
      String lname = lowerFields[i].getText().trim();
      Terminal term;
      Net nt;
      // process upper terminal of column i
      if (!uname.equals("")) {
        nt = n.findNet(uname);
        if (nt == null) {
          nt = new Net(uname);
          n.addNet(nt);
          }
        term = n.getTerminal(i, Terminal.TOP);
        nt.addTerminal(term);
      }
      // now process lower terminal of column i
      if (!lname.equals("")) {
        nt = n.findNet(lname);
        if (nt == null) {
          nt = new Net(lname);
          n.addNet(nt);
        }
        term = n.getTerminal(i, Terminal.BOTTOM);
        nt.addTerminal(term);
      }
    }
    // n.write(System.out);
    n.leftEdgeAlgorithm();
    // n.write(System.out);
  }
  
  public boolean handleEvent(Event evt) {
    if (evt.id == Event.WINDOW_DESTROY) System.exit(0);
    return super.handleEvent(evt);
  }
  
  public boolean action(Event evt, Object arg) {
    if (arg.equals("Route Nets")) {
      scanFieldsAndRoute();
      cv.repaint();
    } else if (arg.equals("Clear Nets")) {
      clearNetFields();
      scanFieldsAndRoute();  // to clear the old nets away
      cv.repaint();
    } else return super.action(evt, arg);
    return true;
  }
  
}

class ChannelRouterCanvas extends Canvas {

  private Netlist nl;
  
  public ChannelRouterCanvas(Netlist n) { nl = n; }

  private static final int BORDER_Y_OFFSET = 10;
  private static final int HEIGHT_ADJUST = 10;
  private static final int WIDTH_ADJUST = 1;
  
  private int colWidth;
  private int trackHeight;
  
  private void setScale() {
    Dimension d = size();
    colWidth = (d.width) / Netlist.NUM_COLS;
    trackHeight = (d.height - HEIGHT_ADJUST - BORDER_Y_OFFSET) / (nl.getMaxTrack() + 1);
  }

  /** @return the Y coordinate for track number. */
  public int trackY(int track) {
    return BORDER_Y_OFFSET + trackHeight * track;
  }
  /** X coordinate of column */
  public int colX(int col) {
    return colWidth/2 + colWidth * col;
  }
  
  public int channelWidth() {
    return (colWidth * Netlist.NUM_COLS - 1);
  }
  
  public int channelHeight() {
    return (trackY(nl.getMaxTrack() +1) - trackY(0) + 1);
  }
  

  private Font f;
  private FontMetrics fm;
  private boolean fontsSet = false;
  
  private void setFonts(Graphics g) {
    if (fontsSet) return;
    f = new Font("Helvetica", Font.PLAIN, 12);
    fm = g.getFontMetrics(f);
    g.setFont(f);
    fontsSet = true;
  }

  public void paint(Graphics g) {
    setFonts(g);
    setScale();  
    g.drawRect(0, BORDER_Y_OFFSET, channelWidth(), channelHeight());  
    if (nl.getCyclesPresent()) {
      g.drawString("This circuit contains unresolved vertical constraint cycles", 
        20, trackY(1) - 5);
    }
    for (Enumeration e = nl.getNets() ; e.hasMoreElements() ;) { 
      Net n = (Net)e.nextElement(); 
      if (n.getTrack() != Net.TRACK_UNASSIGNED) {
        g.setColor(Color.blue);
        g.fillRect(colX(n.getLeftEdge()), trackY(n.getTrack())-2,
          colX(n.getRightEdge()) - colX(n.getLeftEdge())+2, 5);
        // int xnoff = fm.stringWidth(n.getName()) / 2;
        for (Enumeration eterms = n.getTerminals(); eterms.hasMoreElements() ; ) {
          Terminal t = (Terminal)eterms.nextElement();
          g.setColor(Color.red);
          if (t.getTopOrBottom()) {
            g.fillRect(colX(t.getColumn())-2, trackY(0),
              5, trackY(n.getTrack()) - trackY(0) + 3);               
            //g.drawString(n.getName(), colX(t.getColumn()) - xnoff, trackY(0) - 2);
          } else {
            g.fillRect(colX(t.getColumn()) - 2, trackY(n.getTrack())-2,
              5, trackY(nl.getMaxTrack()+1) - trackY(n.getTrack())+4);
            //g.drawString(n.getName(), colX(t.getColumn()) - xnoff, trackY(nl.getMaxTrack()+1) + fm.getAscent());
          }
          g.setColor(Color.black);
          g.fillRect(colX(t.getColumn())-3, trackY(n.getTrack())-3, 7, 7);
        }
      }
    }
  }

}
  
class Netlist
{
  private Terminal[] upperTerms;
  private Terminal[] lowerTerms;
  private Vector nlist;
  private int maxTrack = Net.TRACK_UNASSIGNED;
  private boolean cyclesPresent = false;
    
  public static final int NUM_COLS = 10;
  
  public Netlist() {
    nlist = new Vector();
    upperTerms = new Terminal[NUM_COLS];
    lowerTerms = new Terminal[NUM_COLS];
    for (int i = 0; i < NUM_COLS; i++) {
      upperTerms[i] = new Terminal(i, Terminal.TOP);
      lowerTerms[i] = new Terminal(i, Terminal.BOTTOM);
    }
  }  
  
  public boolean getCyclesPresent() { return cyclesPresent; }
  
  /** return terminal object for the given position */
  public Terminal getTerminal(int pos, boolean topOrBottom) {
    if (topOrBottom == Terminal.TOP) return upperTerms[pos];
    else return lowerTerms[pos];
  }

  /** insert net into netList sorted by left edge */  
  public void addNet(Net n) {
    int i;
    for (i = 0; i < nlist.size(); i++) {
      if (((Net)nlist.elementAt(i)).getLeftEdge() > n.getLeftEdge()) {
        nlist.insertElementAt(n, i);
        return;
      }
    }
    nlist.addElement(n);
  }
  
  void unmarkNets() {
    for (Enumeration e = getNets() ; e.hasMoreElements() ; )  
      ((Net)e.nextElement()).setMark(false);
  }
  
  /** make vertical constraints and check for cycles - if they occur, just remove one constraint
       even though it's wrong. */ 
  public void makeConstraints() {
    for (int i = 0; i < NUM_COLS; i++) {
      Net unet = upperTerms[i].getNet();
      Net lnet = lowerTerms[i].getNet();
      if (unet != null && lnet != null && unet != lnet) {
        //System.out.println("Adding constraint " + unet.getName() 
        //  + " above " + lnet.getName());
        lnet.addConstraint(unet);
     }
   }
   // Search for cycles in constraints - just delete a constraint when one is found (for now)
   cyclesPresent = false;
   for (int i = 0; i < NUM_COLS; i++) {
     unmarkNets();
     Net lnet = lowerTerms[i].getNet();
     if (lnet != null && !lnet.checkConstraint()) cyclesPresent = true;
    }
  }

        
  
  public void clear() { 
    nlist.removeAllElements();
    for (int i = 0; i < NUM_COLS; i++) {
      upperTerms[i].setNet(null);
      lowerTerms[i].setNet(null);
    }
  }
  
  public Enumeration getNets() { return nlist.elements(); }

  public Net findNet(String findName) {
    for (int i = 0; i < nlist.size(); i++) {
      Net nt = (Net)nlist.elementAt(i);
      if (nt.getName().equals(findName)) return nt;
    }
    return null;
  }
  
  public void write(PrintStream os) {
    os.println("Writing netlist");
    for (Enumeration e = nlist.elements() ; e.hasMoreElements() ;) {  
      ((Net)e.nextElement()).writeNet(os);
   } 
  }
  
  /** determine whether we can assign net testNet to track trk by scanning nets */
  private boolean canRoute(Net testNet, int trk) {
    for (Enumeration ne = nlist.elements(); ne.hasMoreElements() ; ) {
      Net n = (Net)ne.nextElement();
      if (n.getTrack() == trk && testNet.overlap(n)) return false;
    }
    return true;
  }

  /** assign nets to tracks in channel using the Left Edge Algorithm */  
  public void leftEdgeAlgorithm() {
    makeConstraints();
    int track = 0;
    boolean done = false;
    while (!done) {
      track++;
      done = true;
      // place unrouted nets in current track if they fit
      for (Enumeration e = nlist.elements(); e.hasMoreElements() ; ) {
        Net n = (Net)e.nextElement();
        if (n.isRouted()) continue;  // skip nets already done
        if (n.testConstraints() && canRoute(n, track)) {
          //System.out.println("Assigning net " + n.getName() + 
          //    "(" + n.getLeftEdge() + ":" + n.getRightEdge() + 
          //    ") to track" + track);
          n.setTrack(track);
        }
        else done = false;  // at least one net (this one) remains unrouted!         
      }
    }
    maxTrack = track;
  }

  public int getMaxTrack() { return maxTrack; }

}

class Net {

  /** Symbolic constant for unrouted net */
  public static final int TRACK_UNASSIGNED = 0;

  private String name;
  private Vector terms = new Vector(5);
  private Vector constraints = new Vector(2);  // contains vertical constraints (if any)  
  private int leftEdge = Integer.MAX_VALUE;
  private int rightEdge = 0;
  private int track = TRACK_UNASSIGNED;
  private boolean mark = false;
  
  /** Create a new net from a string containing the net name
     */
  public Net(String n) { ;
      name = n;
  }
  
  /** @return true if net assigned to a track */
  public boolean isRouted() {
    return (getTrack() != Net.TRACK_UNASSIGNED);
  }
  
  /** Get the assigned track of this net
     * @return 	the assigned track of this net
     */
  public int getTrack() { return track; }

  /** Assign this net to a track
      * @param t	The track this net will be assigned to.
      */
  public void setTrack(int t) { track = t; }
  
  /** get the left edge of this net
     * @return the leftmost column of this net
     */
  public int getLeftEdge() { return leftEdge; }
  
 /** get the right edge of this net
     * @return the rightmost column of this net
     */
  public int getRightEdge() { return rightEdge; }
  
  public boolean overlap(Net n2) {
    int l1 = getLeftEdge();
    int r1 = getRightEdge();
    int l2 = n2.getLeftEdge();
    int r2 = n2.getRightEdge();
    return (l2 <= r1 && r2 >= l1) || (l1 <= r2 && r1 >= l2);
  }
  
  /** write out the net */
  public void writeNet(PrintStream os) {
    os.print(name + " ");
    if (getTrack() != TRACK_UNASSIGNED) os.print(track + " ");
    for (Enumeration e = terms.elements(); e.hasMoreElements() ; )
      ((Terminal)e.nextElement()).writeTerminal(os);
    os.println();
  }
  
  /** @return the name of this net
     */
  public String getName() { return name; }
  
  /** Add a terminal to this net
      * @param trm	The terminal to be added to this net.
      */
  public void addTerminal(Terminal trm){
    terms.addElement(trm);
    trm.setNet(this);
    if (trm.getColumn() < leftEdge) leftEdge = trm.getColumn();
    if (trm.getColumn() > rightEdge) rightEdge = trm.getColumn();
  }
  
  /** @return an enumeration containing the terminals of this net */
  public Enumeration getTerminals() {
    return terms.elements();
  }
  
  public void addConstraint(Net nt) {
    if (!constraints.contains(nt))
      constraints.addElement(nt);
  }
    
  public boolean checkConstraint() {
    if (constraints == null) return true;
    boolean result = true;
    setMark(true);
    for ( int i = 0; i < constraints.size(); i++ ) {
      Net ctgt = (Net)constraints.elementAt(i);
      if (ctgt.getMark()) {
        //System.out.println("Constraint cycle found from Net " + getName() + //debug
        //    " to " + ctgt.getName() + " ... removing");
        constraints.removeElementAt(i);
        result = false;
      } else {
        if (!ctgt.checkConstraint()) result = false;
      }
    }
    setMark(false);
    return result;
  }
  
  public Enumeration getConstraints() {
    return constraints.elements();
  }
  
  /** Test vertical constraints on this net;  @return true if all nets in constraint are already routed */
  public boolean testConstraints() {
    for (Enumeration e = getConstraints(); e.hasMoreElements(); ) {
      Net testnet = (Net)e.nextElement();
      if (!testnet.isRouted()) return false;
    }
    return true;
  }
  
  public void setMark(boolean m) { mark = m; }
  
  public boolean getMark() { return mark; }

}


class Terminal {

  private int column;
  private boolean topOrBottom;
  private Net owner;
  
  /** Symbolic constant for terminals at top of channel */
  public static final boolean TOP = true;
  /** Symbolic constants for terminals at bottom of channel */
  public static final boolean BOTTOM = false;

  public Terminal(int col, boolean tOrB) {
    column = col;
    topOrBottom = tOrB;
  }
  
  public Terminal(String spec) throws IOException {
    switch (spec.charAt(spec.length() - 1)) {
      case 'u':
      case 'U': topOrBottom = Terminal.TOP;
                 break;
      case 'l':
      case 'L': topOrBottom = Terminal.BOTTOM;
                 break;
      default:  throw new IOException();
    }
    String s = spec.substring(0, spec.length()-1);
    column = Integer.parseInt(spec.substring(0, spec.length() - 1));
  }
  
  public int getColumn() { return column; }
  
  public boolean getTopOrBottom() { return topOrBottom; }
  
  public Net getNet() { return owner; }
  
  public void setNet(Net n) { owner = n; }
  
  public void writeTerminal(PrintStream os) {
    os.print(column);
    if (topOrBottom == Terminal.TOP) os.print("U ");
    else os.print("L ");
  }


};