// Applet Turing Machine Simulator
// Suzanne Skinner
// 1996, All Rights Reserved
// Modification by Paul Rendell 2009

import java.awt.*;

class Transition {
  int  currentState;
  char currentChar;
  int  newState;
  char newChar;
  int  direction;
  int count;

  public Transition(int cs, char cc, int ns, char nc, int dir) {
    currentState = cs;
    currentChar = cc;
    newState = ns;
    newChar = nc;
    direction = dir;
    count = 0;
  }

  public String TransFreq(){
	  return " "+currentState+"/"+currentChar+"\t "+count;
  }
}

class History {
	int oldState;
	int oldPos;
	char oldChar;

	public History (int os, int op, char oc){
		oldState = os;
		oldPos = op;
		oldChar = oc;
	}
}

class PresetTM {
  public static final String[] names =
    { "<New>",
      "Example Progam doubler",
      "Two symbol doubler",
      "Optimized two symbol doubler",
      "Rogozhin's 6/4 UTM",
      "Rendell's 8/13 direct UTM",
      "Rendell's 8/16 direct UTM",
      "Subtracter",
      "Palindrome Detector",
      "5-State B.B. (501)",
      "5-State B.B. (1915)",
      "5-State B.B. (4098)",
      "6-State Busy Beaver",
    };
  int leadingBlanks;
  int initHeadPos;
  String initChars;
  String programming;
  String comment;

  public PresetTM(String desc) {
	initHeadPos = 0;
    if (desc.indexOf("Busy Beaver") > -1 || desc.indexOf("B.B.") > -1) {
      leadingBlanks = TM.TAPESIZE/2;
	  initHeadPos = TM.TAPESIZE/2;
      initChars = "";
      comment = "";
      if (desc.indexOf("501") > -1) {
        programming = "1,_  2,1,>\n1,1  3,_,<\n" +
                      "2,_  3,1,>\n2,1  4,1,>\n" +
                      "3,_  1,1,<\n3,1  2,_,>\n" +
                      "4,_  5,_,>\n4,1  H,1,>\n" +
                      "5,_  3,1,<\n5,1  1,1,>";
      } else if (desc.indexOf("1915") > -1) {
        programming = "1,_  2,1,>\n1,1  3,1,<\n" +
                      "2,_  1,_,<\n2,1  4,_,<\n" +
                      "3,_  1,1,<\n3,1  H,1,<\n" +
                      "4,_  2,1,<\n4,1  5,1,>\n" +
                      "5,_  4,_,>\n5,1  2,_,>";
      } else if (desc.indexOf("4098") > -1) {
        programming = "1,_  2,1,<\n1,1  3,1,>\n" +
                      "2,_  3,1,<\n2,1  2,1,<\n" +
                      "3,_  4,1,<\n3,1  5,_,>\n" +
                      "4,_  1,1,>\n4,1  4,1,>\n" +
                      "5,_  H,1,<\n5,1  1,_,>";
      } else if (desc.indexOf("6-State") > -1) {
        programming = "1,_  2,1,<\n1,1  1,1,<\n" +
                      "2,_  3,1,>\n2,1  2,1,>\n" +
                      "3,_  6,_,>\n3,1  4,1,>\n" +
                      "4,_  1,1,<\n4,1  5,_,>\n" +
                      "5,_  1,_,<\n5,1  3,1,>\n" +
                      "6,_  5,1,<\n6,1  H,1,<";
      } else
        leadingBlanks = -1;
    } else if (desc.equals("Example Progam doubler")) {
      leadingBlanks = 4;
	  initHeadPos = 4;
      initChars = "111";
      comment = "Example program in Conway's Game Life Turing Machine";
      programming = "1,_,3,_,<\n1,1,2,2,<\n1,2,1,2,>\n2,_,1,2,>\n" +
					"2,2,2,2,<\n3,_,H,_,>\n3,2,3,1,<";
    } else if (desc.equals("Two symbol doubler")) {
      leadingBlanks = 7;
	  initHeadPos = 7;
      initChars = "1_1_1";
      comment = "Two symbol string doubler\n variant of the example program in Conway's Game Life Turing Machine";

      programming = "1, 1, 2, 1, >\n1, _, 8, _, <\n2, _, 3, 1, <\n" +
					"2, 1, 1, 1, >\n3, 1, 4, 1, <\n4, _, 6, 1, <\n" +
					"4, 1, 5, 1, <\n5, 1, 4, 1, <\n6, _, 7, 1, >\n" +
					"6, 1, 4, 1, <\n7, 1, 1, 1, >\n8, _, H, _, >\n" +
					"8, 1, 9, _, <\n9, 1, 8, 1, <";
    } else if (desc.equals("Optimized two symbol doubler")) {
      leadingBlanks = 7;
	  initHeadPos = 7;
      initChars = "1_1_1";
      comment = "Variant of the two symbol string doubler\nwith less states.\nA variant of the example program in Conway's Game Life Turing Machine";

      programming = "1, 1, 2, 1, >\n1, _, 5, _, <\n2, _, 3, 1, <\n" +
					"2, 1, 1, 1, >\n3, _, 2, 1, >\n3, 1, 4, 1, <\n" +
					"4, _, 3, 1, <\n4, 1, 3, 1, <\n5, _, H, _, >\n" +
					"5, 1, 6, _, <\n6, 1, 5, 1, <";

    } else if (desc.equals("Rogozhin's 6/4 UTM")) {
      leadingBlanks = 2;
	  initHeadPos = 106;
	  comment = "Rogozhin's 4 state 6 symbol Universal Turing Machine\n" +
	            "The initial tape given codes AABBH A-1, B-9, D-17. H-21\n" +
	            "with 2-tag productions to convert this to ABBA";
      programming = "1,1,1,L,<\n1,b,1,R,>\n1,R,1,b,<\n1,L,1,_,>\n1,_,1,L,<\n" +
					"1,C,4,_,>\n2,1,2,_,>\n2,b,3,R,<\n2,R,2,L,>\n2,L,2,R,<\n" +
					"2,_,2,1,<\n2,C,2,b,>\n3,1,3,1,>\n3,b,4,L,>\n3,R,3,b,>\n" +
					"3,_,1,C,>\n3,L,H,L,>\n3,C,1,1,>\n4,1,4,_,>\n4,b,2,C,<\n" +
					"4,R,4,L,>\n4,_,2,C,<\n4,C,4,b,>";
      initChars =   "Lbb1bbbb1b1bb1:9;bb1:17;bb1:8;b1b1:9;bb1bb1:17;bb1:16;b1C1C1:9;C1:9;C1:21;";

    } else if (desc.equals("Rendell's 8/13 direct UTM")) {
      leadingBlanks = 0;

	  initHeadPos = 24;
      programming = "01, 0, 02, A, >\n01, 1, 03, B, >\n01, A, 01, A, >\n01, B, 01, B, >\n01, D, 01, D, >\n01, X, 01, X, >\n" +
		 			"02, B, 11, M, >\n02, A,  H, A, <\n02, D, 01, D, >\n02, X, 01, X, >\n02, 0, 04, A, <\n02, 1, 05, B, <\n" +
					"03, 0, 06, A, <\n03, 1, 07, B, <\n03, A, 03, A, <\n03, B, 03, B, <\n03, D, 03, D, <\n03, M, 03, 1, <\n03, X, 12, M, >\n" +
					"04, 0, 08, A, <\n04, 1, 08, A, <\n04, A, 04, A, <\n04, B, 04, B, <\n04, D, 04, D, <\n04, X, 04, X, <\n04, M, 02, B, >\n" +
					"05, 0, 08, 0, >\n05, 1, 08, 0, >\n05, A, 05, A, <\n05, B, 05, B, <\n05, D, 05, D, <\n05, X, 05, X, <\n" +
					"06, 0, 08, B, <\n06, 1, 08, B, <\n06, A, 06, A, <\n06, B, 06, B, <\n06, D, 06, D, <\n06, X, 06, X, <\n" +
					"07, 0, 08, 1, >\n07, 1, 08, 1, >\n07, A, 07, A, <\n07, B, 07, B, <\n07, D, 07, D, <\n07, X, 07, X, <\n" +
					"08, 0, 10, 0, >\n08, 1, 09, 1, >\n08, A, 10, 0, >\n08, B, 09, 1, >\n" +
					"09, 0, 09, A, >\n09, 1, 09, B, >\n09, A, 09, A, >\n09, B, 09, B, >\n09, C, 10, D, >\n09, D, 09, D, >\n09, X, 09, X, >\n" +
					"10, 0, 03, A, <\n10, 1, 11, M, >\n10, A, 10, A, >\n10, B, 10, B, >\n10, D, 10, D, >\n10, X, 10, X, >\n10, M, 13, M, <\n10, C, 13, C, <\n" +
					"11, 0, 11, A, >\n11, 1, 11, B, >\n11, A, 11, A, >\n11, B, 11, B, >\n11, X, 11, X, >\n11, C, 11, D, >\n11, D, 11, D, >\n11, M, 04, X, <\n" +
					"12, 0, 03, A, <\n12, 1, 12, M, >\n12, A, 12, A, >\n12, B, 12, B, >\n12, D, 12, D, >\n12, C, 13, C, <\n12, M, 13, M, <\n" +
					"13, A, 13, 0, <\n13, B, 13, 1, <\n13, D, 13, C, <\n13, M, 13, M, <\n13, X, 01, X, <";

      initChars =  "0000000001ABABAAXBABDBBX110C11M1000C00M1111C00M1010C11M0010C1M0010C00M";

    } else if (desc.equals("Rendell's 8/16 direct UTM")) {
      leadingBlanks = 2;
	  initHeadPos = 19;
      programming = "1,0,1,0,>\n1,1,1,1,>\n1,X,1,X,>\n1,M,1,M,>\n1,A,7,A,>\n" +
					"1,B,7,B,>\n1,C,2,0,>\n1,D,3,1,>\n2,0,6,A,<\n2,1,4,B,>\n" +
					"2,X,2,X,>\n2,M,H,M,>\n2,A,2,A,>\n2,B,2,B,>\n2,C,2,C,>\n" +
					"2,D,2,D,>\n3,0,4,A,>\n3,1,6,B,<\n3,X,3,X,>\n3,M,H,M,>\n" +
					"3,A,3,A,>\n3,B,3,B,>\n3,C,3,C,>\n3,D,3,D,>\n4,0,4,A,>\n" +
					"4,1,4,B,>\n4,X,5,X,<\n5,0,5,C,<\n5,1,5,D,<\n5,X,5,X,<\n" +
					"5,M,1,M,>\n5,A,5,A,<\n5,B,5,B,<\n5,C,5,C,<\n5,D,5,D,<\n" +
					"6,0,1,0,>\n6,1,1,1,>\n6,X,6,X,<\n6,A,6,A,<\n6,B,6,B,<\n" +
					"6,C,6,C,<\n6,D,6,D,<\n7,0,8,A,<\n7,1,9,B,<\n7,X,7,X,>\n" +
					"7,A,7,A,>\n7,B,7,B,>\n7,C,7,C,>\n7,D,7,D,>\n8,0,7,C,>\n" +
					"8,1,7,C,>\n8,X,8,X,<\n8,M,11,M,>\n8,A,8,A,<\n8,B,8,B,<\n" +
					"8,C,8,C,<\n8,D,8,D,<\n9,0,7,D,>\n9,1,7,D,>\n9,X,9,X,<\n" +
					"9,M,10,M,<\n9,A,9,A,<\n9,B,9,B,<\n9,C,9,C,<\n9,D,9,D,<\n" +
					"10,0,11,1,>\n10,X,10,X,<\n10,M,10,M,<\n10,A,10,A,<\n10,B,10,B,<\n" +
					"10,C,10,C,<\n10,D,10,D,<\n11,0,12,0,<\n11,1,14,1,<\n11,X,11,X,>\n" +
					"11,M,11,M,>\n11,A,11,A,>\n11,B,11,B,>\n11,C,11,C,>\n11,D,11,D,>\n" +
					"12,0,13,X,<\n12,1,13,M,<\n12,X,12,X,<\n12,M,12,M,<\n12,A,12,0,<\n" +
					"12,B,12,1,<\n12,C,12,C,<\n12,D,12,D,<\n13,0,1,0,>\n13,1,16,0,>\n" +
					"14,0,15,0,>\n14,1,15,1,>\n14,X,14,X,<\n14,M,14,M,<\n14,A,14,0,<\n" +
					"14,B,14,1,<\n14,C,14,C,<\n14,D,14,D,<\n15,X,1,0,>\n15,M,16,0,>\n" +
					"16,X,16,X,>\n16,M,16,M,>\n16,C,3,1,>";

      initChars =  "00000000XMXMXXXXMDCCD1001010011X1010100011X1011001010X1100110010X" +
                   "1101011000X1110101010X0010110010X0011010011X0100110010X0001101000" +
                   "X0101000001XM";
    } else if (desc.equals("Subtracter")) {
      leadingBlanks = 0;
      initChars = "111111-1111=";
      comment = "Subtracts numbers in unary.\nInput format: '<num>-<num>='";
      programming = "1,_  1,_,>\n1,1  1,1,>\n1,-  1,-,>\n1,=  2,_,<\n" +
                    "2,1  3,=,<\n2,-  H,_,<\n" +
                    "3,1  3,1,<\n3,-  4,-,<\n" +
                    "4,_  4,_,<\n4,1  1,_,>";
    } else if (desc.equals("Palindrome Detector")) {
      leadingBlanks = 0;
      initChars = " BABBBAABBBAB";
      comment = "Determines whether a given string of\n"+
                "A's and B's is a palindrome.\n"+
                "Leave one blank space before the string.";
      programming = "1,_ 2,#,>\n" +
                    "2,A 3,_,>\n2,B 4,_,>\n2,_ 7,_,<\n" +
                    "3,A 3,A,>\n3,B 3,B,>\n3,_ 5,_,<\n" +
		    "4,A 4,A,>\n4,B 4,B,>\n4,_ 6,_,<\n" +
		    "5,A 11,_,<\n5,B 12,_,<\n5,_ 7,_,<\n" +
		    "6,A 12,_,<\n6,B 11,_,<\n6,_ 7,_,<\n" +
		    "7,_ 7,_,<\n7,# 8,_,>\n" +
		    "8,_ 9,Y,>\n" +
		    "9,_ 10,E,>\n" +
		    "10,_ H,S,>\n" +
		    "11,A 11,A,<\n11,B 11,B,<\n11,_ 2,_,>\n" +
		    "12,A 12,_,<\n12,B 12,_,<\n12,_ 12,_,<\n12,# 13,_,>\n" +
		    "13,_ 14,N,>\n" +
		    "14,_ H,O,>\n";
    } else if (desc.equals("<New>")) {
      leadingBlanks = 0;
      initChars = "";
      programming = "";
    } else
      leadingBlanks = -1;
  }
}

class miscUtil {

  public static boolean isLetterOrDigit(char ch) {     // for Java 1.0
    String list = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
                  "0123456789";
    return (list.indexOf(ch) > -1);
  }

  public static String[] split(String text, String splitChars) {
  // Perl-ish split function
  // Splits text into an array of Strings, with the split points at any
  // characters within splitChars (these are not included in the new strings).
  // Discards any empty entries where two splitters occur together or one
  // occurs at the beginning or end of the string.

    int pos = -1;
    int totalSplitters = 0;
    while (++pos < text.length())
      if (splitChars.indexOf(text.charAt(pos)) > -1)
        totalSplitters++;
    int resultSize = totalSplitters+1;
    String[] result = new String[resultSize];
    pos = -1;
    int resultPos = 0;
    int subStart = 0;
    while (++pos < text.length()) {
      if (splitChars.indexOf(text.charAt(pos)) > -1) {
        if (pos > subStart)
          result[resultPos++] = text.substring(subStart, pos);
        subStart = pos+1;
      }
    }
    if (pos > subStart)   // if the last character is not a splitter
      result[resultPos++] = text.substring(subStart, pos);
    if (resultPos < resultSize) {
      // if the array was over-allocated (there were padding separators),
      // trim it down
      resultSize = resultPos;
      String[] paredResult = new String[resultSize];
      for (int i=0; i < resultSize; i++)
        paredResult[i] = result[i];
      return paredResult;
    }
    else
      return result;
  }
}

class TM implements Runnable {

   // transition/program results
  public static final int SUCCESS = 0, HALTED = -1, NOTFOUND = -2,
                          ABNORMAL = -3, NOPROG = -4, USERINT = -5;
  // speeds
  public static final int SLOW = 0, FAST = 1, VFAST = 2, COMPUTE = 3;
  // special states
  public static final int HALT = -1, RUNNING = -2, NA = -3;
  // special chars
  public static final char NUL = 0;
  // directions
  public static final int LEFT = 0, RIGHT = 1, STAY = 2;
  // defaults
  public static final int TAPESIZE = 30000;

  TMapplet app;
  Component display;
  boolean programmed = false;
  boolean compfrequ = false;
  int speed = SLOW;
  Transition[] transTable;
  int tableSize;
  History[] histTable;
  int historySize;
  int historyPos;

  char[] tape;
  int state;
  int tapePos = 0;
  int leadingBlanks;
  String initChars;
  String comment;

  int initNonBlanks = 0, nonBlanks;
  int moving = STAY;
  long totalTransitions;


  public TM(TMapplet daddy, Component disp) {
    tape = new char[TAPESIZE];
    app = daddy;
    display = disp;
    setState(NA, 0);
    comment = new String("");


    historySize=500;
    histTable = new History [historySize];
    historyPos=1;
    for (int i=0; i < historySize; i++) {
      histTable[i] = new History(0,-1,' ');
    }

  }

  public boolean validTapeChar(char ch) {
    return (miscUtil.isLetterOrDigit(ch) || " +/*-!@#$%^&()=,.".indexOf(ch) > -1);
  }

  public void setSpeed(String newSpeed) {
    if (newSpeed.equals("Slow"))
      speed = SLOW;
    else if (newSpeed.equals("Fast"))
      speed = FAST;
    else if (newSpeed.equals("Very Fast"))
      speed = VFAST;
    else {
      speed = COMPUTE;
      compfrequ = newSpeed.equals("Comp Freq");
    }
  }

  public void setState(int newState, long steps) {
    state = newState;
    if (state == HALT)
      app.tapePanel.currentState.setText("State: H");
    else if (state == RUNNING)
      app.tapePanel.currentState.setText("State: R");
    else if (state == NA)
      app.tapePanel.currentState.setText("State: N/A");
    else
      app.tapePanel.currentState.setText("State: " + state);
    app.tapePanel.countTransaction.setText(Long.toString(steps));
  }

  public String tapeString(){
	  String tapecontents;
	  char lastC;
	  long lastCount;
	  tapecontents = "";
	  lastC=' ';
	  lastCount = 0;
	  for (int i=0; i <TAPESIZE; i++){
		if (tape[i] == lastC){
		  lastCount += 1;
		} else {
		  if ((lastC != ' ') || (tapecontents != "")){
			if(lastC == ' ') lastC = '_';
			if (lastCount < 5 ){
			  for ( int j = 0; j < lastCount;j++) tapecontents += lastC;
			} else tapecontents += lastC+":"+Long.toString(lastCount)+';';
		  }
		lastCount = 1;
		lastC = tape[i];
	    }
	  }
	  if ((lastC != ' ') && (lastCount > 0)){
		if(lastC == ' ') lastC = '_';
    	if (lastCount < 5 ){
		  for ( int j = 0; j < lastCount;j++) tapecontents += lastC;
		} else tapecontents += lastC+":"+Long.toString(lastCount)+';';
	  }
	  return tapecontents;

  }// tape String

  public boolean initMachine(int leadingBlanks, int initHeadPos,  String initChars, StringBuffer errorMsg) {
    char c;
    this.leadingBlanks = leadingBlanks;
    this.initChars = initChars;
    int numChars = initChars.length();
    int nextPos = 0;
    int rleCount=0;
    boolean rle = false;
    for (int i=0; i < TAPESIZE; i++)
      tape[i] = ' ';
    tapePos = initHeadPos;
    nextPos = leadingBlanks;
    if (leadingBlanks + numChars >= TAPESIZE)
      numChars = TAPESIZE - leadingBlanks - 1;
    initNonBlanks = 0;
    for (int i=0; i < numChars; i++) {
      c = initChars.charAt(i);
      if (c == '_')
        c = ' ';
      if (!validTapeChar(c)) {
		if ((c == ':')&& !rle){
		  // rle multiplier follows
		  rle = true;
	    } else if ( (c == ';') && rle ){
		  // rle finished
		  c = tape[nextPos-1];
		  for (int k = 0; k < rleCount-1; k++) {
		    tape[nextPos++] = c;
            if (c != ' ')
              initNonBlanks++;
		  }
		  rle = false;
		  rleCount = 0;
	    } else {
          errorMsg.append("Invalid tape character '" + c + "'");
          return false;
	    }
      } else if (rle && ("0123456789".indexOf(c) > -1)){
		  rleCount = rleCount * 10 + "0123456789".indexOf(c);
      } else {
         tape[nextPos++] = c;
         if (c != ' ')
           initNonBlanks++;
	  }
    }


    historyPos=1;
    for (int i=0; i < historySize; i++) {
      histTable[i].oldPos=-1;
    }
    for (int i = 0; i< tableSize; i++) {
		transTable[i].count=0;
	}
    nonBlanks = initNonBlanks;
    totalTransitions = 0;
    setState(1, 0);
    display.repaint();
    return true;
  }



  public boolean scrollTape(int dir) {
    if (dir == LEFT) {
      if (tapePos == 0)
        return false;
      else
        tapePos--;
    }
    else if (dir == RIGHT) {
      if (tapePos == TAPESIZE-1)
        return false;
      else
        tapePos++;
    }
    return true;
  }

  public void run() {
    int result = SUCCESS;

    if (speed != COMPUTE)
      display.repaint();
    while (result == SUCCESS) {
      if (speed == SLOW)
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {}
      else if (speed == FAST)
        try {
          Thread.sleep(200);
        } catch (InterruptedException e) {}
      else if (speed == VFAST)
        try {
          Thread.sleep(50);
        } catch (InterruptedException e) {}
      result = transition();
    }
    moving = STAY;
    display.repaint();
    app.controlPanel.showResults(result);
  }


  public boolean loadTrans(String[] nextTrans, int i,  StringBuffer errorMsg) {
    int  currentState;
    char currentChar;
    int  newState;
    char newChar;
    int  direction;
    char c;

      if (nextTrans.length < 4 || nextTrans.length > 5) {
      errorMsg.append("In line " + i + ": bad format");
      setState(NA, 0);
      return false;
    }
    if (nextTrans[0].equals("H")) {
      errorMsg.append("In line " + i + ": cannot transition from halt state");
      setState(NA, 0);
      return false;
    }
    try {
      currentState = Integer.parseInt(nextTrans[0]);
    }
    catch (NumberFormatException e) {
      errorMsg.append("In line " + i + ": invalid state '" + nextTrans[0] + "'");
      setState(NA, 0);
      return false;
    }
    if (currentState < 1 || currentState > 99) {
      errorMsg.append("In line " + i + ": invalid state '" + currentState + "'");
      setState(NA, 0);
      return false;
    }
    if (nextTrans[1].length() > 1) {
      errorMsg.append("In line " + i + ": bad format");
      setState(NA, 0);
      return false;
    }
    currentChar = nextTrans[1].charAt(0);
    if (currentChar == '_')
      currentChar = ' ';
    if (!validTapeChar(currentChar)) {
      errorMsg.append("In line " + i + ": Invalid tape character '" + currentChar + "'");
      setState(NA, 0);
      return false;
    }
    if (nextTrans[2].equals("H"))
      newState = HALT;
    else {
      try {
        newState = Integer.parseInt(nextTrans[2]);
      }
      catch (NumberFormatException e) {
        errorMsg.append("In line " + i + ": invalid state '" + nextTrans[2] + "'");
       setState(NA, 0);
        return false;
      }
      if (newState < 1 || newState > 99) {
        errorMsg.append("In line " + i + ": invalid state '" + newState + "'");
        setState(NA, 0);
        return false;
      }
    }
    if (nextTrans[3].length() > 1) {
      errorMsg.append("In line " + i + ": bad format");
      setState(NA, 0);
      return false;
    }
    if (nextTrans.length == 5) {
      if (nextTrans[4].length() > 1) {
        errorMsg.append("In line " + i + ": bad format");
        setState(NA, 0);
        return false;
      }
    }
    c = nextTrans[3].charAt(0);
    if (c == '<' || c == '>') {
      direction = (c == '<') ? LEFT : RIGHT;
      if (nextTrans.length == 4)
        newChar = NUL;
      else {
        newChar = nextTrans[4].charAt(0);
        if (newChar == '_')
          newChar = ' ';
        if (!validTapeChar(newChar)) {
          errorMsg.append("In line " + i + ": Invalid tape character '" + newChar + "'");
          setState(NA, 0);
          return false;
        }
      }
    }
    else {
      newChar = nextTrans[3].charAt(0);
      if (newChar == '_')
        newChar = ' ';
      if (!validTapeChar(newChar)) {
        errorMsg.append("In line " + i + ": Invalid tape character '" + newChar + "'");
        setState(NA, 0);
        return false;
      }
      if (nextTrans.length == 4)
        direction = STAY;
      else {
        c = nextTrans[4].charAt(0);
        if (c != '<' && c != '>') {
          errorMsg.append("In line " + i + ": bad format");
          setState(NA, 0);
          return false;
        }
        direction = (c == '<') ? LEFT : RIGHT;
      }
    }
    transTable[tableSize++] = new Transition(currentState, currentChar, newState, newChar, direction);
    return true;
  }



// the following is a sugestion
// add some formatting
//  #N name           - done
//  #T Tapecontent    - done
//  #H head position  - done
//  #L leading blanks - done
//  #C Comment line   - done

  public boolean program( String programming, StringBuffer errorMsg) {

	tableSize = 0;

    String[] codeLines = miscUtil.split(programming, "\n");
    if (codeLines.length < 1) {
        errorMsg.append("No programming entered");
        setState(NA, 0);
        return false;
    }
    transTable = new Transition[codeLines.length];

    for (int i=1; i <= codeLines.length; i++) {
	  if(codeLines[i-1].charAt(0) == '#') {
	    if(codeLines[i-1].charAt(1) == 'L') {         //#L leading blank Tape
	      try {
		    int leadingBlanks = Integer.parseInt(codeLines[i-1].substring(3).trim());
		    app.specsPanel.leadingBlanksBar.setValue(leadingBlanks);
		    app.specsPanel.leadingBlanks.setText(""+leadingBlanks);
          }
            catch (NumberFormatException e) {
            errorMsg.append("In line " + i + ": Invalid integer for leading blanks");
            setState(NA, 0);
            return false;
          }
		} else if(codeLines[i-1].charAt(1) == 'H') {         //#H Head Position
	      try {
		    int initHeadPos = Integer.parseInt(codeLines[i-1].substring(3).trim());
		    app.specsPanel.initHeadPosBar.setValue(initHeadPos);
		    app.specsPanel.initHeadPos.setText(""+initHeadPos);
          }
            catch (NumberFormatException e) {
            errorMsg.append("In line " + i + ": Invalid integer for Head Position");
            setState(NA, 0);
            return false;
          }
	    } else if(codeLines[i-1].charAt(1) == 'N') {         //#N Machine Name
		  app.specsPanel.machineName.setText(codeLines[i-1].substring(3,codeLines[i-1].length()));
	    } else if(codeLines[i-1].charAt(1) == 'T') {         //#T Tape Content
		  app.specsPanel.initChars.setText(codeLines[i-1].substring(3,codeLines[i-1].length()));
	    } else if(codeLines[i-1].charAt(1) == 'C') {         //#C Comment line
	      if (comment.length()>0) comment += "\n";
		  comment += codeLines[i-1].substring(3,codeLines[i-1].length());
	    }
	  } else {
// transition description line
        String[] nextTrans = miscUtil.split(codeLines[i-1], ", ");
        if (!loadTrans(nextTrans, i, errorMsg)) return false;
	  }
    }
    programmed = true;
    setState(1, 0);
    return true;
  }

  public int transition() {
    Transition currentTrans;

    if (!programmed)
      return NOPROG;
    else if (state == HALT) {
      setState(HALT, totalTransitions);
      return HALTED;
    } else {
	  histTable[historyPos].oldState = state;
	  histTable[historyPos].oldPos = tapePos;
	  histTable[historyPos].oldChar = tape[tapePos];

      for (int i=0; i < tableSize; i++) {
        currentTrans = transTable[i];
        if (currentTrans.currentState == state &&
            currentTrans.currentChar == tape[tapePos]) {
          if (currentTrans.newChar != NUL) {
            if (currentTrans.newChar == ' ' && tape[tapePos] != ' ')
              nonBlanks--;
            else if (currentTrans.newChar != ' ' && tape[tapePos] == ' ')
              nonBlanks++;
            tape[tapePos] = currentTrans.newChar;
          }
          historyPos +=1;
          if (historyPos == historySize) {
			  historyPos = 0;
			  histTable[1].oldPos=-1;
		  } else if (historyPos+1 < historySize) {
			  histTable[historyPos+1].oldPos=-1;
		  } else {
			  histTable[0].oldPos = -1;
		  }
          boolean scrResult = scrollTape(currentTrans.direction);
          if (!scrResult) {
            setState(HALT, totalTransitions);
            return ABNORMAL;
          }
          totalTransitions++;
          if (speed == COMPUTE)
            state = currentTrans.newState;
          else
            setState(currentTrans.newState, totalTransitions);
          if (moving != currentTrans.direction)
            moving = currentTrans.direction;
          if (speed != COMPUTE)
            display.repaint();


		  currentTrans.count += 1;
          return SUCCESS;
        }
      }
      setState(HALT, totalTransitions);
      return NOTFOUND;
    }
  }
}

class TapeDisplay extends Canvas implements Runnable {
  static final int MINW = 200, MINH = 50,
                   CELLHEIGHT = 20, CELLWIDTH = 20;

  int[] leftTriXs, rightTriXs, triYs;
  Dimension minSize;
  TMapplet app;
  Thread scroller = new Thread(this);
  int scrollDir;
  int width, height, xpos, ypos, numCells;
  Font tapeFont;
  Color darkYellow = new Color(189, 193, 0);

  public TapeDisplay(TMapplet daddy) {
    minSize = new Dimension(MINW, MINH);
    tapeFont = new Font("Helvetica", Font.BOLD, 12);
    app = daddy;
  }

  public void initGraphics() {
    width = size().width; height = size().height;
    ypos = height/2 - CELLHEIGHT/2;
    numCells = width/CELLWIDTH - 3;
    xpos = (width - numCells*CELLWIDTH) / 2;
    int[] ltx = {20, 4, 20, 20};
    leftTriXs = ltx;
    int[] rtx = {width-21, width-5, width-21, width-21};
    rightTriXs = rtx;
    int[] ty = {ypos, height/2, ypos+CELLHEIGHT, ypos};
    triYs = ty;
  }

  public void paint(Graphics g) {
    g.setFont(tapeFont);
    g.setColor(new Color(192, 0, 0));
    g.drawRect(0, 0, width-1, height-1);
    g.drawRect(1, 1, width-3, height-3);
    g.fillPolygon(new int[]{2, 2, 15, 2}, new int[]{15, 2, 2, 15}, 4);
    g.fillPolygon(new int[]{2, 2, 15, 2},
                  new int[]{height-15, height-2, height-2, height-15}, 4);
    g.fillPolygon(new int[]{width-2, width-2, width-15, width-2},
                  new int[]{15, 2, 2, 15}, 4);
    g.fillPolygon(new int[]{width-2, width-2, width-15, width-2},
                  new int[]{height-15, height-2, height-2, height-15}, 4);
    if (app.machine.moving == TM.LEFT)
      g.setColor(Color.yellow);
    else
      g.setColor(darkYellow);
    g.fillPolygon(leftTriXs, triYs, 4);
    if (app.machine.moving == TM.RIGHT)
      g.setColor(Color.yellow);
    else
      g.setColor(darkYellow);
    g.fillPolygon(rightTriXs, triYs, 4);
    g.setColor(Color.black);
    g.drawPolygon(leftTriXs, triYs, 4);
    g.drawPolygon(rightTriXs, triYs, 4);

    g.setColor(Color.black);
    int actualNumCells = numCells;
    if (!app.machine.programmed) {
      for (int x=xpos, count=0; count < numCells; x+=CELLWIDTH, count++) {
        if (count == numCells/2) {
          g.setColor(Color.red);
          g.drawRect(x, ypos, CELLWIDTH, CELLHEIGHT);
          g.drawRect(x-1, ypos-1, CELLWIDTH+2, CELLHEIGHT+2);
          g.setColor(Color.black);
        }
        else
          g.drawRect(x, ypos, CELLWIDTH, CELLHEIGHT);
        if (count == numCells/2 + 1) {
          g.setColor(Color.red);
          g.drawLine(x, ypos, x, ypos+CELLHEIGHT);
          g.setColor(Color.black);
        }
      }
    }
    else {
      char[] tape = app.machine.tape;
      int tapePos = app.machine.tapePos;
      int centerCell = numCells/2;
      int centerCellPos = xpos + CELLWIDTH*centerCell;
      int charsToLeft = numCells/2;
      int charsToRight = (numCells%2 == 0) ? numCells/2-1: numCells/2;
      int firstCell, lastCell, startx;
      if (tapePos < charsToLeft) {
        firstCell = 0;
        startx = xpos + CELLWIDTH * (charsToLeft - tapePos);
        actualNumCells -= (charsToLeft - tapePos);
      }
      else {
        firstCell = tapePos - charsToLeft;
        startx = xpos;
      }
      if (tapePos + charsToRight >= TM.TAPESIZE)
        actualNumCells -= ((tapePos+charsToRight+1) - TM.TAPESIZE);
      lastCell = firstCell + actualNumCells - 1;
      for (int x=startx, tpos = firstCell; tpos <= lastCell;
           x+=CELLWIDTH, tpos++) {
        if (x == centerCellPos) {
          g.setColor(Color.red);
          g.drawRect(x, ypos, CELLWIDTH, CELLHEIGHT);
          g.drawRect(x-1, ypos-1, CELLWIDTH+2, CELLHEIGHT+2);
          g.setColor(Color.black);
        }
        else
          g.drawRect(x, ypos, CELLWIDTH, CELLHEIGHT);
        if (x == centerCellPos + CELLWIDTH) {
          g.setColor(Color.red);
          g.drawLine(x, ypos, x, ypos+CELLHEIGHT);
          g.setColor(Color.black);
        }
        if (tape[tpos] != ' ')
          g.drawString("" + tape[tpos], x+CELLWIDTH/2-4, ypos+CELLHEIGHT*3/4);
      }
    }
  }

  public boolean mouseDown(Event evt, int x, int y) {
    if (!app.execution.isAlive() && !scroller.isAlive()) {
      if (x >= leftTriXs[1] && x <= leftTriXs[0] &&
          y >= triYs[0] && y <= triYs[2]) {
        scrollDir = TM.LEFT;
        if (app.machine.scrollTape(scrollDir)) {
          app.machine.moving = scrollDir;
          this.repaint();
          scroller = new Thread(this);
          scroller.start();
        }
      }
      else if (x >= rightTriXs[0] && x <= rightTriXs[1] &&
               y >= triYs[0] && y <= triYs[2]) {
        scrollDir = TM.RIGHT;
        if (app.machine.scrollTape(scrollDir)) {
          app.machine.moving = scrollDir;
          this.repaint();
          scroller = new Thread(this);
          scroller.start();
        }
      }
    }
    return true;
  }

  public boolean mouseUp(Event evt, int x, int y) {
    if (scroller.isAlive()) {
      scroller.stop();
      app.machine.moving = TM.STAY;
      repaint();
    }
    return true;
  }

  public void run() {
    boolean offTape = false;
    while (!offTape) {
      try {
        Thread.sleep(100);
      }
      catch (InterruptedException e) {}
      offTape = !app.machine.scrollTape(scrollDir);
      this.repaint();
    }
    app.machine.moving = TM.STAY;
    repaint();
  }

  public Dimension minimumSize() {
    return new Dimension(MINW, MINH);
  }

  public Dimension preferredSize() {
    return new Dimension(MINW, MINH);
  }
}

class TapePanel extends Panel {
  Label currentState;
  Label countStatic;
  Label countTransaction;
  Choice speed;
  TapeDisplay tapeDisplay;
  TMapplet app;

  public TapePanel(TMapplet daddy) {
    app = daddy;
    GridBagLayout gbl = new GridBagLayout();
    GridBagConstraints gbcon = new GridBagConstraints();

    currentState = new Label("State: H", Label.CENTER);
    countStatic = new Label("Steps:", Label.CENTER);
    countTransaction = new Label("00000000", Label.LEFT);
    Button start = new Button("Start"),
           stop = new Button("Stop"),
           resume = new Button("Resume"),
           step = new Button("Step"),
           back = new Button("Back");
    Label speedLabel = new Label("Speed:", Label.RIGHT);
    speed = new Choice();
    speed.addItem("Slow");
    speed.addItem("Fast");
    speed.addItem("Very Fast");
    speed.addItem("Compute");
    speed.addItem("Comp Freq");
    tapeDisplay = new TapeDisplay(app);

    setLayout(gbl);

    // add current state label
    gbcon.gridx = 0; gbcon.gridy = 0;
    gbcon.gridwidth = 7;
    gbcon.anchor = GridBagConstraints.NORTH;
    gbcon.weighty = 0.1;

    Panel pnlHeader = new Panel(new GridLayout(1, 3)); // horizontal layout
	pnlHeader.add(currentState);
	pnlHeader.add(countStatic);
	pnlHeader.add(countTransaction);

    gbl.setConstraints(pnlHeader, gbcon);
    add(pnlHeader);


    // add start, stop,step  and back control buttons
    gbcon.gridy = 1;
    gbcon.gridwidth = 1;
    gbcon.insets = new Insets(0,0,0,40);
    gbcon.weightx = 0.2; gbcon.weighty = 0.1;
    gbl.setConstraints(start, gbcon);
    add(start);
    gbcon.gridx = 1;
    gbl.setConstraints(stop, gbcon);
    add(stop);
    gbcon.gridx = 2;
    gbl.setConstraints(resume, gbcon);
    add(resume);
    gbcon.gridx = 3;
    gbl.setConstraints(step, gbcon);
    add(step);
    gbcon.gridx = 4;
    gbl.setConstraints(back, gbcon);
    add(back);

    // add speed pull-down list control
    gbcon.gridx = 5;
    gbcon.insets = new Insets(0,0,0,5);
    gbl.setConstraints(speedLabel, gbcon);
    add(speedLabel);
    gbcon.gridx = 6;
    gbl.setConstraints(speed, gbcon);
    add(speed);

    // add turing tape display canvas
    gbcon.gridx = 0; gbcon.gridy = 2;
    gbcon.insets = new Insets(0,0,5,0);
    gbcon.gridwidth = 7;
    gbcon.fill = GridBagConstraints.BOTH;
    gbcon.anchor = GridBagConstraints.SOUTH;
    gbcon.weightx = 0; gbcon.weighty = 0.8;
    gbl.setConstraints(tapeDisplay, gbcon);
    add(tapeDisplay);
  }

  public boolean action(Event evt, Object arg) {
    if (evt.target instanceof Button) {
      String command = (String)arg;
      int result;
      TM machine = app.machine;
      ControlPanel cp = app.controlPanel;
      SpecsPanel sp = app.specsPanel;
      if (command.equals("Step")) {
        if (machine.state != TM.HALT) {
          app.machine.setSpeed("Slow");
          result = machine.transition();
        } else result = TM.HALTED;
        tapeDisplay.repaint();
        if (result == TM.HALTED)
          cp.addMessage("Machine is halted");
        else if (result == TM.ABNORMAL)
          cp.addMessage("The machine has run\noff the end of the tape");
        else if (result == TM.NOTFOUND)
          cp.addMessage("No applicable transition found state: " + app.machine.state + " for: " + app.machine.tape[app.machine.tapePos]+" "+app.machine.histTable[app.machine.historyPos].oldState);



        else if (result == TM.NOPROG)
          cp.addMessage("No program entered");
        else if (machine.state == TM.HALT)
          cp.addMessage("Halt state reached");
        return true;
      }
      else if (command.equals("Start") || command.equals("Resume")) {
        if (!app.machine.programmed)
          cp.addMessage("Cannot start: No program entered");
        else if (app.execution.isAlive())
          cp.addMessage("Already running");
        else {
          StringBuffer errorMsg = new StringBuffer(50);
          boolean success = true;
          if (command.equals("Start"))
            success = app.machine.initMachine(sp.leadingBlanksBar.getValue(),sp.initHeadPosBar.getValue(),
                      sp.initChars.getText(), errorMsg);
          if (success) {
            app.machine.setSpeed(speed.getSelectedItem());
            cp.addMessage("Running...");
            app.execution = new Thread(app.machine);
            app.execution.start();
          }
          else
            cp.addMessage("Error initializing machine:\n"+errorMsg);
        }
      }
      else if (command.equals("Stop")) {
        if (app.execution.isAlive()) {
          app.execution.stop();
          if (app.machine.speed == TM.COMPUTE)
            app.machine.setState(app.machine.state, app.machine.totalTransitions);
          app.machine.moving = TM.STAY;
          tapeDisplay.repaint();
          cp.showResults(TM.USERINT);
        }
        else
          cp.addMessage("Machine is not running");
      }


      else if (command.equals("Back")) {
		int prevHpos;
		int prevTpos;
        if (app.execution.isAlive())
          app.execution.stop();
        app.machine.moving = TM.STAY;

        prevHpos = app.machine.historyPos-1;
        if (prevHpos < 0 )
          prevHpos = app.machine.historySize-1;
        if ((app.machine.histTable[prevHpos].oldPos != -1) && ! app.machine.compfrequ) {

          prevTpos = app.machine.histTable[prevHpos].oldPos;
		  if (app.machine.histTable[prevHpos].oldChar != app.machine.tape[prevTpos]){
		    if (app.machine.tape[prevTpos] == ' ') {
              app.machine.nonBlanks++;
		    } else if ( app.machine.histTable[prevHpos].oldChar == ' ') {
		       app.machine.nonBlanks--;
		    }
		  }
		  app.machine.totalTransitions--;
		  app.machine.setState(app.machine.histTable[prevHpos].oldState, app.machine.totalTransitions);
          app.machine.tape[prevTpos] = app.machine.histTable[prevHpos].oldChar;
		  app.machine.tapePos = prevTpos;
		  app.machine.historyPos = prevHpos;
          tapeDisplay.repaint();
        }
        else  if (app.machine.histTable[prevHpos].oldPos != -1)
          cp.addMessage("Back Buffer Empty");
        else
          cp.addMessage("Back disabled by Compute Frequancy");
      }

    }
    return false;
  }
}

class SpecsPanel extends Panel {
  TMapplet app;
  Choice loader;
  TextField machineName;
  Label leadingBlanks;
  Scrollbar leadingBlanksBar;
  Label initHeadPos;
  Scrollbar initHeadPosBar;
  TextField initChars;

  public SpecsPanel(TMapplet daddy) {
    app = daddy;
    GridBagLayout gbl = new GridBagLayout();
    GridBagConstraints gbcon = new GridBagConstraints();

    Label nameLabel = new Label("Machine name"),
          leadingBlanksLabel = new Label("Leading Blanks"),
          initHeadPosLabel = new Label("Initial head position"),
          initCharsLabel = new Label("Initial characters on tape");
    Button loadButton = new Button("Load new program:");
    loader = new Choice();
    for (int i=0; i < PresetTM.names.length; i++)
      loader.addItem(PresetTM.names[i]);
    machineName = new TextField(30);
    leadingBlanksBar = new Scrollbar(Scrollbar.HORIZONTAL, TM.TAPESIZE/2, 50, 0, TM.TAPESIZE);
    leadingBlanks = new Label("" + (TM.TAPESIZE/2), Label.RIGHT);

    initHeadPosBar = new Scrollbar(Scrollbar.HORIZONTAL, TM.TAPESIZE/2, 50, 0, TM.TAPESIZE);
    initHeadPos = new Label("" + (TM.TAPESIZE/2), Label.RIGHT);
    initChars = new TextField(30);

    setLayout(gbl);

    gbcon.gridx = 0; gbcon.gridy = 0;
    gbcon.insets = new Insets(0,0,5,5);
    gbcon.anchor = GridBagConstraints.NORTHWEST;
    gbl.setConstraints(loadButton, gbcon);
    add(loadButton);
    gbcon.gridx = 1;
    gbcon.gridwidth = 2;
    gbcon.fill = GridBagConstraints.NONE;
    gbl.setConstraints(loader, gbcon);
    add(loader);

    gbcon.gridx = 0; gbcon.gridy = 1;
    gbcon.fill = GridBagConstraints.NONE;
    gbl.setConstraints(nameLabel, gbcon);
    add(nameLabel);
    gbcon.gridx = 1;
    gbcon.gridwidth = 2;
    gbcon.fill = GridBagConstraints.HORIZONTAL;
    gbl.setConstraints(machineName, gbcon);
    add(machineName);

    gbcon.gridx = 0; gbcon.gridy = 2;
    gbcon.gridwidth = 1;
    gbcon.fill = GridBagConstraints.NONE;
    gbl.setConstraints(leadingBlanksLabel, gbcon);
    add(leadingBlanksLabel);
    gbcon.gridx = 1;
    gbcon.fill = GridBagConstraints.HORIZONTAL;
    gbcon.weightx = 0.7;
    gbl.setConstraints(leadingBlanksBar, gbcon);
    add(leadingBlanksBar);

    gbcon.weightx = 0.0;
    gbcon.gridx = 2;
    gbcon.fill = GridBagConstraints.NONE;
    gbl.setConstraints(leadingBlanks, gbcon);
    add(leadingBlanks);
//    leadingBlanks.setText("0");
//    leadingBlanksBar.setValue(0);

    gbcon.gridx = 0; gbcon.gridy = 3;
    gbcon.gridwidth = 1;
    gbcon.fill = GridBagConstraints.NONE;
    gbl.setConstraints(initHeadPosLabel, gbcon);
    add(initHeadPosLabel);
    gbcon.gridx = 1;
    gbcon.fill = GridBagConstraints.HORIZONTAL;
    gbcon.weightx = 0.7;
    gbl.setConstraints(initHeadPosBar, gbcon);
    add(initHeadPosBar);
    gbcon.weightx = 0;
    gbcon.gridx = 2;
    gbcon.fill = GridBagConstraints.NONE;
    gbl.setConstraints(initHeadPos, gbcon);
    add(initHeadPos);
//    initHeadPos.setText("0");
//    initHeadPosBar.setValue(0);

    gbcon.gridx = 0; gbcon.gridy = 4;
    gbcon.fill = GridBagConstraints.NONE;
    gbl.setConstraints(initCharsLabel, gbcon);
    add(initCharsLabel);
    gbcon.gridx = 1;
    gbcon.gridwidth = 2;
    gbcon.fill = GridBagConstraints.HORIZONTAL;
    gbl.setConstraints(initChars, gbcon);
    add(initChars);
  }

  public boolean action(Event evt, Object arg) {
    StringBuffer errorMsg = new StringBuffer(50);

    if (!(evt.target instanceof Button) || app.execution.isAlive())
      return true;
    String name = loader.getSelectedItem();
    ControlPanel cp = app.controlPanel;
    PresetTM preset = new PresetTM(name);
    if (preset.leadingBlanks < 0) {
      cp.addMessage("Machine unimplemented");
      return true;
    }
    app.machine = new TM(app, app.tapePanel.tapeDisplay);
    if (name.equals("<New>"))
      machineName.setText("");
    else
      machineName.setText(name);
    leadingBlanks.setText(""+preset.leadingBlanks);
    leadingBlanksBar.setValue(preset.leadingBlanks);
    initHeadPos.setText(""+preset.initHeadPos);
    initHeadPosBar.setValue(preset.initHeadPos);
    initChars.setText(preset.initChars);
    app.programPanel.programming.setText(preset.programming);
    if (!name.equals("<New>")) {
      cp.addMessage("\nEntering program...");
      boolean success = app.machine.program(preset.programming, errorMsg) &&
                        app.machine.initMachine(preset.leadingBlanks, preset.initHeadPos,
                                                preset.initChars, errorMsg);
      if (success) {
        cp.addMessage("Machine programmed successfully.");
        if (preset.comment.length() > 0)
          cp.addMessage("\nProgram description:\n"+preset.comment+"\n");
      }
      else {
        cp.addMessage("\nError:");
        cp.addMessage(errorMsg.toString());
      }
    }
    app.tapePanel.tapeDisplay.repaint();
    return true;
  }

  public boolean handleEvent(Event evt) {
    if (evt.target instanceof Scrollbar) {
      leadingBlanks.setText("" + leadingBlanksBar.getValue());
      initHeadPos.setText("" + initHeadPosBar.getValue());
      return true;
    }
    else
      return super.handleEvent(evt);
  }
}

class ProgramPanel extends Panel {
  TextArea programming;

  public ProgramPanel() {
    GridBagLayout gbl = new GridBagLayout();
    GridBagConstraints gbcon = new GridBagConstraints();

    Label programmingLabel = new Label("Programming");
    programming = new TextArea(10, 20);

    setLayout(gbl);

    gbcon.gridx = 0; gbcon.gridy = 0;
    gbcon.insets = new Insets(0,0,5,5);
    gbcon.weighty = 0.1;
    gbl.setConstraints(programmingLabel, gbcon);
    add(programmingLabel);
    gbcon.gridy = 1;
    gbcon.fill = GridBagConstraints.BOTH;
    gbcon.anchor = GridBagConstraints.NORTHWEST;
    gbcon.weighty = 0.9;
    gbl.setConstraints(programming, gbcon);
    add(programming);
  }
}

class ControlPanel extends Panel {
  TextArea messages;
  TMapplet app;

  public ControlPanel(TMapplet daddy) {
    app = daddy;
    GridBagLayout gbl = new GridBagLayout();
    GridBagConstraints gbcon = new GridBagConstraints();

    Button reset = new Button("Clear Program"),
           install = new Button("Install Program");
    messages = new TextArea(5, 30);
    messages.setEditable(false);
    Button clearMsgs = new Button("Clear Message Box");

    setLayout(gbl);

    gbcon.gridx = 0; gbcon.gridy = 0;
    gbcon.insets = new Insets(0,0,5,5);
    gbcon.anchor = GridBagConstraints.NORTHEAST;
    gbcon.weightx = 0.5; gbcon.weighty = 0.1;
    gbl.setConstraints(reset, gbcon);
    add(reset);
    gbcon.gridx = 1;
    gbl.setConstraints(install, gbcon);
    add(install);

    gbcon.gridx = 0; gbcon.gridy = 1;
    gbcon.gridwidth = 2;
    gbcon.fill = GridBagConstraints.BOTH;
    gbcon.anchor = GridBagConstraints.SOUTHEAST;
    gbcon.weightx = 0; gbcon.weighty = 0.8;
    gbl.setConstraints(messages, gbcon);
    add(messages);

    gbcon.gridx = 0; gbcon.gridy = 2;
    gbcon.gridwidth = 2;
    gbcon.fill = GridBagConstraints.NONE;
    gbcon.anchor = GridBagConstraints.CENTER;
    gbcon.weightx = 0; gbcon.weighty = 0.1;
    gbl.setConstraints(clearMsgs, gbcon);
    add(clearMsgs);
  }

  public boolean action(Event evt, Object arg) {
    StringBuffer errorMsg = new StringBuffer(50);

    if (evt.target instanceof Button) {
      String command = (String)arg;
      if (command.equals("Clear Program"))
        app.programPanel.programming.setText("");
      else if (command.equals("Install Program")) {
        int leadingBlanks = app.specsPanel.leadingBlanksBar.getValue();
        String programming = app.programPanel.programming.getText();
        app.machine = new TM(app, app.tapePanel.tapeDisplay);
        addMessage("\nEntering program...");
        boolean success = app.machine.program(programming, errorMsg) &&
                          app.machine.initMachine(leadingBlanks, app.specsPanel.initHeadPosBar.getValue(),
                                                  app.specsPanel.initChars.getText(), errorMsg);

        app.tapePanel.tapeDisplay.repaint();
        if (success)
          addMessage("Machine programmed successfully");
        else {
          addMessage("Error: ");
          addMessage(errorMsg.toString());
        }
        if (app.machine.comment.length() > 0)
          addMessage("\nProgram description:\n"+app.machine.comment+"\n");
      }
      else if (command.equals("Clear Message Box"))
        messages.setText("");
    }
    return true;
  }

  public void addMessage(String msg) {
    messages.appendText(msg+"\n");
  }

  public void showResults(int haltReason) {
    addMessage("\nMachine halted:");
    switch (haltReason) {
      case TM.HALTED: addMessage("Halt state reached"); break;
      case TM.ABNORMAL: addMessage("The machine ran off the tape"); break;
      case TM.NOTFOUND: addMessage("No applicable transition found"); break;
      case TM.USERINT: addMessage("User interrupt"); break;
    }
    addMessage("" + app.machine.totalTransitions + " total transitions");
    addMessage("" + app.machine.nonBlanks + " non-blank characters on tape");
    addMessage("Tape: " + app.machine.tapeString());
    if (app.machine.compfrequ){
      addMessage("\n State/Symbol Frequency ");
      for (int i=0; i < app.machine.tableSize; i++)
         addMessage(app.machine.transTable[i].TransFreq());
	}
  }
}

public class TMapplet extends java.applet.Applet {
  static final Color BGCOLOR=new Color(192, 192, 192);

  TapePanel tapePanel;
  SpecsPanel specsPanel;
  ProgramPanel programPanel;
  ControlPanel controlPanel;

  TM machine;

  Thread execution;

  public void init() {
    setBackground(BGCOLOR);

    tapePanel = new TapePanel(this);
    specsPanel = new SpecsPanel(this);
    programPanel = new ProgramPanel();
    controlPanel = new ControlPanel(this);
    execution = new Thread(machine);
    GridBagLayout gbl = new GridBagLayout();
    GridBagConstraints gbcon = new GridBagConstraints();
    setLayout(gbl);

    gbcon.gridx = 0; gbcon.gridy = 0;
    gbcon.gridwidth = 2;
    gbcon.fill = GridBagConstraints.BOTH;
    gbcon.insets = new Insets(0,0,0,0);
    gbcon.anchor = GridBagConstraints.NORTHWEST;
    gbcon.weighty = 0.6;
    gbl.setConstraints(tapePanel, gbcon);
    add(tapePanel);
    tapePanel.validate();

    gbcon.gridy = 1;
    gbcon.weighty = 0.2;
    gbl.setConstraints(specsPanel, gbcon);
    add(specsPanel);
    specsPanel.validate();

    gbcon.gridy = 2;
    gbcon.gridwidth = 1;
    gbcon.anchor = GridBagConstraints.SOUTHEAST;
    gbcon.weighty = 0.2;
    gbcon.insets = new Insets(0,0,0,15);
    gbl.setConstraints(programPanel, gbcon);
    add(programPanel);
    programPanel.validate();

    gbcon.gridx = 1;
    gbcon.insets = new Insets(0,15,0,0);
    gbl.setConstraints(controlPanel, gbcon);
    add(controlPanel);
    controlPanel.validate();


    machine = (TM)new TM(this, tapePanel.tapeDisplay); //placeholder
  }

  public void start() {
    tapePanel.tapeDisplay.initGraphics();
  }
}

