/*******************************************************************************
 * Copyright (c) 2000, 2020 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.fix;

import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

import org.eclipse.text.edits.TextEditGroup;

import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTMatcher;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ContinueStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
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.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.QualifiedName;
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.ThisExpression;
import org.eclipse.jdt.core.dom.Type;
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.ImportRewrite.TypeLocation;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;

import org.eclipse.jdt.internal.core.manipulation.StubUtility;
import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.GenericVisitor;
import org.eclipse.jdt.internal.corext.dom.JdtASTMatcher;
import org.eclipse.jdt.internal.corext.dom.ModifierRewrite;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;

public class ConvertForLoopOperation extends ConvertLoopOperation {
	private static final String LENGTH_QUERY= "length"; //$NON-NLS-1$
	private static final String SIZE_QUERY= "size"; //$NON-NLS-1$
	private static final String GET_QUERY= "get"; //$NON-NLS-1$
	private static final String ISEMPTY_QUERY= "isEmpty"; //$NON-NLS-1$

	private static final class InvalidBodyError extends Error {
		private static final long serialVersionUID= 1L;
	}

	private IVariableBinding fIndexBinding;
	private IVariableBinding fLengthBinding;
	private IBinding fArrayBinding;
	private Expression fArrayAccess;
	private VariableDeclarationFragment fElementDeclaration;
	private boolean fMakeFinal;
	private boolean fIsCollection;
	private IMethodBinding fSizeMethodBinding;
	private IMethodBinding fGetMethodBinding;
	private MethodInvocation fSizeMethodAccess;
	private boolean fCheckLoopVarUsed;
	private boolean fLoopVarReferenced;

	public ConvertForLoopOperation(ForStatement forStatement) {
		this(forStatement, new String[0], false, false);
	}

	public ConvertForLoopOperation(ForStatement forStatement, String[] usedNames, boolean makeFinal, boolean checkLoopVarUsed) {
		super(forStatement, usedNames);
		fMakeFinal= makeFinal;
		fCheckLoopVarUsed= checkLoopVarUsed;
	}

	@Override
	public IStatus satisfiesPreconditions() {
		ForStatement statement= getForStatement();
		CompilationUnit ast= (CompilationUnit)statement.getRoot();

		IJavaElement javaElement= ast.getJavaElement();
		if (javaElement == null)
			return ERROR_STATUS;

		if (!JavaModelUtil.is50OrHigher(javaElement.getJavaProject()))
			return ERROR_STATUS;

		if (!validateInitializers(statement))
			return ERROR_STATUS;

		if (!validateExpression(statement))
			return ERROR_STATUS;

		if (!validateUpdaters(statement))
			return ERROR_STATUS;

		if (!validateBody(statement))
			return ERROR_STATUS;

		if (fCheckLoopVarUsed && !fLoopVarReferenced)
			return ERROR_STATUS;

		return Status.OK_STATUS;
	}


	/*
	 * Must be one of:
	 * <ul>
	 * <li>int [result]= 0;</li>
	 * <li>int [result]= 0, [lengthBinding]= [arrayBinding].length;</li>
	 * <li>int , [result]= 0;</li>
	 * </ul>
	 */
	private boolean validateInitializers(ForStatement statement) {
		List<Expression> initializers= statement.initializers();
		if (initializers.size() != 1)
			return false;

		Expression expression= initializers.get(0);
		if (!(expression instanceof VariableDeclarationExpression))
			return false;

		VariableDeclarationExpression declaration= (VariableDeclarationExpression)expression;
		ITypeBinding declarationBinding= declaration.resolveTypeBinding();
		if (declarationBinding == null)
			return false;

		if (!declarationBinding.isPrimitive())
			return false;

		if (!PrimitiveType.INT.toString().equals(declarationBinding.getQualifiedName()))
			return false;

		List<VariableDeclarationFragment> fragments= declaration.fragments();
		if (fragments.size() == 1) {
			IVariableBinding indexBinding= getIndexBindingFromFragment(fragments.get(0));
			if (indexBinding == null)
				return false;

			fIndexBinding= indexBinding;
			return true;
		} else if (fragments.size() == 2) {
			IVariableBinding indexBinding= getIndexBindingFromFragment(fragments.get(0));
			if (indexBinding == null) {
				indexBinding= getIndexBindingFromFragment(fragments.get(1));
				if (indexBinding == null)
					return false;

				if (!validateLengthFragment(fragments.get(0)))
					return false;
			} else {
				if (!validateLengthFragment(fragments.get(1)))
					return false;
			}

			fIndexBinding= indexBinding;
			return true;
		}
		return false;
	}


	/*
	 * [lengthBinding]= [arrayBinding].length
	 */
	private boolean validateLengthFragment(VariableDeclarationFragment fragment) {
		Expression initializer= fragment.getInitializer();
		if (initializer == null)
			return false;

		if (!validateLengthQuery(initializer))
			return false;

		IVariableBinding lengthBinding= (IVariableBinding)fragment.getName().resolveBinding();
		if (lengthBinding == null)
			return false;
		fLengthBinding= lengthBinding;

		return true;
	}


	/*
	 * Must be one of:
	 * <ul>
	 * <li>[result]= 0</li>
	 * </ul>
	 */
	private IVariableBinding getIndexBindingFromFragment(VariableDeclarationFragment fragment) {
		if (!Long.valueOf(0).equals(ASTNodes.getIntegerLiteral(fragment.getInitializer())))
			return null;

		return (IVariableBinding)fragment.getName().resolveBinding();
	}


	/*
	 * Must be one of:
	 * <ul>
	 * <li>[indexBinding] < [result].length;</li>
	 * <li>[result].length > [indexBinding];</li>
	 * <li>[indexBinding] < [lengthBinding];</li>
	 * <li>[lengthBinding] > [indexBinding];</li>
	 * <li>[indexBinding] != [result].length;</li>
	 * <li>[result].length != [indexBinding];</li>
	 * <li>[indexBinding] != [lengthBinding];</li>
	 * <li>[lengthBinding] != [indexBinding];</li>
	 * </ul>
	 */
	private boolean validateExpression(ForStatement statement) {
		Expression expression= statement.getExpression();
		if (!(expression instanceof InfixExpression))
			return false;

		InfixExpression infix= (InfixExpression)expression;

		Expression left= infix.getLeftOperand();
		Expression right= infix.getRightOperand();
		if (left instanceof SimpleName && right instanceof SimpleName) {
			IVariableBinding lengthBinding= fLengthBinding;
			if (lengthBinding == null)
				return false;

			IBinding leftBinding= ((SimpleName)left).resolveBinding();
			IBinding righBinding= ((SimpleName)right).resolveBinding();

			if (fIndexBinding.equals(leftBinding)) {
				return lengthBinding.equals(righBinding)
						&& ASTNodes.hasOperator(infix, InfixExpression.Operator.LESS, InfixExpression.Operator.NOT_EQUALS);
			}

			if (fIndexBinding.equals(righBinding)) {
				return lengthBinding.equals(leftBinding)
						&& ASTNodes.hasOperator(infix, InfixExpression.Operator.GREATER, InfixExpression.Operator.NOT_EQUALS);
			}
		} else if (left instanceof SimpleName) {
			if (!fIndexBinding.equals(((SimpleName)left).resolveBinding()))
				return false;

			if (!ASTNodes.hasOperator(infix, InfixExpression.Operator.LESS, InfixExpression.Operator.NOT_EQUALS))
				return false;

			return validateLengthQuery(right);
		} else if (right instanceof SimpleName) {
			if (!fIndexBinding.equals(((SimpleName)right).resolveBinding()))
				return false;

			if (!ASTNodes.hasOperator(infix, InfixExpression.Operator.GREATER, InfixExpression.Operator.NOT_EQUALS))
				return false;

			return validateLengthQuery(left);
		}

		return false;
	}


	/*
	 * Must be one of:
	 * <ul>
	 * <li>[result].length</li>
	 * <li>[result].size()</li>
	 * </ul>
	 */
	private boolean validateLengthQuery(Expression lengthQuery) {
		if (lengthQuery instanceof QualifiedName) {
			QualifiedName qualifiedName= (QualifiedName)lengthQuery;
			SimpleName name= qualifiedName.getName();
			if (!LENGTH_QUERY.equals(name.getIdentifier()))
				return false;

			Name arrayAccess= qualifiedName.getQualifier();
			ITypeBinding accessType= arrayAccess.resolveTypeBinding();
			if (accessType == null)
				return false;

			if (!accessType.isArray())
				return false;

			IBinding arrayBinding= arrayAccess.resolveBinding();
			if (arrayBinding == null)
				return false;

			fArrayBinding= arrayBinding;
			fArrayAccess= arrayAccess;
			return true;
		} else if (lengthQuery instanceof FieldAccess) {
			FieldAccess fieldAccess= (FieldAccess)lengthQuery;
			SimpleName name= fieldAccess.getName();
			if (!LENGTH_QUERY.equals(name.getIdentifier()))
				return false;

			Expression arrayAccess= fieldAccess.getExpression();
			ITypeBinding accessType= arrayAccess.resolveTypeBinding();
			if (accessType == null)
				return false;

			if (!accessType.isArray())
				return false;

			IBinding arrayBinding= getBinding(arrayAccess);
			if (arrayBinding == null)
				return false;

			fArrayBinding= arrayBinding;
			fArrayAccess= arrayAccess;
			return true;
		} else if (lengthQuery instanceof MethodInvocation) {
			MethodInvocation methodCall= (MethodInvocation)lengthQuery;
			SimpleName name= methodCall.getName();
			if (!SIZE_QUERY.equals(name.getIdentifier())
					|| !methodCall.arguments().isEmpty()) {
				return false;
			}
			IMethodBinding methodBinding= methodCall.resolveMethodBinding();
			if (methodBinding == null) {
				return false;
			}
			ITypeBinding classBinding= methodBinding.getDeclaringClass();

			if (isCollection(classBinding)) {
				fIsCollection= true;
				fSizeMethodBinding= methodBinding;
				fSizeMethodAccess= methodCall;
				return true;
			}
		}

		return false;
	}

	private boolean isCollection(ITypeBinding classBinding) {
		if (classBinding.getErasure().getQualifiedName().startsWith("java.util.Collection")) { //$NON-NLS-1$
			return true;
		}

		ITypeBinding superClass= classBinding.getSuperclass();

		if (superClass != null && isCollection(superClass)) {
			return true;
		}

		for (ITypeBinding binding : classBinding.getInterfaces()) {
			if (isCollection(binding)) {
				return true;
			}
		}

		return false;
	}

	/*
	 * Must be one of:
	 * <ul>
	 * <li>[indexBinding]++</li>
	 * <li>++[indexBinding]</li>
	 * <li>[indexBinding]+= 1</li>
	 * <li>[indexBinding]= [indexBinding] + 1</li>
	 * <li>[indexBinding]= 1 + [indexBinding]</li>
	 * <ul>
	 */
	private boolean validateUpdaters(ForStatement statement) {
		List<Expression> updaters= statement.updaters();
		if (updaters.size() != 1)
			return false;

		Expression updater= updaters.get(0);
		if (updater instanceof PostfixExpression) {
			PostfixExpression postfix= (PostfixExpression)updater;

			if (!PostfixExpression.Operator.INCREMENT.equals(postfix.getOperator()))
				return false;

			IBinding binding= getBinding(postfix.getOperand());
			if (!fIndexBinding.equals(binding))
				return false;

			return true;
		} else if (updater instanceof PrefixExpression) {
			PrefixExpression prefix= (PrefixExpression) updater;

			if (!PrefixExpression.Operator.INCREMENT.equals(prefix.getOperator()))
				return false;

			IBinding binding= getBinding(prefix.getOperand());
			if (!fIndexBinding.equals(binding))
				return false;

			return true;
		} else if (updater instanceof Assignment) {
			Assignment assignment= (Assignment)updater;
			Expression left= assignment.getLeftHandSide();
			IBinding binding= getBinding(left);
			if (!fIndexBinding.equals(binding))
				return false;

			if (Assignment.Operator.PLUS_ASSIGN.equals(assignment.getOperator())) {
				return Long.valueOf(1).equals(ASTNodes.getIntegerLiteral(assignment.getRightHandSide()));
			} else if (Assignment.Operator.ASSIGN.equals(assignment.getOperator())) {
				Expression right= assignment.getRightHandSide();
				if (!(right instanceof InfixExpression))
					return false;

				InfixExpression infixExpression= (InfixExpression)right;
				Expression leftOperand= infixExpression.getLeftOperand();
				IBinding leftBinding= getBinding(leftOperand);
				Expression rightOperand= infixExpression.getRightOperand();
				IBinding rightBinding= getBinding(rightOperand);

				if (fIndexBinding.equals(leftBinding)) {
					return Long.valueOf(1).equals(ASTNodes.getIntegerLiteral(rightOperand));
				} else if (fIndexBinding.equals(rightBinding)) {
					return Long.valueOf(1).equals(ASTNodes.getIntegerLiteral(leftOperand));
				}
			}
		}
		return false;
	}

	/*
	 * returns false iff
	 * <ul>
	 * <li><code>indexBinding</code> is used for anything else then accessing
	 * an element of <code>arrayBinding</code></li> or as a parameter to <code>getBinding</code>
	 * <li><code>arrayBinding</code> is assigned</li>
	 * <li>an element of <code>arrayBinding</code> is assigned</li>
	 * <li><code>lengthBinding</code> is referenced</li>
	 * <li>a method call is made to anything but get(<code>indexBinding</code>) or size() or isEmpty()
	 * </ul>
	 * within <code>body</code>
	 */
	private boolean validateBody(ForStatement statement) {
		Statement body= statement.getBody();
		try {
			body.accept(new GenericVisitor() {
				@Override
				protected boolean visitNode(ASTNode node) {
					if (node instanceof ContinueStatement) {
						return false;
					}
					if (node instanceof Name) {
						Name name= (Name)node;
						IBinding nameBinding= name.resolveBinding();
						if (nameBinding == null)
							throw new InvalidBodyError();

						if (nameBinding.equals(fIndexBinding)) {
							fLoopVarReferenced= true;
							if (node.getLocationInParent() == ArrayAccess.INDEX_PROPERTY) {
								if (fIsCollection)
									throw new InvalidBodyError();
								ArrayAccess arrayAccess= (ArrayAccess)node.getParent();
								Expression array= arrayAccess.getArray();
								if (array instanceof QualifiedName) {
									if (!(fArrayAccess instanceof QualifiedName))
										throw new InvalidBodyError();

									IBinding varBinding1= ((QualifiedName) array).getQualifier().resolveBinding();
									if (varBinding1 == null)
										throw new InvalidBodyError();

									IBinding varBinding2= ((QualifiedName) fArrayAccess).getQualifier().resolveBinding();
									if (!varBinding1.equals(varBinding2))
										throw new InvalidBodyError();
								} else if (array instanceof FieldAccess) {
									Expression arrayExpression= ((FieldAccess) array).getExpression();
									if (arrayExpression instanceof ThisExpression) {
										if (fArrayAccess instanceof FieldAccess) {
											Expression arrayAccessExpression= ((FieldAccess) fArrayAccess).getExpression();
											if (!(arrayAccessExpression instanceof ThisExpression))
												throw new InvalidBodyError();
										} else if (fArrayAccess instanceof QualifiedName) {
											throw new InvalidBodyError();
										}
									} else {
										if (!(fArrayAccess instanceof FieldAccess))
											throw new InvalidBodyError();

										Expression arrayAccessExpression= ((FieldAccess) fArrayAccess).getExpression();
										if (!arrayExpression.subtreeMatch(new JdtASTMatcher(), arrayAccessExpression)) {
											throw new InvalidBodyError();
										}
									}
								} else {
									if (fArrayAccess instanceof QualifiedName) {
										throw new InvalidBodyError();
									}
									if (fArrayAccess instanceof FieldAccess) {
										Expression arrayAccessExpression= ((FieldAccess) fArrayAccess).getExpression();
										if (!(arrayAccessExpression instanceof ThisExpression))
											throw new InvalidBodyError();
									}
								}

								IBinding binding= getBinding(array);
								if (binding == null)
									throw new InvalidBodyError();

								if (!fArrayBinding.equals(binding))
									throw new InvalidBodyError();

							} else if (node.getLocationInParent() == MethodInvocation.ARGUMENTS_PROPERTY) {
								MethodInvocation method= (MethodInvocation)node.getParent();
								IMethodBinding methodBinding= method.resolveMethodBinding();
								if (methodBinding == null)
									throw new InvalidBodyError();
								ITypeBinding[] parms= methodBinding.getParameterTypes();
								if (!fIsCollection
										|| !GET_QUERY.equals(method.getName().getFullyQualifiedName())
										|| parms.length != 1
										|| !"int".equals(parms[0].getName()) //$NON-NLS-1$
										|| !areTypeBindingEqual(fSizeMethodBinding.getDeclaringClass(), methodBinding.getDeclaringClass())
										|| fSizeMethodAccess.getExpression() == null
										|| !fSizeMethodAccess.getExpression().subtreeMatch(new ASTMatcher(), method.getExpression()))
									throw new InvalidBodyError();
								fGetMethodBinding= methodBinding;
							} else {
								throw new InvalidBodyError();
							}
						} else if (nameBinding.equals(fArrayBinding)) {
							if (isAssigned(node))
								throw new InvalidBodyError();
						} else if (nameBinding.equals(fLengthBinding)) {
							throw new InvalidBodyError();
						} else if (fElementDeclaration != null && nameBinding.equals(fElementDeclaration.getName().resolveBinding())) {
							if (isAssigned(node))
								fElementDeclaration= null;
						}
					} else if (fIsCollection && node instanceof MethodInvocation) {
						MethodInvocation method= (MethodInvocation)node;
						IMethodBinding binding= method.resolveMethodBinding();
						if (binding == null) {
							throw new InvalidBodyError();
						}
						if (areTypeBindingEqual(fSizeMethodBinding.getDeclaringClass(), binding.getDeclaringClass())) {
							String methodName= method.getName().getFullyQualifiedName();
							if (!SIZE_QUERY.equals(methodName) &&
									!GET_QUERY.equals(methodName) &&
									!ISEMPTY_QUERY.equals(methodName)) {
								throw new InvalidBodyError();
							}
						}
					}

					return true;
				}

				private boolean isAssigned(ASTNode current) {
					while (current != null && !(current instanceof Statement)) {
						if (current.getLocationInParent() == Assignment.LEFT_HAND_SIDE_PROPERTY)
							return true;

						if (current instanceof PrefixExpression
								&& !((PrefixExpression) current).getOperator().equals(PrefixExpression.Operator.NOT))
							return true;

						if (current instanceof PostfixExpression)
							return true;

						current= current.getParent();
					}

					return false;
				}

				@Override
				public boolean visit(ArrayAccess node) {
					if (fElementDeclaration != null)
						return super.visit(node);

					IBinding binding= getBinding(node.getArray());
					if (!fIsCollection && fArrayBinding.equals(binding)) {
						IBinding index= getBinding(node.getIndex());
						if (fIndexBinding.equals(index)) {
							if (node.getLocationInParent() == VariableDeclarationFragment.INITIALIZER_PROPERTY) {
								fElementDeclaration= (VariableDeclarationFragment)node.getParent();
							}
						}
					}
					return super.visit(node);
				}

				@Override
				public boolean visit(MethodInvocation node) {
					if (fElementDeclaration != null || !fIsCollection)
						return super.visit(node);

					IMethodBinding nodeBinding= node.resolveMethodBinding();
					if (nodeBinding == null) {
						return super.visit(node);
					}
					ITypeBinding[] args= nodeBinding.getParameterTypes();
					if (GET_QUERY.equals(nodeBinding.getName()) && args.length == 1 &&
							"int".equals(args[0].getName()) && //$NON-NLS-1$
							areTypeBindingEqual(nodeBinding.getDeclaringClass(), fSizeMethodBinding.getDeclaringClass())) {
						IBinding index= getBinding((Expression)node.arguments().get(0));
						if (fIndexBinding.equals(index)) {
							if (node.getLocationInParent() == VariableDeclarationFragment.INITIALIZER_PROPERTY) {
								fElementDeclaration= (VariableDeclarationFragment)node.getParent();
							}
						}
					}
					return super.visit(node);
				}
			});
		} catch (InvalidBodyError e) {
			return false;
		}

		return true;
	}

	private static boolean areTypeBindingEqual(ITypeBinding binding1, ITypeBinding binding2) {
		if (binding1 == null
				|| binding2 == null) {
			return false;
		}

		if (binding1.isEqualTo(binding2)) {
			return true;
		}

		if (binding1.getErasure() == null
				|| binding2.getErasure() == null
				|| binding1.getTypeArguments() == null
				|| binding2.getTypeArguments() == null
				|| binding1.getTypeArguments().length != binding2.getTypeArguments().length
				|| binding1.getDimensions() != binding2.getDimensions()
				|| binding1.isAnonymous()
				|| binding2.isAnonymous()) {
			return false;
		}

		return binding1.getErasure().isEqualTo(binding2.getErasure());
	}

	private static IBinding getBinding(Expression expression) {
		if (expression instanceof FieldAccess) {
			return ((FieldAccess)expression).resolveFieldBinding();
		} else if (expression instanceof Name) {
			return ((Name)expression).resolveBinding();
		}

		return null;
	}

	@Override
	public String getIntroducedVariableName() {
		if (fElementDeclaration != null) {
			return fElementDeclaration.getName().getIdentifier();
		} else {
			ForStatement forStatement= getForStatement();
			IJavaProject javaProject= ((CompilationUnit)forStatement.getRoot()).getJavaElement().getJavaProject();
			String[] proposals= null;
			if (this.fIsCollection) {
				proposals= getVariableNameProposalsCollection(fSizeMethodAccess, javaProject);
			} else {
				proposals= getVariableNameProposals(fArrayAccess.resolveTypeBinding(), javaProject);
			}
			return proposals[0];
		}
	}

	@Override
	protected Statement convert(CompilationUnitRewrite cuRewrite, TextEditGroup group, LinkedProposalModelCore positionGroups) throws CoreException {
		ASTRewrite rewrite= cuRewrite.getASTRewrite();
		ImportRewrite importRewrite= cuRewrite.getImportRewrite();

		ForStatement forStatement= getForStatement();

		IJavaProject javaProject= ((CompilationUnit)forStatement.getRoot()).getJavaElement().getJavaProject();
		String[] proposals= null;

		if (this.fIsCollection) {
			proposals= getVariableNameProposalsCollection(fSizeMethodAccess, javaProject);
		} else {
			proposals= getVariableNameProposals(fArrayAccess.resolveTypeBinding(), javaProject);
		}

		String parameterName;
		if (fElementDeclaration != null) {
			parameterName= fElementDeclaration.getName().getIdentifier();
		} else {
			parameterName= proposals[0];
		}

		LinkedProposalPositionGroupCore pg= positionGroups.getPositionGroup(parameterName, true);
		if (fElementDeclaration != null)
			pg.addProposal(parameterName, 10);
		for (String proposal : proposals) {
			pg.addProposal(proposal, 10);
		}

		AST ast= forStatement.getAST();
		EnhancedForStatement result= ast.newEnhancedForStatement();

		SingleVariableDeclaration parameterDeclaration= null;
		Expression parameterExpression= null;

		if (this.fIsCollection) {
			parameterExpression= fSizeMethodAccess.getExpression();
			parameterDeclaration= createParameterDeclarationCollection(parameterName, fElementDeclaration, fSizeMethodAccess, forStatement, importRewrite, rewrite, group, pg, fMakeFinal);
		} else {
			parameterExpression= fArrayAccess;
			parameterDeclaration= createParameterDeclaration(parameterName, fElementDeclaration, fArrayAccess, forStatement, importRewrite, rewrite, group, pg, fMakeFinal);
		}
		result.setParameter(parameterDeclaration);

		result.setExpression((Expression)rewrite.createCopyTarget(parameterExpression));

		if (this.fIsCollection) {
			convertBodyCollection(forStatement.getBody(), fIndexBinding, fGetMethodBinding, parameterName, rewrite, group, pg);
		} else {
			convertBody(forStatement.getBody(), fIndexBinding, fArrayBinding, parameterName, rewrite, group, pg);
		}
		result.setBody(getBody(cuRewrite, group, positionGroups));

		positionGroups.setEndPosition(rewrite.track(result));

		return result;
	}

	private void convertBody(Statement body, final IBinding indexBinding, final IBinding arrayBinding, final String parameterName, final ASTRewrite rewrite, final TextEditGroup editGroup, final LinkedProposalPositionGroupCore pg) {
		final AST ast= body.getAST();

		body.accept(new GenericVisitor() {
			@Override
			public boolean visit(ArrayAccess node) {
				IBinding binding= getBinding(node.getArray());
				if (arrayBinding.equals(binding)) {
					IBinding index= getBinding(node.getIndex());
					if (indexBinding.equals(index)) {
						replaceAccess(node);
					}
				}

				return super.visit(node);
			}

			private void replaceAccess(ASTNode node) {
				if (fElementDeclaration != null && node.getLocationInParent() == VariableDeclarationFragment.INITIALIZER_PROPERTY) {
					VariableDeclarationFragment fragment= (VariableDeclarationFragment)node.getParent();
					IBinding targetBinding= fragment.getName().resolveBinding();
					if (targetBinding != null) {
						VariableDeclarationStatement statement= (VariableDeclarationStatement)fragment.getParent();

						if (statement.fragments().size() == 1) {
							rewrite.remove(statement, editGroup);
						} else {
							ListRewrite listRewrite= rewrite.getListRewrite(statement, VariableDeclarationStatement.FRAGMENTS_PROPERTY);
							listRewrite.remove(fragment, editGroup);
						}

					} else {
						SimpleName name= ast.newSimpleName(parameterName);
						ASTNodes.replaceButKeepComment(rewrite, node, name, editGroup);
						pg.addPosition(rewrite.track(name), true);
					}
				} else {
					SimpleName name= ast.newSimpleName(parameterName);
					ASTNodes.replaceButKeepComment(rewrite, node, name, editGroup);
					pg.addPosition(rewrite.track(name), true);
				}
			}
		});
	}

	private SingleVariableDeclaration createParameterDeclaration(String parameterName, VariableDeclarationFragment fragement, Expression arrayAccess, ForStatement statement, ImportRewrite importRewrite, ASTRewrite rewrite, TextEditGroup group, LinkedProposalPositionGroupCore pg, boolean makeFinal) {
		CompilationUnit compilationUnit= (CompilationUnit)arrayAccess.getRoot();
		AST ast= compilationUnit.getAST();

		SingleVariableDeclaration result= ast.newSingleVariableDeclaration();

		SimpleName name= ast.newSimpleName(parameterName);
		pg.addPosition(rewrite.track(name), true);
		result.setName(name);

		ITypeBinding arrayTypeBinding= arrayAccess.resolveTypeBinding();
		Type type= importType(arrayTypeBinding.getElementType(), statement, importRewrite, compilationUnit,
				arrayTypeBinding.getDimensions() == 1 ? TypeLocation.LOCAL_VARIABLE : TypeLocation.ARRAY_CONTENTS);
		if (arrayTypeBinding.getDimensions() != 1) {
			type= ast.newArrayType(type, arrayTypeBinding.getDimensions() - 1);
		}
		result.setType(type);

		if (fragement != null) {
			VariableDeclarationStatement declaration= (VariableDeclarationStatement)fragement.getParent();
			ModifierRewrite.create(rewrite, result).copyAllModifiers(declaration, group);
		}
		if (makeFinal && (fragement == null || ASTNodes.findModifierNode(Modifier.FINAL, ASTNodes.getModifiers(fragement)) == null)) {
			ModifierRewrite.create(rewrite, result).setModifiers(Modifier.FINAL, 0, group);
		}

		return result;
	}

	private String[] getVariableNameProposals(ITypeBinding arrayTypeBinding, IJavaProject project) {
		String[] variableNames= getUsedVariableNames();
		String baseName= modifyBaseName(fArrayBinding.getName());
		String[] elementSuggestions= StubUtility.getLocalNameSuggestions(project, baseName, 0, variableNames);

		String type= arrayTypeBinding.getElementType().getName();
		String[] typeSuggestions= StubUtility.getLocalNameSuggestions(project, type, arrayTypeBinding.getDimensions() - 1, variableNames);

		String[] result= new String[elementSuggestions.length + typeSuggestions.length];
		System.arraycopy(elementSuggestions, 0, result, 0, elementSuggestions.length);
		System.arraycopy(typeSuggestions, 0, result, elementSuggestions.length, typeSuggestions.length);
		return result;
	}

	private String[] getVariableNameProposalsCollection(MethodInvocation sizeMethodAccess, IJavaProject project) {
		String[] variableNames= getUsedVariableNames();
		Expression exp= sizeMethodAccess.getExpression();
		String name= exp instanceof SimpleName ? ((SimpleName)exp).getFullyQualifiedName() : ""; //$NON-NLS-1$
		String baseName= modifyBaseName(name);
		String[] elementSuggestions= StubUtility.getLocalNameSuggestions(project, baseName, 0, variableNames);

		ITypeBinding[] typeArgs= fSizeMethodBinding.getDeclaringClass().getTypeArguments();

		String type;
		if (typeArgs == null || typeArgs.length == 0) {
			type= "Object"; //$NON-NLS-1$
		} else if (typeArgs[0].isCapture()) {
			type= ASTResolving.normalizeWildcardType(typeArgs[0], true, sizeMethodAccess.getRoot().getAST()).getName();
		} else {
			type= typeArgs[0].getName();
		}

		String[] typeSuggestions= StubUtility.getLocalNameSuggestions(project, type, 0, variableNames);

		String[] result= new String[elementSuggestions.length + typeSuggestions.length];
		System.arraycopy(elementSuggestions, 0, result, 0, elementSuggestions.length);
		System.arraycopy(typeSuggestions, 0, result, elementSuggestions.length, typeSuggestions.length);
		return result;
	}

	private void convertBodyCollection(Statement body, final IBinding indexBinding, final IBinding getBinding, final String parameterName, final ASTRewrite rewrite, final TextEditGroup editGroup, final LinkedProposalPositionGroupCore pg) {
		final AST ast= body.getAST();

		body.accept(new GenericVisitor() {
			@Override
			public boolean visit(MethodInvocation node) {
				IBinding binding= node.resolveMethodBinding();
				if (binding != null && binding.isEqualTo(getBinding)) {
					List<Expression> args = node.arguments();
					if (args.size() == 1 && args.get(0) instanceof SimpleName
							&& indexBinding.isEqualTo(((SimpleName)args.get(0)).resolveBinding())) {
						replaceAccess(node);
					}
				}

				return super.visit(node);
			}

			private void replaceAccess(MethodInvocation node) {
				if (fElementDeclaration != null && node.getLocationInParent() == VariableDeclarationFragment.INITIALIZER_PROPERTY) {
					VariableDeclarationFragment fragment= (VariableDeclarationFragment)node.getParent();
					IBinding targetBinding= fragment.getName().resolveBinding();
					if (targetBinding != null && fragment.getName().getFullyQualifiedName().equals(parameterName)) {
						VariableDeclarationStatement statement= (VariableDeclarationStatement)fragment.getParent();

						if (statement.fragments().size() == 1) {
							rewrite.remove(statement, editGroup);
						} else {
							ListRewrite listRewrite= rewrite.getListRewrite(statement, VariableDeclarationStatement.FRAGMENTS_PROPERTY);
							listRewrite.remove(fragment, editGroup);
						}

					} else {
						SimpleName name= ast.newSimpleName(parameterName);
						ASTNodes.replaceButKeepComment(rewrite, node, name, editGroup);
						pg.addPosition(rewrite.track(name), true);
					}
				} else {
					SimpleName name= ast.newSimpleName(parameterName);
					ASTNodes.replaceButKeepComment(rewrite, node, name, editGroup);
					pg.addPosition(rewrite.track(name), true);
				}
			}
		});
	}

	private SingleVariableDeclaration createParameterDeclarationCollection(String parameterName, VariableDeclarationFragment fragment, Expression sizeAccess, ForStatement statement, ImportRewrite importRewrite, ASTRewrite rewrite, TextEditGroup group, LinkedProposalPositionGroupCore pg, boolean makeFinal) {
		CompilationUnit compilationUnit= (CompilationUnit)sizeAccess.getRoot();
		AST ast= compilationUnit.getAST();

		SingleVariableDeclaration result= ast.newSingleVariableDeclaration();

		SimpleName name= ast.newSimpleName(parameterName);
		pg.addPosition(rewrite.track(name), true);
		result.setName(name);

		IMethodBinding sizeTypeBinding= ((MethodInvocation)sizeAccess).resolveMethodBinding();
		ITypeBinding[] sizeTypeArguments= sizeTypeBinding.getDeclaringClass().getTypeArguments();

		ITypeBinding elementType;
		if (sizeTypeArguments == null || sizeTypeArguments.length == 0) {
			elementType= fSizeMethodAccess.getAST().resolveWellKnownType("java.lang.Object"); //$NON-NLS-1$
		} else if (sizeTypeArguments[0].isCapture()) {
			elementType= ASTResolving.normalizeWildcardType(sizeTypeArguments[0], true, ast);
		} else {
			elementType= sizeTypeArguments[0];
		}

		Type type= importType(elementType, statement, importRewrite, compilationUnit, TypeLocation.LOCAL_VARIABLE);
		result.setType(type);

		if (fragment != null) {
			VariableDeclarationStatement declaration= (VariableDeclarationStatement)fragment.getParent();
			ModifierRewrite.create(rewrite, result).copyAllModifiers(declaration, group);
		}
		if (makeFinal && (fragment == null || ASTNodes.findModifierNode(Modifier.FINAL, ASTNodes.getModifiers(fragment)) == null)) {
			ModifierRewrite.create(rewrite, result).setModifiers(Modifier.FINAL, 0, group);
		}

		return result;
	}
}
