package tests;

import static org.junit.Assert.*;
import static tokenizer.TokenType.NUMBER;

import java.io.StringReader;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.junit.Before;
import org.junit.Test;

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

/**
 * @author David Matuszek
 * @version March 31, 2014
 */
public class DavesTokenizerTest {
    private Tokenizer t; // Defined by use(String) method
    private static final Token EOL = new Token(TokenType.EOL, "\n");
    private static String stringOfKeywords =
            ("penup pendown color home jump set repeat while if else " +
             "do forward left right face def red orange yellow green " +
             "cyan blue purple magenta pink olive black gray white " +
             "brown tan color getX getY");
    private static List<String> listOfKeywords = Arrays.asList(stringOfKeywords.split(" "));
    private static Set<String> keywords = new HashSet(listOfKeywords);
    
    /**
     * Extends token printing for ease in debugging.
     */
    @Before
    public void setUp() {
        Token.includeType(true);
    }

    /**
     * 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_Integer() {
        use("5  25  007");
        assertEquals(new Token(NUMBER, "5"), t.next());
        assertEquals(new Token(NUMBER, "25"), t.next());
        assertEquals(new Token(NUMBER, "007"), t.next());
    }
    
    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_Long() {
        use("3L57l");
        assertEquals(new Token(NUMBER, "3L"), t.next());
        assertEquals(new Token(NUMBER, "57l"), t.next());
    }
    
    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_DoubleStartingWithDecimalPoint() {
        use(".5 .25");
        assertEquals(new Token(NUMBER, ".5"), t.next());
        assertEquals(new Token(NUMBER, ".25"), t.next());
    }

    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_DoubleWithDecimalPoint() {
        use("0.5 7.25");
        assertEquals(new Token(NUMBER, "0.5"), t.next());
        assertEquals(new Token(NUMBER, "7.25"), t.next());
    }

    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_DoubleEndingWithDecimalPoint() {
        use("0. 77.");
        assertEquals(new Token(NUMBER, "0."), t.next());
        assertEquals(new Token(NUMBER, "77."), t.next());
    }

    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_DoubleWithSignedExponent() {
        use(".5e+7 5.e-7 5.5E-77 0E+25");
        assertEquals(new Token(NUMBER, ".5e+7"), t.next());
        assertEquals(new Token(NUMBER, "5.e-7"), t.next());
        assertEquals(new Token(NUMBER, "5.5E-77"), t.next());
        assertEquals(new Token(NUMBER, "0E+25"), t.next());
        
    }
    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_DoubleWithUnsignedExponent() {
        use(".5e7 5.e7 5.5E77 0E25");
        assertEquals(new Token(NUMBER, ".5e7"), t.next());
        assertEquals(new Token(NUMBER, "5.e7"), t.next());
        assertEquals(new Token(NUMBER, "5.5E77"), t.next());
        assertEquals(new Token(NUMBER, "0E25"), t.next());
    }
    
    
    /**
     * Test method for {@link tokenizer.Tokenizer#next()}.
     */
    @Test
    public void testNext_Keyword() {
        use("penup while color"); // Assumes these are in the tokenizer's list of keywords
        assertEquals(new Token(TokenType.KEYWORD, "penup"), t.next());
        assertEquals(new Token(TokenType.KEYWORD, "while"), t.next());
        assertEquals(new Token(TokenType.KEYWORD, "color"), t.next());
        use(stringOfKeywords + " !");
        while (t.next().getType() == TokenType.KEYWORD) { }
        t.pushBack();
        assertEquals(new Token(TokenType.SYMBOL, "!"), 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());
    }
    
    /**
     * 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 testSkipComment() {
        Token name = new Token(TokenType.NAME, "abc");
        use("abc");
        Token integer = new Token(TokenType.NUMBER, "123");
        use("abc// Some comment\n123");
        assertEquals(name, t.next());
        assertEquals(integer, 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#next()}.
     */
    @Test
    public void testHasNext() {
        use(" abc ");
        assertTrue(t.hasNext());
        assertEquals(new Token(TokenType.NAME, "abc"), t.next());
        assertFalse(t.hasNext());
        
        use(" abc \n ");
        assertTrue(t.hasNext());
        assertEquals(new Token(TokenType.NAME, "abc"), t.next());
        assertTrue(t.hasNext());
        assertEquals(EOL, t.next());
        assertFalse(t.hasNext());
    }

    /**
     * Test method for {@link tokenizer.Tokenizer#pushBack()}.
     */
    @Test
    public void testPushBack() {
        use("abc 123 \n xyz");
        assertEquals(new Token(TokenType.NAME, "abc"), t.next());
        assertEquals(new Token(TokenType.NUMBER, "123"), t.next());
        t.pushBack();
        assertEquals(new Token(TokenType.NUMBER, "123"), t.next());
        assertEquals(EOL, t.next());
        assertEquals(new Token(TokenType.NAME, "xyz"), t.next());
        t.pushBack();
        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), keywords);
    }
}
