/*******************************************************************************
 * Copyright (c) 2014, 2017 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.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.CoreException;

import org.eclipse.text.edits.TextEditGroup;

import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.Type;
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.dom.rewrite.ImportRewrite.TypeLocation;

import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.corext.refactoring.util.NoCommentSourceRangeComputer;

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

import org.eclipse.jdt.internal.ui.text.correction.ProblemLocation;

public class TypeParametersFix extends CompilationUnitRewriteOperationsFix {

	public static final class InsertTypeArgumentsVisitor extends ASTVisitor {

		private final ArrayList<ASTNode> fNodes;

		public InsertTypeArgumentsVisitor(ArrayList<ASTNode> nodes) {
			fNodes= nodes;
		}

		@Override
		public boolean visit(ParameterizedType createdType) {
			if (createdType == null || createdType.typeArguments().size() != 0) {
				return true;
			}

			ITypeBinding binding= createdType.resolveBinding();
			if (binding == null) {
				return true;
			}

			ITypeBinding[] typeArguments= binding.getTypeArguments();
			if (typeArguments.length == 0) {
				return true;
			}

			fNodes.add(createdType);
			return true;
		}
	}

	private static class InsertTypeArgumentsOperation extends CompilationUnitRewriteOperation {

		private final ParameterizedType[] fCreatedTypes;

		public InsertTypeArgumentsOperation(ParameterizedType[] parameterizedTypes) {
			fCreatedTypes= parameterizedTypes;
		}

		@Override
		public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel model) throws CoreException {
			TextEditGroup group= createTextEditGroup(FixMessages.TypeParametersFix_insert_inferred_type_arguments_description, cuRewrite);

			ASTRewrite rewrite= cuRewrite.getASTRewrite();
			ImportRewrite importRewrite= cuRewrite.getImportRewrite();
			AST ast= cuRewrite.getRoot().getAST();

			for (int i= 0; i < fCreatedTypes.length; i++) {
				ParameterizedType createdType= fCreatedTypes[i];

				ITypeBinding[] typeArguments= createdType.resolveBinding().getTypeArguments();
				ContextSensitiveImportRewriteContext importContext= new ContextSensitiveImportRewriteContext(cuRewrite.getRoot(), createdType.getStartPosition(), importRewrite);

				ListRewrite argumentsRewrite= rewrite.getListRewrite(createdType, ParameterizedType.TYPE_ARGUMENTS_PROPERTY);
				for (int j= 0; j < typeArguments.length; j++) {
					ITypeBinding typeArgument= typeArguments[j];
					Type argumentNode= importRewrite.addImport(typeArgument, ast, importContext, TypeLocation.TYPE_ARGUMENT);
					argumentsRewrite.insertLast(argumentNode, group);
				}
			}
		}
	}

	private static class RemoveTypeArgumentsOperation extends CompilationUnitRewriteOperation {

		private final ParameterizedType fParameterizedType;

		public RemoveTypeArgumentsOperation(ParameterizedType parameterizedType) {
			fParameterizedType= parameterizedType;
		}

		@Override
		public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel model) throws CoreException {
			TextEditGroup group= createTextEditGroup(FixMessages.TypeParametersFix_remove_redundant_type_arguments_description, cuRewrite);

			ASTRewrite rewrite= cuRewrite.getASTRewrite();
			rewrite.setTargetSourceRangeComputer(new NoCommentSourceRangeComputer());

			ListRewrite listRewrite= rewrite.getListRewrite(fParameterizedType, ParameterizedType.TYPE_ARGUMENTS_PROPERTY);

			List<Type> typeArguments= fParameterizedType.typeArguments();
			for (Iterator<Type> iterator= typeArguments.iterator(); iterator.hasNext();) {
				listRewrite.remove(iterator.next(), group);
			}
		}
	}

	public static TypeParametersFix createInsertInferredTypeArgumentsFix(CompilationUnit compilationUnit, ParameterizedType node) {
		if (node == null)
			return null;

		final ArrayList<ASTNode> changedNodes= new ArrayList<>();
		node.accept(new InsertTypeArgumentsVisitor(changedNodes));

		if (changedNodes.isEmpty())
			return null;

		CompilationUnitRewriteOperation op= new InsertTypeArgumentsOperation(new ParameterizedType[] { node });
		return new TypeParametersFix(FixMessages.TypeParametersFix_insert_inferred_type_arguments_name, compilationUnit, new CompilationUnitRewriteOperation[] { op });
	}

	public static TypeParametersFix createRemoveRedundantTypeArgumentsFix(CompilationUnit compilationUnit, IProblemLocation problem) {
		int id= problem.getProblemId();
		if (id == IProblem.RedundantSpecificationOfTypeArguments) {
			ParameterizedType parameterizedType= getParameterizedType(compilationUnit, problem);
			if (parameterizedType == null)
				return null;
			RemoveTypeArgumentsOperation operation= new RemoveTypeArgumentsOperation(parameterizedType);
			return new TypeParametersFix(FixMessages.TypeParametersFix_remove_redundant_type_arguments_name, compilationUnit, new CompilationUnitRewriteOperation[] { operation });
		}
		return null;
	}

	public static ICleanUpFix createCleanUp(CompilationUnit compilationUnit, boolean insertInferredTypeArguments, boolean removeRedundantTypeArguments) {

		IProblem[] problems= compilationUnit.getProblems();
		IProblemLocation[] locations= new IProblemLocation[problems.length];
		for (int i= 0; i < problems.length; i++) {
			locations[i]= new ProblemLocation(problems[i]);
		}

		return createCleanUp(compilationUnit, locations,
				insertInferredTypeArguments,
				removeRedundantTypeArguments);
	}

	public static ICleanUpFix createCleanUp(CompilationUnit compilationUnit, IProblemLocation[] problems, boolean insertInferredTypeArguments, boolean removeRedundantTypeArguments) {

		if (insertInferredTypeArguments) {
			final ArrayList<ASTNode> changedNodes= new ArrayList<>();
			compilationUnit.accept(new InsertTypeArgumentsVisitor(changedNodes));

			if (changedNodes.isEmpty())
				return null;

			CompilationUnitRewriteOperation op= new InsertTypeArgumentsOperation(changedNodes.toArray(new ParameterizedType[changedNodes.size()]));
			return new TypeParametersFix(FixMessages.TypeParametersFix_insert_inferred_type_arguments_name, compilationUnit, new CompilationUnitRewriteOperation[] { op });

		} else if (removeRedundantTypeArguments) {
			List<CompilationUnitRewriteOperation> result= new ArrayList<>();
			for (int i= 0; i < problems.length; i++) {
				IProblemLocation problem= problems[i];
				int id= problem.getProblemId();

				if (id == IProblem.RedundantSpecificationOfTypeArguments) {
					ParameterizedType parameterizedType= getParameterizedType(compilationUnit, problem);
					if (parameterizedType == null)
						return null;
					result.add(new RemoveTypeArgumentsOperation(parameterizedType));
				}
			}
			if (!result.isEmpty()) {
				return new TypeParametersFix(FixMessages.TypeParametersFix_remove_redundant_type_arguments_name, compilationUnit, result.toArray(new CompilationUnitRewriteOperation[result.size()]));
			}
		}
		return null;
	}

	private static ParameterizedType getParameterizedType(CompilationUnit compilationUnit, IProblemLocation problem) {
		ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
		if (selectedNode == null)
			return null;

		while (!(selectedNode instanceof ParameterizedType) && !(selectedNode instanceof Statement)) {
			selectedNode= selectedNode.getParent();
		}
		if (selectedNode instanceof ParameterizedType) {
			return (ParameterizedType) selectedNode;
		}
		return null;
	}

	protected TypeParametersFix(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperation[] fixRewriteOperations) {
		super(name, compilationUnit, fixRewriteOperations);
	}
}
