/*******************************************************************************
 * 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.participants;

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

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

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory;

import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.participants.CopyArguments;
import org.eclipse.ltk.core.refactoring.participants.CopyParticipant;
import org.eclipse.ltk.core.refactoring.participants.CreateArguments;
import org.eclipse.ltk.core.refactoring.participants.CreateParticipant;
import org.eclipse.ltk.core.refactoring.participants.DeleteArguments;
import org.eclipse.ltk.core.refactoring.participants.DeleteParticipant;
import org.eclipse.ltk.core.refactoring.participants.MoveArguments;
import org.eclipse.ltk.core.refactoring.participants.MoveParticipant;
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.RenameArguments;
import org.eclipse.ltk.core.refactoring.participants.RenameParticipant;
import org.eclipse.ltk.core.refactoring.participants.SharableParticipants;


/**
 * A data structure to collect resource modifications.
 *
 * @since 3.0
 */
public class ResourceModifications {

	private List<IResource> fCreate;
	private List<IResource> fDelete;

	private List<IResource> fMove;
	private List<MoveArguments> fMoveArguments;

	private List<IResource> fRename;
	private List<RenameArguments> fRenameArguments;

	private List<IResource> fCopy;
	private List<CopyArguments> fCopyArguments;

	private int fIgnoreCount;
	private List<DeltaDescription> fDeltaDescriptions;

	public static abstract class DeltaDescription {
		protected IResource fResource;
		public DeltaDescription(IResource resource) {
			fResource= resource;
		}
		public abstract void buildDelta(IResourceChangeDescriptionFactory builder);
		public abstract IPath getDestinationPath();

	}
	public static class DeleteDescription extends DeltaDescription {
		public DeleteDescription(IResource resource) {
			super(resource);
		}
		@Override
		public void buildDelta(IResourceChangeDescriptionFactory builder) {
			builder.delete(fResource);
		}
		@Override
		public IPath getDestinationPath() {
			return null;
		}
	}
	public static class ChangedDescription extends DeltaDescription {
		public ChangedDescription(IFile resource) {
			super(resource);
		}
		@Override
		public void buildDelta(IResourceChangeDescriptionFactory builder) {
			builder.change((IFile)fResource);
		}
		@Override
		public IPath getDestinationPath() {
			return null;
		}
	}
	public static class CreateDescription extends DeltaDescription {
		public CreateDescription(IResource resource) {
			super(resource);
		}
		@Override
		public void buildDelta(IResourceChangeDescriptionFactory builder) {
			builder.create(fResource);
		}
		@Override
		public IPath getDestinationPath() {
			return fResource.getFullPath();
		}
	}
	public static class MoveDescription extends DeltaDescription {
		private IPath fDestination;
		public MoveDescription(IResource resource, IPath destination) {
			super(resource);
			fDestination= destination;
		}
		@Override
		public void buildDelta(IResourceChangeDescriptionFactory builder) {
			IResource existing= ResourcesPlugin.getWorkspace().getRoot().findMember(fDestination);
			if (existing != null && !existing.equals(fResource)) {
				builder.delete(existing);
			}
			builder.move(fResource, fDestination);
		}
		@Override
		public IPath getDestinationPath() {
			return fDestination;
		}
	}
	public static class CopyDescription extends DeltaDescription {
		private IPath fDestination;
		public CopyDescription(IResource resource, IPath destination) {
			super(resource);
			fDestination= destination;
		}
		@Override
		public void buildDelta(IResourceChangeDescriptionFactory builder) {
			IResource existing= ResourcesPlugin.getWorkspace().getRoot().findMember(fDestination);
			if (existing != null && !existing.equals(fResource)) {
				builder.delete(existing);
			}
			builder.copy(fResource, fDestination);
		}
		@Override
		public IPath getDestinationPath() {
			return fDestination;
		}
	}

	/**
	 * Adds the given file to the list of changed files.
	 *
	 * @param file the changed file
	 */
	public void addChanged(IFile file) {
		if (fIgnoreCount == 0) {
			internalAdd(new ChangedDescription(file));
		}
	}

	/**
	 * Adds the given resource to the list of resources
	 * to be created.
	 *
	 * @param create the resource to be add to the list of
	 *  resources to be created
	 */
	public void addCreate(IResource create) {
		if (fCreate == null)
			fCreate= new ArrayList<>(2);
		fCreate.add(create);
		if (fIgnoreCount == 0) {
			internalAdd(new CreateDescription(create));
		}
	}

	/**
	 * Adds the given resource to the list of resources
	 * to be deleted.
	 *
	 * @param delete the resource to be deleted
	 */
	public void addDelete(IResource delete) {
		if (fDelete == null)
			fDelete= new ArrayList<>(2);
		fDelete.add(delete);
		if (fIgnoreCount == 0) {
			internalAdd(new DeleteDescription(delete));
		}
	}

	/**
	 * Adds the given resource to the list of resources
	 * to be moved.
	 *
	 * @param move the resource to be moved
	 * @param arguments the move arguments
	 */
	public void addMove(IResource move, MoveArguments arguments) {
		if (fMove == null) {
			fMove= new ArrayList<>(2);
			fMoveArguments= new ArrayList<>(2);
		}
		fMove.add(move);
		fMoveArguments.add(arguments);
		if (fIgnoreCount == 0) {
			IPath destination= ((IResource)arguments.getDestination()).getFullPath().append(move.getName());
			internalAdd(new MoveDescription(move, destination));
		}
	}

	/**
	 * Adds the given resource to the list of resources
	 * to be copied.
	 *
	 * @param copy the resource to be copied
	 * @param arguments the copy arguments
	 */
	public void addCopy(IResource copy, CopyArguments arguments) {
		if (fCopy == null) {
			fCopy= new ArrayList<>(2);
			fCopyArguments= new ArrayList<>(2);
		}
		fCopy.add(copy);
		fCopyArguments.add(arguments);
		addCopyDelta(copy, arguments);
	}

	/**
	 * Adds the given resource to the list of renamed
	 * resources.
	 *
	 * @param rename the resource to be renamed
	 * @param arguments the arguments of the rename
	 */
	public void addRename(IResource rename, RenameArguments arguments) {
		Assert.isNotNull(rename);
		Assert.isNotNull(arguments);
		if (fRename == null) {
			fRename= new ArrayList<>(2);
			fRenameArguments= new ArrayList<>(2);
		}
		fRename.add(rename);
		fRenameArguments.add(arguments);
		if (fIgnoreCount == 0) {
			IPath newPath= rename.getFullPath().removeLastSegments(1).append(arguments.getNewName());
			internalAdd(new MoveDescription(rename, newPath));
		}
	}

	public RefactoringParticipant[] getParticipants(RefactoringStatus status, RefactoringProcessor processor, String[] natures, SharableParticipants shared) {
		List<RefactoringParticipant> result= new ArrayList<>(5);
		if (fDelete != null) {
			DeleteArguments arguments= new DeleteArguments();
			for (IResource resource : fDelete) {
				DeleteParticipant[] deletes= ParticipantManager.loadDeleteParticipants(status,
					processor, resource,
					arguments, natures, shared);
				result.addAll(Arrays.asList(deletes));
			}
		}
		if (fCreate != null) {
			CreateArguments arguments= new CreateArguments();
			for (IResource resource : fCreate) {
				CreateParticipant[] creates= ParticipantManager.loadCreateParticipants(status,
					processor, resource,
					arguments, natures, shared);
				result.addAll(Arrays.asList(creates));
			}
		}
		if (fMove != null) {
			for (int i= 0; i < fMove.size(); i++) {
				Object element= fMove.get(i);
				MoveArguments arguments= fMoveArguments.get(i);
				MoveParticipant[] moves= ParticipantManager.loadMoveParticipants(status,
					processor, element,
					arguments, natures, shared);
				result.addAll(Arrays.asList(moves));

			}
		}
		if (fCopy != null) {
			for (int i= 0; i < fCopy.size(); i++) {
				Object element= fCopy.get(i);
				CopyArguments arguments= fCopyArguments.get(i);
				CopyParticipant[] copies= ParticipantManager.loadCopyParticipants(status,
					processor, element,
					arguments, natures, shared);
				result.addAll(Arrays.asList(copies));
			}
		}
		if (fRename != null) {
			for (int i= 0; i < fRename.size(); i++) {
				Object resource= fRename.get(i);
				RenameArguments arguments= fRenameArguments.get(i);
				RenameParticipant[] renames= ParticipantManager.loadRenameParticipants(status,
					processor, resource,
					arguments, natures, shared);
				result.addAll(Arrays.asList(renames));
			}
		}
		return result.toArray(new RefactoringParticipant[result.size()]);
	}

	public void ignoreForDelta() {
		fIgnoreCount++;
	}

	public void trackForDelta() {
		fIgnoreCount--;
	}

	public void addDelta(DeltaDescription description) {
		if (fIgnoreCount > 0)
			return;
		internalAdd(description);
	}

	public void addCopyDelta(IResource copy, CopyArguments arguments) {
		if (fIgnoreCount == 0) {
			IPath destination= ((IResource)arguments.getDestination()).getFullPath().append(copy.getName());
			internalAdd(new CopyDescription(copy, destination));
		}
	}

	/**
	 * Checks if the resource will exist in the future based on
	 * the recorded resource modifications.
	 *
	 * @param resource the resource to check
	 * @return whether the resource will exist or not
	 */
	public boolean willExist(IResource resource) {
		if (fDeltaDescriptions == null)
			return false;
		IPath fullPath= resource.getFullPath();
		for (DeltaDescription delta : fDeltaDescriptions) {
			if (fullPath.equals(delta.getDestinationPath()))
				return true;
		}
		return false;
	}

	public void buildDelta(IResourceChangeDescriptionFactory builder) {
		if (fDeltaDescriptions == null)
			return;
		for (DeltaDescription delta : fDeltaDescriptions) {
			delta.buildDelta(builder);
		}
	}

	public static void buildMoveDelta(IResourceChangeDescriptionFactory builder, IResource resource, RenameArguments args) {
		IPath newPath= resource.getFullPath().removeLastSegments(1).append(args.getNewName());
		new MoveDescription(resource, newPath).buildDelta(builder);
	}

	public static void buildMoveDelta(IResourceChangeDescriptionFactory builder, IResource resource, MoveArguments args) {
		IPath destination= ((IResource)args.getDestination()).getFullPath().append(resource.getName());
		new MoveDescription(resource, destination).buildDelta(builder);
	}

	public static void buildCopyDelta(IResourceChangeDescriptionFactory builder, IResource resource, CopyArguments args) {
		IPath destination= ((IResource)args.getDestination()).getFullPath().append(resource.getName());
		new CopyDescription(resource, destination).buildDelta(builder);
	}

	private void internalAdd(DeltaDescription description) {
		if (fDeltaDescriptions == null)
			fDeltaDescriptions= new ArrayList<>();
		fDeltaDescriptions.add(description);
	}
}
