package org.yakindu.sct.model.stext.test.util;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.util.Arrays;
import java.util.List;

import org.eclipse.emf.ecore.EObject;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import org.yakindu.base.expressions.expressions.Expression;
import org.yakindu.base.types.Type;
import org.yakindu.base.types.inferrer.ITypeSystemInferrer;
import org.yakindu.base.types.inferrer.ITypeSystemInferrer.InferenceResult;
import org.yakindu.base.types.typesystem.ITypeSystem;
import org.yakindu.base.types.validation.IValidationIssueAcceptor.ListBasedValidationIssueAcceptor;
import org.yakindu.base.types.validation.IValidationIssueAcceptor.ValidationIssue;
import org.yakindu.base.types.validation.IValidationIssueAcceptor.ValidationIssue.Severity;
import org.yakindu.sct.model.stext.stext.EventDefinition;
import org.yakindu.sct.model.stext.stext.VariableDefinition;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.inject.Inject;

import junit.framework.TestCase;

public abstract class AbstractTypeInferrerTest extends AbstractSTextTest {

	@Rule
	public ExpectedException exception = ExpectedException.none();
	@Inject
	protected ITypeSystem typeSystem;
	@Inject
	protected ITypeSystemInferrer typeInferrer;

	protected ListBasedValidationIssueAcceptor acceptor;

	
	protected Type inferType(String expression) {
		return inferTypeForExpression(expression, super.internalScope() + "\n" + super.interfaceScope());
	}

	protected Type inferType(String expression, String parserRule) {
		return inferType(expression, parserRule, super.internalScope() + "\n" + super.interfaceScope());
	}

	protected InferenceResult inferTypeResultForExpression(String expression, String scopes) {
		return inferTypeResult(expression, Expression.class.getSimpleName(), scopes);
	}
	
	@Deprecated
	protected Type inferTypeForExpression(String expression, String scopes) {
		return inferType(expression, Expression.class.getSimpleName(), scopes);
	}

	@Deprecated
	protected Type inferType(String expression, String parserRule, String scopes) {
 		InferenceResult inferTypeResult = inferTypeResult(expression, parserRule, scopes);
		if(inferTypeResult == null)
			return null;
		return inferTypeResult.getType();
	}
	
	protected InferenceResult inferTypeResult(String expression, String parserRule, String scopes) {
		EObject parseResult = super.parseExpression(expression, parserRule, scopes);
		assertNotNull(parseResult);
		acceptor = new ListBasedValidationIssueAcceptor();
		if (parseResult instanceof Expression) {
			return typeInferrer.infer((Expression) parseResult, acceptor);
		} else if (parseResult instanceof EventDefinition) {
			return typeInferrer.infer((EventDefinition) parseResult, acceptor);
		} else if (parseResult instanceof VariableDefinition) {
			return typeInferrer.infer((VariableDefinition) parseResult, acceptor);
		} else {
			throw new IllegalArgumentException("Unsupported parse result.");
		}
	}
	
	protected boolean isType(Type type, String typeName) {
		return typeSystem.isSame(type, typeSystem.getType(typeName));
	}

	protected boolean isVoidType(Type type) {
		return isType(type, ITypeSystem.VOID);
	}

	protected boolean isIntegerType(Type type) {
		return isType(type, ITypeSystem.INTEGER);
	}

	protected boolean isRealType(Type type) {
		return isType(type, ITypeSystem.REAL);
	}

	protected boolean isBooleanType(Type type) {
		return isType(type, ITypeSystem.BOOLEAN);
	}

	protected boolean isStringType(Type type) {
		return isType(type, ITypeSystem.STRING);
	}
	
	protected boolean isAnyType(Type type) {
		return isType(type, ITypeSystem.ANY);
	}
	
	protected void expectIssue(Type object, String message) {
		if (acceptor.getTraces(
				org.yakindu.base.types.validation.IValidationIssueAcceptor.ValidationIssue.Severity.ERROR).isEmpty()) {
			TestCase.fail("No issue detected.");
		}
		assertEquals(
				message,
				acceptor.getTraces(
						org.yakindu.base.types.validation.IValidationIssueAcceptor.ValidationIssue.Severity.ERROR)
						.iterator().next().getMessage());
	}
	
	protected void expectNoErrors(String expression, String scope) {
		ListBasedValidationIssueAcceptor diagnostics = validate(expression, scope);
		List<ValidationIssue> errors = diagnostics.getTraces(Severity.ERROR);
		assertEquals(errors.toString(), 0, errors.size());
	}
	
	protected void expectWarning(String expression, String scope, String code) {
		List<ValidationIssue> traces = validate(expression, scope).getTraces(Severity.WARNING);
		List<ValidationIssue> issues = filterForIssueCode(traces, code);
		assertEquals(Arrays.toString(traces.toArray()), 1, issues.size());
	}
	
	protected void expectError(String expression, String scope, String code) {
		List<ValidationIssue> traces = validate(expression, scope).getTraces(Severity.ERROR);
		List<ValidationIssue> issues = filterForIssueCode(traces, code);
		assertEquals(Arrays.toString(traces.toArray()), 1, issues.size());
	}
	
	private List<ValidationIssue> filterForIssueCode(List<ValidationIssue> traces, final String code) {
		return Lists.newArrayList(Iterables.filter(traces, new Predicate<ValidationIssue>() {
			@Override
			public boolean apply(ValidationIssue input) {
				return input.getIssueCode().equals(code);
			}
		}));
	}
	
	protected ListBasedValidationIssueAcceptor validate(String expression, String scope) {
		EObject exp = parseExpression(expression, Expression.class.getSimpleName(), scope);
		ListBasedValidationIssueAcceptor diagnostics = new ListBasedValidationIssueAcceptor();
		typeInferrer.infer(exp, diagnostics);
		return diagnostics;
	}
}
