/*
 * LpLexerTest.java
 * JUnit based test
 *
 * Copyright (C) 2006 - 2007 Martin Slota
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/*
 * History:
 * v0.1 (2006-12-27): taken from propositional EVOLP implementation
 * v0.2 (2007-01-02):
 * - testDefaultValues added
 * - testAfterClose added
 * - testNullInput added
 * - documentation added
 * v0.2.1 (2007-01-06):
 * - testNullInput renamed to testConstructorExceptions
 * v0.2.2 (2007-01-12):
 * - testSetInputExceptions modified to expect IllegalArgumentException
 *   instead of NullPointerException
 * - assertion messages added
 * v0.2.3 (2007-01-28):
 * - unnecessary throws clauses removed
 * v0.2.4 (2007-02-08):
 * - changed because LpLexer changed (made reusable...)
 * - refactored (got rid of members testConstructorExceptions renamed to
 *   testSetInputExceptions...)
 * v1.0.0 (2007-05-05):
 * - promoted to version 1.0.0 :o)
 */

package lp.parse;

import java.io.File;
import java.io.Reader;
import junit.framework.*;

import java.util.List;
import java.util.ArrayList;

import static lp.parse.LpTokenType.*;

/**
 * Contains tests of the {@link LpLexer} class.
 *
 * @author Martin Slota
 * @version 1.0.0
 * @see LpLexer
 */
public class LpLexerTest extends TestCase {
	protected LpLexer lexer;
	
	/**
	 * A default test case constructor.
	 *
	 * @param testName the name of the test case
	 */
	public LpLexerTest(String testName) {
		super(testName);
		lexer = new LpLexer();
	}
	
	/**
	 * Tests the lexer on an input with lots of whitespace.
	 *
	 * @throws IOException if an I/O error occurs while reading the input
	 * (should never happen)
	 */
	public void testWhiteSpace() {
		String source = "a \t \n"
				+ "\tb\r"
				+ "  c\r\n"
				+ "\t d\n"
				+ "\r"
				+ "e\n"
				+ "  \t  f  g \t\t h\n";
		List<LpToken> expectedTokens = new ArrayList<LpToken>();
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "a", 1, 1));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "b", 2, 2));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "c", 3, 3));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "d", 4, 3));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "e", 6, 1));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "f", 7, 6));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "g", 7, 9));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "h", 7, 14));
		expectedTokens.add(new LpToken(EOF, "", 8, 1));
		doTest(source, expectedTokens);
	}
	
	/**
	 * Tests the lexer on an input containing some comments.
	 *
	 * @throws IOException if an I/O error occurs while reading the input
	 * (should never happen)
	 */
	public void testComments() {
		String source = "% This line should be ignored\n"
				+ "a % this is also just an ignored in-line comment\n"
				+ "% One can also use various weird symbols in comments: @#$%^&*ľščťžýáíéúäôňĽŠČŤŽÝÁÍÉÚÄÔŇ\n"
				+ "\t b";
		List<LpToken> expectedTokens = new ArrayList<LpToken>();
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "a", 2, 1));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "b", 4, 3));
		expectedTokens.add(new LpToken(EOF, "", 4, 4));
		doTest(source, expectedTokens);
	}
	
	/**
	 * Tests the lexer on an input containing all types of tokens.
	 *
	 * @throws IOException if an I/O error occurs while reading the input
	 * (should never happen)
	 */
	public void testTokensSimple() {
		String source = "% First all token types are tested\n"
				+ "( ) , . <- :- a A\n"
				+ "% Now more attention is paid to words\n"
				+ "a_lowercase_word  \t anotherLowercaseWord \t _aLowercaseWordBegginingWith_\n"
				+ "An_uppercase_word \t AnotherUppercaseWord";
		List<LpToken> expectedTokens = new ArrayList<LpToken>();
		expectedTokens.add(new LpToken(LEFT_PAREN, "(", 2, 1));
		expectedTokens.add(new LpToken(RIGHT_PAREN, ")", 2, 3));
		expectedTokens.add(new LpToken(COMMA, ",", 2, 5));
		expectedTokens.add(new LpToken(DOT, ".", 2, 7));
		expectedTokens.add(new LpToken(RULE_ARROW, "<-", 2, 9));
		expectedTokens.add(new LpToken(RULE_ARROW, ":-", 2, 12));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "a", 2, 15));
		expectedTokens.add(new LpToken(UPPERCASE_WORD, "A", 2, 17));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "a_lowercase_word", 4, 1));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "anotherLowercaseWord", 4, 21));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "_aLowercaseWordBegginingWith_", 4, 44));
		expectedTokens.add(new LpToken(UPPERCASE_WORD, "An_uppercase_word", 5, 1));
		expectedTokens.add(new LpToken(UPPERCASE_WORD, "AnotherUppercaseWord", 5, 21));
		expectedTokens.add(new LpToken(EOF, "", 5, 41));
		doTest(source, expectedTokens);
	}
	
	/**
	 * Tests the lexer on a more complicated input.
	 *
	 * @throws IOException if an I/O error occurs while reading the input
	 * (should never happen)
	 */
	public void testTokens() {
		String source = "% These are examples of some real rules\n"
				+ "this <- is, not a, extraordinary, propositional, rule.\n"
				+ "while(this_is, a, fact).\n"
				+ "this(is, Also, a, fact) <- .\n"
				+ "and(This) <- % ccoommmmeenntt\n"
				+ "\t\t is, a, rule, written(over, two, Lines).\n"
				+ "% A rule with assert/1 and some bugs ;o)\n"
				+ "ťhis <= #rule(Has), not assert(iň <- itš, bódy).";
		// 1st rule
		List<LpToken> expectedTokens = new ArrayList<LpToken>();
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "this", 2, 1));
		expectedTokens.add(new LpToken(RULE_ARROW, "<-", 2, 6));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "is", 2, 9));
		expectedTokens.add(new LpToken(COMMA, ",", 2, 11));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "not", 2, 13));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "a", 2, 17));
		expectedTokens.add(new LpToken(COMMA, ",", 2, 18));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "extraordinary", 2, 20));
		expectedTokens.add(new LpToken(COMMA, ",", 2, 33));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "propositional", 2, 35));
		expectedTokens.add(new LpToken(COMMA, ",", 2, 48));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "rule", 2, 50));
		expectedTokens.add(new LpToken(DOT, ".", 2, 54));
		
		// 2nd rule
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "while", 3, 1));
		expectedTokens.add(new LpToken(LEFT_PAREN, "(", 3, 6));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "this_is", 3, 7));
		expectedTokens.add(new LpToken(COMMA, ",", 3, 14));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "a", 3, 16));
		expectedTokens.add(new LpToken(COMMA, ",", 3, 17));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "fact", 3, 19));
		expectedTokens.add(new LpToken(RIGHT_PAREN, ")", 3, 23));
		expectedTokens.add(new LpToken(DOT, ".", 3, 24));
		
		// 3rd rule
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "this", 4, 1));
		expectedTokens.add(new LpToken(LEFT_PAREN, "(", 4, 5));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "is", 4, 6));
		expectedTokens.add(new LpToken(COMMA, ",", 4, 8));
		expectedTokens.add(new LpToken(UPPERCASE_WORD, "Also", 4, 10));
		expectedTokens.add(new LpToken(COMMA, ",", 4, 14));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "a", 4, 16));
		expectedTokens.add(new LpToken(COMMA, ",", 4, 17));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "fact", 4, 19));
		expectedTokens.add(new LpToken(RIGHT_PAREN, ")", 4, 23));
		expectedTokens.add(new LpToken(RULE_ARROW, "<-", 4, 25));
		expectedTokens.add(new LpToken(DOT, ".", 4, 28));
		
		// 4th rule
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "and", 5, 1));
		expectedTokens.add(new LpToken(LEFT_PAREN, "(", 5, 4));
		expectedTokens.add(new LpToken(UPPERCASE_WORD, "This", 5, 5));
		expectedTokens.add(new LpToken(RIGHT_PAREN, ")", 5, 9));
		expectedTokens.add(new LpToken(RULE_ARROW, "<-", 5, 11));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "is", 6, 4));
		expectedTokens.add(new LpToken(COMMA, ",", 6, 6));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "a", 6, 8));
		expectedTokens.add(new LpToken(COMMA, ",", 6, 9));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "rule", 6, 11));
		expectedTokens.add(new LpToken(COMMA, ",", 6, 15));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "written", 6, 17));
		expectedTokens.add(new LpToken(LEFT_PAREN, "(", 6, 24));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "over", 6, 25));
		expectedTokens.add(new LpToken(COMMA, ",", 6, 29));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "two", 6, 31));
		expectedTokens.add(new LpToken(COMMA, ",", 6, 34));
		expectedTokens.add(new LpToken(UPPERCASE_WORD, "Lines", 6, 36));
		expectedTokens.add(new LpToken(RIGHT_PAREN, ")", 6, 41));
		expectedTokens.add(new LpToken(DOT, ".", 6, 42));
		
		// 5th rule
		expectedTokens.add(new LpToken(UNKNOWN_CHAR, "ť", 8, 1));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "his", 8, 2));
		expectedTokens.add(new LpToken(UNKNOWN_CHAR, "<", 8, 6));
		expectedTokens.add(new LpToken(UNKNOWN_CHAR, "=", 8, 7));
		expectedTokens.add(new LpToken(UNKNOWN_CHAR, "#", 8, 9));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "rule", 8, 10));
		expectedTokens.add(new LpToken(LEFT_PAREN, "(", 8, 14));
		expectedTokens.add(new LpToken(UPPERCASE_WORD, "Has", 8, 15));
		expectedTokens.add(new LpToken(RIGHT_PAREN, ")", 8, 18));
		expectedTokens.add(new LpToken(COMMA, ",", 8, 19));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "not", 8, 21));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "assert", 8, 25));
		expectedTokens.add(new LpToken(LEFT_PAREN, "(", 8, 31));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "i", 8, 32));
		expectedTokens.add(new LpToken(UNKNOWN_CHAR, "ň", 8, 33));
		expectedTokens.add(new LpToken(RULE_ARROW, "<-", 8, 35));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "it", 8, 38));
		expectedTokens.add(new LpToken(UNKNOWN_CHAR, "š", 8, 40));
		expectedTokens.add(new LpToken(COMMA, ",", 8, 41));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "b", 8, 43));
		expectedTokens.add(new LpToken(UNKNOWN_CHAR, "ó", 8, 44));
		expectedTokens.add(new LpToken(LOWERCASE_WORD, "dy", 8, 45));
		expectedTokens.add(new LpToken(RIGHT_PAREN, ")", 8, 47));
		expectedTokens.add(new LpToken(DOT, ".", 8, 48));
		expectedTokens.add(new LpToken(EOF, "", 8, 49));
		doTest(source, expectedTokens);
	}
	
	/**
	 * Tests the default values the lexer should return before
	 * {@link LpLexer#nextToken()} is called for the first time.
	 *
	 * @throws IOException if an I/O error occurs while reading the input
	 * (should never happen)
	 */
	public void testDefaultValues() {
		lexer.setInput("some input.");
		assertEquals("getTokenType() should return null before nextToken() " +
				"is called for the first time.", null, lexer.getTokenType());
		assertEquals("getLexem() should return null before nextToken() " +
				"is called for the first time.", null, lexer.getLexem());
		assertEquals("getPosition() should return -1 before nextToken() " +
				"is called for the first time.", -1, lexer.getPosition());
		assertEquals("getLineNumber() should return -1 before nextToken() " +
				"is called for the first time.", -1, lexer.getLineNumber());
		assertEquals("getToken() should return null before nextToken() " +
				"is called for the first time.", null, lexer.getToken());
	}
	
	/**
	 * Tests the default values the lexer should return after
	 * {@link LpLexer#close()} is called.
	 *
	 * @throws IOException if an I/O error occurs while reading the input
	 * (should never happen)
	 */
	public void testAfterClose() {
		String source = "% First all token types are tested\n"
				+ "( ) , . <- a A\n"
				+ "% Now more attention is paid to words\n"
				+ "a_lowercase_word  \t anotherLowercaseWord \t _aLowercaseWordBegginingWith_\n"
				+ "An_uppercase_word \t AnotherUppercaseWord";
		lexer.setInput(source);
		lexer.nextToken();
		lexer.nextToken();
		lexer.nextToken();
		lexer.close();
		assertEquals("getTokenType() should return null after close() has " +
				"been called.", null, lexer.getTokenType());
		assertEquals("getLexem() should return null after close() has " +
				"been called.", null, lexer.getLexem());
		assertEquals("getPosition() should return -1 after close() has " +
				"been called.", -1, lexer.getPosition());
		assertEquals("getLineNumber() should return -1 after close() has " +
				"been called.", -1, lexer.getLineNumber());
		assertEquals("getToken() should return null after close() has " +
				"been called.", null, lexer.getToken());
	}
	
	/**
	 * Tests the lexer on null input (a {@link IllegalArgumentException} should be
	 * thrown).
	 *
	 * @throws IOException if an I/O error occurs while reading the input
	 * (should never happen)
	 */
	public void testSetInputExceptions() {
		String s = null;
		File f = null;
		Reader r = null;
		
		LpLexer l = new LpLexer();
		
		try {
			l.setInput(s);
			fail("A IllegalArgumentException expected!");
		} catch (IllegalArgumentException e) {
			// behaves as expected
		}
		
		try {
			l.setInput(f);
			fail("A IllegalArgumentException expected!");
		} catch (IllegalArgumentException e) {
			// behaves as expected
		}
		
		try {
			l.setInput(r);
			fail("A IllegalArgumentException expected!");
		} catch (IllegalArgumentException e) {
			// behaves as expected
		}
	}
	
	/**
	 * Uses {@link #lexer} to tokenize {@code source} and compares the results
	 * with contents of {@code expectedTokens}.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter})
	 * if an I/O error occurs while reading the input (should never happen)
	 */
	protected void doTest(String source, List<LpToken> expectedTokens) {
		lexer.setInput(source);
		for (LpToken expected : expectedTokens) {
			lexer.nextToken();
			testEqual(expected, lexer.getTokenType(), lexer.getLexem(),
					lexer.getLineNumber(), lexer.getPosition());
			testEqual(expected, lexer.getToken());
		}
		// now only EOFs should be returned
		lexer.nextToken();
		assertEquals("End of input expected!", lexer.getTokenType(), EOF);
		lexer.close();
	}
	
	/**
	 * Tests whether {@code actual} is as expected.
	 *
	 * @param expected the expected value
	 * @param actual the tested {@link LpToken}.
	 */
	protected void testEqual(LpToken expected, LpToken actual) {
		testEqual(expected, actual.getType(), actual.getLexem(),
				actual.getLineNumber(), actual.getPosition());
	}
	
	/**
	 * Tests whether a {@code type}, {@code lexem}, {@code position} and
	 * {@code lineNumber} are as expected.
	 *
	 * @param expected a {@link LpToken} instance containg the correct values
	 * @param type the tested {@link LpTokenType}
	 * @param lexem the tested lexem
	 * @param lineNumber the tested line number
	 * @param position the tested position
	 */
	protected void testEqual(LpToken expected, LpTokenType type, String lexem,
			int lineNumber, int position) {
		assertEquals("A different token type expected!",
				expected.getType(), type);
		assertEquals("A different lexem expected!",
				expected.getLexem(), lexem);
		assertEquals("A different line number expected!",
				expected.getLineNumber(), lineNumber);
		assertEquals("A different position expected!",
				expected.getPosition(), position);
	}
}