// 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];
            System.arraycopy(result, 0, paredResult, 0, resultSize);
            return paredResult;
        } else {
            return result;
        }
    }
}

final 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 = "";


        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) {
        switch (newSpeed) {
            case "Slow":
                speed = SLOW;
                break;
            case "Fast":
                speed = FAST;
                break;
            case "Very Fast":
                speed = VFAST;
                break;
            default:
                speed = COMPUTE;
                compfrequ = newSpeed.equals("Comp Freq");
                break;
        }
    }

    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 != ' ') || (!"".equals(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;
        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 '").append(c).append("'");
                    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;
    }

    @Override
    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 ").append(i).append(": bad format");
            setState(NA, 0);
            return false;
        }
        if (nextTrans[0].equals("H")) {
            errorMsg.append("In line ").append(i).append(": cannot transition from halt state");
            setState(NA, 0);
            return false;
        }
        try {
            currentState = Integer.parseInt(nextTrans[0]);
        } catch (NumberFormatException e) {
            errorMsg.append("In line ").append(i).append(": invalid state '").append(nextTrans[0]).append("'");
            setState(NA, 0);
            return false;
        }
        if (currentState < 1 || currentState > 99) {
            errorMsg.append("In line ").append(i).append(": invalid state '").append(currentState).append("'");
            setState(NA, 0);
            return false;
        }
        if (nextTrans[1].length() > 1) {
            errorMsg.append("In line ").append(i).append(": bad format");
            setState(NA, 0);
            return false;
        }
        currentChar = nextTrans[1].charAt(0);
        if (currentChar == '_') {
            currentChar = ' ';
        }
        if (!validTapeChar(currentChar)) {
            errorMsg.append("In line ").append(i).append(": Invalid tape character '").append(currentChar).append("'");
            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 ").append(i).append(": invalid state '").append(nextTrans[2]).append("'");
                setState(NA, 0);
                return false;
            }
            if (newState < 1 || newState > 99) {
                errorMsg.append("In line ").append(i).append(": invalid state '").append(newState).append("'");
                setState(NA, 0);
                return false;
            }
        }
        if (nextTrans[3].length() > 1) {
            errorMsg.append("In line ").append(i).append(": bad format");
            setState(NA, 0);
            return false;
        }
        if (nextTrans.length == 5) {
            if (nextTrans[4].length() > 1) {
                errorMsg.append("In line ").append(i).append(": 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 ").append(i).append(": Invalid tape character '").append(newChar).append("'");
                    setState(NA, 0);
                    return false;
                }
            }
        } else {
            newChar = nextTrans[3].charAt(0);
            if (newChar == '_') {
                newChar = ' ';
            }
            if (!validTapeChar(newChar)) {
                errorMsg.append("In line ").append(i).append(": Invalid tape character '").append(newChar).append("'");
                setState(NA, 0);
                return false;
            }
            if (nextTrans.length == 4) {
                direction = STAY;
            } else {
                c = nextTrans[4].charAt(0);
                if (c != '<' && c != '>') {
                    errorMsg.append("In line ").append(i).append(": 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 leadingBlanksL = Integer.parseInt(codeLines[i - 1].substring(3).trim());
                        app.specsPanel.leadingBlanksBar.setValue(leadingBlanksL);
                        app.specsPanel.leadingBlanks.setText("" + leadingBlanksL);
                    } catch (NumberFormatException e) {
                        errorMsg.append("In line ").append(i).append(": 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 ").append(i).append(": 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(TapePanel tp) {
        width = tp.getSize().width+560;
        height = tp.getSize().height+50;
        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;
    }

    @Override
    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);
                }
            }
        }
    }

    @Override
    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;
    }

    @Override
    public boolean mouseUp(Event evt, int x, int y) {
        if (scroller.isAlive()) {
            scroller.stop();
            app.machine.moving = TM.STAY;
            repaint();
        }
        return true;
    }

    @Override
    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();
    }

    @Override
    public Dimension minimumSize() {
        return new Dimension(MINW, MINH);
    }

    @Override
    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);
    }

    @Override
    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;
            switch (command) {
                case "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;
                case "Start":
                case "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);
                        }
                    }
                    break;
                case "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");
                    }
                    break;
                case "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");
                    }
                    break;
            }

        }
        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);
    }

    @Override
    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;
    }

    @Override
    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);
    }

    @Override
    public boolean action(Event evt, Object arg) {
        StringBuffer errorMsg = new StringBuffer(50);

        if (evt.target instanceof Button) {
            String command = (String) arg;
            switch (command) {
                case "Clear Program":
                    app.programPanel.programming.setText("");
                    break;
                case "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");
                    }
                    break;
                case "Clear Message Box":
                    messages.setText("");
                    break;
            }
        }
        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;

    @Override
    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
    }

    @Override
    public void start() {
        tapePanel.tapeDisplay.initGraphics(tapePanel);
    }
}
