package parser;

import static org.junit.Assert.*;

import java.util.List;

import org.junit.Test;
import org.junit.Ignore;

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

/**
 * Test for CIT594 Parser class, Spring 2009.
 * @author David Matuszek
 * @version March 26, 2009
 */
public class DavesParserTest {
    private Parser parser;

    /**
     * Test method for {@link parser.Parser#Parser(java.lang.String)}.
     */
    @Test
    public void testParser() {
        // Not much to test; mostly that the Parser constructor doesn't crash
        parser = new Parser("");
        parser = new Parser("2 + 2");
    }

    /**
     * Test method for {@link parser.Parser#expression()}.
     */
    @Test
    public void testExpression() {
        Tree<Token> expected;
        
        use("250");
        assertTrue(parser.expression());
        assertStackTop(tree("250"));
        
        use("hello");
        assertTrue(parser.expression());
        assertStackTop(tree("hello"));

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

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

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

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

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

    /**
     * Test method for {@link parser.Parser#listOfExpressions()}.
     */
    @Test
    public void testListOfExpressions() {
        use(" 1, 2, abc");
        assertTrue(parser.listOfExpressions());
        assertStackTop(tree("list(1 2 abc)"));
        use("5, 5+3, 3*(4+9)");
        assertTrue(parser.listOfExpressions());
        assertStackTop(tree("list(5 +(5 3) *(3 +(4 9)))"));
    }

    /**
     * Test method for {@link parser.Parser#term()}.
     */
    @Test
    public void testTerm() {        
        use("12");
        assertTrue(parser.term());
        assertStackTop(tree("12"));

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

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

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

    }

    /**
     * Test method for {@link parser.Parser#factor()}.
     */
    @Test
    public void testFactor() {
        use("12");
        assertTrue(parser.factor());
        assertStackTop(tree("12"));

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

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

    /**
     * Test method for {@link parser.Parser#addOperator()}.
     */
    @Test
    public void testAddOperator() {
        use("+ - + $");
        assertTrue(parser.addOperator());
        assertStackTop(tree("+"));
        assertTrue(parser.addOperator());
        assertStackTop(tree("-"));
        assertTrue(parser.addOperator());
        assertFalse(parser.addOperator());
        unused("$");
    }

    /**
     * Test method for {@link parser.Parser#multiplyOperator()}.
     */
    @Test
    public void testMultiply_operator() {
        use("* / $");
        assertTrue(parser.multiplyOperator());
        assertTrue(parser.multiplyOperator());
        assertFalse(parser.multiplyOperator());
        unused("$");
    }

    /**
     * Test method for {@link parser.Parser#variable()}.
     */
    @Test
    final public void testVariable() {
        use("hello red bye abc123 header");
        assertTrue(parser.variable());
        assertFalse(parser.variable());
        unused("red");
        assertTrue(parser.variable());
        assertTrue(parser.variable());
        assertTrue(parser.variable());
    }

    /**
     * Test method for {@link parser.Parser#listOfVariables()}.
     */
    @Test
    public void testListOfVariables() {
        use("abc");
        assertTrue(parser.listOfVariables());
        assertStackTop(tree("list(abc)"));
        use("x y z");
        assertTrue(parser.listOfVariables());
        assertStackTop(tree("list(x y z)"));        
    }

    /**
     * Test method for {@link parser.Parser#program()}.
     */
    @Test
    public void testProgram() {
        use("color red \n" +
            "repeat 4 { \n" +
        	    "call drawAndTurn 50, 90 \n" +
        	"} \n" +
        	"define drawAndTurn howFar degrees { \n" +
        	    "forward howFar \n" +
        	    "left degrees \n" +
        	"} \n");
        assertTrue(parser.program());
        Tree<Token> expected =
            tree("program(block(color(red)" +
            		"           repeat(4 block(call(drawAndTurn list(50 90)))))" +
            		"list(define(header(drawAndTurn list(howFar degrees))" +
            		"    block(forward(howFar) left(degrees)))))");
        assertStackTop(expected);
    }

    /**
     * Test method for {@link parser.Parser#command()}.
     */
    @Test
    public void testCommand() {
        // Move commands
        testCommand2("forward 5 \n", tree("forward(5)"));
        testCommand2("left 5 \n", tree("left(5)"));
        testCommand2("right 5 \n", tree("right(5)"));
        testCommand2("forward a + b + c \n", tree("forward(+(+(a b) c))"));
        
        // Other simple commands
        testCommand2("penup \n", tree("penup"));
        testCommand2("pendown \n", tree("pendown"));
        testCommand2("color red \n", tree("color(red)"));
        testCommand2("home \n", tree("home"));
        testCommand2("set n n + 1 \n", tree("set(n +(n 1))"));
        
        // Create block for use in testing nested commands
        String fancyBlock = "{ \n" +
        		                "penup \n" +
        		                "if 2 = 2 { \n" +
        		                    "forward 5 \n" +
        		                    "pendown \n" +
        		                "} \n" +
        		            "} \n";
        use(fancyBlock);
        assertTrue(parser.block());
        String blockTree = "block(penup if(=(2 2) block(forward(5) pendown)))";

        testCommand2("repeat 5 { \n } \n", tree("repeat(5 block)"));
        testCommand2("repeat 5" + fancyBlock,
                     tree("repeat(5 " + blockTree + ")"));

        testCommand2("while 2 < 2 { \n } \n", tree("while(<(2 2) block)"));
        testCommand2("while 2 < 2 " + fancyBlock,
                     tree("while(<(2 2) " + blockTree + ")"));
        

        testCommand2("if 2 = 2 { \n } \n", tree("if(=(2 2) block)"));        
        testCommand2("if 2=2 { \n } \n else { \n } \n",
                     tree("if(=(2 2) block block)"));
        testCommand2("if 2 = 2" + fancyBlock + "else" + fancyBlock,
                     tree("if(=(2 2)" + blockTree + blockTree + ")"));
        
        testCommand2("call foo \n", tree("call(foo list)"));
        testCommand2("call foo a, b \n", tree("call(foo list(a b))"));
    }

    private void testCommand2(String string, Tree<Token> tree) {
        use(string);
        assertTrue(parser.command());
        assertStackTop(tree);
    }

    /**
     * Test method for {@link parser.Parser#listOfCommands()}.
     */
    @Test
    public void testListOfCommands() {
        use("");
        assertTrue(parser.listOfCommands());
        assertStackTop(tree("block()"));

        use("pendown \n");
        assertTrue(parser.listOfCommands());
        assertStackTop(tree("block(pendown)"));

        use("set n 0 \n while n < 10 { \n set n n + 1 \n } \n penup \n");
        assertTrue(parser.listOfCommands());
        assertStackTop(tree("block(set(n 0) while(<(n 10) block(set(n +(n 1)))) penup)"));
    }

    /**
     * Test method for {@link parser.Parser#color()}.
     */
    @Test
    public void testColor() {
        use("color red \n");
        assertTrue(parser.command());
        assertStackTop(tree("color(red)"));
        
        use("color 12345 \n");
        assertTrue(parser.command());
        assertStackTop(tree("color(12345)"));
        
        use("color 2*(3+4) \n");
        assertTrue(parser.command());
        assertStackTop(tree("color(*(2 +(3 4)))"));
        
        String[] colors =
            new String[] { "red","orange", "yellow", "green", "blue",
                           "purple", "violet", "brown", "black", "white",
                           "gray", "pink" };
        for (String color : colors) {
            use("color " + color + "\n");
            assertTrue(parser.command());
            assertStackTop(tree("color(" + color + ")"));
        }
        
        use("color ultraviolet \n");
        assertTrue(parser.command()); // because it's an expression
    }
     
    /**
     * Test method for {@link parser.Parser#color()}.
     */
    @Test(expected=RuntimeException.class)
    public void testBadColorCommand() {
        use("color \n");
        assertFalse(parser.command());
    }

    /**
     * Test method for {@link parser.Parser#home()}.
     */
    @Test
    public void testHome() {
        use("home \n");
        assertTrue(parser.home());
        assertStackTop(tree("home"));
    }

    /**
     * Test method for {@link parser.Parser#set()}.
     */
    @Test
    public void testSet() {
        use("set x 5 \n");
        assertTrue(parser.set());
        assertStackTop(tree("set(x 5)"));
    }

    /**
     * Test method for {@link parser.Parser#block()}.
     */
    @Test
    public void testBlock() {
        use("{ \n } \n");
        assertTrue(parser.block());
        assertStackTop(tree("block"));

        use("{ \n penup \n} \n");
        assertTrue(parser.block());
        assertStackTop(tree("block(penup)"));

        use("{ \n penup \n pendown \n } \n");
        assertTrue(parser.block());
        assertStackTop(tree("block(penup pendown)"));

        use("{ \n penup \n color red \n set m 23 \n pendown \n } \n");
        assertTrue(parser.block());
        assertStackTop(tree("block(penup color(red) set(m 23) pendown)"));
    }

    /**
     * Test method for {@link parser.Parser#repeatCommand()}.
     */
    @Test
    public void testRepeatCommand() {
        use("repeat 5 { \n } \n");
        assertTrue(parser.repeatCommand());
        assertStackTop(tree("repeat(5 block)"));
        
        use("repeat 5 { \n repeat 10 {\n set n n + 1 \n set n n / 2 \n } \n } \n");
        assertTrue(parser.repeatCommand());
        assertStackTop(tree("repeat(5 block(repeat(10 block(set(n +(n 1)) set(n /(n 2))))))"));
    }

    /**
     * Test method for {@link parser.Parser#whileCommand()}.
     */
    @Test
    public void testWhileCommand() {
        use("while 2 + 2 = 5 { \n } \n");
        assertTrue(parser.whileCommand());
        assertStackTop(tree("while(=(+(2 2) 5) block)"));
        
        use("while n + 2 > 5 { \n while n + 2 < 10 {\n" +
        		"set n n + 1 \n set n n / 2 \n } \n } \n");
        assertTrue(parser.whileCommand());
        assertStackTop(tree("while(>(+(n 2) 5) block(while(<(+(n 2) 10)" +
        		" block(set(n +(n 1)) set(n /(n 2))))))"));
    }

    /**
     * Test method for {@link parser.Parser#ifCommand()}.
     */
    @Test
    public void testIfCommand() {
        use("if 2 = 2 { \n } \n");
        assertTrue(parser.ifCommand());
        assertStackTop(tree("if(=(2 2) block)"));
        
        use("if 2=2 { \n } \n else { \n } \n");
        assertTrue(parser.ifCommand());
        assertStackTop(tree("if(=(2 2) block block)"));
    }

    /**
     * Test method for {@link parser.Parser#call()}.
     */
    @Test
    public void testCall() {
        use("call foo \n");
        assertTrue(parser.call());
        assertStackTop(tree("call(foo list)"));

        use("call foo a, b, c\n");
        assertTrue(parser.call());
        assertStackTop(tree("call(foo list(a b c))"));

        use("call foo 2 + 2, 4\n");
        assertTrue(parser.call());
        assertStackTop(tree("call(foo list(+(2 2) 4))"));
    }

    /**
     * Test method for {@link parser.Parser#move()}.
     */
    @Test
    public void testMove() {
        use("forward 5 \n");
        assertTrue(parser.command());
        assertStackTop(tree("forward(5)"));

        use("left 5 \n");
        assertTrue(parser.command());
        assertStackTop(tree("left(5)"));

        use("right 5 \n");
        assertTrue(parser.command());
        assertStackTop(tree("right(5)"));

        use("forward a + b + c \n");
        assertTrue(parser.command());
        assertStackTop(tree("forward(+(+(a b) c))"));
    }

    /**
     * Test method for {@link parser.Parser#condition()}.
     */
    @Test
    public void testCondition() {
        use("a = b");
        assertTrue(parser.condition());
        assertStackTop(tree("=(a b)"));
        
        use("a < b");
        assertTrue(parser.condition());
        assertStackTop(tree("<(a b)"));
        
        use("a > b");
        assertTrue(parser.condition());
        assertStackTop(tree(">(a b)"));
        
        use("3 * 4 = (2 + 2) * 3");
        assertTrue(parser.condition());
        assertStackTop(tree("=(*(3 4) *(+(2 2) 3))"));
    }

    /**
     * Test method for {@link parser.Parser#comparator()}.
     */
    @Test
    public void testComparator() {
        use("< = > <=>");
        for (int i = 0; i < 2; i++) {
            assertTrue(parser.comparator());
            assertStackTop(tree("<"));
            assertTrue(parser.comparator());
            assertStackTop(tree("="));
            assertTrue(parser.comparator());
            assertStackTop(tree(">"));
        }
    }

    /**
     * Test method for {@link parser.Parser#procedure()}.
     */
    @Test
    public void testProcedure() {
        use("define foo { \n } \n");
        assertTrue(parser.procedure());
        assertStackTop(tree("define(header(foo list) block)"));

        use("define foo a b{ \n } \n");
        assertTrue(parser.procedure());
        assertStackTop(tree("define(header(foo list(a b)) block)"));

        use("define foo { \n penup \n pendown \n } \n");
        assertTrue(parser.procedure());
        assertStackTop(tree("define(header(foo list) block(penup pendown))"));
        
        use("define foo a b { \n penup \n pendown \n } \n");
        assertTrue(parser.procedure());
        assertStackTop(tree("define(header(foo list(a b))" +
        		            "block(penup pendown))"));
    }

    /**
     * Test method for {@link parser.Parser#listOfProcedures()}.
     */
    @Test
    public void testListOfProcedures() {
        String procedure = "define foo { \n } \n";
        String procedure2 = "define bar { \n } \n";
        String procedureCode = "define(header(foo list) block)";
        String procedureCode2 = "define(header(bar list) block)";
        
        use("");
        assertTrue(parser.listOfProcedures());
        assertStackTop(tree("list"));
        
        use(procedure);
        assertTrue(parser.listOfProcedures());
        assertStackTop(tree("list(" + procedureCode + ")"));
        
        use(procedure + procedure2);
        assertTrue(parser.listOfProcedures());
        assertStackTop(tree("list(" + procedureCode +
                            procedureCode2 + ")"));
    }

    /**
     * Test method for {@link parser.Parser#end()}.
     */
    @Test
    public void testEnd() {
        use("+ \n");
        assertTrue(parser.addOperator());
        assertTrue(parser.end());
        assertStackTop(tree("+"));

        use("\n \n \n *");
        assertTrue(parser.end());
        assertTrue(parser.multiplyOperator());
    }


//  ----- "Helper" methods
    
    /**
     * Prints a couple of trees in order to test the tree.print() method.
     */
    @Ignore("Prints the results of a helper method; test has been passed.")
    @Test
    public void testTree() {
        Tree<Token> tree = tree("a(b c)");
        tree.print();
        tree = tree("+(+(a b) c)");
        tree.print();
    }

    /**
     * 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.
     * 
     * @param whatShouldFollow The string of the next few expected tokens.
     */
    private void unused(String whatShouldFollow) {
        Tokenizer actual = parser.getTokenizer();
        Tokenizer expected = new Tokenizer(whatShouldFollow);
        while (true) {
            Token expectedNextToken = expected.next();
            if (expectedNextToken.equals(new Token(TokenType.EOI, ""))) break;
            Token actualNextToken = actual.next();
            assertEquals(expectedNextToken, actualNextToken);
        }    
    }
    
    /**
     * Asserts that the parameter is equal to the top of the stack.
     * 
     * @param t The Tree to compare against the top of the stack.
     */
    private void assertStackTop(Tree<Token> t) {
        assertEquals(t, parser.stack.peek());
    }
    
    /**
     * Creates a Tree of Tokens from a String.
     * 
     * @param description The string representation of the Tree
     * (Note: Cannot include parentheses as values.)
     * 
     * @return A Tree of Tokens.
     */
    private Tree<Token> tree(String description) {
        
        int lpar = 0, rpar = 0;
        for (int i = 0; i < description.length(); i++) {
            char ch = description.charAt(i);
            if (ch == '(') lpar++;
            if (ch == ')') rpar++;
        }
        assertEquals(lpar, rpar);
        
        Tree<String> treeOfStrings = Tree.parse(description);
        return convertToTreeOfTokens(treeOfStrings);
    }
    
    /**
     * Creates a Tree of Tokens from a Tree of Strings. The given
     * Tree of Strings is unchanged.
     * 
     * @param tree The tree to be translated.
     * @return The resultant tree of tokens.
     */
    private Tree<Token> convertToTreeOfTokens(Tree<String> tree) {
        Tree<Token> root = new Tree<Token>(makeOneToken(tree.getValue()));

        List<Tree<String>> children = tree.children();
        for (Tree<String> child : children) {
            root.addChild(convertToTreeOfTokens(child));
        }
        return root;
    }
    
    /**
     * Returns a single token from the given string.
     * 
     * @param word The thing to be turned into a Token.
     * @return The corresponding Token.
     */
    private Token makeOneToken(String word) {
        Tokenizer tokenizer = new Tokenizer(word);
        return tokenizer.next();
    }

    /**
     * 
     * @param s The string to be parsed.
     */
    private void use(String s) {
        parser = new Parser(s);
    }
}
