package pp.s1184725.boppi.test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

import java.util.List;
import java.util.logging.*;

import org.antlr.v4.runtime.CharStream;
import org.junit.Test;

import pp.s1184725.boppi.ToolChain;

/**
 * Test cases for the semantic checking stage for all language features.
 */
public class CheckerTest {
	private List<LogRecord> log;

	private void checkString(String code) {
		check(ToolChain.getCharStream(code));
	}

	private void checkFile(String file) {
		check(ToolChain.getCharStream(AllTests.directory.resolve(file)));
	}

	private void check(CharStream stream) {
		Logger logger = Logger.getAnonymousLogger();
		log = ToolChain.makeListLog(logger);
		logger.setLevel(AllTests.logLevel);

		ToolChain.getAnnotations(ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program(), logger);
	}

	/**
	 * Correct basic expressions
	 */
	@Test
	public void correctExpressionTest() {
		checkFile("simpleExpression.boppi");
		assertThat(log, is(empty()));
	}

	/**
	 * Basic expressions with type errors
	 */
	@Test
	public void wrongExpressionTest() {
		checkString("+true");
		assertThat(log, hasSize(1));

		checkString("5 || true");
		assertThat(log, hasSize(1));

		checkString("6 + 'c'");
		assertThat(log, hasSize(1));

		checkString("4 + print(5, 6)");
		assertThat(log, hasSize(1));

		checkString("print(print(3, 5))");
		assertThat(log, hasSize(1));
	}

	/**
	 * Correct variable use
	 */
	@Test
	public void correctVariableTest() {
		checkFile("simpleVariable.boppi");
		assertThat(log, is(empty()));
	}

	/**
	 * Variable use with type errors
	 */
	@Test
	public void wrongVariableTest() {
		checkString("var bool name; name := 5");
		assertThat(log, hasSize(1));

		checkString("undefinedName");
		assertThat(log, hasSize(1));

		checkString("var undefinedType name; 1");
		assertThat(log, hasSize(1));

		checkString("var bool endsWithDeclaration;");
		assertThat(log, hasSize(1));

		checkString("var bool var1; var var1 var2; var2 := 'c' ");
		assertThat(log, hasSize(1));
	}

	/**
	 * Correct variable scoping
	 */
	@Test
	public void correctScopeTest() {
		checkFile("simpleScope.boppi");
		assertThat(log, is(empty()));
	}

	/**
	 * Variable redeclaring and out-of-scope errors
	 */
	@Test
	public void wrongScopeTest() {
		checkString("var bool var1; var bool var1; 1");
		assertThat(log, hasSize(1));

		checkString("var bool var1; var char var1; 1");
		assertThat(log, hasSize(1));

		checkString("{ var int var1; var1 := 4}; var int var2; var1");
		assertThat(log, hasSize(1));
	}

	/**
	 * Correct if-else and loop use
	 */
	@Test
	public void correctConditionalTest() {
		checkFile("if.boppi");
		assertThat(log, is(empty()));

		checkFile("while.boppi");
		assertThat(log, is(empty()));
	}

	/**
	 * Correct function declaration and calls
	 */
	@Test
	public void correctFunctionTest() {
		checkString("function int id(int a) a; 1");
		assertThat(log, is(empty()));

		checkString("function int id(int a) a; id(1); 1");
		assertThat(log, is(empty()));

		checkString("var int a; function int const() a; 1");
		assertThat(log, is(empty()));

		checkString("function int add(int a, int b) a+b; print(add(4, 5)+1)");
		assertThat(log, is(empty()));

		checkString("function char const(char a, char b) a; print(const('A', 'T'))");
		assertThat(log, is(empty()));

		checkString("function int factorial(int n) {if (n > 1) then n*factorial(n-1) else 1 fi}");
		assertThat(log, is(empty()));
	}

	/**
	 * Function use with type errors
	 */
	@Test
	public void wrongFunctionTest() {
		checkString("function int add(int a, char b) a+b; print(add(4, 'T')+1)");
		assertThat(log, hasSize(1));

		checkString("function char add(int a, int b) a+b; print(add(4, 5))");
		assertThat(log, hasSize(1));

		checkString("function int add(int a, int b) a+b; print(add(4, 'T')+1)");
		assertThat(log, hasSize(1));
	}
}
