package tokenizer;

import static org.junit.Assert.*;
import java.io.StringReader;
import org.junit.Test;

/**
 * @author David Matuszek
 * @version March 2, 2009
 */
public class TokenizerTest {
    private Tokenizer t; // Defined by use(String) method
    private static final Token EOI = new Token(TokenType.EOI, "");
    private static final Token EOL = new Token(TokenType.EOL, "");
    
    /**
     * Test method for {@link tokenizer.Tokenizer#hasNext()}.
     */
    @Test
    public void testHasNext() {
        use("abc");
        assertTrue(t.hasNext());
        assertEquals(new Token(TokenType.NAME, "abc"), t.next());
        // Problem in assignment: Tokenizer has both a hasNext() method and an
        // EOI token, so it's unclear what hasNext() should do
        if (t.hasNext()) {
            // If tokenizer says it has an EOI token, then nothing, that's okay
            assertEquals(EOI, t.next());
            assertFalse(t.hasNext());
        } else {
            // But if tokenizer says it doesn't have a next token, that's okay too
        }
        // Same tests as above, but with backing up
        use("abc");
        assertTrue(t.hasNext());
        assertEquals(new Token(TokenType.NAME, "abc"), t.next());
        if (t.hasNext()) {
            assertEquals(EOI, t.next());
            t.backUp();
            assertEquals(EOI, t.next());
            assertFalse(t.hasNext());
        } else {
            t.backUp();
            assertEquals(new Token(TokenType.NAME, "abc"), t.next());
        }
    }

    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_Name() {
        use("ABC ABC123 ABC_DEF aBcD _abc");
        assertEquals(new Token(TokenType.NAME, "ABC"), t.next());
        assertEquals(new Token(TokenType.NAME, "ABC123"), t.next());
        assertEquals(new Token(TokenType.NAME, "ABC_DEF"), t.next());
        assertEquals(new Token(TokenType.NAME, "aBcD"), t.next());
        assertEquals(new Token(TokenType.NAME, "_abc"), t.next());
    }
    
    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_Number() {
        use(" 1 123  2 3.1416");
        assertEqualDoubles(1, t.next());
        assertEqualDoubles(123, t.next());
        assertEqualDoubles(2, t.next());
        assertEqualDoubles(3.1416, t.next());
    }
    
    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_Keyword() {
        use("if while do"); // Assumes these are in the tokenizer's list of keywords
        assertEquals(new Token(TokenType.KEYWORD, "if"), t.next());
        assertEquals(new Token(TokenType.KEYWORD, "while"), t.next());
        assertEquals(new Token(TokenType.KEYWORD, "do"), t.next());
    }
    
    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_Symbol() {
        use("`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?");
        assertEquals(new Token(TokenType.SYMBOL, "`"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "~"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "!"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "@"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "#"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "$"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "%"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "^"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "&"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "*"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "("), t.next());
        assertEquals(new Token(TokenType.SYMBOL, ")"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "-"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "="), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "+"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "["), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "{"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "]"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "}"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "\\"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "|"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, ";"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, ":"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "'"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "\""), t.next());
        assertEquals(new Token(TokenType.SYMBOL, ","), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "<"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "."), t.next());
        assertEquals(new Token(TokenType.SYMBOL, ">"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "/"), t.next());
        assertEquals(new Token(TokenType.SYMBOL, "?"), t.next());
    }
    
    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_SkipWhitespace() {
        Token abc = new Token(TokenType.NAME, "abc");
        use("abc");
        assertEquals(abc, t.next());
        use("    abc    abc  ");
        assertEquals(abc, t.next());
        assertEquals(abc, t.next());
        use("\tabc  \t  abc ");
        assertEquals(abc, t.next());
        assertEquals(abc, t.next());
    }
    
    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_Eol() {
        use("\n");
        assertEquals(EOL, t.next());
        use("   \n");
        assertEquals(EOL, t.next());
        use("\t\n");
        assertEquals(EOL, t.next());
        use("hello\n");
        t.next();
        assertEquals(EOL, t.next());
        use("hello \n world \n");
        t.next();
        assertEquals(EOL, t.next());
        t.next();
        assertEquals(EOL, t.next());
    }

    /**
     * Test method for {@link tokenizer.Tokenizer#backUp()}.
     */
    @Test
    public void testBackUp() {
        use("abc 123 \n xyz");
        assertEquals(new Token(TokenType.NAME, "abc"), t.next());
        assertEqualDoubles(123, t.next());
        t.backUp();
        assertEqualDoubles(123, t.next());
        assertEquals(EOL, t.next());
        assertEquals(new Token(TokenType.NAME, "xyz"), t.next());
        t.backUp();
        assertEquals(new Token(TokenType.NAME, "xyz"), t.next());
    }

    /**
     * Sets the string to be tokenized.
     * @param string The String to be tokenized.
     */
    private void use(String string) {
        t = new Tokenizer(new StringReader(string));
    }
    
    /**
     * Since the assignment was very unclear about how to handle numbers,
     * this method is intended to be very forgiving.
     * 
     * @param expected The number we expect to get.
     * @param actualToken The token we actually got.
     * @throws AssertionException If we didn't get the expected number.
     */
    private void assertEqualDoubles(double expected, Token actualToken) {
        assertEquals(expected,
                     Double.parseDouble(actualToken.getValue()),
                     0.000001);
    }
}
