package completeRecognizer;

import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;

import tokenizer.Token;
import tokenizer.TokenType;
import tokenizer.Tokenizer;

/**
 * @author David Matuszek
 * @version Jan 30, 2004.
 */
public class RecognizerTest {

    Recognizer r;
    Recognizer recognizer;
    /**
     * Constructor for RecognizerTest.
     * @param arg0
     */
    public RecognizerTest() {
        r = new Recognizer("2 + 2");
        r = new Recognizer("");
    }
    
    @Test
    public void testProgram() {
        assertTrue(use("program {}").program());
        assertTrue(use("program {set x 5;}").program());
        assertTrue(use("program {call x 5;} def x n {}").program());
    }
    
    @Test
    public void testCommand() {
        assertTrue(use("set x 5;").command());
        assertTrue(use("forward 5;").command());
    }
    
    @Test
    public void testThought() {
        assertTrue(use("set x 5;").thought());
        assertTrue(use("repeat 5 {};").thought());
        assertTrue(use("while 0 == 1 {};").thought());
        assertTrue(use("if 0 == 1 {};").thought());
        assertTrue(use("if 0 == 1 {} else {};").thought());
        assertTrue(use("call shotgun;").thought());
    }
    
    @Test
    public void testAction() {
        assertTrue(use("forward 5;").action());
        assertTrue(use("turn right;").action());
        assertTrue(use("take heed;").action());
        assertTrue(use("drop dead;").action());
        assertTrue(use("stop;").action());
    }
    
    @Test
    public void testComparator() {
        assertTrue(use("<").comparator());
        assertTrue(use("<=").comparator());
        assertTrue(use("==").comparator());
        assertTrue(use("!=").comparator());
        assertTrue(use(">").comparator());
        assertTrue(use(">=").comparator());
        assertFalse(use("=").comparator());
    }
    
    @Test
    public void testProcedure() {
        assertTrue(use("def foo {}").procedure());
        assertTrue(use("def foo x y z {}").procedure());
        assertTrue(use("def foo x y z { set x 5; } ").procedure());
        assertTrue(use("def foo x y z { set x y + z; set z 0; }").procedure());
    }
    
    @Test(expected=RuntimeException.class)
    public void testBadProcedure() {
        use("def foo x y z { set x y + z; set z 0}").procedure();

    }
    
    @Test
    public void testExpression() {
        r = new Recognizer("250");
        assertTrue(r.expression());

        r = new Recognizer("hello");
        assertTrue(r.expression());
        
        r = new Recognizer("(row + 3)");
        assertTrue(r.expression());

        r = new Recognizer("12 * 5 - 3 * 4 / 6 + 8");
        assertTrue(r.expression());
        
        r = new Recognizer("12 * ((5 - 3) * 4) / 6 + (8)");
        assertTrue(r.expression());

        r = new Recognizer("");
        assertFalse(r.expression());

        r = new Recognizer("#");
        assertFalse(r.expression());
        
        r = new Recognizer("-25");
        assertTrue(r.expression());    
        
        r = new Recognizer("(-5+10)");
        assertTrue(r.expression());      
    }
    
    @Test(expected=RuntimeException.class)
    public void testBadExpression1() {
        r = new Recognizer("17 +");
        r.expression();
    }
    
    @Test(expected=RuntimeException.class)
    public void testBadExpression2() {
        r = new Recognizer("22 *");
        r.expression();
    }

    @Test
    public void testSingleTerm() {
        r = new Recognizer("");
        assertFalse(r.term());

        r = new Recognizer("250");
        assertTrue(r.term());

        r = new Recognizer("hello");
        assertTrue(r.term());

        r = new Recognizer("distance");
        assertTrue(r.term());

        r = new Recognizer("3 * distance");
        assertTrue(r.term());
        matchEverythingThatsLeft(r, "");

        r = new Recognizer("row / 3");
        assertTrue(r.term());
        matchEverythingThatsLeft(r, "");

        r = new Recognizer("4 * column % 3");
        assertTrue(r.term());
        matchEverythingThatsLeft(r, "");
    }
    
    @Test
    public void testMultipleTerms() {
        r = new Recognizer("(xyz + 3)");
        assertTrue(r.term());
        matchEverythingThatsLeft(r, "");

        r = new Recognizer("12 * 5 - 3 * 4 / 6 + 8");
        assertTrue(r.term());                                           // 12 * 5
        assertEquals(new Token(TokenType.OPERATOR, "-"), nextToken(r)); // -
        assertTrue(r.term());                                           // 3 * 4 / 6
        matchEverythingThatsLeft(r, "+ 8");

        r = new Recognizer("12 * ((5 - 3) * 4) / 6 + (8)");
        assertTrue(r.term());                                           // 12 * ((5 - 3) * 4) / 6
        assertEquals(new Token(TokenType.OPERATOR, "+"), nextToken(r)); // +
        assertTrue(r.term());                                           // (8)
        matchEverythingThatsLeft(r, "");
    }

    @Test
    public void testFactorByItself() {
        r = new Recognizer("250");
        assertTrue(r.factor());
        
        r = new Recognizer("hello");
        assertTrue(r.factor());
        
        r = new Recognizer("(xyz + 3)");
        assertTrue(r.factor());
        
        r = new Recognizer("");
        assertFalse(r.factor());
    }
    
    @Test
    public void testFactorKeywords() {        
        r = new Recognizer("row");
        assertTrue(r.factor());
        
        r = new Recognizer("column");
        assertTrue(r.factor());
        
        r = new Recognizer("distance");
        assertTrue(r.factor());
        
        r = new Recognizer("(row + 3)");
        assertTrue(r.factor());
        
        r = new Recognizer("(column)");
        assertTrue(r.factor());
        
        r = new Recognizer("((distance))");
        assertTrue(r.factor());
    }
    
    @Test
    public void testInitialFactor() {        
        r = new Recognizer("12 * 5 - 3 * 4 / 6 + 8");
        assertTrue(r.factor());
        matchEverythingThatsLeft(r, "* 5 - 3 * 4 / 6 + 8");
        
        r = new Recognizer("12 * ((5 - 3) * 4) / 6 + (8)");
        assertTrue(r.factor());
        matchEverythingThatsLeft(r, "* ((5");
        
        r = new Recognizer("17 +");
        assertTrue(r.factor());
        matchEverythingThatsLeft(r, "+");
        
        r = new Recognizer("22 *");
        assertTrue(r.factor());
        matchEverythingThatsLeft(r, "*");

        r = new Recognizer("#");
        assertFalse(r.factor());
        matchEverythingThatsLeft(r, "#");
    }

    @Test
    public void testAddOperator() {
        r = new Recognizer("+ - $");
        assertTrue(r.addOperator());
        assertTrue(r.addOperator());
        assertFalse(r.addOperator());
        matchEverythingThatsLeft(r, "$");
    }

    @Test
    public void testMultiplyOperator() {
        r = new Recognizer("* / % $");
        assertTrue(r.multiplyOperator());
        assertTrue(r.multiplyOperator());
        assertTrue(r.multiplyOperator());
        assertFalse(r.multiplyOperator());
        matchEverythingThatsLeft(r, "$");
    }
    
    @Test
    final public void testVariable() {
        use("hello there row");
        assertTrue(recognizer.isVariable());
        assertTrue(recognizer.isVariable());
        assertFalse(recognizer.isVariable());
    }
    
    @Test
    final public void testThing() { // same as "variable"
        use("hello there row");
        assertTrue(recognizer.thing());
        assertTrue(recognizer.thing());
        assertFalse(recognizer.thing());
    }
    
    @Test
    public void testTurnCommand() {
        use("turn around; end");
        assertTrue(recognizer.command());
        use("turn right");
        try {
            recognizer.command();
            fail("No semicolon after 'turn' action.");
        }
        catch (RuntimeException e) {}
    }
    
    @Test
    public void testTakeCommand() {
        use("take thing; end");
        assertTrue(recognizer.command());
        use("take ;");
        try {
            recognizer.command();
            fail("No argument to 'take'.");
        }
        catch (RuntimeException e) {}
        use("take thing");
        try {
            recognizer.command();
            fail("No semicolon after 'take' action.");
        }
        catch (RuntimeException e) {}
        use("take thing1 thing2");
        try {
            recognizer.command();
            fail("Tried to 'take' more than one thing.");
        }
        catch (RuntimeException e) {}
    }
    
    @Test
    public void testDropCommand() {
        use("drop thing; end");
        assertTrue(recognizer.command());
        use("drop ;");
        try {
            recognizer.command();
            fail("No argument after 'drop'.");
        }
        catch (RuntimeException e) {}
        use("drop thing");
        try {
            recognizer.command();
            fail("No semicolon after 'drop' action.");
        }
        catch (RuntimeException e) {}
        use("drop thing1 thing2");
        try {
            recognizer.command();
            fail("Tried to 'drop' more than one thing.");
        }
        catch (RuntimeException e) {}
    }
    
    @Test
    public void testStopCommand() {
        use("stop ; .");
        assertTrue(recognizer.command());
        matchEverythingThatsLeft(recognizer, ".");

        use("stop . ;");
        try {
            recognizer.command();
            fail("Junk after \"stop\" command");
        }
        catch (RuntimeException e) {}
    }
    
    @Test
    public void testCallCommand() {
        use("call this;");
        assertTrue(recognizer.command());

        use("call this and that ;");
        assertTrue(recognizer.command());

        use("call foo x + y and x - y;");
        assertTrue(recognizer.command());

        use("call this other thing");
        try {
            recognizer.command();
            fail("Error in 'call' command.");
        }
        catch (RuntimeException e) {}
        
        use("call call;");
        try {
            recognizer.command();
            fail("Error in 'call' command.");
        }
        catch (RuntimeException e) {}
    }
    
    @Test
    public void testSet() {
        use("set x y ; .");
        assertTrue(recognizer.command());
        matchEverythingThatsLeft(recognizer, ".");
        
        use("set x y + 1 ; .");
        assertTrue(recognizer.command());
        matchEverythingThatsLeft(recognizer, ".");
        
        use("set 5 x + 1 ;");
        try {
            recognizer.command();
            fail("'set' not followed by a variable.");
        }
        catch (RuntimeException e) {}
        
        use("set x y");
        try {
            recognizer.command();
            fail("Set command not followed by a semicolon.");
        }
        catch (RuntimeException e) {}
    }
    
    @Test
    public void testRepeat() {
        String block = "{ take banana ; }";
        
        use("repeat 5 " + block);
        assertTrue(recognizer.command());
        
        use("repeat 5 + " + block);
        try {
            recognizer.command();
            fail("Error in \"repeat\" expression");
        }
        catch (RuntimeException e) {}
        
        use("repeat 5 ;");
        try {
            recognizer.command();
            fail("Missing or erroneous \"repeat\" block");
        }
        catch (RuntimeException e) {}
    }
    
    @Test
    public void testWhile() {
        String block = "{ stop ; }";
        use("while 2 + 2 == 4 " + block);
        assertTrue(recognizer.command());
        
        use("while 2 + 2 " + block);
        try {
            recognizer.command();
            fail("'while' not followed by a valid condition.");
        }
        catch (RuntimeException e) {}
        
        use("while 2 = 2");
        try {
            recognizer.command();
            fail("'while' <condition> not followed by a block");
        }
        catch (RuntimeException e) {}
    }
    
    @Test
    public void testIf() {
        String block = "{ stop ; }";
        use("if 2 == 2 " + block);
        assertTrue(recognizer.command());
        
        use("if 2 + 2 " + block);
        try {
            recognizer.command();
            fail("Error in \"if\" condition");
        }
        catch (RuntimeException e) {}
        
        use("if 2 == 2");
        try {
            recognizer.command();
            fail("Missing or erroneous \"if\" block");
        }
        catch (RuntimeException e) {}
    }
    
    @Test
    final public void testMove() {
        use("forward 7; and back x+y; end");
        assertTrue(recognizer.command()); //forward 7 ;
        assertFalse(recognizer.command()); //and
        assertEquals(new Token(TokenType.NAME, "and"), nextToken(recognizer));
        assertTrue(recognizer.command()); // back x+y ;
        assertFalse(recognizer.command()); // end
        matchEverythingThatsLeft(recognizer, "end");
    }
    
    @Test
    final public void testMoveCommand() {
        use("forward 5 ;");
        assertTrue(recognizer.command());
        
        use("left x ;");
        assertFalse(recognizer.command());
        
        use("right x + 5 ;");
        assertFalse(recognizer.command());
        
        use("back 5 ;");
        assertTrue(recognizer.command());
        
        use("backward 5 ;");
        assertFalse(recognizer.command());

        use("forward ;");
        try {
            recognizer.command();
            fail("Movement command lacks an expression");
        }
        catch (RuntimeException e) {}
        
        use("forward 5 + ;");
        try {
            recognizer.command();
            fail("Junk after movement command");
        }
        catch (RuntimeException e) {}
    }
    
    @Test
    final public void testBlock() {
        use("{}");
        assertTrue(recognizer.block());
        
        use("{ stop ; }");
        assertTrue(recognizer.block());

        use("{ stop ; stop; }");
        assertTrue(recognizer.block());

        use("{ stop ; stop;\n \n }");
        assertTrue(recognizer.block());

        use("[ ] ;");
        assertFalse(recognizer.block());

        use("{ stop ; stop }");
        try {
            recognizer.block();
            fail("Error in block");
        }
        catch (RuntimeException e) {}
    }
    
    @Test
    final public void testArithmeticCondition() {
        use("2 < 3, x == 4 + y, 3 * 3 > 5, 2 + 2 == 3 + 1, x <");
        Token comma = new Token(TokenType.OPERATOR, ",");

        assertTrue(recognizer.condition()); // 2 < 3
        assertFalse(recognizer.condition());
        assertEquals(comma, nextToken(recognizer));

        assertTrue(recognizer.condition()); // x == 4 + y
        assertFalse(recognizer.condition());
        assertEquals(comma, nextToken(recognizer));

        assertTrue(recognizer.condition()); // 3 * 3 > 5
        assertFalse(recognizer.condition());
        assertEquals(comma, nextToken(recognizer));

        assertTrue(recognizer.condition()); // 2 + 2 == 3 + 1
        assertFalse(recognizer.condition());
        assertEquals(comma, nextToken(recognizer));
    }
    
    @Test
    final public void testKeywordCondition() {
        use("seeing red");
        assertTrue(recognizer.condition());
        
        use("holding court");
        assertTrue(recognizer.condition());
        
        try {
            use("seeing;");
            recognizer.condition();
            fail("Not a condition");
        }
        catch (RuntimeException e) {}
    }
    
    @Test
    final public void testNegatedCondition() {
        use("not 2 < 3 not seeing thing not holding thing not row == 0 not not not 2 + 2 == 4 ]");
        assertTrue(recognizer.condition());
        assertTrue(recognizer.condition());
        assertTrue(recognizer.condition());
        assertTrue(recognizer.condition());
        assertTrue(recognizer.condition());
        assertFalse(recognizer.condition());
    }
    
    @Test
    final public void testDirection() {
        use("right and left around end");
        assertTrue(recognizer.direction()); //right
        assertFalse(recognizer.direction()); //and
        assertEquals(new Token(TokenType.NAME, "and"), nextToken(recognizer));
        assertTrue(recognizer.direction()); // left
        assertTrue(recognizer.direction()); // around
        assertFalse(recognizer.direction()); // end
        matchEverythingThatsLeft(recognizer, "end");
    }

//  ----- "Helper" methods
    
    private Recognizer use(String s) {
        recognizer = new Recognizer(s);
        return recognizer;
    }

    /**
     * Determines whether the <code>recognizer</code> will return
     * tokens corresponding to those in <code>rest</code>, and
     * throws an AssertionFailedException if it does not. Note that
     * the input string may contain more tokens than specified by
     * <code>rest</code>.
     * 
     * @param recognizer The recognizer to be used.
     * @param rest The next tokens we expect the recognizer to return.
     */
    private static void matchEverythingThatsLeft(Recognizer recognizer, String rest) {
        Tokenizer actual = recognizer.tokenizer;
        Tokenizer expected = new Tokenizer(rest);
        while (expected.hasNext()) {
            assertTrue("Insufficient tokens.", actual.hasNext());
            Token actualToken = actual.next();
            Token expectedToken = expected.next();
            assertEquals(expectedToken, actualToken);
        }
    }

    private static Token nextToken(Recognizer r) {
        return r.tokenizer.next();
    }
}

