/**
 * Starter code for testing a parser for a Logo-like language;
 * used as part of an assignment in CIT594, Spring 2009.
 */
package parser;

import static org.junit.Assert.*;

import java.util.List;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.Ignore;

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

/**
 * @author David Matuszek
 * @author // TODO fill in your name
 * @version March 19, 2009
 */
public class ParserTest {
    private Parser parser;
    
    /**
     * @throws java.lang.Exception
     */
    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
    }

    /**
     * @throws java.lang.Exception
     */
    @Before
    public void setUp() throws Exception {
    }

    /**
     * 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() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * 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 list 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() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#program()}.
     */
    @Test
    public void testProgram() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#command()}.
     */
    @Test
    public void testCommand() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#listOfCommands()}.
     */
    @Test
    public void testListOfCommands() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#penup()}.
     */
    @Test
    public void testPenup() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#pendown()}.
     */
    @Test
    public void testPendown() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#color()}.
     */
    @Test
    public void testColor() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#home()}.
     */
    @Test
    public void testHome() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#set()}.
     */
    @Test
    public void testSet() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#block()}.
     */
    @Test
    public void testBlock() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#repeatCommand()}.
     */
    @Test
    public void testRepeatCommand() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#whileCommand()}.
     */
    @Test
    public void testWhileCommand() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#ifCommand()}.
     */
    @Test
    public void testIfCommand() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#call()}.
     */
    @Test
    public void testCall() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#move()}.
     */
    @Test
    public void testMove() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#condition()}.
     */
    @Test
    public void testCondition() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#comparator()}.
     */
    @Test
    public void testComparator() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#procedure()}.
     */
    @Test
    public void testProcedure() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#listOfProcedures()}.
     */
    @Test
    public void testListOfProcedures() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link parser.Parser#eol()}.
     */
    @Test
    public void testEnd() {
        fail("Not yet implemented"); // TODO
    }


//  ----- "Helper" methods
    
    @Ignore("To test the tree(String) helper method, this prints its results.")
    @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 (expected.hasNext()) {
            Token expectedNextToken = expected.next();
            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) {
        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);
    }
    
    /**
     * Removes and ignores the next Token.
     */
    private void skip() {
        Tokenizer tokenizer = parser.getTokenizer();
        tokenizer.next();
    }
}
