package spoon.test.refactoring.testclasses;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.function.Consumer;
import java.util.function.Function;

import static org.junit.jupiter.api.Assertions.*;

public class CtRenameLocalVariableRefactoringTestSubject
{
	public CtRenameLocalVariableRefactoringTestSubject()
	{
		int local1 = 0;
	}
	
	public void checkModelConsistency() throws Throwable {
		//call all not private methods of this class automatically, to check assertions, which are there
		Method[] methods = getClass().getDeclaredMethods();
		for (Method method : methods) {
			if("checkModelConsistency".equals(method.getName())) {
				continue;
			}
			try {
				if(Modifier.isPrivate(method.getModifiers())) {
					continue;
				}
				method.invoke(this);
			} catch (InvocationTargetException e) {
				throw e.getTargetException();
			} catch (IllegalAccessException | IllegalArgumentException e) {
				throw new RuntimeException("Invocation of method "+method.getName()+" failed", e);
			}
		}
	}
	
	void callConflictWithParam() {
		conflictWithParam(2);
	}
	
	/**
	 * tests conflict of local variable with parameter
	 */
	private void conflictWithParam(@TestTryRename("-var1") int var2) {
		@TestTryRename("-var2")
		int var1 = 1;
		assertTrue(var1 == 1);
		assertTrue(var2 == 2);
	}
	
	/**
	 * tests conflict of local variable with CtCatchVariable
	 */
	private void conflictWithCatchVariable() {
		@TestTryRename({"-var2", "-var3"})
		int var1 = 1;
		try {
			assertTrue(var1 == 1);
			@TestTryRename({"-var1", "var3"})
			int var2 = 2;
			assertTrue(var2 == 2);
			throw new NumberFormatException();
		} catch (@TestTryRename({"-var1", "var2"}) NumberFormatException var3) {
			assertTrue(var1 == 1);
		}
		assertTrue(var1 == 1);
	}

	/**
	 * Tests nested class and conflict with field, 
	 * and nested local variable references, which must would be shadowed
	 */
	void nestedClassMethodWithRefs() {
		@TestTryRename({"-var2", "-var3", "-var4", "-var5", "-var6"})
		int var1 = 1;
		new Consumer<Integer>() {
			//must not rename to var1, because it would shadow var1 reference below
			@TestTryRename({"-var1", "-var3", "-var4", "-var5", "-var6"})
			int var2 = 2;
			@Override
			public void accept(
					//must not rename to var1, because reference to var1 below would be shadowed
					@TestTryRename({"-var1", "var2", "-var3", "-var5", "-var6"}) Integer var4
				) {
				//cannot rename to var1, because reference to var1 below would be shadowed 
				@TestTryRename({"-var1", "var2", "-var4", "-var5", "-var6"})
				int var3 = 3;
				try {
					//cannot rename to var1, because reference to var1 below would be shadowed 
					@TestTryRename({"-var1", "var2", "-var3", "-var4", "var6"})
					int var5 = 5;
					assertTrue(var1 == 1);
					assertTrue(var2 == 2);
					assertTrue(var3 == 3);
					assertTrue(var4 == 4);
					assertTrue(var5 == 5);
					throw new NumberFormatException();
				} catch (
						//cannot rename to var1, because reference to var1 below would be shadowed 
						@TestTryRename({"-var1", "var2", "-var3", "-var4", "var5"}) NumberFormatException var6) {
					assertTrue(var1 == 1);
					assertTrue(var2 == 2);
					assertTrue(var3 == 3);
					assertTrue(var4 == 4);
				}
			}
		}.accept(4);
		assertTrue(var1 == 1);
	}
	/**
	 * Tests nested class and conflict with field, 
	 * and no nested local variable references so rename is possible
	 */
	void nestedClassMethodWithoutRefs() {
		@TestTryRename({"var2", "var3", "var4", "var5", "var6"})
		int var1 = 1;
		new Consumer<Integer>() {
			//must not rename to var1, because it would shadow var1 reference below
			@TestTryRename({"-var1", "var3", "var4", "var5", "var6"})
			int var2 = 2;
			@Override
			public void accept(
					//must not rename to var1, because reference to var1 below would be shadowed
					@TestTryRename({"-var1", "var2", "-var3", "-var5", "-var6"}) Integer var4
				) {
				//can rename to var1, because reference to var1 below is not shadowed 
				@TestTryRename({"var1", "var2", "-var4", "-var5", "-var6"})
				int var3 = 3;
				try {
					//can rename to var1, because reference to var1 below is not shadowed 
					@TestTryRename({"var1", "var2", "-var3", "-var4", "var6"})
					int var5 = 5;
//					assertTrue(var1 == 1);//do not reference it in scope of other vars, so it can be renamed
					assertTrue(var2 == 2);
					assertTrue(var3 == 3);
					assertTrue(var4 == 4);
					assertTrue(var5 == 5);
					throw new NumberFormatException();
				} catch (
						//can rename to var1, because reference to var1 below is not shadowed 
						@TestTryRename({"var1", "var2", "-var3", "-var4", "var5"}) NumberFormatException var6) {
//					assertTrue(var1 == 1);//do not reference it in scope of other vars, so it can be renamed
					assertTrue(var2 == 2);
					assertTrue(var3 == 3);
					assertTrue(var4 == 4);
				}
			}
		}.accept(4);
		assertTrue(var1 == 1);
	}
	
	void nestedClassMethodWithShadowVarWithRefs() {
		@TestTryRename({"-var2", "var3"})
		int var1 = 2;
		new Runnable() {
			@TestTryRename({"var1", "var3"})
			int var2 = 3;
			@Override
			public void run() {
				assertTrue(var1 == 2);
				@TestTryRename({"-var1", "var2"})
				int var3 = 1;
				//this var1 shadows above defined var1. It can be renamed
				@TestTryRename({"var2", "-var3"})
				int var1 = 4;
				assertTrue(var1 == 4);
				assertTrue(var2 == 3);
				assertTrue(var3 == 1);
			}
		}.run();
		assertTrue(var1 == 2);
	}
	void nestedClassMethodWithShadowVarWithoutRefs() {
		@TestTryRename({"var2", "var3"})
		int var1 = 2;
		new Runnable() {
			@TestTryRename({"var1", "var3"})
			int var2 = 3;
			@Override
			public void run() {
				@TestTryRename({"-var1", "var2"})
//				assertTrue(var1 == 2); //the var1 is not referenced so it can be renamed
				int var3 = 1;
				//this var1 shadows above defined var1. It can be renamed
				@TestTryRename({"var2", "-var3"})
				int var1 = 4;
				assertTrue(var1 == 4);  
				assertTrue(var2 == 3);
				assertTrue(var3 == 1);
			}
		}.run();
		assertTrue(var1 == 2);
	}

	void nestedClassMethodWithShadowVarAndField() {
		@TestTryRename({"var2", "var3"})
		int var1 = 2;
		new Runnable() {
			@TestTryRename({"var2", "var3"})
			//this var1 shadows above defined var1.
			int var1 = 3;
			@Override
			public void run() {
				@TestTryRename({"-var1", "var2"})
				int var2 = 1;
				assertTrue(var1 == 3);
				@TestTryRename({"-var2", "var3"})
				int var1 = 4;
				assertTrue(var1 == 4);
				assertTrue(this.var1 == 3);
				assertTrue(var2 == 1);
			}
		}.run();
		assertTrue(var1 == 2);
	}

	void lambda() {
		@TestTryRename({"-var2", "-var3"})
		int var1 = 1;
		assertTrue(var1 == 1);
		Function<Integer, Integer> fnc = (@TestTryRename({"-var1", "-var3"}) Integer var2)->{
			@TestTryRename({"-var1", "-var2"})
			int var3 = 3;
			assertTrue(var1 == 1);
			assertTrue(var2 == 2);
			assertTrue(var3 == 3);
			return var2;
		};
		assertTrue(fnc.apply(2) == 2);
	}
	
	void tryCatch() {
		@TestTryRename({"-var2", "-var3", "-var4"})
		int var1 = 1;
		assertTrue(var1 == 1);
		try {
			@TestTryRename({"-var1","var3","var4"})
			int var2 = 2;
			assertTrue(var1 == 1);
			assertTrue(var2 == 2);
			throw new Exception("ex2");
		} catch (@TestTryRename({"-var1", "var2", "-var4"}) Exception var3) {
			@TestTryRename({"-var1", "var2", "-var3"})
			int var4 = 4;
			assertTrue(var1 == 1);
			assertTrue(var4 == 4);
		}
	}
}
