package toy;

import toy.dialog.*;

/**
 *
 * @author Brian Tsang
 * @version 7.0
 */

public class ToyWorkspace
{
    ////////////////////////////////////////////////////////////////////////////
    // Constants
    public static final String HEADER_BAR = "--------------------------------------------------------------------------------";
    public static final int COMMENT_WIDTH = 33;

    public static final int UNDO_MEMORY = 60;

    ////////////////////////////////////////////////////////////////////////////
    // Variables
    private String text;
    private String savedText;

    private ToyTextAreaState undoData[];
    private int undoCtr;

    private String name;
    private String fileName;

    private boolean hasFatalError;

    private ToyVirtualMachine parsedVirtualMachine;
        //this virtual machine comes straight from the text this is compared
        //to initialVirtualMachine to see if any changes were made with the
        //load button
    private ToyVirtualMachine initialVirtualMachine;
        //this virtual machine is the parsedVirtualMachine + changes made with
        //the load button (this is the machine that virtualMachine is reset to
    private ToyVirtualMachine virtualMachine;
        //this is the virtual machine that is stepped through

    ////////////////////////////////////////////////////////////////////////////
    // The Constructor
    public ToyWorkspace(String parseString)
    {
        name = "Untitled";
        fileName = "";
        savedText = parseString;

        undoData = new ToyTextAreaState[UNDO_MEMORY];
        undoCtr = -1;

        parsedVirtualMachine = new ToyVirtualMachine();
        initialVirtualMachine = new ToyVirtualMachine();
        virtualMachine = new ToyVirtualMachine();

        setText(parseString, false);
    }

    public ToyWorkspace(String parseString, String newFileName)
    {
        this(parseString, newFileName, new ToyWord[0]);
    }

    public ToyWorkspace(String parseString, String newFileName,
                        ToyWord newStdin[])
    {
        try
        {
            //Process the filename so we can get a clean name for the
            //workspace name
            name = newFileName;

            int directoryChop = Math.max(
                name.lastIndexOf('\\'),
                name.lastIndexOf('/')
                );
            if (directoryChop >= 0)
                name = name.substring(directoryChop + 1);

            //drop the extention (".toy" or whatever)
            if (name.lastIndexOf('.') > name.length() - 6)
                name = name.substring(0, name.lastIndexOf('.'));

            //replace underscores with spaces
            name = name.replace('_', ' ');

            //uppercase the first character
            name = Character.toUpperCase(name.charAt(0)) + name.substring(1);

            //uppercase any character after that which was preceded by
            //a character which was not a letter
            for (int ctr = 1; ctr < name.length(); ctr++)
                if (!Character.isLetter(name.charAt(ctr - 1)))
                    name = name.substring(0, ctr) +
                           Character.toUpperCase(name.charAt(ctr)) +
                           name.substring(ctr + 1);
        }
        catch (Exception e)
        {
            name = "Untitled";
        }

        fileName = newFileName;
        savedText = parseString;

        undoData = new ToyTextAreaState[UNDO_MEMORY];
        undoCtr = -1;

        parsedVirtualMachine = new ToyVirtualMachine();
        initialVirtualMachine = new ToyVirtualMachine();
            initialVirtualMachine.setStdin(newStdin);
        virtualMachine = new ToyVirtualMachine();

        setText(parseString, false);
    }

    ////////////////////////////////////////////////////////////////////////////
    // the get and set functions
    public ToyVirtualMachine getVirtualMachine()
    {
        return virtualMachine;
    }

    public ToyVirtualMachine getInitialVirtualMachine()
    {
        return initialVirtualMachine;
    }

    public boolean memChanged()
    {
        boolean answer = false;

        for (int ctr = 0; !answer && ctr < ToyVirtualMachine.MEM_CARDINALITY; ctr++)
            answer = !initialVirtualMachine.getMem(ctr).equals(
                parsedVirtualMachine.getMem(ctr)
                );

        return answer;
    }

    public boolean hasFatalError()
    {
        return hasFatalError;
    }

    public String getText()
    {
        return text;
    }

    //the setText function is really big so it has its own section

    public void addUndoData(ToyTextAreaState newUndoData)
    {
        if (undoCtr == -1)
        {
            undoCtr = 0;
            undoData[0] = newUndoData;
        }
        else {
            if (!undoData[undoCtr].equals(newUndoData))
            {
                undoCtr++;

                if (undoCtr >= UNDO_MEMORY)
                {
                    for (int ctr = 1; ctr < UNDO_MEMORY; ctr++)
                        undoData[ctr - 1] = undoData[ctr];
                    undoCtr--;
                }

                undoData[undoCtr] = newUndoData;

                //if something was done we can't redo anything from a different
                //timeline
                for (int ctr = undoCtr + 1; ctr < UNDO_MEMORY; ctr++)
                    undoData[ctr] = null;
            }
            else
                undoData[undoCtr] = newUndoData;
        }
    }

    public boolean canUndo()
    {
        return undoCtr > 0 && undoData[undoCtr - 1] != null;
    }

    public ToyTextAreaState undo()
    {
        if (canUndo())
            undoCtr--;

        return undoData[undoCtr];
    }

    public boolean canRedo()
    {
        return undoCtr != -1 && undoCtr + 1 < UNDO_MEMORY && undoData[undoCtr + 1] != null;
    }

    public ToyTextAreaState redo()
    {
        if (canRedo())
            undoCtr++;

        return undoData[undoCtr];
    }


    public String getName()
    {
        return name;
    }

    public void setName(String newName)
    {
        name = newName;
    }

    public String getFileName()
    {
        return fileName;
    }

    public void setFileName(String newFileName)
    {
        fileName = newFileName;
    }

    ////////////////////////////////////////////////////////////////////////////
    // workSaved() and isSaved() are two functions that help the ToyFrame decide
    // wheter or not the current text of t program has been saved or not
    public void workSaved()
    {
        savedText = text;
    }

    public boolean isSaved()
    {
        return savedText.equals(text);
    }

    public void revertToSaved()
    {
        setText(savedText, false);
    }

    ////////////////////////////////////////////////////////////////////////////
    // reset() resets the virtualMachine and reinitializes the memory with the
    // text from the most recently parsed string
    public void reset()
    {
        ToyWord stdin[] = initialVirtualMachine.getStdin();
        ToyWord stdinCopy[] = new ToyWord[stdin.length];

        for (int ctr = 0; ctr < stdin.length; ctr++)
            stdinCopy[ctr] = stdin[ctr];

        virtualMachine.reset();

        for (int ctr = 0; ctr < ToyVirtualMachine.MEM_CARDINALITY; ctr++)
            virtualMachine.setMem(ctr, initialVirtualMachine.getMem(ctr));

        virtualMachine.setStdin(stdinCopy);
    }

    ////////////////////////////////////////////////////////////////////////////
    // setText() parses a string into the virtualMachine's code and returns
    // a ToyWarningHash object so the Frame can display the problems with the code
    public ToyWarningHash setText(String parseString, boolean warningHashEnabled)
    {
        int line, charCtr;
        short address, previousAddress = -1;
        short op, rd, rs, rt;
        boolean functionEncountered = false;
        ToyWarningHash warningHash = new ToyWarningHash();

        text = parseString;

        //change the name if a new one can be parsed
        //check for changes to the program name
        if (text.startsWith("Program "))
        {
            int endIndex = text.indexOf('\n');

            if (endIndex < 0)
                endIndex = text.length();

            name = text.substring("Program ".length(), endIndex).trim();
        }


        //reset the parsedVirtualMachine
        parsedVirtualMachine.reset();

        //parse the text to rebuild both mem and warningHashs
        charCtr = 0;
        line = 1;

        //advance to first non-whitespace character or newline
        while (charCtr < text.length() &&
               Character.isWhitespace(text.charAt(charCtr)) &&
               text.charAt(charCtr) != '\n')
            charCtr++;

        while (charCtr + 7 < text.length() && !warningHash.maxedOut())
        {
            if (ToyWord.isHexDigit(text.charAt(charCtr)) &&
                ToyWord.isHexDigit(text.charAt(charCtr + 1)) &&
                text.charAt(charCtr + 2) == ':' &&
                text.charAt(charCtr + 3) == ' ' &&
                ToyWord.isHexDigit(text.charAt(charCtr + 4)) &&
                ToyWord.isHexDigit(text.charAt(charCtr + 5)) &&
                ToyWord.isHexDigit(text.charAt(charCtr + 6)) &&
                ToyWord.isHexDigit(text.charAt(charCtr + 7)))
            {
                address = (short)(ToyWord.convertFromHexDigit(text.charAt(charCtr)) * 0x10 +
                          ToyWord.convertFromHexDigit(text.charAt(charCtr + 1)));
                op = ToyWord.convertFromHexDigit(text.charAt(charCtr + 4));
                rd = ToyWord.convertFromHexDigit(text.charAt(charCtr + 5));
                rs = ToyWord.convertFromHexDigit(text.charAt(charCtr + 6));
                rt = ToyWord.convertFromHexDigit(text.charAt(charCtr + 7));

                //Fatal errors on which the text can't even be translated
                //into mem blocks
                if (previousAddress >= address)
                {
                    if (parsedVirtualMachine.getMem(address).isDefined())
                    {
                        warningHash.add(
                            line,
                            "Fatal error at " + ToyWord.toHexString(address).substring(2)+ ":",
                            "This address location has already been defined.  Please renumber " +
                            "your lines appropriately."
                            );
                        hasFatalError = true;
                        return warningHash;
                    }
                    else
                    {
                        warningHash.add(
                            line,
                            "Fatal error at " + ToyWord.toHexString(address).substring(2)+ ":",
                            "This address location is out of order.  Please renumber your " +
                            "lines appropriately."
                            );
                        hasFatalError = true;
                        return warningHash;
                    }
                }

                //Just warningHashs, disable if user doesn't want it
                //(Note how commands in the lower area of memory are not subject
                // to warningHashs because it's conventionally used for long-term
                // variable storage)
                if (warningHashEnabled && address >= 0x10)
                {
                    //check to make sure 10 is defined
                    if (!parsedVirtualMachine.getMem(0x10).isDefined() &&
                        address > 0x10 && previousAddress < 0x10)
                        warningHash.add(
                            line,
                            "Warning at 10:",
                            "The line 0x10 must be defined if your program is to run at all.  " +
                            "Please renumber your lines appropriately."
                            );

                    //check to make sure FF isn't being defined
                    if (address == 0xFF)
                        warningHash.add(
                            line,
                            "Warning at FF:",
                            "Memory location FF is reserved for input and output operations.  " +
                            "You may not be able to access this data."
                            );

                    //check for a missing line
                    if (address == previousAddress + 2 && previousAddress >= 0x10 &&
                        !functionEncountered)
                        warningHash.add(
                            line,
                            "Warning between " +
                            ToyWord.toHexString(previousAddress).substring(2) +
                            " and " + ToyWord.toHexString(address).substring(2) + ":",
                            "There is no definition of line " +
                            ToyWord.toHexString((short)(address - 1)).substring(2) +
                            ".  Note that this may not be a fatal error, it may be " +
                            "the space between functions.  If this really is the space " +
                            "between functions, please leave a function comment (Tools|" +
                            "Insert Function Comment)."
                            );

                    //check for missing lines
                    if (address > previousAddress + 2 && previousAddress >= 0x10 &&
                        !functionEncountered)
                    {
                        if (address - previousAddress == 7 && address % 0x10 == 0)
                            warningHash.add(
                                line,
                                "Warning between " +
                                ToyWord.toHexString(previousAddress).substring(2) +
                                " and " + ToyWord.toHexString(address).substring(2) + ":",
                                "There are undefined lines.  And really seems that you've " +
                                "forgotten that addresses are in base 16.  For instance, the " +
                                "number after 0x19 is not 0x20 it's 0x1A.  If this really is " +
                                "the space between functions, please leave a function comment " +
                                "(Tools|Insert Function Comment)."
                                );
                        else
                            warningHash.add(
                                line,
                                "Warning between " +
                                ToyWord.toHexString(previousAddress).substring(2) +
                                " and " + ToyWord.toHexString(address).substring(2) + ":",
                                "There are undefined lines.  Note that this may not be a " +
                                "fatal error, it may be the space between functions.  If this " +
                                "really is the space between functions, please leave a function " +
                                "comment (Tools|Insert Function Comment)."
                                );
                    }

                    //check for attempts to assign a value to a constant except
                    //with a 1000 command
                    if (rd == 0 && (op >= 1 && op <= 8 || op == 0xA || op == 0xF) &&
                        !(op == 1 && rd == 0 && rs == 0 && rt == 0))
                        warningHash.add(
                            line,
                            "Warning at " + ToyWord.toHexString(address).substring(2) + ":",
                            "Register 0 is a constant you cannot change its value."
                            );

                    //check for non-zero assignments to characters which arn't used
                    if (op == 0 && (rd != 0 || rs != 0 || rt != 0))
                        warningHash.add(
                            line,
                            "Warning at " + ToyWord.toHexString(address).substring(2) + ":",
                            "Operator 0 (halt) does not require an d, s, or t.  It is " +
                            "conventional to halt a program with \"0000\"."
                            );

                    if (op == 0xA && rs != 0)
                        warningHash.add(
                            line,
                            "Warning at " + ToyWord.toHexString(address).substring(2) + ":",
                            "Operator A (load indirect) does not require an s.  It is " +
                            "conventional to assign the 3rd digit to '0'."
                            );

                    if (op == 0xB && rs != 0)
                        warningHash.add(
                            line,
                            "Warning at " + ToyWord.toHexString(address).substring(2) + ":",
                            "Operator B (store indirect) does not require an s.  It is " +
                            "conventional to assign the 3rd digit to '0'."
                            );

                    if (op == 0xE && (rs != 0 || rt != 0))
                        warningHash.add(
                            line,
                            "Warning at " + ToyWord.toHexString(address).substring(2) + ":",
                            "Operator E (jump register) does not require an s or t.  It is " +
                            "conventional to terminate such a command with two '0's."
                            );

                    //check for simple infinite loops
                    if (op == 0xC &&
                        rs * 16 + rt == address)
                        warningHash.add(
                            line,
                            "Warning at " + ToyWord.toHexString(address).substring(2) + ":",
                            "This command is an infinite loop."
                            );

                    if ((op == 0xD || op == 0xF) &&
                        rs * 16 + rt == address + 1)
                        warningHash.add(
                            line,
                            "Warning at " + ToyWord.toHexString(address).substring(2) + ":",
                            "This command could be an infinite loop."
                            );

                    //check for lines which are guaranteed not to do a thing
                    if ((op == 0xC || op == 0xD) &&
                        rs * 16 + rt == address + 1)
                        warningHash.add(
                            line,
                            "Warning at " + ToyWord.toHexString(address).substring(2) + ":",
                            "This is a command to jump to the next line.  In other words, " +
                            "this line appears to serve no purpose."
                            );

                    if (op == 0xD && rd == 0)
                        warningHash.add(
                            line,
                            "Warning at " + ToyWord.toHexString(address).substring(2) + ":",
                            "Operator D (branch positive) is guaranteed not to branch since " +
                            "R[0] is always 0.  In other words, his line appears to serve no " +
                            "purpose."
                            );

                    if ((op == 1 || op == 3) && (rd == rs && rt == 0 || rd == rt && rs == 0) && !(op == 1 && rd == 0 && rs == 0 && rt == 0) ||
                         op == 2 && rd == rs && rt == 0)
                        warningHash.add(
                            line,
                            "Warning at " + ToyWord.toHexString(address).substring(2) + ":",
                            "This command is guaranteed not to change R[" +
                            ToyWord.convertToHexDigit(rd) + "] because R[0] is always 0; " +
                            "In other words, this line appears to serve no purpose."
                            );

                    if (op == 4 && rd == rs && rs == rt ||
                        (op == 5 || op == 6) && rd == rs && rt == 0)
                        warningHash.add(
                            line,
                            "Warning at " + ToyWord.toHexString(address).substring(2) + ":",
                            "This command is guaranteed not to change R[" +
                            ToyWord.convertToHexDigit(rd) + "].  In other words, this " +
                            "line appears to serve no purpose."
                            );
                }

                parsedVirtualMachine.setMem(
                    address,
                    new ToyWord((short)((op << 12) | (rd << 8) | (rs << 4) | rt))
                    );

                previousAddress = address;

                functionEncountered = false;
            }
            else {
                //if it's not a hex command, maybe it's a function comment

                if (charCtr + 12 < text.length() &&
                    text.charAt(charCtr) == '/' &&
                    text.charAt(charCtr + 1) == '/' &&
                    text.substring(charCtr).startsWith("// Function "))
                    functionEncountered = true;
            }


            //advance to and past the next newline
            while (charCtr < text.length() && text.charAt(charCtr) != '\n')
                charCtr++;
            charCtr++;

            //advance to first non-whitespace character or newline
            while (charCtr < text.length() &&
                   Character.isWhitespace(text.charAt(charCtr)) &&
                   text.charAt(charCtr) != '\n')
                charCtr++;

            line++;
        }

        //copy the parsedVirtualMachine over to the initialVirtualMachine
        //(note how we're leaving the stdin of initialVirtualMachine intact by
        // not resetting it)
        for (int ctr = 0; ctr < ToyVirtualMachine.MEM_CARDINALITY; ctr++)
            initialVirtualMachine.setMem(
                ctr,
                parsedVirtualMachine.getMem(ctr)
                );

        reset();

        hasFatalError = false;
        return warningHash;
    }

    ////////////////////////////////////////////////////////////////////////////
    // getDebugHash()
    public ToyDebugHash getDebugHash()
    {
        int stringCtr = 0;
        boolean printedPreviousLine = false;
        String codeLine;
        ToyDebugHash answer = new ToyDebugHash();

        //go through each line and print the defined ones
        //if a line is undefined and the previous line was defined, print a "..."
        //but if a line was undefined and it's predecessor was undefined, don't
        //print a "..."
        //(this will make it so that only one "..." is printed for every block
        // of undefined lines)
        for (short ctr = 0; ctr <= 0xFF; ctr++)
        {
            if (virtualMachine.getMem(ctr).isDefined())
            {
                codeLine = Integer.toHexString(ctr).toUpperCase();
                if (codeLine.length() < 2)
                    codeLine = "0" + codeLine;
                codeLine += ": ";
                codeLine += virtualMachine.getMem(ctr).toHexString() + "  ";

                if (ctr < 0x10)
                    //if we're in the low memory range, also print the binary and
                    //decimal translations of the mem[] contents
                    codeLine += "(" + virtualMachine.getMem(ctr).toFormattedBinaryString() + ", " +
                                      virtualMachine.getMem(ctr).toDecimalString() + ")";
                    else
                    //if we're in the upper memory range, also print the pseudocode
                    //equilvanet of the mem[] contents
                    codeLine += virtualMachine.getMem(ctr).toPseudoCodeString();

                answer.add(ctr, codeLine);
                printedPreviousLine = true;
            }
            else
            {
                if (printedPreviousLine)
                {
                    answer.add(ctr, "...");
                    printedPreviousLine = false;
                }
            }
        }

        return answer;
    }

    ////////////////////////////////////////////////////////////////////////////
    // getModifiedText() returns either a modification of a specified string, or
    // a modification of the most recently parsed string.  This modification
    // will alter the text just enough so that it will correctly parse into the
    // current state of the virtual machine
    public String getModifiedText()
    {
        return getModifiedText(text);
    }

    public String getModifiedText(String newText)
    {
        int charCtr;
        short addr, data;
        short op, rd, rs, rt;
        short addrCtr;

        //first, go through the text, and update all entries that are already
        //there

        //advance to first non-whitespace character or newline
        charCtr = 0;
        while (charCtr < newText.length() &&
               Character.isWhitespace(newText.charAt(charCtr)) &&
               newText.charAt(charCtr) != '\n')
            charCtr++;

        while (charCtr + 7 < newText.length())
        {
            if (ToyWord.isHexDigit(newText.charAt(charCtr)) &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 1)) &&
                newText.charAt(charCtr + 2) == ':' &&
                newText.charAt(charCtr + 3) == ' ' &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 4)) &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 5)) &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 6)) &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 7)))
            {
                addr = (short)(ToyWord.convertFromHexDigit(newText.charAt(charCtr)) * 0x10
                        + ToyWord.convertFromHexDigit(newText.charAt(charCtr + 1)));

                newText = newText.substring(0, charCtr) +
                          ToyWord.toHexString(addr).substring(2)
                          + ": " + virtualMachine.getMem(addr).toHexString()
                          + newText.substring(charCtr + 8);
            }

            while (charCtr < newText.length() && newText.charAt(charCtr) != '\n')
                charCtr++;
            charCtr++;

            //advance to first non-whitespace character or newline
            while (charCtr < newText.length() &&
                   Character.isWhitespace(newText.charAt(charCtr)) &&
                   newText.charAt(charCtr) != '\n')
                charCtr++;
        }

        //next, go through the text, and add any new defined entries
        addrCtr = 0;

        //find our first defined mem slot
        while (addrCtr < ToyVirtualMachine.MEM_CARDINALITY &&
               !virtualMachine.getMem(addrCtr).isDefined())
            addrCtr++;

        //advance to first non-whitespace character or newline
        charCtr = 0;
        while (charCtr < newText.length() &&
               Character.isWhitespace(newText.charAt(charCtr)) &&
               newText.charAt(charCtr) != '\n')
            charCtr++;

        while (addrCtr < ToyVirtualMachine.MEM_CARDINALITY &&
               charCtr + 7 < newText.length())
        {
            if (ToyWord.isHexDigit(newText.charAt(charCtr)) &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 1)) &&
                newText.charAt(charCtr + 2) == ':' &&
                newText.charAt(charCtr + 3) == ' ' &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 4)) &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 5)) &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 6)) &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 7)))
            {
                addr = (short)(ToyWord.convertFromHexDigit(newText.charAt(charCtr)) * 0x10
                        + ToyWord.convertFromHexDigit(newText.charAt(charCtr + 1)));

                //if the next defined mem slot correlates with the next
                //definition in the code, find the next mem slot
                if (addrCtr == addr)
                {
                    addrCtr++;
                    while (addrCtr < ToyVirtualMachine.MEM_CARDINALITY &&
                           !virtualMachine.getMem(addrCtr).isDefined())
                        addrCtr++;
                }

                //if the next defined mem slot is smaller than the next
                //definition in the code...
                if (addrCtr < addr)
                {
                    //insert the string of that mem slot into the code
                    newText = newText.substring(0, charCtr)
                              + ToyWord.toHexString(addrCtr).substring(2)
                              + ": " + virtualMachine.getMem(addrCtr).toHexString() + "\n"
                              + newText.substring(charCtr);

                    //advance to the next defined mem slot
                    addrCtr++;
                    while (addrCtr < ToyVirtualMachine.MEM_CARDINALITY &&
                           !virtualMachine.getMem(addrCtr).isDefined())
                        addrCtr++;
                }
            }

            //advance to the next line
            while (charCtr < newText.length() && newText.charAt(charCtr) != '\n')
                charCtr++;
            charCtr++;

            //advance to first non-whitespace character or newline
            while (charCtr < newText.length() &&
                   Character.isWhitespace(newText.charAt(charCtr)) &&
                   newText.charAt(charCtr) != '\n')
                charCtr++;
        }

        //we arn't done yet! tag along any remianing defined mem slots to the
        //end of the string
        if (addrCtr < ToyVirtualMachine.MEM_CARDINALITY)
        {
            newText = newText.trim();
            newText += "\n\n";
        }
        while (addrCtr < ToyVirtualMachine.MEM_CARDINALITY)
        {
            int oldAddrCtr = addrCtr;
            newText += ToyWord.toHexString(addrCtr).substring(2)
                      + ": " + virtualMachine.getMem(addrCtr).toHexString() + "\n";

            addrCtr++;
            while (addrCtr < ToyVirtualMachine.MEM_CARDINALITY &&
                   !virtualMachine.getMem(addrCtr).isDefined())
                addrCtr++;

            if (addrCtr - oldAddrCtr > 1 && addrCtr < ToyVirtualMachine.MEM_CARDINALITY)
                newText += "\n";
        }

        //finally, go through the newText, and add pseudocode to the code lines
        //that are of the form "##: ####*\n" where * is any amount of whitespace
        //or COMMENT_WIDTH characters that begin with three spaces and end with
        //at least one space (no newlines in between obviously)

        //advance to first non-whitespace character or newline
        charCtr = 0;
        while (charCtr < newText.length() &&
               Character.isWhitespace(newText.charAt(charCtr)) &&
               newText.charAt(charCtr) != '\n')
            charCtr++;

        while (charCtr + 7 < newText.length())
        {
            if (ToyWord.isHexDigit(newText.charAt(charCtr)) &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 1)) &&
                newText.charAt(charCtr + 2) == ':' &&
                newText.charAt(charCtr + 3) == ' ' &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 4)) &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 5)) &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 6)) &&
                ToyWord.isHexDigit(newText.charAt(charCtr + 7)))
            {
                int endCtr = charCtr + 8;

                //try the all whitespace condition
                while (endCtr < newText.length() &&
                       Character.isWhitespace(newText.charAt(endCtr)) &&
                       newText.charAt(endCtr) != '\n')
                    endCtr++;

                //try the COMMENT_WITH character condition
                if (endCtr < newText.length() && newText.charAt(endCtr) != '\n')
                {
                    while (endCtr < newText.length() &&
                           endCtr - (charCtr + 8) < COMMENT_WIDTH &&
                           newText.charAt(endCtr) != '\n')
                        endCtr++;
                }

                addr = (short)(ToyWord.convertFromHexDigit(newText.charAt(charCtr)) * 0x10
                    + ToyWord.convertFromHexDigit(newText.charAt(charCtr + 1)));

                data = (short)(ToyWord.convertFromHexDigit(newText.charAt(charCtr + 4)) << 12 |
                               ToyWord.convertFromHexDigit(newText.charAt(charCtr + 5)) << 8 |
                               ToyWord.convertFromHexDigit(newText.charAt(charCtr + 6)) << 4 |
                               ToyWord.convertFromHexDigit(newText.charAt(charCtr + 7)));

                if (addr < 0x10)
                {
                    String comment = "   " + ToyWord.toString(data);

                    while (comment.length() < COMMENT_WIDTH)
                        comment += " ";

                    newText = newText.substring(0, charCtr + 8)
                              + comment
                              + newText.substring(endCtr);
                }
                else {
                    String comment = "   " + ToyWord.toPseudoCodeString(data);

                    while (comment.length() < COMMENT_WIDTH)
                        comment += " ";

                    newText = newText.substring(0, charCtr + 8)
                              + comment
                              + newText.substring(endCtr);
                }
            }

            while (charCtr < newText.length() && newText.charAt(charCtr) != '\n')
                charCtr++;
            charCtr++;

            //advance to first non-whitespace character or newline
            while (charCtr < newText.length() &&
                   Character.isWhitespace(newText.charAt(charCtr)) &&
                   newText.charAt(charCtr) != '\n')
                charCtr++;
        }

        return newText;
    }
}
