package metamutator;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.junit.Test;
import org.junit.runner.Runner;
import org.junit.runners.BlockJUnit4ClassRunner;

import spoon.processing.AbstractProcessor;
import spoon.reflect.code.CtBlock;
import spoon.reflect.code.CtCodeSnippetStatement;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.filter.AnnotationFilter;

/**
 * Creates a unique test case inside a unique test class.
 * 
 * The created class is runnable as main or as a test case itself.
 * 
 */
@SuppressWarnings("rawtypes")
public class UniqueTestGenerator extends AbstractProcessor<CtClass> {

	// contais the block to be created
	CtBlock bWithBlock;

	@Override
	public void processingDone() {
		createClass("TestSuite");
		System.out.println("done");
	}

	private void createClass(String klassName) {
		CtClass c = getFactory().Core().createClass();
		c.addModifier(ModifierKind.PUBLIC);
		c.setSimpleName(klassName.replace('-', '_').replace('.', '_'));
		

//		// main
		{
		CtMethod m = getFactory().Core().createMethod();
		m.setSimpleName("main");
		m.addModifier(ModifierKind.STATIC);
		m.addModifier(ModifierKind.PUBLIC);
		m.setBody(bWithBlock);		
		m.setType(getFactory().Type().VOID_PRIMITIVE);
		List<CtParameter> l = new ArrayList<>();
		CtParameter par = getFactory().Core().createParameter();
		par.setType((CtTypeReference) getFactory().Core().createTypeReference().setSimpleName("String[]"));
		par.setSimpleName("args");
		l.add(par);
		m.setParameters(l);
		c.addMethod(m);		
		m.addThrownType((CtTypeReference) getFactory().Core().createTypeReference().setSimpleName("Exception"));		
		
		CtCodeSnippetStatement e = getFactory().Core().createCodeSnippetStatement ();
		e.setValue("new "+c.getSimpleName()+"().test()");
		CtBlock bod= getFactory().Core().createBlock();
		bod.addStatement(e);
		m.setBody(bod);
		}
	
		{
		CtMethod m = getFactory().Core().createMethod();
		m.setSimpleName("test");
		m.addModifier(ModifierKind.PUBLIC);
		getFactory().Annotation().annotate(m, Test.class);
		m.setBody(bWithBlock);		
		m.addThrownType((CtTypeReference) getFactory().Core().createTypeReference().setSimpleName("Exception"));		
		m.setType(getFactory().Type().VOID_PRIMITIVE);
		c.addMethod(m);
		}
		
		// adding the class
		CtPackage p = getFactory().Package().getOrCreate("test");
		p.addType(c);
	}
	
	@Override
	public boolean isToBeProcessed(CtClass element) {
		return element.getElements(new AnnotationFilter<>((Class<? extends Annotation>) Test.class)).size()>0
				&& !element.getModifiers().contains(ModifierKind.ABSTRACT)
				&& element.isTopLevel();
	}
	
	@SuppressWarnings({ "unchecked"})
	@Override
	public void process(CtClass element) {
		try {
			// run normal
			if (bWithBlock == null) {
				bWithBlock = getFactory().Core().createBlock();
			}

			CtCodeSnippetStatement e = createTestSnippet(element, BlockJUnit4ClassRunner.class);
			bWithBlock.addStatement(e);

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private CtCodeSnippetStatement createTestSnippet(CtClass element, Class<? extends Runner> runner) {
		CtCodeSnippetStatement e = getFactory().Core()
				.createCodeSnippetStatement();
		String val = "	new "+runner.getCanonicalName()+"("
				+ element.getQualifiedName()
				+ ".class).run(new org.junit.runner.notification.RunNotifier() {\n"
				+ "		@Override\n"
				+ "		public void fireTestFailure(org.junit.runner.notification.Failure failure) {\n"
				+ "			if (failure.getException() instanceof RuntimeException) throw (RuntimeException)failure.getException(); \n"
				+ "			if (failure.getException() instanceof Error) throw (Error)failure.getException(); \n"
				+ "         throw new RuntimeException(failure.getException());\n"
				+ "		}\n" + "	})";
		e.setValue(val);
		return e;
	}
}