/*******************************************************************************
 * Copyright (c) 2000, 2018 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.refactoring.rename;

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

import org.eclipse.text.edits.ReplaceEdit;

import org.eclipse.ltk.core.refactoring.GroupCategorySet;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.core.search.MethodDeclarationMatch;
import org.eclipse.jdt.core.search.SearchMatch;

import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringAvailabilityTester;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup;
import org.eclipse.jdt.internal.corext.refactoring.delegates.DelegateMethodCreator;
import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext;
import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager;
import org.eclipse.jdt.internal.corext.util.Messages;

import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;

public class RenameNonVirtualMethodProcessor extends RenameMethodProcessor {

	/**
	 * Creates a new rename method processor.
	 * <p>
	 * This constructor is only invoked by <code>RenameTypeProcessor</code>.
	 * </p>
	 *
	 * @param method the method
	 * @param manager the change manager
	 * @param categorySet the group category set
	 */
	RenameNonVirtualMethodProcessor(IMethod method, TextChangeManager manager, GroupCategorySet categorySet) {
		super(method, manager, categorySet);
	}

	/**
	 * Creates a new rename method processor.
	 * @param method the method, or <code>null</code> if invoked by scripting
	 */
	public RenameNonVirtualMethodProcessor(IMethod method) {
		super(method);
	}

	/**
	 * Creates a new rename method processor from scripting arguments
	 *
	 * @param method the method, or <code>null</code> if invoked by scripting
	 * @param arguments the arguments
	 * @param status the resulting status
	 */
	public RenameNonVirtualMethodProcessor(IMethod method, JavaRefactoringArguments arguments, RefactoringStatus status) {
		this(method);
		RefactoringStatus initializeStatus= initialize(arguments);
		status.merge(initializeStatus);
	}

	@Override
	public boolean isApplicable() throws CoreException {
		return RefactoringAvailabilityTester.isRenameNonVirtualMethodAvailable(getMethod());
	}

	//----------- preconditions --------------

	@Override
	protected RefactoringStatus doCheckFinalConditions(IProgressMonitor pm, CheckConditionsContext checkContext) throws CoreException {
		try{
			pm.beginTask("", 3); //$NON-NLS-1$
			RefactoringStatus result= new RefactoringStatus();
			result.merge(super.doCheckFinalConditions(new SubProgressMonitor(pm, 1), checkContext));
			if (result.hasFatalError())
				return result;

			final IMethod method= getMethod();
			final IType declaring= method.getDeclaringType();
			final String name= getNewElementName();
			IMethod[] hierarchyMethods= hierarchyDeclaresMethodName(
				new SubProgressMonitor(pm, 1), declaring.newTypeHierarchy(new SubProgressMonitor(pm, 1)), method, name);

			for (IMethod hierarchyMethod : hierarchyMethods) {
				RefactoringStatusContext context= JavaStatusContext.create(hierarchyMethod);
				if (Checks.compareParamTypes(method.getParameterTypes(), hierarchyMethod.getParameterTypes())) {
					String message= Messages.format(
						RefactoringCoreMessages.RenamePrivateMethodRefactoring_hierarchy_defines,
						new String[]{BasicElementLabels.getJavaElementName(declaring.getFullyQualifiedName('.')), BasicElementLabels.getJavaElementName(name)});
					result.addError(message, context);
				}else {
					String message= Messages.format(
						RefactoringCoreMessages.RenamePrivateMethodRefactoring_hierarchy_defines2,
						new String[]{BasicElementLabels.getJavaElementName(declaring.getFullyQualifiedName('.')), BasicElementLabels.getJavaElementName(name)});
					result.addWarning(message, context);
				}
			}
			return result;
		} finally{
			pm.done();
		}
	}

	/*
	 * @see RenameMethodProcessor#addOccurrences(org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager, org.eclipse.core.runtime.IProgressMonitor, RefactoringStatus)
	 */
	@Override
	protected void addOccurrences(TextChangeManager manager, IProgressMonitor pm, RefactoringStatus status) throws CoreException {
		pm.beginTask("", 1); //$NON-NLS-1$
		// declaration update must be registered first
		addDeclarationUpdate(manager);
		if (getUpdateReferences())
			addReferenceUpdates(manager, pm);
		pm.worked(1);
	}

	private ICompilationUnit getDeclaringCU() {
		return getMethod().getCompilationUnit();
	}

	private void addDeclarationUpdate(TextChangeManager manager) throws CoreException {

		if (getDelegateUpdating()) {
			// create the delegate
			CompilationUnitRewrite rewrite= new CompilationUnitRewrite(getDeclaringCU());
			rewrite.setResolveBindings(true);
			MethodDeclaration methodDeclaration= ASTNodeSearchUtil.getMethodDeclarationNode(getMethod(), rewrite.getRoot());
			DelegateMethodCreator creator= new DelegateMethodCreator();
			creator.setDeclaration(methodDeclaration);
			creator.setDeclareDeprecated(getDeprecateDelegates());
			creator.setSourceRewrite(rewrite);
			creator.setCopy(true);
			creator.setNewElementName(getNewElementName());
			creator.prepareDelegate();
			creator.createEdit();
			CompilationUnitChange cuChange= rewrite.createChange(true);
			if (cuChange != null) {
				cuChange.setKeepPreviewEdits(true);
				manager.manage(getDeclaringCU(), cuChange);
			}
		}

		String editName= RefactoringCoreMessages.RenameMethodRefactoring_update_declaration;
		ISourceRange nameRange= getMethod().getNameRange();
		ReplaceEdit replaceEdit= new ReplaceEdit(nameRange.getOffset(), nameRange.getLength(), getNewElementName());
		addTextEdit(manager.get(getDeclaringCU()), editName, replaceEdit);
	}

	private void addReferenceUpdates(TextChangeManager manager, IProgressMonitor pm) {
		for (SearchResultGroup group : getOccurrences()) {
			ICompilationUnit cu= group.getCompilationUnit();
			TextChange change= manager.get(cu);
			for (SearchMatch match : group.getSearchResults()) {
				if (!(match instanceof MethodDeclarationMatch)) {
					ReplaceEdit replaceEdit= createReplaceEdit(match, cu);
					String editName= RefactoringCoreMessages.RenamePrivateMethodRefactoring_update;
					addTextEdit(change, editName, replaceEdit);
				}
			}
		}
		pm.done();
	}

	@Override
	public String getDelegateUpdatingTitle(boolean plural) {
		if (plural)
			return RefactoringCoreMessages.DelegateMethodCreator_keep_original_renamed_plural;
		else
			return RefactoringCoreMessages.DelegateMethodCreator_keep_original_renamed_singular;
	}
}
