package allTests;

import static org.junit.Assert.*;

import org.junit.Test;

import parser.Parser;

import tokenizer.Token;
import tokenizer.Tokenizer;
import tree.Tree;

/**
 * @author Dave Matuszek
 * @version April 1, 2012
 */
public class ParserTest {

    @Test
    public final void testSimpleExpression() {
        Parser p;
        Tree<Token> actual, expected;
        
        p = new Parser("5");
        assertTrue(p.expression());
        actual = p.getParseTree();
        expected = makeTree("5");
        assertEquals(expected, actual);
        
        p = new Parser("-5");
        assertTrue(p.expression());
        actual = p.getParseTree();
        expected = makeTree("-(5)");
        assertEquals(expected, actual);
        
        p = new Parser("+5");
        assertTrue(p.expression());
        actual = p.getParseTree();
        expected = makeTree("+(5)");
        assertEquals(expected, actual);
        
        p = new Parser("2 * 3");
        assertTrue(p.expression());
        actual = p.getParseTree();
        expected = makeTree("*(2, 3)");
        assertEquals(expected, actual);
        
        // <expression> ::= <term> { <add_operator> <term> }
        p = new Parser("2 * 3 + 4 * 5;");
        assertTrue(p.expression());
        actual = p.getParseTree();
        expected = makeTree("+( *(2, 3), *(4, 5))");
        assertEquals(expected, actual);
        
        p = new Parser("2 * 3 * 4");
        assertTrue(p.expression());
        actual = p.getParseTree();
        expected = makeTree("*(*(2, 3), 4)");
        assertEquals(expected, actual);
        
        p = new Parser("2 + 3 - 4");
        assertTrue(p.expression());
        actual = p.getParseTree();
        expected = makeTree("-(+(2, 3), 4)");
        assertEquals(expected, actual);
    }
    
    @Test
    public final void testExpressionWithParentheses() {
        Parser p;
        Tree<Token> actual, expected;
        
        p = new Parser("-(5)");
        assertTrue(p.expression());
        actual = p.getParseTree();
        expected = makeTree("-(5)");
        assertEquals(expected, actual);
        
        p = new Parser("-(2 * 3)");
        assertTrue(p.expression());
        actual = p.getParseTree();
        expected = makeTree("-(*(2, 3))");
        assertEquals(expected, actual);
        
        p = new Parser("2 * (3 + 4) * 5");
        assertTrue(p.expression());
        actual = p.getParseTree();
        expected = makeTree("*(*(2, +(3, 4)), 5)");
        assertEquals(expected, actual);
        
        Tree<Token> t = makeTree("+(-(*(12, 5), /(*(3, 4), 6)), 8)");
        p = new Parser("12 * 5 - 3 * 4 / 6 + 8");
        assertTrue(p.expression());
        assertEquals(t, p.getParseTree());
    }

    @Test
    public final void testTerm() {
        // <term> ::= <factor> { <multiply_operator> <factor> }
        Parser p;
        p = new Parser("5");
        assertTrue(p.term());
        assertEquals(makeTree("5"), p.getParseTree());

        p = new Parser("3 * 5");
        assertTrue(p.term());
        assertEquals(makeTree("*(3, 5)"), p.getParseTree());

        p = new Parser("3 * 5 / 7");
        assertTrue(p.term());
        assertEquals(makeTree("/(*(3, 5), 7)"), p.getParseTree());

        p = new Parser("3 / 5 * 7 % 9");
        assertTrue(p.term());
        assertEquals(makeTree("%(*(/(3, 5), 7), 9)"), p.getParseTree());
    }

    @Test
    public final void testFactor() {
        // <factor> ::= <variable> | <integer> | "distance" | "(" <expression> ")"
        Parser p;
        p = new Parser("5");
        assertTrue(p.factor());
        assertEquals(makeTree("5"), p.getParseTree());

        p = new Parser("x");
        assertTrue(p.factor());
        assertEquals(makeTree("x"), p.getParseTree());
        
        p = new Parser("distance");
        assertTrue(p.factor());
        assertEquals(makeTree("distance"), p.getParseTree());
        
        p = new Parser("while");
        assertFalse(p.factor());

        p = new Parser("(3 * 5)");
        assertTrue(p.factor());
        assertEquals(makeTree("*(3, 5)"), p.getParseTree());

        p = new Parser("(3 - 5)");
        assertTrue(p.factor());
        assertEquals(makeTree("-(3, 5)"), p.getParseTree());

        p = new Parser("(12 * 5 - 3 * 4 / 6 + 8)");
        assertTrue(p.factor());
        assertEquals(makeTree("+(-(*(12, 5), /(*(3, 4), 6)), 8)"), p.getParseTree());        
    }

    @Test
    public final void testAddOperator() {
        // <add_operator> ::= "+" | "-"
        Parser p;
        p = new Parser("+");
        assertTrue(p.addOperator());
        assertEquals(makeTree("+"), p.getParseTree());

        p = new Parser("-");
        assertTrue(p.addOperator());
        assertEquals(makeTree("-"), p.getParseTree());

        p = new Parser("*");
        assertFalse(p.addOperator());
    }

    @Test
    public final void testMultiplyOperator() {
        // <multiply_operator> ::= "*" | "/" | "%"
        Parser p;
        p = new Parser("*");
        assertTrue(p.multiplyOperator());
        assertEquals(makeTree("*"), p.getParseTree());

        p = new Parser("/");
        assertTrue(p.multiplyOperator());
        assertEquals(makeTree("/"), p.getParseTree());

        p = new Parser("%");
        assertTrue(p.multiplyOperator());
        assertEquals(makeTree("%"), p.getParseTree());

        p = new Parser("+");
        assertFalse(p.multiplyOperator());
    }

    @Test
    public final void testVariable() {
        // <variable> ::= <name>
        Parser p;
        p = new Parser("x");
        assertTrue(p.variable());
        assertEquals(makeTree("x"), p.getParseTree());

        p = new Parser("abc123");
        assertTrue(p.variable());
        assertEquals(makeTree("abc123"), p.getParseTree());
        
        p = new Parser("123");
        assertFalse(p.multiplyOperator());
        
        p = new Parser("call");
        assertFalse(p.multiplyOperator());
    }

    @Test
    public final void testCommand() {
        // <command> ::= <thought> | <action>
        //          <move> <expression> ";"
        //        | "turn" <direction> ";"
        //        | "take" <object> ";"
        //        | "drop" <object> ";"
        //        | "set" <variable> <expression> ";"
        //        | "repeat" <expression> <block>
        //        | "while" <condition> <block>
        //        | "if" <condition> <block> [ "else" <block> ] 
        //        | "stop" ";"
        //        | "call" <name> { <expression> } ";"
        Parser p;
        p = new Parser("forward 5; set x 2 ;");
        assertTrue(p.command());
        assertEquals(makeTree("forward(5)"), p.getParseTree());
        
        assertTrue(p.command());
        assertEquals(makeTree("set(x, 2)"), p.getParseTree());
    }

    @Test
    public final void testMove() {
        // <command> ::= <move> <expression> ";"
        // <move> ::= "forward" | "back"
        Parser p = new Parser("forward 5;");
        assertTrue(p.action());
        assertEquals(makeTree("forward(5)"), p.getParseTree());
        
        p = new Parser("back 2 + 3 ;");
        assertTrue(p.action());
        assertEquals(makeTree("back(+(2, 3))"), p.getParseTree());
    }

    @Test
    public final void testTake() {
        // <command> ::= "take" <object> ";"
        Parser p = new Parser("take object ;");
        assertTrue(p.action());
        assertEquals(makeTree("take(object)"), p.getParseTree());        
    }

    @Test
    public final void testDrop() {
        // <command> ::= "drop" <object> ";"
        Parser p = new Parser("drop object ;");
        assertTrue(p.action());
        assertEquals(makeTree("drop(object)"), p.getParseTree());      
    }

    @Test
    public final void testSet() {
        // <command> ::= "set" <variable> <expression> ";"
        Parser p = new Parser("set x 2 ;");
        assertTrue(p.thought());
        assertEquals(makeTree("set(x, 2)"), p.getParseTree());
        
        p = new Parser("set x 2 + 3 ;");
        assertTrue(p.thought());
        assertEquals(makeTree("set(x, +(2, 3))"), p.getParseTree());
    }

    @Test
    public final void testRepeat() {
        // <command> ::= "repeat" <expression> <block>
        Parser p = new Parser("repeat 5 { stop; }");
        assertTrue(p.thought());
        Tree<Token> expected = makeTree("repeat(5, block(stop)))");
        assertEquals(expected, p.getParseTree());
    }

    @Test
    public final void testWhile() {
        // <command> ::= "while" <condition> <block>
        Parser p = new Parser("while x < y { stop; }");
        assertTrue(p.thought());
        Tree<Token> expected = makeTree("while(<(x, y), block(stop))");
        assertEquals(expected, p.getParseTree());
    }

    @Test
    public final void testIfCommand() {
        // <command> ::= "if" <condition> <block> [ "else" <block> ] 
        Parser p;
        Tree<Token> t;
        
        p = new Parser("if seeing thing { }");
        t = makeTree("if(seeing(thing), block)");
        assertTrue(p.thought());
        assertEquals(t, p.getParseTree());
        
        p = new Parser("if seeing thing { stop; }");
        t = makeTree("if(seeing(thing), block(stop))");
        assertTrue(p.thought());
        assertEquals(t, p.getParseTree());
        
    }

    @Test
    public final void testIfElseCommand() {
        // <command> ::= "if" <condition> <block> [ "else" <block> ] 
        Parser p;
        Tree<Token> t;
        
        p = new Parser("if seeing thing { } else { }");
        t = makeTree("if(seeing(thing), block, block)");
        assertTrue(p.thought());
        assertEquals(t, p.getParseTree());
        
        p = new Parser("if seeing thing { forward 3; } else { stop; }");
        t = makeTree("if(seeing(thing), block(forward(3)), block(stop))");
        assertTrue(p.thought());
        assertEquals(t, p.getParseTree());
        
    }

    @Test
    public final void testCall() {
        // <command> ::= "call" <name> { <expression> } ";"
        Parser p;
        Tree<Token> t;        
        p = new Parser("call foo ; call foo 5 ; call foo 1 + 2 3 * 4;");
        
        assertTrue(p.thought());
        t = makeTree("call(foo)");
        assertEquals(t, p.getParseTree());
        
        assertTrue(p.thought());
        t = makeTree("call(foo, 5)");
        assertEquals(t, p.getParseTree());
        
        assertTrue(p.thought());
        t = makeTree("call(foo, +(1, 2), *(3, 4))");
        assertEquals(t, p.getParseTree());
        
    }

    @Test
    public final void testStop() {
        // <command> ::= "stop" ";"
        Parser p = new Parser("stop;");
        assertTrue(p.action());
        assertEquals(makeTree("stop"), p.getParseTree());
    }

    @Test
    public final void testDirection() {
        // <direction> ::= "right" | "left" | "around"
        Parser p = new Parser("right left around about");
        assertTrue(p.direction());
        assertEquals(makeTree("right"), p.getParseTree());
        assertTrue(p.direction());
        assertEquals(makeTree("left"), p.getParseTree());
        assertTrue(p.direction());
        assertEquals(makeTree("around"), p.getParseTree());
        assertFalse(p.direction());
    }

    @Test
    public final void testBlock() {
        // <block> ::= "{" { <command> } "}"
        Parser p = new Parser("{}");
        assertTrue(p.block());
        assertEquals(makeTree("block"), p.getParseTree());
        
        p = new Parser("{ stop; }");
        assertTrue(p.block());        
        assertEquals(makeTree("block(stop)"), p.getParseTree());
        
        p = new Parser("{stop ; stop;}");
        assertTrue(p.block());
        assertEquals(makeTree("block(stop, stop)"), p.getParseTree());
    }

    @Test
    public final void testCondition() {
        // <condition> ::=
        //      <expression> <comparator> <expression>
        //      | "seeing" <object>
        //      | "holding" <object>
        Parser p;
        p = new Parser("1 <= 1");
        assertTrue(p.condition());
        assertEquals(makeTree("<=(1, 1)"), p.getParseTree());
        
        p = new Parser("seeing beeper");
        assertTrue(p.condition());
        assertEquals(makeTree("seeing(beeper)"), p.getParseTree());
        
        p = new Parser("holding beeper");
        assertTrue(p.condition());
        assertEquals(makeTree("holding(beeper)"), p.getParseTree());
    }

    @Test
    public final void testComparator() {
        // <comparator> ::= "<" | "=" | ">"
        Parser p;
        p = new Parser("<");
        assertTrue(p.comparator());
        assertEquals(makeTree("<"), p.getParseTree());

        p = new Parser("==");
        assertTrue(p.comparator());
        assertEquals(makeTree("=="), p.getParseTree());

        p = new Parser(">");
        assertTrue(p.comparator());
        assertEquals(makeTree(">"), p.getParseTree());
    }


    @Test
    public final void testProgram() {
        Parser p;
        p = new Parser("program { stop; }");
        Tree<Token> t = makeTree("program(block(stop))");
        assertTrue(p.program());
        assertEquals(t, p.getParseTree());
    }

    @Test
    public final void testProcedure() {
        // <procedure> ::= "def" <name> { <variable> } <block>
        Parser p;
        Tree<Token> t;
        
        p = new Parser("def foo { } ") ;
        assertTrue(p.procedure());
        t = makeTree("def(header(foo), block)");
        assertEquals(t, p.getParseTree());
        
        p = new Parser("def foo x { stop; } ");
        assertTrue(p.procedure());
        t = makeTree("def(header(foo, x), block(stop))");
        assertEquals(t, p.getParseTree());
        
        p = new Parser("def foo x y { stop ; stop; } ");
        assertTrue(p.procedure());
        t = makeTree("def(header(foo, x, y), block(stop, stop))");
        assertEquals(t, p.getParseTree());
    }

    @Test
    public final void testThing() {
        // <object> ::= <name>
        Parser p = new Parser("x while");
        assertTrue(p.thing());
        assertEquals(makeTree("x"), p.getParseTree());
        assertFalse(p.thing());
    }
    
    Parser parser;
    
    private void use(String s) {
        parser = new Parser(s);
    }

    /**
     * Given a String of the form "root(child, ..., child)",
     * creates the corresponding Tree. For example, the tree<pre>
     *       -
     *      / \
     *     +   4
     *    / \
     *   2   3  </pre>
     * representing the expression "2 + 3 - 4", can be created
     * by calling this method with the string "-(+(2, 3), 4)".
     * Some error checking is done, but it is not comprehensive.
     * 
     * @param s The string representing a tree.
     * @return The tree corresponding to the string.
     */
    private Tree<Token> makeTree(String s) {
        return makeTree(new Tokenizer(s));
    }
    
    /**
     * Given a Tokenizer, construct a Tree from the Tokens.
     * See makeTree(String) for details.
     * 
     * @param tokenizer The source of the Tokens.
     * @return The tree corresponding to this sequence of Tokens.
     */
    private Tree<Token> makeTree(Tokenizer tokenizer) {
        //  <tree> ::= <word> [ "(" <tree> { "," <tree> } ")" ]
        Token token;
        Tree<Token> root;
        Tree<Token> tree;
        
        if (!tokenizer.hasNext()) error(tokenizer);
        root = new Tree<Token>(tokenizer.next());  // <word>
        if (!tokenizer.hasNext()) return root;
        
        token = tokenizer.next();                  // [ "("
        // if token is eoi, return root
        if (!"(".equals(token.text)) {
            tokenizer.backUp();
            return root;
        }
        tree = makeTree(tokenizer);                // <tree>
        root.addChild(tree);
        
        if (!tokenizer.hasNext()) error(tokenizer);
        token = tokenizer.next();
        while (",".equals(token.text)) {           // { ","
            tree = makeTree(tokenizer);            // <tree> }
            root.addChild(tree);
            if (!tokenizer.hasNext()) error(tokenizer);
            token = tokenizer.next();
        }
        if (!")".equals(token.text)) {       // ")" ]
            error(tokenizer);
        }
        return root;
    }

    /**
     * Throws a RuntimeException with a message containing
     * the remaining Tokens from the Tokenizer.
     * 
     * @param tokenizer The source of tokens to include
     * in the message.
     * @throws RuntimeException After printing remaining tokens.
     */
    private void error(Tokenizer tokenizer) throws RuntimeException {
        String message;
        if (tokenizer.hasNext()) {
            message = "Remaining tokens:";
            while (tokenizer.hasNext()) {
                message += " " + tokenizer.next();
            }
        }
        else message = "Incomplete tree";
        throw new RuntimeException(message);
    }
}
