/*******************************************************************************
 * Copyright (c) 2020 Fabrice TIERCELIN 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:
 *     Fabrice TIERCELIN - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.fix;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;

import org.eclipse.text.edits.TextEditGroup;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConditionalExpression;
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.InfixExpression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NullLiteral;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.TypeDeclaration;
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.refactoring.CompilationUnitChange;

import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.OrderedInfixExpression;
import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation;
import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;

import org.eclipse.jdt.ui.cleanup.CleanUpRequirements;
import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
import org.eclipse.jdt.ui.text.java.IProblemLocation;

/**
 * A fix that rewrites Eclipse-autogenerated hashcode method by Eclipse-autogenerated hashcode method for Java 7:
 * <ul>
 * <li>It relies on the <code>Objects.hashCode()</code> method.</li>
 * </ul>
 */
public class HashCleanUp extends AbstractMultiFix implements ICleanUpFix {
	private static final String HASH_CODE_METHOD= "hashCode"; //$NON-NLS-1$

	private static final class CollectedData {
		public List<Expression> fields= new ArrayList<>();
		public SimpleName primeId;
		public SimpleName resultId;
		public Iterator<Statement> stmtIterator;
		public SimpleName tempVar;
		public boolean tempValueUsed= true;
		public boolean hasReturnStatement;
	}

	public HashCleanUp() {
		this(Collections.emptyMap());
	}

	public HashCleanUp(Map<String, String> options) {
		super(options);
	}

	@Override
	public CleanUpRequirements getRequirements() {
		boolean requireAST= isEnabled(CleanUpConstants.MODERNIZE_HASH);
		return new CleanUpRequirements(requireAST, false, false, null);
	}

	@Override
	public String[] getStepDescriptions() {
		if (isEnabled(CleanUpConstants.MODERNIZE_HASH)) {
			return new String[] { MultiFixMessages.HashCleanup_description };
		}

		return new String[0];
	}

	@Override
	public String getPreview() {
		if (isEnabled(CleanUpConstants.MODERNIZE_HASH)) {
			return "" //$NON-NLS-1$
				+ "return Objects.hash(aShort);\n\n\n\n"; //$NON-NLS-1$
		}

		return "" //$NON-NLS-1$
			+ "final int prime = 31;\n" //$NON-NLS-1$
			+ "int result = 1;\n" //$NON-NLS-1$
			+ "result = prime * result + aShort;\n" //$NON-NLS-1$
			+ "return result;\n"; //$NON-NLS-1$
	}

	@Override
	protected ICleanUpFix createFix(CompilationUnit unit) throws CoreException {
		if (!isEnabled(CleanUpConstants.MODERNIZE_HASH) || !JavaModelUtil.is1d7OrHigher(unit.getJavaElement().getJavaProject())) {
			return null;
		}

		final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>();

		unit.accept(new ASTVisitor() {
			@Override
			public boolean visit(final MethodDeclaration node) {
				Block body= node.getBody();

				if (ASTNodes.usesGivenSignature(node, Object.class.getCanonicalName(), HASH_CODE_METHOD) && body != null) {
					List<Statement> statements= body.statements();

					if (statements.size() > 2) {
						CollectedData data= new CollectedData();
						data.stmtIterator= statements.iterator();

						data.primeId= isVariableValid(data, 31);
						data.resultId= isVariableValid(data, 1);

						if (data.primeId != null
								&& data.resultId != null
								&& data.stmtIterator.hasNext()) {
							while (!data.hasReturnStatement && data.stmtIterator.hasNext()) {
								if (!isStmtValid(data)) {
									return true;
								}
							}

							if (data.hasReturnStatement && !data.stmtIterator.hasNext()) {
								rewriteOperations.add(new HashOperation(node, data));
								return false;
							}
						}
					}
				}

				return true;
			}

			private SimpleName isVariableValid(final CollectedData data, final int initValue) {
				Statement statement= data.stmtIterator.next();
				VariableDeclarationStatement varDecl= ASTNodes.as(statement, VariableDeclarationStatement.class);
				VariableDeclarationFragment fragment= ASTNodes.getUniqueFragment(varDecl);

				if (fragment != null
						&& ASTNodes.hasType(varDecl.getType().resolveBinding(), int.class.getSimpleName())
						&& Long.valueOf(initValue).equals(ASTNodes.getIntegerLiteral(fragment.getInitializer()))) {
					return fragment.getName();
				}

				return null;
			}

			private boolean isStmtValid(final CollectedData data) {
				Statement statement= data.stmtIterator.next();
				ExpressionStatement exprStatement= ASTNodes.as(statement, ExpressionStatement.class);

				if (exprStatement != null) {
					return isAssignmentValid(data, exprStatement);
				}

				ReturnStatement returnStatement= ASTNodes.as(statement, ReturnStatement.class);

				if (returnStatement != null) {
					data.hasReturnStatement= true;
					Expression expression= returnStatement.getExpression();

					return isGivenVariable(expression, data.resultId) || isHashValid(data, expression);
				}

				VariableDeclarationStatement varStatement= ASTNodes.as(statement, VariableDeclarationStatement.class);

				if (varStatement != null && data.tempVar == null) {
					VariableDeclarationFragment fragment= ASTNodes.getUniqueFragment(varStatement);

					if (ASTNodes.hasType(varStatement.getType().resolveBinding(), long.class.getSimpleName()) && fragment != null) {
						data.tempVar= fragment.getName();
						Expression initializer= fragment.getInitializer();

						if (fragment.getExtraDimensions() == 0) {
							if (initializer != null) {
								SimpleName fieldToFind= isDoubleToLongBitsMethod(data, initializer);
								data.tempValueUsed= false;

								if (fieldToFind != null && data.stmtIterator.hasNext()) {
									boolean assignmentValid= isStmtValid(data);

									if (assignmentValid) {
										data.fields.add(ASTNodes.getUnparenthesedExpression(fieldToFind));
										return true;
									}
								}
							} else if (data.stmtIterator.hasNext()) {
								return isStmtValid(data);
							}
						}
					}
				}

				return false;
			}

			private boolean isAssignmentValid(final CollectedData data, final ExpressionStatement statement) {
				Assignment assignment= ASTNodes.as(statement.getExpression(), Assignment.class);

				if (assignment != null && ASTNodes.hasOperator(assignment, Assignment.Operator.ASSIGN)) {
					Expression field= assignment.getLeftHandSide();
					Expression resultComputation= assignment.getRightHandSide();

					if (isGivenVariable(field, data.resultId)) {
						return isHashValid(data, resultComputation);
					}
					if (data.tempVar != null && isGivenVariable(field, data.tempVar)) {
						SimpleName fieldToFind= isDoubleToLongBitsMethod(data, resultComputation);

						if (fieldToFind != null && data.stmtIterator.hasNext()) {
							data.tempValueUsed= false;
							boolean assignmentValid= isStmtValid(data);

							if (assignmentValid) {
								data.fields.add(ASTNodes.getUnparenthesedExpression(fieldToFind));
								return true;
							}
						}
					}
				}

				return false;
			}

			private SimpleName isDoubleToLongBitsMethod(final CollectedData data, final Expression initializer) {
				SimpleName fieldToFind= null;
				MethodInvocation doubleToLongBits= ASTNodes.as(initializer, MethodInvocation.class);

				if (doubleToLongBits != null && ASTNodes.usesGivenSignature(doubleToLongBits, Double.class.getCanonicalName(), "doubleToLongBits", double.class.getSimpleName())) { //$NON-NLS-1$
					SimpleName fieldName= ASTNodes.as((Expression) doubleToLongBits.arguments().get(0), SimpleName.class);

					if (fieldName != null
							&& !ASTNodes.isSameVariable(fieldName, data.primeId)
							&& !ASTNodes.isSameVariable(fieldName, data.resultId)) {
						fieldToFind= fieldName;
					}
				}

				return fieldToFind;
			}

			private boolean isHashValid(final CollectedData data, final Expression hashComputation) {
				InfixExpression hashAddition= ASTNodes.as(hashComputation, InfixExpression.class);

				if (hashAddition != null) {
					InfixExpression primeTimesResult= ASTNodes.as(hashAddition.getLeftOperand(), InfixExpression.class);
					Expression newHash= hashAddition.getRightOperand();

					if (!hashAddition.hasExtendedOperands()
							&& ASTNodes.hasOperator(hashAddition, InfixExpression.Operator.PLUS)
							&& primeTimesResult != null
							&& !primeTimesResult.hasExtendedOperands()
							&& ASTNodes.hasOperator(primeTimesResult, InfixExpression.Operator.TIMES)
							&& (isGivenVariable(primeTimesResult.getLeftOperand(), data.primeId)
									&& isGivenVariable(primeTimesResult.getRightOperand(), data.resultId)
									|| isGivenVariable(primeTimesResult.getLeftOperand(), data.resultId)
											&& isGivenVariable(primeTimesResult.getRightOperand(), data.primeId))) {
						return isNewHashValid(data, newHash);
					}
				}

				return false;
			}

			private boolean isNewHashValid(final CollectedData data, final Expression newHash) {
				if (newHash instanceof ParenthesizedExpression) {
					ParenthesizedExpression newHashWithoutBrackets= (ParenthesizedExpression) newHash;

					return isNewHashValid(data, newHashWithoutBrackets.getExpression());
				}

				if ((newHash instanceof Name || newHash instanceof FieldAccess || newHash instanceof SuperFieldAccess) && data.tempValueUsed) {
					SimpleName fieldName= ASTNodes.getField(newHash);

					if (fieldName != null
							&& !ASTNodes.isSameVariable(data.primeId, fieldName)
							&& !ASTNodes.isSameVariable(data.resultId, fieldName)) {
						data.fields.add(ASTNodes.getUnparenthesedExpression(fieldName));
						return true;
					}
				} else if (newHash instanceof ConditionalExpression && data.tempValueUsed) {
					ConditionalExpression condition= (ConditionalExpression) newHash;
					return isObjectValid(data, condition) || isBooleanValid(data, condition);
				} else if (newHash instanceof MethodInvocation && data.tempValueUsed) {
					MethodInvocation specificMethod= (MethodInvocation) newHash;
					TypeDeclaration innerClass= ASTNodes.getTypedAncestor(newHash, TypeDeclaration.class);
					TypeDeclaration topLevelClass= ASTNodes.getTypedAncestor(innerClass, TypeDeclaration.class);

					if (ASTNodes.usesGivenSignature(specificMethod, Float.class.getCanonicalName(), "floatToIntBits", float.class.getSimpleName())) { //$NON-NLS-1$
						SimpleName fieldName= ASTNodes.getField((Expression) specificMethod.arguments().get(0));

						if (fieldName != null
								&& !ASTNodes.isSameVariable(fieldName, data.primeId)
								&& !ASTNodes.isSameVariable(fieldName, data.resultId)) {
							data.fields.add(ASTNodes.getUnparenthesedExpression(fieldName));
							return true;
						}
					} else if (ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, boolean[].class.getCanonicalName())
							|| ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, byte[].class.getCanonicalName())
							|| ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, char[].class.getCanonicalName())
							|| ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, double[].class.getCanonicalName())
							|| ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, float[].class.getCanonicalName())
							|| ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, int[].class.getCanonicalName())
							|| ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, Object[].class.getCanonicalName())
							|| ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, long[].class.getCanonicalName())
							|| ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, short[].class.getCanonicalName())) {
						SimpleName fieldName= ASTNodes.getField((Expression) specificMethod.arguments().get(0));

						if (fieldName != null
								&& !ASTNodes.isSameVariable(fieldName, data.primeId)
								&& !ASTNodes.isSameVariable(fieldName, data.resultId)) {
							data.fields.add(ASTNodes.getUnparenthesedExpression(specificMethod));
							return true;
						}
					} else if (innerClass != null
							&& innerClass.resolveBinding() != null
							&& topLevelClass != null
							&& topLevelClass.resolveBinding() != null
							&& ASTNodes.usesGivenSignature(specificMethod, topLevelClass.resolveBinding().getQualifiedName(), HASH_CODE_METHOD)) {
						return isEnclosingHashCode(data, specificMethod, innerClass, topLevelClass);
					}
				} else if (newHash instanceof CastExpression) {
					return isGreatNumberValid(data, (CastExpression) newHash);
				}

				return false;
			}

			private boolean isEnclosingHashCode(final CollectedData data, final MethodInvocation specificMethod,
					final TypeDeclaration innerClass, final TypeDeclaration topLevelClass) {
				MethodInvocation getEnclosingInstanceMethod= ASTNodes.as(specificMethod.getExpression(), MethodInvocation.class);

				if (ASTNodes.usesGivenSignature(getEnclosingInstanceMethod, innerClass.resolveBinding().getQualifiedName(), "getEnclosingInstance")) { //$NON-NLS-1$
					MethodDeclaration getEnclosingInstanceDeclaration= null;

					for (MethodDeclaration innerMethod : innerClass.getMethods()) {
						if ("getEnclosingInstance".equals(innerMethod.getName().getIdentifier()) //$NON-NLS-1$
								&& (innerMethod.parameters() == null || innerMethod.parameters().isEmpty())
								&& !innerMethod.isConstructor()
								&& innerMethod.resolveBinding() != null
								&& ASTNodes.hasType(innerMethod.resolveBinding().getReturnType(), topLevelClass.resolveBinding().getQualifiedName())) {
							getEnclosingInstanceDeclaration= innerMethod;
							break;
						}
					}

					if (getEnclosingInstanceDeclaration != null) {
						ReturnStatement returnStatement= ASTNodes.as(getEnclosingInstanceDeclaration.getBody(), ReturnStatement.class);

						if (returnStatement != null) {
							ThisExpression thisExpression= ASTNodes.as(returnStatement.getExpression(),
									ThisExpression.class);

							if (thisExpression != null) {
								SimpleName topLevelClassReference= ASTNodes.as(thisExpression.getQualifier(),
										SimpleName.class);

								if (topLevelClassReference != null
										&& topLevelClass.getName().getIdentifier().equals(topLevelClassReference.getIdentifier())) {
									data.fields.add(ASTNodes.getUnparenthesedExpression(specificMethod));
									return true;
								}
							}
						}
					}
				}

				return false;
			}

			private boolean isGreatNumberValid(final CollectedData data, final CastExpression newHash) {
				OrderedInfixExpression<Expression, InfixExpression> orderedBitwise= ASTNodes.orderedInfix(newHash.getExpression(), Expression.class, InfixExpression.class);

				if (ASTNodes.hasType(newHash, int.class.getSimpleName())
						&& orderedBitwise != null
						&& ASTNodes.hasType(newHash.getExpression(), long.class.getSimpleName(), double.class.getSimpleName())
						&& InfixExpression.Operator.XOR.equals(orderedBitwise.getOperator())) {
					SimpleName field= ASTNodes.getField(orderedBitwise.getFirstOperand());
					InfixExpression moveExpression= orderedBitwise.getSecondOperand();

					if (field != null
							&& moveExpression != null
							&& !ASTNodes.isSameVariable(field, data.primeId)
							&& !ASTNodes.isSameVariable(field, data.resultId)
							&& ASTNodes.hasOperator(moveExpression, InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED)) {
						SimpleName againFieldName= ASTNodes.getField(moveExpression.getLeftOperand());
						Long hash= ASTNodes.getIntegerLiteral(moveExpression.getRightOperand());

						if (Long.valueOf(32).equals(hash)
								&& againFieldName != null
								&& ASTNodes.isSameVariable(againFieldName, field)) {
							if (data.tempValueUsed) {
								data.fields.add(ASTNodes.getUnparenthesedExpression(againFieldName));
								return true;
							}

							if (ASTNodes.isSameVariable(data.tempVar, field)) {
								data.tempValueUsed= true;
								return true;
							}
						}
					}
				}

				return false;
			}

			private boolean isBooleanValid(final CollectedData data, final ConditionalExpression newHash) {
				SimpleName booleanField= ASTNodes.getField(newHash.getExpression());
				Long hashForTrue= ASTNodes.getIntegerLiteral(newHash.getThenExpression());
				Long hashForFalse= ASTNodes.getIntegerLiteral(newHash.getElseExpression());

				if (booleanField != null
						&& hashForTrue != null
						&& hashForFalse != null
						&& ASTNodes.hasType(booleanField, boolean.class.getSimpleName())
						&& !ASTNodes.isSameVariable(booleanField, data.primeId)
						&& !ASTNodes.isSameVariable(booleanField, data.resultId) && Long.valueOf(1231).equals(hashForTrue)
						&& Long.valueOf(1237).equals(hashForFalse)) {
					data.fields.add(ASTNodes.getUnparenthesedExpression(booleanField));
					return true;
				}

				return false;
			}

			private boolean isObjectValid(final CollectedData data, final ConditionalExpression condition) {
				OrderedInfixExpression<Expression, NullLiteral> orderedIsFieldNull= ASTNodes.orderedInfix(condition.getExpression(), Expression.class, NullLiteral.class);

				if (orderedIsFieldNull != null
						&& Arrays.asList(InfixExpression.Operator.EQUALS, InfixExpression.Operator.NOT_EQUALS).contains(orderedIsFieldNull.getOperator())) {
					SimpleName field= ASTNodes.getField(orderedIsFieldNull.getFirstOperand());

					if (field != null) {
						Long zero;
						MethodInvocation hashOnField;

						if (InfixExpression.Operator.EQUALS.equals(orderedIsFieldNull.getOperator())) {
							zero= ASTNodes.getIntegerLiteral(condition.getThenExpression());
							hashOnField= ASTNodes.as(condition.getElseExpression(), MethodInvocation.class);
						} else {
							hashOnField= ASTNodes.as(condition.getThenExpression(), MethodInvocation.class);
							zero= ASTNodes.getIntegerLiteral(condition.getElseExpression());
						}

						if (Long.valueOf(0).equals(zero)
								&& hashOnField != null
								&& hashOnField.getExpression() != null
								&& HASH_CODE_METHOD.equals(hashOnField.getName().getIdentifier())
								&& (hashOnField.arguments() == null || hashOnField.arguments().isEmpty())) {
							SimpleName fieldToHash= ASTNodes.getField(hashOnField.getExpression());

							if (fieldToHash != null
									&& ASTNodes.isSameVariable(field, fieldToHash)) {
								data.fields.add(ASTNodes.getUnparenthesedExpression(fieldToHash));
								return true;
							}
						}
					}
				}

				return false;
			}

			private boolean isGivenVariable(final Expression expression, final SimpleName varId) {
				SimpleName field= ASTNodes.getField(expression);
				return field != null && ASTNodes.isSameVariable(varId, field);
			}
		});

		if (rewriteOperations.isEmpty()) {
			return null;
		}

		return new CompilationUnitRewriteOperationsFix(MultiFixMessages.HashCleanup_description, unit,
				rewriteOperations.toArray(new CompilationUnitRewriteOperation[0]));
	}

	@Override
	public CompilationUnitChange createChange(IProgressMonitor progressMonitor) throws CoreException {
		return null;
	}

	@Override
	public boolean canFix(final ICompilationUnit compilationUnit, final IProblemLocation problem) {
		return false;
	}

	@Override
	protected ICleanUpFix createFix(final CompilationUnit unit, final IProblemLocation[] problems) throws CoreException {
		return null;
	}

	private static class HashOperation extends CompilationUnitRewriteOperation {
		private final MethodDeclaration node;
		private final CollectedData data;

		public HashOperation(final MethodDeclaration node, final CollectedData data) {
			this.node= node;
			this.data= data;
		}

		@Override
		public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException {
			ASTRewrite rewrite= cuRewrite.getASTRewrite();
			AST ast= cuRewrite.getRoot().getAST();
			ImportRewrite importRewrite= cuRewrite.getImportRewrite();
			TextEditGroup group= createTextEditGroup(MultiFixMessages.HashCleanup_description, cuRewrite);

			String objectsNameText= importRewrite.addImport(Objects.class.getCanonicalName());

			List<Statement> statements= node.getBody().statements();
			Name objectsClassName= ASTNodeFactory.newName(ast, objectsNameText);

			MethodInvocation methodInvocation= ast.newMethodInvocation();
			methodInvocation.setExpression(objectsClassName);
			methodInvocation.setName(ast.newSimpleName("hash")); //$NON-NLS-1$
			methodInvocation.arguments().addAll(ASTNodes.createMoveTarget(rewrite, data.fields));

			ReturnStatement newReturnStatement= ast.newReturnStatement();
			newReturnStatement.setExpression(methodInvocation);
			ASTNodes.replaceButKeepComment(rewrite, statements.get(0),
					newReturnStatement, group);

			for (int i= 1; i < statements.size(); i++) {
				rewrite.remove(statements.get(i), group);
			}
		}
	}
}
