package gr.uom.java.jdeodorant.refactoring.manipulators;

import gr.uom.java.ast.ASTReader;
import gr.uom.java.ast.ClassObject;
import gr.uom.java.ast.CompilationUnitCache;
import gr.uom.java.ast.decomposition.cfg.MethodCallAnalyzer;
import gr.uom.java.ast.util.ExpressionExtractor;
import gr.uom.java.ast.util.MethodDeclarationUtility;
import gr.uom.java.ast.util.StatementExtractor;
import gr.uom.java.ast.util.TypeVisitor;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.IPackageBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.InstanceofExpression;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;

import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.ChangeDescriptor;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;

public class MoveMethodRefactoring extends Refactoring {
	
	private CompilationUnit sourceCompilationUnit;
	private CompilationUnit targetCompilationUnit;
	private TypeDeclaration sourceTypeDeclaration;
	private TypeDeclaration targetTypeDeclaration;
	private MethodDeclaration sourceMethod;
	private String targetClassVariableName;
	private Set<String> additionalArgumentsAddedToMovedMethod;
	private Set<ITypeBinding> additionalTypeBindingsToBeImportedInTargetClass;
	private Map<MethodInvocation, MethodDeclaration> additionalMethodsToBeMoved;
	private Set<FieldDeclaration> fieldDeclarationsChangedWithPublicModifier;
	private Set<BodyDeclaration> memberTypeDeclarationsChangedWithPublicModifier;
	private boolean leaveDelegate;
	private String movedMethodName;
	private boolean isTargetClassVariableParameter;
	private int targetClassVariableParameterIndex;
	private Map<ICompilationUnit, CompilationUnitChange> fChanges;
	private MultiTextEdit sourceMultiTextEdit;
	private MultiTextEdit targetMultiTextEdit;
	private CompilationUnitChange sourceCompilationUnitChange;
	private CompilationUnitChange targetCompilationUnitChange;
	
	public MoveMethodRefactoring(CompilationUnit sourceCompilationUnit, CompilationUnit targetCompilationUnit, 
			TypeDeclaration sourceTypeDeclaration, TypeDeclaration targetTypeDeclaration, MethodDeclaration sourceMethod,
			Map<MethodInvocation, MethodDeclaration> additionalMethodsToBeMoved, boolean leaveDelegate, String movedMethodName) {
		this.sourceCompilationUnit = sourceCompilationUnit;
		this.targetCompilationUnit = targetCompilationUnit;
		this.sourceTypeDeclaration = sourceTypeDeclaration;
		this.targetTypeDeclaration = targetTypeDeclaration;
		this.sourceMethod = sourceMethod;
		this.targetClassVariableName = null;
		this.additionalArgumentsAddedToMovedMethod = new LinkedHashSet<String>();
		this.additionalTypeBindingsToBeImportedInTargetClass = new LinkedHashSet<ITypeBinding>();
		this.additionalMethodsToBeMoved = additionalMethodsToBeMoved;
		this.fieldDeclarationsChangedWithPublicModifier = new LinkedHashSet<FieldDeclaration>();
		this.memberTypeDeclarationsChangedWithPublicModifier = new LinkedHashSet<BodyDeclaration>();
		this.leaveDelegate = leaveDelegate;
		this.movedMethodName = movedMethodName;
		this.isTargetClassVariableParameter = false;
		this.targetClassVariableParameterIndex = -1;
		this.fChanges = new LinkedHashMap<ICompilationUnit, CompilationUnitChange>();
		
		ICompilationUnit sourceICompilationUnit = (ICompilationUnit)sourceCompilationUnit.getJavaElement();
		this.sourceMultiTextEdit = new MultiTextEdit();
		this.sourceCompilationUnitChange = new CompilationUnitChange("", sourceICompilationUnit);
		sourceCompilationUnitChange.setEdit(sourceMultiTextEdit);
		fChanges.put(sourceICompilationUnit, sourceCompilationUnitChange);
		
		ICompilationUnit targetICompilationUnit = (ICompilationUnit)targetCompilationUnit.getJavaElement();
		if(sourceICompilationUnit.equals(targetICompilationUnit)) {
			this.targetMultiTextEdit = sourceMultiTextEdit;
			this.targetCompilationUnitChange = sourceCompilationUnitChange;
		}
		else {
			this.targetMultiTextEdit = new MultiTextEdit();
			this.targetCompilationUnitChange = new CompilationUnitChange("", targetICompilationUnit);
			targetCompilationUnitChange.setEdit(targetMultiTextEdit);
			fChanges.put(targetICompilationUnit, targetCompilationUnitChange);
		}
	}

	public Map<ICompilationUnit, CompilationUnitChange> getChanges() {
		return fChanges;
	}

	public String getMovedMethodName() {
		return movedMethodName;
	}

	public void setMovedMethodName(String movedMethodName) {
		this.movedMethodName = movedMethodName;
	}

	public void setLeaveDelegate(boolean leaveDelegate) {
		this.leaveDelegate = leaveDelegate;
	}

	public void apply() {
		createMovedMethod();
		addAdditionalMethodsToTargetClass();
		if(!sourceCompilationUnit.equals(targetCompilationUnit))
			addRequiredTargetImportDeclarations();
		
		modifyMovedMethodInvocationInSourceClass();
		if(leaveDelegate) {
			addDelegationInSourceMethod();
			removeAdditionalMethodsFromSourceClass();
		}
		else {
			//removes also additional methods used only by the moved method
			removeSourceMethod();
		}
	}

	private void addRequiredTargetImportDeclarations() {
		TypeVisitor typeVisitor = new TypeVisitor();
		sourceMethod.accept(typeVisitor);
		ImportRewrite targetImportRewrite = ImportRewrite.create(targetCompilationUnit, true);
		
		for(ITypeBinding typeBinding : typeVisitor.getTypeBindings()) {
			if(!typeBinding.isNested() || (typeBinding.isNested() && sourceTypeDeclaration.resolveBinding().isEqualTo(typeBinding.getDeclaringClass())))
				targetImportRewrite.addImport(typeBinding);
		}
		for(ITypeBinding typeBinding : additionalTypeBindingsToBeImportedInTargetClass) {
			if(!typeBinding.isNested() || (typeBinding.isNested() && sourceTypeDeclaration.resolveBinding().isEqualTo(typeBinding.getDeclaringClass())))
				targetImportRewrite.addImport(typeBinding);
		}
		
		try {
			TextEdit targetImportEdit = targetImportRewrite.rewriteImports(null);
			if(targetImportRewrite.getCreatedImports().length > 0) {
				targetMultiTextEdit.addChild(targetImportEdit);
				targetCompilationUnitChange.addTextEditGroup(new TextEditGroup("Add required import declarations", new TextEdit[] {targetImportEdit}));
			}
		}
		catch(CoreException coreException) {
			coreException.printStackTrace();
		}
	}

	private void createMovedMethod() {
		ASTRewrite targetRewriter = ASTRewrite.create(targetCompilationUnit.getAST());
		AST ast = targetTypeDeclaration.getAST();
		MethodDeclaration newMethodDeclaration = (MethodDeclaration)ASTNode.copySubtree(ast, sourceMethod);
		
		targetRewriter.set(newMethodDeclaration, MethodDeclaration.NAME_PROPERTY, ast.newSimpleName(movedMethodName), null);
		ListRewrite modifierRewrite = targetRewriter.getListRewrite(newMethodDeclaration, MethodDeclaration.MODIFIERS2_PROPERTY);
		Modifier publicModifier = newMethodDeclaration.getAST().newModifier(Modifier.ModifierKeyword.PUBLIC_KEYWORD);
		boolean modifierFound = false;
		List<IExtendedModifier> modifiers = newMethodDeclaration.modifiers();
		for(IExtendedModifier extendedModifier : modifiers) {
			if(extendedModifier.isModifier()) {
				Modifier modifier = (Modifier)extendedModifier;
				if(modifier.getKeyword().equals(Modifier.ModifierKeyword.PUBLIC_KEYWORD)) {
					modifierFound = true;
				}
				else if(modifier.getKeyword().equals(Modifier.ModifierKeyword.PRIVATE_KEYWORD) ||
						modifier.getKeyword().equals(Modifier.ModifierKeyword.PROTECTED_KEYWORD)) {
					modifierFound = true;
					modifierRewrite.replace(modifier, publicModifier, null);
				}
			}
		}
		if(!modifierFound) {
			modifierRewrite.insertFirst(publicModifier, null);
		}
		
		ListRewrite parametersRewrite = targetRewriter.getListRewrite(newMethodDeclaration, MethodDeclaration.PARAMETERS_PROPERTY);
		List<SingleVariableDeclaration> sourceMethodParameters = sourceMethod.parameters();
		List<SingleVariableDeclaration> newMethodParameters = newMethodDeclaration.parameters();
		ExpressionExtractor expressionExtractor = new ExpressionExtractor();
		List<Expression> newVariableInstructions = expressionExtractor.getVariableInstructions(newMethodDeclaration.getBody());
		
		int i = 0;
		for(SingleVariableDeclaration parameter : sourceMethodParameters) {
			ITypeBinding parameterTypeBinding = parameter.getType().resolveBinding();
			if(parameterTypeBinding.isEqualTo(targetTypeDeclaration.resolveBinding())){
				for(Expression expression : newVariableInstructions) {
					SimpleName simpleName = (SimpleName)expression;
					if(parameter.getName().getIdentifier().equals(simpleName.getIdentifier())) {
						targetClassVariableName = parameter.getName().getIdentifier();
						parametersRewrite.remove(newMethodParameters.get(i), null);
						removeParamTagElementFromJavadoc(newMethodDeclaration, targetRewriter, targetClassVariableName);
						isTargetClassVariableParameter = true;
						targetClassVariableParameterIndex = i;
						break;
					}
				}
				if(targetClassVariableName != null)
    				break;
			}
			i++;
		}
		
		FieldDeclaration[] fieldDeclarations = sourceTypeDeclaration.getFields();
		
		if(targetClassVariableName == null) {
        	for(FieldDeclaration fieldDeclaration : fieldDeclarations) {
        		List<VariableDeclarationFragment> fragments = fieldDeclaration.fragments();
        		for(VariableDeclarationFragment fragment : fragments) {
	        		if(fieldDeclaration.getType().resolveBinding().isEqualTo(targetTypeDeclaration.resolveBinding())) {
	        			for(Expression expression : newVariableInstructions) {
	        				SimpleName simpleName = (SimpleName)expression;
	        				if(fragment.getName().getIdentifier().equals(simpleName.getIdentifier())) {
	        					targetClassVariableName = fragment.getName().getIdentifier();
	        					break;
	        				}
	        			}
	        			if(targetClassVariableName != null)
	        				break;
	        		}
        		}
        		if(targetClassVariableName != null)
    				break;
        	}
		}
		
		List<Expression> oldMethodInvocations = expressionExtractor.getMethodInvocations(sourceMethod.getBody());
		MethodDeclaration[] methodDeclarations = sourceTypeDeclaration.getMethods();
		
		if(targetClassVariableName == null) {
			for(Expression oldMethodInvocation : oldMethodInvocations) {
				if(oldMethodInvocation instanceof MethodInvocation) {
					MethodInvocation methodInvocation = (MethodInvocation)oldMethodInvocation;
					if(methodInvocation.resolveMethodBinding().getDeclaringClass().isEqualTo(sourceTypeDeclaration.resolveBinding())) {
						for(MethodDeclaration methodDeclaration : methodDeclarations) {
							if(methodInvocation.resolveMethodBinding().isEqualTo(methodDeclaration.resolveBinding())) {
								SimpleName fieldInstruction = MethodDeclarationUtility.isGetter(methodDeclaration);
								if(fieldInstruction != null && fieldInstruction.resolveTypeBinding().isEqualTo(targetTypeDeclaration.resolveBinding())) {
									targetClassVariableName = fieldInstruction.getIdentifier();
									break;
								}
							}
						}
						if(targetClassVariableName != null)
	        				break;
					}
				}
			}
		}
		
		if(targetClassVariableName == null) {
			for(Expression oldMethodInvocation : oldMethodInvocations) {
				if(oldMethodInvocation instanceof MethodInvocation) {
					MethodInvocation methodInvocation = (MethodInvocation)oldMethodInvocation;
					if(methodInvocation.resolveMethodBinding().getDeclaringClass().isEqualTo(sourceTypeDeclaration.resolveBinding())) {
						for(MethodDeclaration methodDeclaration : methodDeclarations) {
							if(methodInvocation.resolveMethodBinding().isEqualTo(methodDeclaration.resolveBinding())) {
								MethodInvocation delegation = MethodDeclarationUtility.isDelegate(methodDeclaration);
								if(delegation != null && delegation.resolveMethodBinding().getDeclaringClass().isEqualTo(targetTypeDeclaration.resolveBinding())) {
									List<Expression> delegateMethodVariableInstructions = expressionExtractor.getVariableInstructions(methodDeclaration.getBody());
									for(Expression expression : delegateMethodVariableInstructions) {
										SimpleName fieldInstruction = (SimpleName)expression;
										IBinding fieldInstructionBinding = fieldInstruction.resolveBinding();
										if(fieldInstructionBinding != null && fieldInstructionBinding.getKind() == IBinding.VARIABLE) {
											IVariableBinding fieldInstructionVariableBinding = (IVariableBinding)fieldInstructionBinding;
											if(fieldInstructionVariableBinding.isField() && fieldInstructionVariableBinding.getType().isEqualTo(targetTypeDeclaration.resolveBinding())) {
												targetClassVariableName = fieldInstruction.getIdentifier();
												break;
											}
										}
									}
									if(targetClassVariableName != null)
				        				break;
								}
							}
						}
						if(targetClassVariableName != null)
	        				break;
					}
				}
			}
		}
		
		if(targetClassVariableName == null) {
        	for(FieldDeclaration fieldDeclaration : fieldDeclarations) {
        		List<VariableDeclarationFragment> fragments = fieldDeclaration.fragments();
        		for(VariableDeclarationFragment fragment : fragments) {
	        		if(targetTypeDeclaration.resolveBinding().isEqualTo(fieldDeclaration.getType().resolveBinding().getSuperclass())) {
	        			for(Expression expression : newVariableInstructions) {
	        				SimpleName simpleName = (SimpleName)expression;
	        				if(fragment.getName().getIdentifier().equals(simpleName.getIdentifier())) {
	        					targetClassVariableName = fragment.getName().getIdentifier();
	        					break;
	        				}
	        			}
	        			if(targetClassVariableName != null)
	        				break;
	        		}
        		}
        		if(targetClassVariableName != null)
    				break;
        	}
		}
		
		modifySourceMemberAccessesInTargetClass(newMethodDeclaration, targetRewriter);
		if(targetClassVariableName != null) {
			modifyTargetMethodInvocations(newMethodDeclaration, targetRewriter);
			modifyTargetPublicFieldInstructions(newMethodDeclaration, targetRewriter);
		}
		modifySourceStaticFieldInstructionsInTargetClass(newMethodDeclaration, targetRewriter);
		modifySourceStaticMethodInvocationsInTargetClass(newMethodDeclaration, targetRewriter);
		modifyRecursiveMethodInvocationsOfTheMovedMethod(newMethodDeclaration, targetRewriter);
		replaceTargetClassVariableNameWithThisExpressionInMethodInvocationArguments(newMethodDeclaration, targetRewriter);
		replaceTargetClassVariableNameWithThisExpressionInClassInstanceCreationArguments(newMethodDeclaration, targetRewriter);
		replaceTargetClassVariableNameWithThisExpressionInVariableDeclarationInitializers(newMethodDeclaration, targetRewriter);
		replaceTargetClassVariableNameWithThisExpressionInInfixExpressions(newMethodDeclaration, targetRewriter);
		replaceTargetClassVariableNameWithThisExpressionInCastExpressions(newMethodDeclaration, targetRewriter);
		replaceTargetClassVariableNameWithThisExpressionInInstanceofExpressions(newMethodDeclaration, targetRewriter);
		replaceTargetClassVariableNameWithThisExpressionInAssignments(newMethodDeclaration, targetRewriter);
		replaceTargetClassVariableNameWithThisExpressionInReturnStatements(newMethodDeclaration, targetRewriter);
		replaceThisExpressionWithSourceClassParameterInMethodInvocationArguments(newMethodDeclaration, targetRewriter);
		replaceThisExpressionWithSourceClassParameterInClassInstanceCreationArguments(newMethodDeclaration, targetRewriter);
		replaceThisExpressionWithSourceClassParameterInVariableDeclarationInitializers(newMethodDeclaration, targetRewriter);

		ListRewrite targetClassBodyRewrite = targetRewriter.getListRewrite(targetTypeDeclaration, TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
		targetClassBodyRewrite.insertLast(newMethodDeclaration, null);
		
		try {
			TextEdit targetEdit = targetRewriter.rewriteAST();
			targetMultiTextEdit.addChild(targetEdit);
			targetCompilationUnitChange.addTextEditGroup(new TextEditGroup("Add moved method", new TextEdit[] {targetEdit}));
		}
		catch(JavaModelException javaModelException) {
			javaModelException.printStackTrace();
		}
	}

	private void addAdditionalMethodsToTargetClass() {
		AST ast = targetTypeDeclaration.getAST();
		Set<MethodDeclaration> methodsToBeMoved = new LinkedHashSet<MethodDeclaration>(additionalMethodsToBeMoved.values());
		for(MethodDeclaration methodDeclaration : methodsToBeMoved) {
			TypeVisitor typeVisitor = new TypeVisitor();
			methodDeclaration.accept(typeVisitor);
			for(ITypeBinding typeBinding : typeVisitor.getTypeBindings()) {
				this.additionalTypeBindingsToBeImportedInTargetClass.add(typeBinding);
			}
			MethodDeclaration newMethodDeclaration = (MethodDeclaration)ASTNode.copySubtree(ast, methodDeclaration);
			ASTRewrite targetRewriter = ASTRewrite.create(targetCompilationUnit.getAST());
			ListRewrite targetClassBodyRewrite = targetRewriter.getListRewrite(targetTypeDeclaration, TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
			targetClassBodyRewrite.insertLast(newMethodDeclaration, null);
			try {
				TextEdit targetEdit = targetRewriter.rewriteAST();
				targetMultiTextEdit.addChild(targetEdit);
				targetCompilationUnitChange.addTextEditGroup(new TextEditGroup("Add additional moved method", new TextEdit[] {targetEdit}));
			}
			catch(JavaModelException javaModelException) {
				javaModelException.printStackTrace();
			}
		}
	}

	private void removeAdditionalMethodsFromSourceClass() {
		Set<MethodDeclaration> methodsToBeMoved = new LinkedHashSet<MethodDeclaration>(additionalMethodsToBeMoved.values());
		for(MethodDeclaration methodDeclaration : methodsToBeMoved) {
			ASTRewrite sourceRewriter = ASTRewrite.create(sourceCompilationUnit.getAST());
			ListRewrite sourceClassBodyRewrite = sourceRewriter.getListRewrite(sourceTypeDeclaration, TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
			sourceClassBodyRewrite.remove(methodDeclaration, null);
			try {
				TextEdit sourceEdit = sourceRewriter.rewriteAST();
				sourceMultiTextEdit.addChild(sourceEdit);
				sourceCompilationUnitChange.addTextEditGroup(new TextEditGroup("Remove additional moved method", new TextEdit[] {sourceEdit}));
			}
			catch(JavaModelException javaModelException) {
				javaModelException.printStackTrace();
			}
		}
	}

	private void removeSourceMethod() {
		ASTRewrite sourceRewriter = ASTRewrite.create(sourceCompilationUnit.getAST());
		ListRewrite classBodyRewrite = sourceRewriter.getListRewrite(sourceTypeDeclaration, TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
		classBodyRewrite.remove(sourceMethod, null);
		Set<MethodDeclaration> methodsToBeMoved = new LinkedHashSet<MethodDeclaration>(additionalMethodsToBeMoved.values());
		for(MethodDeclaration methodDeclaration : methodsToBeMoved) {
			classBodyRewrite.remove(methodDeclaration, null);
		}
		try {
			TextEdit sourceEdit = sourceRewriter.rewriteAST();
			sourceMultiTextEdit.addChild(sourceEdit);
			sourceCompilationUnitChange.addTextEditGroup(new TextEditGroup("Remove moved method", new TextEdit[] {sourceEdit}));
		}
		catch(JavaModelException javaModelException) {
			javaModelException.printStackTrace();
		}
	}

	private void addDelegationInSourceMethod() {
		List<SingleVariableDeclaration> sourceMethodParameters = sourceMethod.parameters();
		String targetClassVariableName = null;
		for(SingleVariableDeclaration parameter : sourceMethodParameters) {
			ITypeBinding parameterTypeBinding = parameter.getType().resolveBinding();
			if(parameterTypeBinding.isEqualTo(targetTypeDeclaration.resolveBinding()) &&
					parameter.getName().getIdentifier().equals(this.targetClassVariableName)) {
				targetClassVariableName = parameter.getName().getIdentifier();
				break;
			}
		}
		FieldDeclaration[] fieldDeclarations = sourceTypeDeclaration.getFields();
		if(targetClassVariableName == null) {
        	for(FieldDeclaration fieldDeclaration : fieldDeclarations) {
        		List<VariableDeclarationFragment> fragments = fieldDeclaration.fragments();
        		for(VariableDeclarationFragment fragment : fragments) {
	        		if(fieldDeclaration.getType().resolveBinding().isEqualTo(targetTypeDeclaration.resolveBinding()) &&
	        				fragment.getName().getIdentifier().equals(this.targetClassVariableName)) {
	        			targetClassVariableName = fragment.getName().getIdentifier();
	        			break;
	        		}
        		}
        	}
		}
		if(targetClassVariableName == null) {
        	for(FieldDeclaration fieldDeclaration : fieldDeclarations) {
        		List<VariableDeclarationFragment> fragments = fieldDeclaration.fragments();
        		for(VariableDeclarationFragment fragment : fragments) {
	        		if(targetTypeDeclaration.resolveBinding().isEqualTo(fieldDeclaration.getType().resolveBinding().getSuperclass()) &&
	        				fragment.getName().getIdentifier().equals(this.targetClassVariableName)) {
	        			targetClassVariableName = fragment.getName().getIdentifier();
	        			break;
	        		}
        		}
        	}
		}
		
		ASTRewrite sourceRewriter = ASTRewrite.create(sourceCompilationUnit.getAST());
		ListRewrite methodBodyRewrite = sourceRewriter.getListRewrite(sourceMethod.getBody(), Block.STATEMENTS_PROPERTY);
		List<Statement> sourceMethodStatements = sourceMethod.getBody().statements();
		for(Statement statement : sourceMethodStatements) {
			methodBodyRewrite.remove(statement, null);
		}
		
		Type sourceMethodReturnType = sourceMethod.getReturnType2();
		ITypeBinding sourceMethodReturnTypeBinding = sourceMethodReturnType.resolveBinding();
		AST ast = sourceMethod.getBody().getAST();
		MethodInvocation delegation = ast.newMethodInvocation();
		sourceRewriter.set(delegation, MethodInvocation.NAME_PROPERTY, ast.newSimpleName(movedMethodName), null);
		SimpleName expressionName = ast.newSimpleName(targetClassVariableName);
		sourceRewriter.set(delegation, MethodInvocation.EXPRESSION_PROPERTY, expressionName, null);
		
		ListRewrite argumentRewrite = sourceRewriter.getListRewrite(delegation, MethodInvocation.ARGUMENTS_PROPERTY);
		for(SingleVariableDeclaration parameter : sourceMethodParameters) {
			if(!targetClassVariableName.equals(parameter.getName().getIdentifier())) {
				SimpleName argumentName = ast.newSimpleName(parameter.getName().getIdentifier());
				argumentRewrite.insertLast(argumentName, null);
			}
		}
		for(String argument : additionalArgumentsAddedToMovedMethod) {
			if(argument.equals("this"))
				argumentRewrite.insertLast(ast.newThisExpression(), null);
			else
				argumentRewrite.insertLast(ast.newSimpleName(argument), null);
		}
		if(sourceMethodReturnTypeBinding.getName().equals("void")) {
			ExpressionStatement expressionStatement = ast.newExpressionStatement(delegation);
			methodBodyRewrite.insertLast(expressionStatement, null);
		}
		else {
			ReturnStatement returnStatement = ast.newReturnStatement();
			sourceRewriter.set(returnStatement, ReturnStatement.EXPRESSION_PROPERTY, delegation, null);
			methodBodyRewrite.insertLast(returnStatement, null);
		}
		
		try {
			TextEdit sourceEdit = sourceRewriter.rewriteAST();
			sourceMultiTextEdit.addChild(sourceEdit);
			sourceCompilationUnitChange.addTextEditGroup(new TextEditGroup("Leave delegate to moved method", new TextEdit[] {sourceEdit}));
		}
		catch(JavaModelException javaModelException) {
			javaModelException.printStackTrace();
		}
	}

	private void modifyMovedMethodInvocationInSourceClass() {
		ExpressionExtractor expressionExtractor = new ExpressionExtractor();
		MethodDeclaration[] methodDeclarations = sourceTypeDeclaration.getMethods();
		for(MethodDeclaration methodDeclaration : methodDeclarations) {
			if(!sourceMethod.equals(methodDeclaration)) {
				Block methodBody = methodDeclaration.getBody();
				if(methodBody != null) {
					List<Statement> statements = methodBody.statements();
					for(Statement statement : statements) {
						List<Expression> methodInvocations = expressionExtractor.getMethodInvocations(statement);
						for(Expression expression : methodInvocations) {
							if(expression instanceof MethodInvocation) {
								MethodInvocation methodInvocation = (MethodInvocation)expression;
								if(sourceMethod.resolveBinding().isEqualTo(methodInvocation.resolveMethodBinding())) {
									ASTRewrite sourceRewriter = ASTRewrite.create(sourceCompilationUnit.getAST());
									AST ast = methodInvocation.getAST();
									sourceRewriter.set(methodInvocation, MethodInvocation.NAME_PROPERTY, ast.newSimpleName(movedMethodName), null);
									List<Expression> arguments = methodInvocation.arguments();
									boolean foundInArguments = false;
									for(Expression argument : arguments) {
										if(argument.resolveTypeBinding().isEqualTo(targetTypeDeclaration.resolveBinding())) {
											foundInArguments = true;
											ListRewrite argumentRewrite = sourceRewriter.getListRewrite(methodInvocation, MethodInvocation.ARGUMENTS_PROPERTY);
											argumentRewrite.remove(argument, null);
											if(argument instanceof CastExpression) {
												ParenthesizedExpression parenthesizedExpression = ast.newParenthesizedExpression();
												sourceRewriter.set(parenthesizedExpression, ParenthesizedExpression.EXPRESSION_PROPERTY, argument, null);
												sourceRewriter.set(methodInvocation, MethodInvocation.EXPRESSION_PROPERTY, parenthesizedExpression, null);
											}
											else {
												sourceRewriter.set(methodInvocation, MethodInvocation.EXPRESSION_PROPERTY, argument, null);
											}
											break;
										}
									}
									if(!foundInArguments && isTargetClassVariableParameter) {
										for(Expression argument : arguments) {
											if(targetTypeDeclaration.resolveBinding().isEqualTo(argument.resolveTypeBinding().getSuperclass())) {
												foundInArguments = true;
												ListRewrite argumentRewrite = sourceRewriter.getListRewrite(methodInvocation, MethodInvocation.ARGUMENTS_PROPERTY);
												argumentRewrite.remove(argument, null);
												sourceRewriter.set(methodInvocation, MethodInvocation.EXPRESSION_PROPERTY, argument, null);
												break;
											}
										}
									}
									boolean foundInFields = false;
									if(!foundInArguments) {
										FieldDeclaration[] fieldDeclarations = sourceTypeDeclaration.getFields();
										for(FieldDeclaration fieldDeclaration : fieldDeclarations) {
											List<VariableDeclarationFragment> fragments = fieldDeclaration.fragments();
											for(VariableDeclarationFragment fragment : fragments) {
												if(fieldDeclaration.getType().resolveBinding().isEqualTo(targetTypeDeclaration.resolveBinding()) &&
														fragment.getName().getIdentifier().equals(targetClassVariableName)) {
													foundInFields = true;
													sourceRewriter.set(methodInvocation, MethodInvocation.EXPRESSION_PROPERTY, fragment.getName(), null);
													break;
												}
											}
										}
									}
									if(!foundInArguments && !foundInFields) {
										FieldDeclaration[] fieldDeclarations = sourceTypeDeclaration.getFields();
										for(FieldDeclaration fieldDeclaration : fieldDeclarations) {
											List<VariableDeclarationFragment> fragments = fieldDeclaration.fragments();
											for(VariableDeclarationFragment fragment : fragments) {
												if(targetTypeDeclaration.resolveBinding().isEqualTo(fieldDeclaration.getType().resolveBinding().getSuperclass()) &&
														fragment.getName().getIdentifier().equals(targetClassVariableName)) {
													sourceRewriter.set(methodInvocation, MethodInvocation.EXPRESSION_PROPERTY, fragment.getName(), null);
													break;
												}
											}
										}
									}
									ListRewrite argumentRewrite = sourceRewriter.getListRewrite(methodInvocation, MethodInvocation.ARGUMENTS_PROPERTY);
									for(String argument : additionalArgumentsAddedToMovedMethod) {
										if(argument.equals("this"))
											argumentRewrite.insertLast(ast.newThisExpression(), null);
										else
											argumentRewrite.insertLast(ast.newSimpleName(argument), null);
									}

									try {
										TextEdit sourceEdit = sourceRewriter.rewriteAST();
										sourceMultiTextEdit.addChild(sourceEdit);
										sourceCompilationUnitChange.addTextEditGroup(new TextEditGroup("Change invocation of moved method", new TextEdit[] {sourceEdit}));
									}
									catch(JavaModelException javaModelException) {
										javaModelException.printStackTrace();
									}
								}
							}
						}
					}
				}
			}
		}
	}

	private void modifyTargetMethodInvocations(MethodDeclaration newMethodDeclaration, ASTRewrite targetRewriter) {
		ExpressionExtractor extractor = new ExpressionExtractor();
		List<Expression> sourceMethodInvocations = extractor.getMethodInvocations(sourceMethod.getBody());
		List<Expression> newMethodInvocations = extractor.getMethodInvocations(newMethodDeclaration.getBody());
		int i = 0;
		for(Expression expression : sourceMethodInvocations) {
			if(expression instanceof MethodInvocation) {
				MethodInvocation methodInvocation = (MethodInvocation)expression;
				Expression methodInvocationExpression = methodInvocation.getExpression();
				if(methodInvocationExpression instanceof SimpleName) {
					SimpleName methodInvocationExpressionSimpleName = (SimpleName)methodInvocationExpression;
					if( (methodInvocationExpressionSimpleName.resolveTypeBinding().isEqualTo(targetTypeDeclaration.resolveBinding()) ||
							targetTypeDeclaration.resolveBinding().isEqualTo(methodInvocationExpressionSimpleName.resolveTypeBinding().getSuperclass()) ) &&
							methodInvocationExpressionSimpleName.getIdentifier().equals(targetClassVariableName)) {
						MethodInvocation newMethodInvocation = (MethodInvocation)newMethodInvocations.get(i);
						targetRewriter.remove(newMethodInvocation.getExpression(), null);
					}
				}
				else if(methodInvocationExpression instanceof FieldAccess) {
