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

/**
 * @author David Matuszek
 * @version Feb 20, 2004
 */
public class NewParserTest extends TestCase {
    private 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", "list", "header", "program" };
    public static Set keywords = new TreeSet(Arrays.asList(keywordValues));

    /**
     * Constructor for NewParserTest.
     * @param arg0
     */
    public NewParserTest(String arg0) {
        super(arg0);
    }

    public void testParser() {
        parser = new Parser("");
        parser = new Parser("2 + 2");
    }

    public void testExpression() {
        BinaryTree expected;
        
        use("250");
        assertTrue(parser.expression());
        assertStackTop(bt("250"));
        
        use("hello");
        assertTrue(parser.expression());
        assertStackTop(bt("hello"));

        use("(xyz + 3)");
        assertTrue(parser.expression());
        assertStackTop(bt("+", "xyz", "3"));

        use("a + b + c");
        assertTrue(parser.expression());
        assertStackTop(bt("+", bt("+", "a", "b"), "c"));

        use("3 * 12 - 7");
        assertTrue(parser.expression());
        assertStackTop(bt("-", bt("*", "3", "12"), bt("7")));

        use("12 * 5 - 3 * 4 / 6 + 8");
        assertTrue(parser.expression());
        expected = bt("+",
                      bt("-",
                         bt("*", "12", "5"),
                         bt("/",
                            bt("*", "3", "4"),
                            "6"
                           )
                        ),
                      "8"
                     );
        assertStackTop(expected);
                     
        use("12 * ((5 - 3) * 4) / 6 + (8)");
        assertTrue(parser.expression());
        expected = bt("+",
                      bt("/",
                         bt("*",
                            "12",
                            bt("*",
                               bt("-","5","3"),
                               "4")),
                         "6"),
                      "8");
        assertStackTop(expected);
        
        use("");
        assertFalse(parser.expression());
        
        use("#");
        assertFalse(parser.expression());

        try {
            use("17 +");
            assertFalse(parser.expression());
            fail();
        }
        catch (LogoParseException e) {
        }
        try {
            use("22 *");
            assertFalse(parser.expression());
            fail();
        }
        catch (LogoParseException e) {
        }
    }

    public void testTerm() {        
        use("12");
        assertTrue(parser.term());
        assertStackTop(bt("12"));

        use("3*12");
        assertTrue(parser.term());
        assertStackTop(bt("*", "3", "12"));

        use("x * y * z");
        assertTrue(parser.term());
        assertStackTop(bt("*", bt("*", "x", "y"), "z"));
        
        use("20 * 3 / 4");
        assertTrue(parser.term());
        assertStackTop(bt("/", bt("*", "20", "3"), bt("4")));

        use("20 * 3 / 4 + 5");
        assertTrue(parser.term());
        assertStackTop(bt("/", bt("*", "20", "3"), "4"));
        unused("+ 5");
        
        use("");
        assertFalse(parser.term());
        unused("");
        
        use("#");
        assertFalse(parser.term());unused("#");

    }

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

        use("hello");
        assertTrue(parser.factor());
        assertStackTop(bt("hello"));
        
        use("(xyz + 3)");
        assertTrue(parser.factor());
        assertStackTop(bt("+", "xyz", "3"));
        
        use("12 * 5");
        assertTrue(parser.factor());
        assertStackTop(bt("12"));
        unused("* 5");
        
        use("17 +");
        assertTrue(parser.factor());
        assertStackTop(bt("17"));
        unused("+");

        use("");
        assertFalse(parser.factor());
        unused("");
        
        use("#");
        assertFalse(parser.factor());
        unused("#");
    }

    public void testAdd_operator() {
        use("+ - + $");
        assertTrue(parser.addOperator());
        assertStackTop(bt("+"));
        assertTrue(parser.addOperator());
        assertStackTop(bt("-"));
        assertTrue(parser.addOperator());
        assertFalse(parser.addOperator());
        unused("$");
    }

    public void testMultiply_operator() {
        use("* / $");
        assertTrue(parser.multiplyOperator());
        assertTrue(parser.multiplyOperator());
        assertFalse(parser.multiplyOperator());
        unused("$");
    }
    
    final public void testVariable() {
        use("hello end list header program");
        assertTrue(parser.variable());
        assertFalse(parser.variable());
        unused("end");
        assertTrue(parser.variable());
        assertTrue(parser.variable());
        assertTrue(parser.variable());
    }
    
    public void testProgram() {
        use("penup \n");
        assertTrue(parser.program());
        assertRootValueEquals("program");
        assertLeftChildIsList(1);
        assertRightChildIsList(0);
        assertStackTop(bt("program", bt("list", "penup", null), bt("list")));

        use("penup \n pendown \n to foo \n end \n");
        assertTrue(parser.program());
        assertRootValueEquals("program");
        assertLeftChildIsList(2);
        assertRightChildIsList(1);
    }
    
    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(".");
        
        use("color chartreuse \n");
        try {
            parser.command();
            fail();
        }
        catch (LogoParseException e) {}
    }
    
    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));
                
        use("set x y z \n");
        try {
            parser.command();
            fail("Error in set command");
        }
        catch (LogoParseException e) {}
    }
    
    public void testRepeat() {
        use("repeat 3 * n + 1 [ \n penup \n pendown \n ] \n");
        BinaryTree expr = bt("+", bt("*", "3", "n"), "1");
        assertTrue(parser.command());
        assertRootValueEquals("repeat");
        assertLeftChildEquals(expr);
        assertRightChildIsList(2);
        
        use("repeat 5 [ \n ] \n");
        assertTrue(parser.command());        
        assertRootValueEquals("repeat");
        assertLeftChildEquals(bt("5"));
        assertRightChildIsList(0);
        
        use("repeat 12 \n [ \n penup \n ] \n");
        try {
            parser.command();
            fail("Error in repeat command. ");
        }
        catch (LogoParseException e) {}
    }
    
    public void testWhile() {
        BinaryTree condition = bt("=", "2", "2");

        use("while 2 = 2 [ \n penup \n pendown \n ] \n");
        assertTrue(parser.command());
        assertRootValueEquals("while");
        assertLeftChildEquals(condition);
        assertRightChildIsList(2);
        
        use("while 2 = 2 [ \n ] \n");
        assertTrue(parser.command());
        assertRootValueEquals("while");
        assertLeftChildEquals(condition);
        assertRightChildIsList(0);
        
        use("while 12 [ \n penup \n ] \n");
        try {
            parser.command();
            fail("Error in while command. ");
        }
        catch (LogoParseException e) {}
    }

    public void testIf() {
        BinaryTree condition = bt("=", "2", "2");
        
        use("if 2 = 2 [ \n penup \n pendown \n ] \n");
        assertTrue(parser.command());
        assertRootValueEquals("if");
        assertLeftChildEquals(condition);
        assertRightChildIsList(2);
        
        use("if 2 = 2 [ \n ] \n");
        assertTrue(parser.command());
        assertRootValueEquals("if");
        assertLeftChildEquals(condition);
        assertRightChildIsList(0);
        
        use("if 12 [ \n penup \n ] \n");
        try {
            parser.command();
            fail("Error in if command. ");
        }
        catch (LogoParseException e) {}
    }
    
    public void testDo() {
        use("do foo x y + z \n");
//        BinaryTree expr = bt("+", "y", "z");
//        BinaryTree wholeThing = bt("do", "foo", bt("list", "x", bt("list", expr, null)));
        assertTrue(parser.command());
        assertRootValueEquals("do");
        assertLeftChildEquals(bt("foo"));
        assertRightChildIsList(2);

        use("do foo \n");
        assertTrue(parser.command());
        assertRootValueEquals("do");
        assertLeftChildEquals(bt("foo"));
        assertRightChildIsList(0);
    }

    final public void testMove() {
        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 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"));

        try {
            use("[ ]");
            parser.block();
            fail();
        }
        catch (LogoParseException e) {}
    }
    
    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));
        
        use("2 <");
        try {
            parser.condition();
            fail("Not a condition. ");
        }
        catch (LogoParseException e) {}
    }

    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 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));
        
        use("to foo \n end \n");
        assertTrue(parser.procedure());
        assertStackTop(bt("to", bt("header", "foo", "list"), "list"));
    }
    
    final public void testEol() {
        use("+ \n + \n");
        assertFalse(parser.eol()); skip();
        assertTrue(parser.eol());
        assertFalse(parser.eol()); skip();
        assertTrue(parser.eol());
    }


//  ----- "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 expectedToken = expected.next();
            if (expectedToken.getType() == Token.EOI) {
                return;
            }
            Token actualToken = actual.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);
    }
    
    /**
     * Removes and ignores the next Token.
     */
    private void skip() {
        nextToken(parser);
    }
    
    /**
     * 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 + ").\n";
        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));
    }
    
    /**
     * Checks <i>only the root node</i> of the current parse
     * tree to see whether it is the expected keyword.
     * 
     * @param expected The expected keyword (including those
     * words defined as keywords by the Parser).
     */
    private void assertRootValueEquals(String expected) {
        BinaryTree root = ((BinaryTree) parser.stack.peek());
        assertEquals(new Token(expected, Token.KEYWORD), root.value);
    }
    
    /**
     * Asserts that the left child of the top BinaryTree in the
     * stack is exactly as expected.
     * 
     * @param expected The expected left child.
     */
    private void assertLeftChildEquals(BinaryTree expected) {
        assertEquals(expected, getRootNodeInStack().getLeftChild());
    }
    
    /**
     * Asserts that the right child of the top BinaryTree in the
     * stack is exactly as expected.
     * 
     * @param expected The expected right child.
     */    private void assertRightChildEquals(BinaryTree expected) {
        assertEquals(expected, getRootNodeInStack().getRightChild());
    }
    
    /**
     * @return The top BinaryTree in the stack.
     */
    private BinaryTree getRootNodeInStack() {
        return ((BinaryTree) parser.stack.peek());
    }
    
    /**
     * Tests whether the list structure rooted at the left child of
     * the top BinaryTree in the stack contains <code>count</code>
     * values and has the correct structure.
     * 
     * @param count The expected number of left non-list leaves.
     */
    private void assertLeftChildIsList(int count) {
        checkListStructure(count, getRootNodeInStack().getLeftChild());
    }

    /**
     * Tests whether the list structure rooted at the right child of
     * the top BinaryTree in the stack contains <code>count</code>
     * values and has the correct structure.
     * 
     * @param count The expected number of left non-list leaves.
     */
    private void assertRightChildIsList(int count) {
        checkListStructure(count, getRootNodeInStack().getRightChild());
    }

    /**
     * Tests whether the list structure rooted at the <code>listNode</code>
     * contains <code>count</code> values and has the correct structure.
     * 
     * @param count The expected number of left non-list leaves.
     * @param listNode The root of the BinaryTree to check.
     */
    private void checkListStructure(int count, BinaryTree listNode) {
        if (count == 0) {
            assertEquals(
                "Empty list should consist of a single \"list\" leaf node.\n"
                    + "This error will only be counted once.\n",
                bt("list"),
                listNode);
            return;
        }
        for (int i = 1; i <= count; i++) {
            assertNotNull(
                "\"list\" node should have a left child.\n",
                listNode);
            listNode = listNode.getRightChild();
            if (i < count) {
                assertNotNull("Missing nodes in \"list\".\n", listNode);
            }
            else {
                assertNull(
                    "Bottom \"list\" node should not have a right child.\n"
                        + "This error will only be counted once.\n",
                    listNode);
            }
        }
    }
    
    /**
     * 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)) {
                return new Token(s, Token.KEYWORD);
            } else {
                return new Token(s, Token.NAME);
            }
        }
        else
            return new Token(s, Token.SYMBOL);
    }
}
