package metamutator;

import java.util.EnumSet;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.collect.Sets;

import spoon.processing.AbstractProcessor;
import spoon.reflect.code.BinaryOperatorKind;
import spoon.reflect.code.CtBinaryOperator;
import spoon.reflect.code.CtCodeSnippetExpression;
import spoon.reflect.code.CtConstructorCall;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtRHSReceiver;
import spoon.reflect.code.CtStatement;
import spoon.reflect.declaration.CtAnonymousExecutable;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtConstructor;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtField;
import spoon.reflect.reference.CtTypeReference;
import spoon.support.reflect.code.CtConstructorCallImpl;

/**
 * inserts a mutation hotspot for each binary operator
 */
public class VariabletoNullMetaMutator extends
		AbstractProcessor<CtStatement> {

	public static final String PREFIX =  "_variableNullHotSpot";
	private static int index = 0;
	private static final int procId = 2;
	
	private static final EnumSet<Null> NULLORNOTNULL = EnumSet
			.of(Null.NO, Null.YES);

	private Set<CtElement> hostSpots = Sets.newHashSet();

	@Override
	public boolean isToBeProcessed(CtStatement element) {
		// if (element.getParent(CtAnonymousExecutable.class)!=null) {
				// System.out.println(element.getParent(CtAnonymousExecutable.class));
				// }
		
				if(!(element instanceof CtRHSReceiver))
					return false;
				try {
					Selector.getTopLevelClass(element);
				} catch (Exception e) {
					return false;
				}

				// not in constructors because we use static fields
				if (element.getParent(CtConstructor.class) != null) {
					return false;
				}
				
				if (((CtRHSReceiver)element).getAssignment() == null)
					return false;
				
				CtTypeReference type = ((CtRHSReceiver)element).getAssignment().getType();
				
				if (type == null)
					return false;
				
				if (element.toString().contains("java.lang.String.format"))
					return false;

				return !((CtRHSReceiver)element).getAssignment().getType().isPrimitive() 
						&& (element.getParent(CtAnonymousExecutable.class) == null);
	}

	public void process(CtStatement assignment) {
		
		mutateOperator(((CtRHSReceiver)assignment).getAssignment(), NULLORNOTNULL);

	}

	/**
	 *
	 * @param expression
	 * @param operators
	 */
	private void mutateOperator(final CtExpression expression, EnumSet<Null> operators) {
				
		if (alreadyInHotsSpot(expression)
				|| expression.toString().contains(".is(\"")) {
			System.out
			.println(String
					.format("Expression '%s' ignored because it is included in previous hot spot",
							expression));
			return;
		}		

		int thisIndex = ++index;
		
		String actualExpression = expression.toString();
		
		String newExpression = String.format("(%s%s.is(%s))?"+actualExpression+":null",PREFIX,thisIndex,"metamutator.Null.NO");
		
		CtCodeSnippetExpression codeSnippet = getFactory().Core()
				.createCodeSnippetExpression();
		codeSnippet.setValue('(' + newExpression + ')');
		
		expression.replace(codeSnippet);
		expression.replace(expression);
		
		Selector.generateSelector(expression, Null.NO, thisIndex, operators, PREFIX);

		hostSpots.add(expression);

	}

	/**
	 * Check if this sub expression was already inside an uppermost expression
	 * that was processed has a hot spot. This version does not allowed
	 * conflicting hot spots
	 * 
	 * @param element
	 *            the current expression to test
	 * @return true if this expression is descendant of an already processed
	 *         expression
	 */
	private boolean alreadyInHotsSpot(CtElement element) {
		CtElement parent = element.getParent();
		while (!isTopLevel(parent) && parent != null) {
			if (hostSpots.contains(parent))
				return true;

			parent = parent.getParent();
		}

		return false;
	}

	private boolean isTopLevel(CtElement parent) {
		return parent instanceof CtClass && ((CtClass) parent).isTopLevel();
	}
}