/*******************************************************************************
 * 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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

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

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
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.IParticipantDescriptorFilter;
import org.eclipse.ltk.core.refactoring.participants.MoveArguments;
import org.eclipse.ltk.core.refactoring.participants.ParticipantManager;
import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;
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.ltk.core.refactoring.participants.ValidateEditChecker;

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.IType;

import org.eclipse.jdt.internal.corext.refactoring.participants.ResourceModifications;

public class MoveModifications extends RefactoringModifications {

	private List<Object> fMoves;
	private List<RefactoringArguments> fMoveArguments;
	private List<IParticipantDescriptorFilter> fParticipantDescriptorFilter;

	public MoveModifications() {
		fMoves= new ArrayList<>();
		fMoveArguments= new ArrayList<>();
		fParticipantDescriptorFilter= new ArrayList<>();
	}

	public void move(IResource resource, MoveArguments args) {
		add(resource, args, null);
	}

	public void move(IPackageFragmentRoot sourceFolder, MoveArguments arguments) {
		add(sourceFolder, arguments, null);
		IResource sourceResource= sourceFolder.getResource();
		if (sourceResource != null) {
			getResourceModifications().addMove(sourceResource,
				new MoveArguments(getResourceDestination(arguments), arguments.getUpdateReferences()));
			IFile classpath= getClasspathFile(sourceResource);
			if (classpath != null) {
				getResourceModifications().addChanged(classpath);
			}
			classpath= getClasspathFile(getResourceDestination(arguments));
			if (classpath != null) {
				getResourceModifications().addChanged(classpath);
			}
		}
	}

	public void move(IPackageFragment pack, MoveArguments args) throws CoreException {
		add(pack, args, null);
		if (pack.getResource() == null)
			return;
		IPackageFragmentRoot javaDestination= (IPackageFragmentRoot) args.getDestination();
		if (javaDestination.getResource() == null)
			return;
		IPackageFragment newPack= javaDestination.getPackageFragment(pack.getElementName());
		if (!pack.hasSubpackages() && !newPack.exists()) {
			// we can do a simple move
			IContainer resourceDestination= newPack.getResource().getParent();
			createIncludingParents(resourceDestination);
			getResourceModifications().addMove(
				pack.getResource(),
				new MoveArguments(resourceDestination, args.getUpdateReferences()));
		} else {
			IContainer resourceSource= (IContainer)pack.getResource();
			IContainer resourceDestination= (IContainer) newPack.getResource();
			createIncludingParents(resourceDestination);
			MoveArguments arguments= new MoveArguments(resourceDestination, args.getUpdateReferences());
			Set<IResource> allMembers= new HashSet<>(Arrays.asList(resourceSource.members()));
			for (IResource toMove : collectResourcesOfInterest(pack)) {
				getResourceModifications().addMove(toMove, arguments);
				allMembers.remove(toMove);
			}
			for (Iterator<IResource> iter= allMembers.iterator(); iter.hasNext();) {
				IResource element= iter.next();
				if (element instanceof IFile) {
					getResourceModifications().addDelete(element);
					iter.remove();
				}
			}
			if (allMembers.isEmpty()) {
				getResourceModifications().addDelete(resourceSource);
			}
		}
	}

	public void move(ICompilationUnit unit, MoveArguments args) throws CoreException {
		add(unit, args, null);
		for (IType type : unit.getTypes()) {
			add(type, args, null);
		}
		IResource resourceDestination= getResourceDestination(args);
		if (resourceDestination != null && unit.getResource() != null) {
			IResource parent= resourceDestination;
			while (!parent.exists()) {
				getResourceModifications().addCreate(parent);
				parent= parent.getParent();
			}

			getResourceModifications().addMove(unit.getResource(), new MoveArguments(resourceDestination, args.getUpdateReferences()));
		}
	}

	@Override
	public void buildDelta(IResourceChangeDescriptionFactory builder) {
		for (int i= 0; i < fMoves.size(); i++) {
			Object element= fMoves.get(i);
			if (element instanceof IResource) {
				ResourceModifications.buildMoveDelta(builder, (IResource) element, (MoveArguments) fMoveArguments.get(i));
			}
		}
		getResourceModifications().buildDelta(builder);
	}

	@Override
	public void buildValidateEdits(ValidateEditChecker checker) {
		for (Object element : fMoves) {
			if (element instanceof ICompilationUnit) {
				ICompilationUnit unit= (ICompilationUnit)element;
				IResource resource= unit.getResource();
				if (resource != null && resource.getType() == IResource.FILE) {
					checker.addFile((IFile)resource);
				}
			}
		}
	}

	@Override
	public RefactoringParticipant[] loadParticipants(RefactoringStatus status, RefactoringProcessor owner, String[] natures, SharableParticipants shared) {
		List<RefactoringParticipant> result= new ArrayList<>();
		for (int i= 0; i < fMoves.size(); i++) {
			result.addAll(Arrays.asList(ParticipantManager.loadMoveParticipants(status,
				owner, fMoves.get(i),
				(MoveArguments) fMoveArguments.get(i),
				fParticipantDescriptorFilter.get(i),
				natures, shared)));
		}
		result.addAll(Arrays.asList(getResourceModifications().getParticipants(status, owner, natures, shared)));
		return result.toArray(new RefactoringParticipant[result.size()]);
	}

	private void add(Object element, RefactoringArguments args, IParticipantDescriptorFilter filter) {
		Assert.isNotNull(element);
		Assert.isNotNull(args);
		fMoves.add(element);
		fMoveArguments.add(args);
		fParticipantDescriptorFilter.add(filter);
	}

	private IResource getResourceDestination(MoveArguments args) {
		Object genericDestination= args.getDestination();
		IResource resourceDestination= null;
		if (genericDestination instanceof IJavaElement) {
			resourceDestination= ((IJavaElement)genericDestination).getResource();
		} else if (genericDestination instanceof IResource) {
			resourceDestination= (IResource)genericDestination;
		}
		return resourceDestination;
	}
}
