/*******************************************************************************
 * Copyright (c) 2000, 2021 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
 *     Microsoft Corporation - read formatting options from the compilation unit
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.reorg;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

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

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;

import org.eclipse.core.filebuffers.ITextFileBuffer;

import org.eclipse.text.edits.TextEdit;

import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.NullChange;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.ltk.core.refactoring.resource.DeleteResourceChange;

import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.ISourceManipulation;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;

import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.changes.ClasspathChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.DeletePackageFragmentRootChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.DeleteSourceManipulationChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationStateChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.TextChangeCompatibility;
import org.eclipse.jdt.internal.corext.refactoring.changes.UndoablePackageDeleteChange;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringFileBuffers;
import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager;


class DeleteChangeCreator {
	private DeleteChangeCreator() {
		//private
	}

	/**
	 * @param manager the text change manager
	 * @param resources the resources to delete
	 * @param javaElements the Java elements to delete
	 * @param changeName the name of the change
	 * @param packageDeletes a list of {@link IResource}s that will be deleted
	 *        by the delete operation of the {@link IPackageFragment}s in
	 *        <code>javaElements</code>, or <code>null</code> iff
	 *        <code>javaElements</code> does not contain package fragments
	 * @return the created change
	 * @throws CoreException
	 */
	static Change createDeleteChange(TextChangeManager manager, IResource[] resources,
			IJavaElement[] javaElements, String changeName, List<IResource> packageDeletes) throws CoreException {
		/*
		 * Problem: deleting a package and subpackages can result in
		 * multiple package fragments in fJavaElements but only
		 * one folder in packageDeletes. The way to handle this is to make the undo
		 * change of individual package delete changes an empty change, and
		 * add take care of the undo in UndoablePackageDeleteChange.
		 */
		DynamicValidationStateChange result;
		if (packageDeletes.size() > 0) {
			result= new UndoablePackageDeleteChange(changeName, packageDeletes);
		} else {
			result= new DynamicValidationStateChange(changeName);
		}

		for (IJavaElement element : javaElements) {
			if (! ReorgUtils.isInsideCompilationUnit(element))
				result.add(createDeleteChange(element));
		}
		for (IResource resource : resources) {
			result.add(createDeleteChange(resource));
		}

		Map<ICompilationUnit, List<IJavaElement>> grouped= ReorgUtils.groupByCompilationUnit(getElementsSmallerThanCu(javaElements));
		if (!grouped.isEmpty() ){
			Assert.isNotNull(manager);
			for (Map.Entry<ICompilationUnit, List<IJavaElement>> entry : grouped.entrySet()) {
				ICompilationUnit cu = entry.getKey();
				result.add(createDeleteChange(cu, entry.getValue(), manager));
			}
		}

		return result;
	}

	private static Change createDeleteChange(IResource resource) {
		Assert.isTrue(! (resource instanceof IWorkspaceRoot));//cannot be done
		Assert.isTrue(! (resource instanceof IProject)); //project deletion is handled by the workbench
		return new DeleteResourceChange(resource.getFullPath(), true);
	}

	/*
	 * List<IJavaElement> javaElements
	 */
	private static Change createDeleteChange(ICompilationUnit cu, List<IJavaElement> javaElements, TextChangeManager manager) throws CoreException {
		CompilationUnit cuNode= RefactoringASTParser.parseWithASTProvider(cu, false, null);
		CompilationUnitRewrite rewriter= new CompilationUnitRewrite(cu, cuNode);
		IJavaElement[] elements= javaElements.toArray(new IJavaElement[javaElements.size()]);
		ASTNodeDeleteUtil.markAsDeleted(elements, rewriter, null);
		return addTextEditFromRewrite(manager, cu, rewriter.getASTRewrite());
	}

	private static TextChange addTextEditFromRewrite(TextChangeManager manager, ICompilationUnit cu, ASTRewrite rewrite) throws CoreException {
		try {
			ITextFileBuffer buffer= RefactoringFileBuffers.acquire(cu);
			TextEdit resultingEdits= rewrite.rewriteAST(buffer.getDocument(), cu.getOptions(true));
			TextChange textChange= manager.get(cu);
			if (textChange instanceof TextFileChange) {
				TextFileChange tfc= (TextFileChange) textChange;
				tfc.setSaveMode(TextFileChange.KEEP_SAVE_STATE);
			}
			String message= RefactoringCoreMessages.DeleteChangeCreator_1;
			TextChangeCompatibility.addTextEdit(textChange, message, resultingEdits);
			return textChange;
		} finally {
			RefactoringFileBuffers.release(cu);
		}
	}

	//List<IJavaElement>
	private static List<IJavaElement> getElementsSmallerThanCu(IJavaElement[] javaElements){
		List<IJavaElement> result= new ArrayList<>();
		for (IJavaElement element : javaElements) {
			if (ReorgUtils.isInsideCompilationUnit(element))
				result.add(element);
		}
		return result;
	}

	private static Change createDeleteChange(IJavaElement javaElement) throws JavaModelException {
		Assert.isTrue(! ReorgUtils.isInsideCompilationUnit(javaElement));

		switch(javaElement.getElementType()){
			case IJavaElement.PACKAGE_FRAGMENT_ROOT:
				return createPackageFragmentRootDeleteChange((IPackageFragmentRoot)javaElement);

			case IJavaElement.PACKAGE_FRAGMENT:
				return createSourceManipulationDeleteChange((IPackageFragment)javaElement);

			case IJavaElement.COMPILATION_UNIT:
				return createSourceManipulationDeleteChange((ICompilationUnit)javaElement);

			case IJavaElement.CLASS_FILE:
				//if this assert fails, it means that a precondition is missing
				Assert.isTrue(((IClassFile)javaElement).getResource() instanceof IFile);
				return createDeleteChange(((IClassFile)javaElement).getResource());

			case IJavaElement.JAVA_MODEL: //cannot be done
			case IJavaElement.JAVA_PROJECT: //handled differently
				Assert.isTrue(false);
				return null;

			case IJavaElement.TYPE:
			case IJavaElement.FIELD:
			case IJavaElement.METHOD:
			case IJavaElement.INITIALIZER:
			case IJavaElement.PACKAGE_DECLARATION:
			case IJavaElement.IMPORT_CONTAINER:
			case IJavaElement.IMPORT_DECLARATION:
			default://there's no more kinds
				Assert.isTrue(false);//not done here
				return new NullChange();
		}
	}

	private static Change createSourceManipulationDeleteChange(ISourceManipulation element) {
		//XXX workaround for bug 31384, in case of linked ISourceManipulation delete the resource
		if (element instanceof ICompilationUnit || element instanceof IPackageFragment){
			IResource resource;
			if (element instanceof ICompilationUnit)
				resource= ReorgUtils.getResource((ICompilationUnit)element);
			else
				resource= ((IPackageFragment)element).getResource();
			if (resource != null && resource.isLinked())
				return createDeleteChange(resource);
		}
		return new DeleteSourceManipulationChange(element, true);
	}

	private static Change createPackageFragmentRootDeleteChange(IPackageFragmentRoot root) throws JavaModelException {
		IResource resource= root.getResource();
		if (resource != null && resource.isLinked()){
			//XXX using this code is a workaround for jcore bug 31998
			//jcore cannot handle linked stuff
			//normally, we should always create DeletePackageFragmentRootChange
			CompositeChange composite= new DynamicValidationStateChange(RefactoringCoreMessages.DeleteRefactoring_delete_package_fragment_root);

			ClasspathChange change= ClasspathChange.removeEntryChange(root.getJavaProject(), root.getRawClasspathEntry());
			if (change != null) {
				composite.add(change);
			}
			Assert.isTrue(! Checks.isClasspathDelete(root));//checked in preconditions
			composite.add(createDeleteChange(resource));

			return composite;
		} else {
			Assert.isTrue(! root.isExternal());
			// TODO remove the query argument
			return new DeletePackageFragmentRootChange(root, true, null);
		}
	}
}
