package recognizer;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;
import tokenizer.Token;
import tokenizer.TokenType;
import tokenizer.Tokenizer;

/**
 * Test for CIT594 Recognizer class, Spring 2009.
 * @author David Matuszek
 * @version March 26, 2009
 */
public class DavesRecognizerTest {
    private Recognizer recognizer;

    /**
     * Test method for {@link recognizer.Recognizer#Recognizer(java.lang.String)}.
     */
    @Test
    public void testRecognizer() {
        // Not much to test; mostly that the Recognizer constructor doesn't crash
        recognizer = new Recognizer("");
        recognizer = new Recognizer("2 + 2");
    }

    /**
     * Test method for {@link recognizer.Recognizer#expression()}.
     */
    @Test
    public void testExpression() {
        use("250");
        assertTrue(recognizer.expression());
        
        use("hello");
        assertTrue(recognizer.expression());

        use("(xyz + 3)");
        assertTrue(recognizer.expression());

        use("a + b + c");
        assertTrue(recognizer.expression());

        use("3 * 12 - 7");
        assertTrue(recognizer.expression());

        use("12 * 5 - 3 * 4 / 6 + 8");
        assertTrue(recognizer.expression());
                     
        use("12 * ((5 - 3) * 4) / 6 + (8)");
        assertTrue(recognizer.expression());
        
        use("");
        assertFalse(recognizer.expression());
        
        use("#");
        assertFalse(recognizer.expression());

        try {
            use("17 +");
            assertFalse(recognizer.expression());
            fail();
        }
        catch (RuntimeException e) {
        }
        try {
            use("22 *");
            assertFalse(recognizer.expression());
            fail();
        }
        catch (RuntimeException e) {
        }
    }

    /**
     * Test method for {@link recognizer.Recognizer#term()}.
     */
    @Test
    public void testTerm() {        
        use("12");
        assertTrue(recognizer.term());

        use("3*12");
        assertTrue(recognizer.term());

        use("x * y * z");
        assertTrue(recognizer.term());
        
        use("20 * 3 / 4");
        assertTrue(recognizer.term());

        use("20 * 3 / 4 + 5");
        assertTrue(recognizer.term());
        unused("+ 5");
        
        use("");
        assertFalse(recognizer.term());
        unused("");
        
        use("#");
        assertFalse(recognizer.term());
        unused("#");

    }

    /**
     * Test method for {@link recognizer.Recognizer#factor()}.
     */
    @Test
    public void testFactor() {
        use("12");
        assertTrue(recognizer.factor());

        use("hello");
        assertTrue(recognizer.factor());
        
        use("(xyz + 3)");
        assertTrue(recognizer.factor());
        
        use("12 * 5");
        assertTrue(recognizer.factor());
        unused("* 5");
        
        use("17 +");
        assertTrue(recognizer.factor());
        unused("+");

        use("");
        assertFalse(recognizer.factor());
        
        use("#");
        assertFalse(recognizer.factor());
    }

    /**
     * Test method for {@link recognizer.Recognizer#addOperator()}.
     */
    @Test
    public void testAddOperator() {
        use("+ - + $");
        assertTrue(recognizer.addOperator());
        assertTrue(recognizer.addOperator());
        assertTrue(recognizer.addOperator());
        assertFalse(recognizer.addOperator());
        unused("$");
    }

    /**
     * Test method for {@link recognizer.Recognizer#multiplyOperator()}.
     */
    @Test
    public void testMultiply_operator() {
        use("* / $");
        assertTrue(recognizer.multiplyOperator());
        assertTrue(recognizer.multiplyOperator());
        assertFalse(recognizer.multiplyOperator());
        unused("$");
    }

    /**
     * Test method for {@link recognizer.Recognizer#variable()}.
     */
    @Test
    final public void testVariable() {
        use("hello red bye abc123 header");
        assertTrue(recognizer.variable());
        assertFalse(recognizer.variable());
        unused("red");
        assertTrue(recognizer.variable());
        assertTrue(recognizer.variable());
        assertTrue(recognizer.variable());
    }

    /**
     * Test method for {@link recognizer.Recognizer#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(recognizer.program());
    }

    /**
     * Test method for {@link recognizer.Recognizer#command()}.
     */
    @Test
    public void testCommand() {
        // Move commands
        testCommand2("forward 5 \n");
        testCommand2("left 5 \n");
        testCommand2("right 5 \n");
        testCommand2("forward a + b + c \n");
        
        // Other simple commands
        testCommand2("penup \n");
        testCommand2("pendown \n");
        testCommand2("color red \n");
        testCommand2("home \n");
        testCommand2("set n n + 1 \n");
        
        // 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(recognizer.block());

        testCommand2("repeat 5 { \n } \n");
        testCommand2("repeat 5" + fancyBlock);

        testCommand2("while 2 < 2 { \n } \n");
        testCommand2("while 2 < 2 " + fancyBlock);
        

        testCommand2("if 2 = 2 { \n } \n");        
        testCommand2("if 2=2 { \n } \n else { \n } \n");
        testCommand2("if 2 = 2" + fancyBlock + "else" + fancyBlock);
        
        testCommand2("call foo \n");
        testCommand2("call foo a, b \n");
    }

    private void testCommand2(String string) {
        use(string);
        assertTrue(recognizer.command());
    }

    /**
     * Test method for {@link recognizer.Recognizer#color()}.
     */
    @Test
    public void testColor() {
        use("color red \n");
        assertTrue(recognizer.command());
        
        use("color 12345 \n");
        assertTrue(recognizer.command());
        
        use("color 2*(3+4) \n");
        assertTrue(recognizer.command());
        
        String[] colors = ("red orange yellow green blue purple " +
        		"violet brown black white gray pink").split(" ");
        for (String color : colors) {
            use("color " + color + "\n");
            assertTrue(recognizer.command());
        }
        
        use("color ultraviolet \n");
        assertTrue(recognizer.command()); // because it's an expression
    }
     
    /**
     * Test method for {@link recognizer.Recognizer#color()}.
     */
    @Test(expected=RuntimeException.class)
    public void testBadColorCommand() {
        use("color \n");
        assertFalse(recognizer.command());
    }

    /**
     * Test method for {@link recognizer.Recognizer#home()}.
     */
    @Test
    public void testHome() {
        use("home \n");
        assertTrue(recognizer.command());
    }

    /**
     * Test method for {@link recognizer.Recognizer#set()}.
     */
    @Test
    public void testSet() {
        use("set x 5 \n");
        assertTrue(recognizer.command());
    }

    /**
     * Test method for {@link recognizer.Recognizer#block()}.
     */
    @Test
    public void testBlock() {
        use("{ \n } \n");
        assertTrue(recognizer.block());

        use("{ \n penup \n} \n");
        assertTrue(recognizer.block());

        use("{ \n penup \n pendown \n } \n");
        assertTrue(recognizer.block());

        use("{ \n penup \n color red \n set m 23 \n pendown \n } \n");
        assertTrue(recognizer.block());

        use("{ \n } \n 5");
        assertTrue(recognizer.block());
        assertTrue(recognizer.expression());
    }

    /**
     * Test method for {@link recognizer.Recognizer#repeatCommand()}.
     */
    @Test
    public void testRepeatCommand() {
        use("repeat 5 { \n } \n");
        assertTrue(recognizer.command());
        
        use("repeat 5 { \n repeat 10 {\n set n n + 1 \n set n n / 2 \n } \n } \n");
        assertTrue(recognizer.command());
    }

    /**
     * Test method for {@link recognizer.Recognizer#whileCommand()}.
     */
    @Test
    public void testWhileCommand() {
        use("while 2 + 2 = 5 { \n } \n");
        assertTrue(recognizer.command());
        
        use("while n + 2 > 5 { \n while n + 2 < 10 {\n" +
                "set n n + 1 \n set n n / 2 \n } \n } \n");
        assertTrue(recognizer.command());
    }

    /**
     * Test method for {@link recognizer.Recognizer#ifCommand()}.
     */
    @Test
    public void testIfCommand() {
        use("if 2 = 2 { \n } \n");
        assertTrue(recognizer.command());
        
        use("if 2=2 { \n } \n else { \n } \n");
        assertTrue(recognizer.command());
    }

    /**
     * Test method for {@link recognizer.Recognizer#call()}.
     */
    @Test
    public void testCall() {
        use("call foo \n");
        assertTrue(recognizer.command());

        use("call foo a, b, c\n");
        assertTrue(recognizer.command());

        use("call foo 2 + 2, 4\n");
        assertTrue(recognizer.command());
    }

    /**
     * Test method for {@link recognizer.Recognizer#move()}.
     */
    @Test
    public void testMove() {
        use("forward 5 \n");
        assertTrue(recognizer.move());

        use("left 5 \n");
        assertTrue(recognizer.move());

        use("right 5 \n");
        assertTrue(recognizer.move());

        use("forward a + b + c \n");
        assertTrue(recognizer.move());
    }

    /**
     * Test method for {@link recognizer.Recognizer#condition()}.
     */
    @Test
    public void testCondition() {
        use("a = b");
        assertTrue(recognizer.condition());
        
        use("a < b");
        assertTrue(recognizer.condition());
        
        use("a > b");
        assertTrue(recognizer.condition());
        
        use("3 * 4 = (2 + 2) * 3");
        assertTrue(recognizer.condition());
    }

    /**
     * Test method for {@link recognizer.Recognizer#comparator()}.
     */
    @Test
    public void testComparator() {
        use("< = > <=>");
        for (int i = 0; i < 2; i++) {
            assertTrue(recognizer.comparator());
            assertTrue(recognizer.comparator());
            assertTrue(recognizer.comparator());
        }
    }

    /**
     * Test method for {@link recognizer.Recognizer#procedure()}.
     */
    @Test
    public void testProcedure() {
        use("define foo { \n } \n");
        assertTrue(recognizer.procedure());

        use("define foo a b{ \n } \n");
        assertTrue(recognizer.procedure());

        use("define foo { \n penup \n pendown \n } \n");
        assertTrue(recognizer.procedure());
        
        use("define foo a b { \n penup \n pendown \n } \n");
        assertTrue(recognizer.procedure());
    }

    /**
     * Test method for {@link recognizer.Recognizer#end()}.
     */
    @Test
    public void testEnd() {
        use("+ \n");
        assertTrue(recognizer.addOperator());
        assertTrue(recognizer.end());

        use("\n \n \n *");
        assertTrue(recognizer.end());
        assertTrue(recognizer.multiplyOperator());
    }


//  ----- "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.
     * 
     * @param whatShouldFollow The string of the next few expected tokens.
     */
    private void unused(String whatShouldFollow) {
        Tokenizer actual = recognizer.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);
        }    
    }

    /**
     * 
     * @param s The string to be parsed.
     */
    private void use(String s) {
        recognizer = new Recognizer(s);
    }
}
