import junit.framework.TestCase;
import java.util.*;

/*
 * Created on Feb 4, 2004
 */

/**
 * @author David Matuszek
 * @version Feb 6, 2004
 */
public class DrDavesTests extends TestCase {
    Parser parser;
    private static String[] keywordValues = {
        "penup", "pendown", "color", "home", "set", "repeat",
        "while", "if", "do", "forward", "right", "left",
        "red", "orange", "yellow", "green", "blue", "purple",
        "violet", "brown", "black", "white", "gray", "pink",
         "to", "end" };
         
    public static Set keywords = new TreeSet(Arrays.asList(keywordValues));
        
    static Set extraKeywords =
        new TreeSet(
            Arrays.asList(new String[] { "list", "header", "program" }));
    
    /**
     * Constructor for ParserTest.
     * @param arg0
     */
    public DrDavesTests(String arg0) {
        super(arg0);
    }

    public void testExpression() {
        BinaryTree expected;
        
        use("12 * 5 - 3 * 4 / 6 + 8");
        assertTrue(parser.expression());
        expected = bt("+",
                      bt("-",
                         bt("*", "12", "5"),
                         bt("/",
                            bt("*", "3", "4"),
                            "6"
                           )
                        ),
                      "8"
                     );
        assertStackTop(expected);
    }

    public void testTerm() {        
        use("20 * 3 / 4");
        assertTrue(parser.term());
        assertStackTop(bt("/", bt("*", "20", "3"), bt("4")));
    }

    public void testFactor() {
        use("(12)");
        assertTrue(parser.factor());
        assertStackTop(bt("12"));
    }

    final public void testVariable() {
        use("hello end");
        assertTrue(parser.variable());
        assertFalse(parser.variable());
        unused("end");
    }
    
    public void testProgram() {
        use("penup \n");
        assertTrue(parser.program());
        assertStackTop(bt("program", bt("list", "penup", null), bt("list")));
    }
    
    public void testPenup() {
        use("penup \n");
        assertTrue(parser.command());
        assertStackTop(bt("penup"));
    }
    
    public void testPendown() {
        use("pendown \n");
        assertTrue(parser.command());
        assertStackTop(bt("pendown"));
    }
    
    public void testColor() {
        use("color red \n .");
        assertTrue(parser.command());
        assertStackTop(bt("color", "red", null));
        unused(".");
    }
    
    public void testHome() {
        use("home \n");
        assertTrue(parser.command());
        assertStackTop(bt("home"));
    }
    
    public void testSet() {
        use("set x y \n");
        assertTrue(parser.command());
        assertStackTop(bt("set", "x", "y"));
        
        use("set x 3 * n + 1 \n");
        assertTrue(parser.command());
        BinaryTree expr = bt("+", bt("*", "3", "n"), "1");
        assertStackTop(bt("set", "x", expr));
    }
    
    public void testRepeat() {
        use("repeat 5 [ \n ] \n");
        assertTrue(parser.command());
        assertStackTop(bt("repeat", "5", "list"));
        
        use("repeat 3 * n + 1 [ \n penup \n pendown \n ] \n");
        BinaryTree expr = bt("+", bt("*", "3", "n"), "1");
        BinaryTree list = bt("list", bt("penup"), bt("list", "pendown", null));
        assertTrue(parser.command());
        assertStackTop(bt("repeat", expr, list));
    }
    
    public void testWhile() {
        use("while 2 = 2 [ \n ] \n");
        assertTrue(parser.command());
        BinaryTree condition = bt("=", "2", "2");
        assertStackTop(bt("while", condition, "list"));
        
        use("while 2 = 2 [ \n penup \n pendown \n ] \n");
        BinaryTree list = bt("list", bt("penup"), bt("list", "pendown", null));
        assertTrue(parser.command());
        assertStackTop(bt("while", condition, list));
    }

    public void testIf() {
        use("if 2 = 2 [ \n ] \n");
        assertTrue(parser.command());
        BinaryTree condition = bt("=", "2", "2");
        assertStackTop(bt("if", condition, "list"));
        
        use("if 2 = 2 [ \n penup \n pendown \n ] \n");
        BinaryTree list = bt("list", bt("penup"), bt("list", "pendown", null));
        assertTrue(parser.command());
        assertStackTop(bt("if", condition, list));
    }
    
    public void testDo() {
        use("do foo \n");
        assertTrue(parser.command());
        assertStackTop(bt("do", "foo", "list"));
        
        use("do foo x y + z \n");
        assertTrue(parser.command());
        BinaryTree expr = bt("+", "y", "z");
        assertStackTop(bt("do", "foo", bt("list", "x", bt("list", expr, null))));
    }

    final public void testMoveCommand() {
        use("forward 5 \n");
        assertTrue(parser.command());
        assertStackTop(bt("forward", "5", null));
        
        use("left 5 \n");
        assertTrue(parser.command());
        assertStackTop(bt("left", "5", null));
        
        use("right 2 + 2 \n");
        assertTrue(parser.command());
        assertStackTop(bt("right", bt("+", "2", "2"), null));
    }

    final public void testMove() {
        use("forward");
        assertTrue(parser.move());
        assertStackTop(bt("forward"));
        
        use("left");
        assertTrue(parser.move());
        assertStackTop(bt("left"));
        
        use("right");
        assertTrue(parser.move());
        assertStackTop(bt("right"));
    }
    
    final public void testColorName() {
        use("red orange yellow green blue purple " +
                            "violet brown black white gray pink x");
        assertTrue(parser.colorName()); //red
        assertStackTop(bt("red"));
        assertTrue(parser.colorName()); //orange
        assertTrue(parser.colorName()); // yellow
        assertTrue(parser.colorName()); // green
        assertTrue(parser.colorName()); // blue
        assertTrue(parser.colorName()); // purple
        assertTrue(parser.colorName()); // violet
        assertTrue(parser.colorName()); // brown
        assertTrue(parser.colorName()); // black
        assertTrue(parser.colorName()); // white
        assertTrue(parser.colorName()); // gray
        assertTrue(parser.colorName()); // pink
        assertFalse(parser.colorName()); // x
        unused("x");
    }

    public void testBlock() {
        use("[ \n penup \n pendown \n ] \n");
        assertTrue(parser.block());
        BinaryTree list = bt("list", bt("penup"), bt("list", "pendown", null));
        assertStackTop(bt(list));

        use("[ \n ] \n");
        assertTrue(parser.block());
        assertStackTop(bt("list"));
    }
    
    final public void testCondition() {
        BinaryTree expr = bt("+", "4", "y");
        Token comma = symbol(",");

        use("2 < 3, 4 + y = 4 + y");
        
        assertTrue(parser.condition()); // 2 < 3
        assertStackTop(bt("<", "2", "3"));
        
        assertFalse(parser.condition());
        assertEquals(comma, nextToken(parser));

        assertTrue(parser.condition()); // 4 + y = 4 + y
        assertStackTop(bt("=", expr, expr));
    }

    final public void testComparator() {
        use("< = <> !");
        assertTrue(parser.comparator()); //  <
        assertStackTop(bt("<"));
        assertTrue(parser.comparator()); //  =
        assertTrue(parser.comparator()); //  <
        assertTrue(parser.comparator()); //  >
        assertFalse(parser.comparator()); // !
        unused("!");
    }
    
    final public void testProcedure() {
        use("to foo \n end \n");
        assertTrue(parser.procedure());
        assertStackTop(bt("to", bt("header", "foo", "list"), "list"));
        
        use("to foo x y \n penup \n end \n");
        BinaryTree varList = bt("list", "x", bt("list", "y", null));
        BinaryTree header = bt("header", "foo", varList);
        BinaryTree commandList = bt("list", "penup", null);
        assertTrue(parser.procedure());
        assertStackTop(bt("to", header, commandList));
    }
    
    final public void testEol() {
        use("+ \n \n + \r +");
        assertFalse(parser.eol()); skip(parser);
        assertTrue(parser.eol());
        assertFalse(parser.eol()); skip(parser);
        assertFalse(parser.eol());
        unused("\r +");
    }

//  ----- "Helper" methods

    /**
     * Tests whether the next symbols returned by the Tokenizer
     * correspond to the symbols in the given String, and throws
     * an assertion error if they do not. (This method was previously
     * named "remainder," but the name was changed to reflect changed
     * functionality.
     * 
     * @param whatShouldFollow The string of the next few expected tokens.
     */
    private void unused(String whatShouldFollow) {
        Tokenizer actual = parser.tokenizer;
        Tokenizer expected = new Tokenizer(whatShouldFollow);
        while (expected.hasNext()) {
            Token actualToken = actual.next();
            Token expectedToken = expected.next();
            assertEquals(expectedToken, actualToken);
        }    
    }
    
    /**
     * Sets the <code>parser</code> instance to use the given string.
     * 
     * @param s The string to be parsed.
     */
    private void use(String s) {
        parser = new Parser(s);
    }
    
    private void skip(Parser p) {
        nextToken(p);
    }
    
    /**
     * Creates a Token of type NAME with the specified value.
     * 
     * @param value The value of the new Token.
     * @return The new Token.
     */
    private Token name(String value) {
        return new Token(value, Token.NAME);
    }
    
    /**
     * Creates a Token of type SYMBOL with the specified value.
     * 
     * @param value The value of the new Token.
     * @return The new Token.
     */
    private Token symbol(String value) {
        return new Token(value, Token.SYMBOL);
    }

    /**
     * Returns the next Token from the given Recognizer.
     * 
     * @param r The Recognizer to use.
     * @return The next Token from the given Recognizer.
     */
    private Token nextToken(Parser p) {
        return p.tokenizer.next();
    }
    
    /**
     * Asserts that the parameter is equal to the top of the stack.
     * 
     * @param bt The BinaryTree to compare against the top of the stack.
     */
    private void assertStackTop(BinaryTree bt) {
        assertEquals(bt, parser.stack.peek());
    }
    
    /**
     * Returns a BinaryTree node consisting of a single leaf; the
     * node will contain a Token with a String as its value. <br>
     * Given a BinaryTree, return the same BinaryTree.<br>
     * Given a Token, return a BinaryTree with the Token as its value.<br>
     * Given a String, make it into a Token, return a BinaryTree
     * with the Token as its value.
     * 
     * @param value A BinaryTree, Token, or String from which to
              construct the BinaryTree node.
     * @return A BinaryTree leaf node containing a Token whose value
     *         is the parameter.
     */
    private BinaryTree bt(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof BinaryTree) {
            return (BinaryTree) value;
        }
        if (value instanceof Token) {
            return new BinaryTree(value);
        }
        else if (value instanceof String) {
            return new BinaryTree(makeToken((String) value));
        }
        assert false: "Illegal argument: bt(" + value + ")";
        return null; 
    }
    
    /**
     * Builds a BinaryTree that can be compared with the one the
     * Parser produces. Any String or Token arguments will be
     * converted to BinaryTree nodes containing Tokens.
     * 
     * @param op The String value to use in the Token in the root.
     * @param leftChild The object to be made into a left child.
     * @param rightChild The object to be made into a right child.
     * @return The resultant BinaryTree.
     */
    private BinaryTree bt(String op, Object leftChild, Object rightChild) {
        return new BinaryTree(makeToken(op), bt(leftChild), bt(rightChild));
    }
    
    /**
     * Quick'n'dirty routine to make a Token from a String. The
     * type (name, number, or symbol) is inferred from the first
     * character; no error checking is done.
     * 
     * @param s The string to turn into a Token.
     * @return A Token whose value is the given string and whose
     *         type has been inferred from the first character.
     */
    private Token makeToken(String s) {
        char ch = s.charAt(0);
        if (Character.isDigit(ch))
            return new Token(s, Token.NUMBER);
        if (Character.isLetter(ch)) {
            if (keywords.contains(s) || extraKeywords.contains(s)) {
                return new Token(s, Token.KEYWORD);
            } else {
                return new Token(s, Token.NAME);
            }
        }
        else
            return new Token(s, Token.SYMBOL);
    }
}
