/*******************************************************************************
 * Copyright (c) 2000, 2011 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.reorg;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

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

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory;

import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.participants.DeleteArguments;
import org.eclipse.ltk.core.refactoring.participants.ParticipantManager;
import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant;
import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor;
import org.eclipse.ltk.core.refactoring.participants.SharableParticipants;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;

import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaElementUtil;

/**
 * A modification collector for Java element delete operations.
 */
public class DeleteModifications extends RefactoringModifications {

	private List<IJavaElement> fDelete;

	/**
	 * Contains the actual packages when executing
	 * <code>handlePackageFragmentDelete</code>. This is part of the
	 * algorithm to check if a parent folder can be deleted.
	 */
	private Set<IPackageFragment> fPackagesToDelete;

	public DeleteModifications() {
		fDelete= new ArrayList<>();
		fPackagesToDelete= new LinkedHashSet<>();
	}

	public void delete(IResource resource) {
		getResourceModifications().addDelete(resource);
	}

	public void delete(IResource[] resources) {
		for (IResource resource : resources) {
			delete(resource);
		}
	}

	public void delete(IJavaElement[] elements) throws CoreException {
		for (IJavaElement element : elements) {
			delete(element);
		}
	}

	public void delete(IJavaElement element) throws CoreException {
		switch(element.getElementType()) {
			case IJavaElement.JAVA_MODEL:
				return;
			case IJavaElement.JAVA_PROJECT:
				fDelete.add(element);
				if (element.getResource() != null)
					getResourceModifications().addDelete(element.getResource());
				return;
			case IJavaElement.PACKAGE_FRAGMENT_ROOT:
				fDelete.add(element);
				IResource resource= element.getResource();
				// Flag an resource change even if we have an archive. If it is
				// internal (we have a underlying resource then we have a resource
				// change.
				if (resource != null)
					getResourceModifications().addDelete(resource);
				for (IJavaProject referencingProject : JavaElementUtil.getReferencingProjects((IPackageFragmentRoot) element)) {
					IFile classpath= referencingProject.getProject().getFile(".classpath"); //$NON-NLS-1$
					getResourceModifications().addChanged(classpath);
				}
				return;
			case IJavaElement.PACKAGE_FRAGMENT:
				fDelete.add(element);
				fPackagesToDelete.add((IPackageFragment) element);
				return;
			case IJavaElement.COMPILATION_UNIT:
				fDelete.add(element);
				IType[] types= ((ICompilationUnit)element).getTypes();
				fDelete.addAll(Arrays.asList(types));
				if (element.getResource() != null)
					getResourceModifications().addDelete(element.getResource());
				return;
			default:
				fDelete.add(element);
		}

	}

	/**
	 * @return a List of IResources that are removed by package deletes
	 * @throws CoreException if accessing package structure of packages to delete fails
	 */
	public List<IResource> postProcess() throws CoreException {
		return postProcess(new NullProgressMonitor());
	}

	/**
	 * @param monitor may be null
	 * @return a List of IResources that are removed by package deletes
	 * @throws CoreException if accessing package structure of packages to delete fails
	 */
	public List<IResource> postProcess(IProgressMonitor monitor) throws CoreException {
		String taskName= RefactoringCoreMessages.DeleteRefactoring_progress_collecting_resources;
		SubMonitor subMonitor= SubMonitor.convert(monitor, taskName, fPackagesToDelete.size());

		IsCompletelySelected isCompletelySelected = new IsCompletelySelected(fPackagesToDelete, subMonitor);

		ArrayList<IResource> resourcesCollector= new ArrayList<>();
		for (IPackageFragment pack : fPackagesToDelete) {
			subMonitor.checkCanceled();
			handlePackageFragmentDelete(pack, resourcesCollector, isCompletelySelected);
			subMonitor.worked(1);
		}
		return resourcesCollector;
	}

	@Override
	public void buildDelta(IResourceChangeDescriptionFactory deltaFactory) {
		getResourceModifications().buildDelta(deltaFactory);
	}

	@Override
	public RefactoringParticipant[] loadParticipants(RefactoringStatus status, RefactoringProcessor owner, String[] natures, SharableParticipants shared) {
		List<RefactoringParticipant> result= new ArrayList<>();
		for (IJavaElement javaElement : fDelete) {
			result.addAll(Arrays.asList(ParticipantManager.loadDeleteParticipants(status,
				owner, javaElement,
				new DeleteArguments(), natures, shared)));
		}
		result.addAll(Arrays.asList(getResourceModifications().getParticipants(status, owner, natures, shared)));
		return result.toArray(new RefactoringParticipant[result.size()]);
	}

	/**
	 * This method collects file and folder deletion for notifying
	 * participants. Participants will get notified of
	 *
	 * * deletion of the package (in any case)
	 * * deletion of files within the package if only the files are deleted without
	 *   the package folder ("package cleaning")
	 * * deletion of the package folder if it is not only cleared and if its parent
	 *   is not removed as well.
	 *
	 * All deleted resources are added to <code>resourcesCollector</code>
	 * @param pack the package
	 * @param isCompletelySelected predicate which states whether all sub-packages of a package are to be deleted
	 *
	 * @param resourcesCollector a collector for IResources to be deleted
	 * @throws CoreException if accessing package structure of {@code pack} fails
	 */
	private void handlePackageFragmentDelete(IPackageFragment pack, ArrayList<IResource> resourcesCollector, IsCompletelySelected isCompletelySelected) throws CoreException {
		final IContainer container= (IContainer)pack.getResource();
		if (container == null)
			return;

		final IResource[] members= container.members();

		/*
		 * Check whether this package is removed completely or only cleared.
		 * The default package can never be removed completely.
		 */
		if (!pack.isDefaultPackage() && isCompletelySelected.test(pack)) {
			// This package is removed completely, which means its folder will be
			// deleted as well. We only notify participants of the folder deletion
			// if the parent folder of this folder will not be deleted as well:
			boolean parentIsMarked= false;
			final IPackageFragment parent= JavaElementUtil.getParentSubpackage(pack);
			if (parent == null) {
				// "Parent" is the default package which will never be
				// deleted physically
				parentIsMarked= false;
			} else {
				// Parent is marked if it is in the list
				parentIsMarked= fPackagesToDelete.contains(parent);
			}

			if (parentIsMarked) {
				// Parent is marked, but is it really deleted or only cleared?
				if (isCompletelySelected.test(parent)) {
					// Parent can be removed completely, so we do not add
					// this folder to the list.
				} else {
					// Parent cannot be removed completely, but as this folder
					// can be removed, we notify the participant
					resourcesCollector.add(container);
					getResourceModifications().addDelete(container);
				}
			} else {
				// Parent will not be removed, but we will
				resourcesCollector.add(container);
				getResourceModifications().addDelete(container);
			}
		} else {
			// This package is only cleared because it has subpackages (=subfolders)
			// which are not deleted. As the package is only cleared, its folder
			// will not be removed and so we must notify the participant of the deleted children.
			for (IResource member : members) {
				if (member instanceof IFile) {
					IFile file= (IFile)member;
					if ("class".equals(file.getFileExtension()) && file.isDerived()) //$NON-NLS-1$
						continue;
					if (pack.isDefaultPackage() && ! JavaCore.isJavaLikeFileName(file.getName()))
						continue;
					resourcesCollector.add(member);
					getResourceModifications().addDelete(member);
				}
				if (!pack.isDefaultPackage() && member instanceof IFolder) {
					// Normally, folder children of packages are packages
					// as well, but in case they have been removed from the build
					// path, notify the participant
					IPackageFragment frag= (IPackageFragment) JavaCore.create(member);
					if (frag == null) {
						resourcesCollector.add(member);
						getResourceModifications().addDelete(member);
					}
				}
			}
		}
	}
}
