/*******************************************************************************
 * Copyright (c) 2000, 2015 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.core.tests.model;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.*;

import junit.framework.AssertionFailedError;

abstract public class CopyMoveTests extends ModifyingResourceTests {
public CopyMoveTests(String name) {
	super(name);
}
/**
 * Attempts to copy the element with optional
 * forcing. The operation should fail with the failure code.
 */
public void copyNegative(IJavaElement element, IJavaElement destination, IJavaElement sibling, String rename, boolean force, int failureCode) {
	try {
		((ISourceManipulation)element).copy(destination, sibling, rename, force, null);
	} catch (JavaModelException jme) {
		assertTrue("Code not correct for JavaModelException: " + jme, jme.getStatus().getCode() == failureCode);
		return;
	}
	assertTrue("The copy should have failed for: " + element, false);
	return;
}
/**
 * Attempts to copy the elements with optional
 * forcing. The operation should fail with the failure code.
 */
public void copyNegative(IJavaElement[] elements, IJavaElement[] destinations, IJavaElement[] siblings, String[] renames, boolean force, int failureCode) {
	try {
		getJavaModel().copy(elements, destinations, siblings, renames, force, null);
	} catch (JavaModelException jme) {
		assertTrue("Code not correct for JavaModelException: " + jme, jme.getStatus().getCode() == failureCode);
		return;
	}
	assertTrue("The move should have failed for for multiple elements: ", false);
	return;
}
/**
 * Copies the element to the container with optional rename
 * and forcing. The operation should succeed, so any exceptions
 * encountered are thrown.
 */
public IJavaElement copyPositive(IJavaElement element, IJavaElement container, IJavaElement sibling, String rename, boolean force) throws JavaModelException {
	// if forcing, ensure that a name collision exists
	if (force) {
		IJavaElement collision = generateHandle(element, rename, container);
		assertTrue("Collision does not exist", collision.exists());
	}
	DeltaListener listener = new DeltaListener();
	IJavaElement copy;
	try {
		startDeltas(listener);

		// copy
	 	((ISourceManipulation) element).copy(container, sibling, rename, force, null);

		// ensure the original element still exists
		assertTrue("The original element must still exist", element.exists());

		waitForAutoBuild();

		// generate the new element	handle
		copy = generateHandle(element, rename, container);
		assertTrue("Copy should exist", copy.exists());

		//ensure correct position
		if (element.getElementType() > IJavaElement.COMPILATION_UNIT) {
			ensureCorrectPositioning((IParent) container, sibling, copy);
			if (copy.getElementType() != IJavaElement.IMPORT_DECLARATION) {
				ensureChildExists((IParent) container, copy);
			}
		} else {
			if (container.getElementType() == IJavaElement.PACKAGE_FRAGMENT_ROOT) {
			} else {
				// ensure package name is correct
				if (container.getElementName().equals("")) {
					// default package - should be no package decl
					IJavaElement[] children = ((ICompilationUnit) copy).getChildren();
					boolean found = false;
					for (int i = 0; i < children.length; i++) {
						if (children[i] instanceof IPackageDeclaration) {
							found = true;
							break;
						}
					}
					assertTrue("Should not find package decl", !found);
				} else {
					IJavaElement[] children = ((ICompilationUnit) copy).getChildren();
					boolean found = false;
					for (int i = 0; i < children.length; i++) {
						if (children[i] instanceof IPackageDeclaration) {
							assertTrue("package declaration incorrect", ((IPackageDeclaration) children[i]).getElementName().equals(container.getElementName()));
							found = true;
						}
					}
					assertTrue("Did not find package decl", found);
				}
			}
		}
		if (copy.getElementType() == IJavaElement.IMPORT_DECLARATION)
			container = ((ICompilationUnit) container).getImportContainer();
		IJavaElementDelta destDelta = listener.getDeltaFor(container, true);
		if(destDelta == null) {
			fail("No delta found for " + container + ", all deltas: " + listener.getAllDeltas());
		}
		assertTrue("Destination container not changed", destDelta.getKind() == IJavaElementDelta.CHANGED);
		IJavaElementDelta[] deltas = destDelta.getAddedChildren();
		assertTrue("Added children not correct for element copy", deltas[0].getElement().equals(copy));
	} catch (AssertionFailedError afe) {
		System.err.println(listener.stackTraces());
		throw afe;
	} finally {
		stopDeltas(listener);
	}
	return copy;
}
/**
 * Generates a new handle to the original element in
 * its new container.
 */
public IJavaElement generateHandle(IJavaElement original, String rename, IJavaElement container) {
	String name = original.getElementName();
	if (rename != null) {
		name = rename;
	}
	switch (container.getElementType()) {
		case IJavaElement.PACKAGE_FRAGMENT_ROOT :
			switch (original.getElementType()) {
				case IJavaElement.PACKAGE_FRAGMENT :
					return ((IPackageFragmentRoot) container).getPackageFragment(name);
				default :
					assertTrue("illegal child type", false);
					break;
			}
			break;
		case IJavaElement.PACKAGE_FRAGMENT :
			switch (original.getElementType()) {
				case IJavaElement.COMPILATION_UNIT :
					return ((IPackageFragment) container).getCompilationUnit(name);
				default :
					assertTrue("illegal child type", false);
					break;
			}
			break;
		case IJavaElement.COMPILATION_UNIT :
			switch (original.getElementType()) {
				case IJavaElement.IMPORT_DECLARATION :
					return ((ICompilationUnit) container).getImport(name);
				case IJavaElement.PACKAGE_DECLARATION :
					return ((ICompilationUnit) container).getPackageDeclaration(name);
				case IJavaElement.TYPE :
					if (isMainType(original, container)) {
						//the cu has been renamed as well
						container = ((IPackageFragment) container.getParent()).getCompilationUnit(name + ".java");
					}
					return ((ICompilationUnit) container).getType(name);
				default :
					assertTrue("illegal child type", false);
					break;
			}
			break;
		case IJavaElement.TYPE :
			switch (original.getElementType()) {
				case IJavaElement.METHOD :
					if (name.equals(original.getParent().getElementName())) {
						//method is a constructor
						return ((IType) container).getMethod(container.getElementName(), ((IMethod) original).getParameterTypes());
					}
					return ((IType) container).getMethod(name, ((IMethod) original).getParameterTypes());
				case IJavaElement.FIELD :
					return ((IType) container).getField(name);
				case IJavaElement.TYPE :
					return ((IType) container).getType(name);
				case IJavaElement.INITIALIZER :
					//hack to return the first initializer
					return ((IType) container).getInitializer(1);
				default :
					assertTrue("illegal child type", false);
					break;
			}
			break;
		default :
			assertTrue("unsupported container type", false);
			break;
	}
	assertTrue("should not get here", false);
	return null;
}
/**
 * Returns true if this element is the main type of its compilation unit.
 */
protected boolean isMainType(IJavaElement element, IJavaElement parent) {
	if (parent instanceof ICompilationUnit) {
		if (element instanceof IType) {
			ICompilationUnit cu= (ICompilationUnit)parent;
			String typeName = cu.getElementName();
			typeName = typeName.substring(0, typeName.length() - 5);
			return element.getElementName().equals(typeName) && element.getParent().equals(cu);
		}
	}
	return false;
}
/**
 * Attempts to move the element with optional
 * forcing. The operation should fail with the failure code.
 */
public void moveNegative(IJavaElement element, IJavaElement destination, IJavaElement sibling, String rename, boolean force, int failureCode) {
	try {
		((ISourceManipulation)element).move(destination, sibling, rename, force, null);
	} catch (JavaModelException jme) {
		assertTrue("Code not correct for JavaModelException: " + jme, jme.getStatus().getCode() == failureCode);
		return;
	}
	assertTrue("The move should have failed for: " + element, false);
	return;
}
/**
 * Attempts to move the element with optional
 * forcing. The operation should fail with the failure code.
 */
public void moveNegative(IJavaElement[] elements, IJavaElement[] destinations, IJavaElement[] siblings, String[] renames, boolean force, int failureCode) {
	try {
		getJavaModel().move(elements, destinations, siblings, renames, force, null);
	} catch (JavaModelException jme) {
		assertTrue("Code not correct for JavaModelException: " + jme, jme.getStatus().getCode() == failureCode);
		return;
	}
	assertTrue("The move should have failed for for multiple elements: ", false);
	return;
}
/**
 * Moves the element to the container with optional rename
 * and forcing. The operation should succeed, so any exceptions
 * encountered are thrown.
 */
public void movePositive(IJavaElement element, IJavaElement container, IJavaElement sibling, String rename, boolean force) throws JavaModelException {
	IJavaElement[] siblings = new IJavaElement[] {sibling};
	String[] renamings = new String[] {rename};
	if (sibling == null) {
		siblings = null;
	}
	if (rename == null) {
		renamings = null;
	}
	movePositive(new IJavaElement[] {element}, new IJavaElement[] {container}, siblings, renamings, force);
}
/**
 * Moves the elements to the containers with optional renaming
 * and forcing. The operation should succeed, so any exceptions
 * encountered are thrown.
 */
public void movePositive(IJavaElement[] elements, IJavaElement[] destinations, IJavaElement[] siblings, String[] names, boolean force) throws JavaModelException {
	movePositive(elements, destinations, siblings, names, force, null);
}
public void movePositive(IJavaElement[] elements, IJavaElement[] destinations, IJavaElement[] siblings, String[] names, boolean force, IProgressMonitor monitor) throws JavaModelException {
	movePositive(elements, destinations, siblings, names, force, true, null);
}
/**
 * Moves the elements to the containers with optional renaming
 * and forcing. The operation should succeed, so any exceptions
 * encountered are thrown.
 */
public void movePositive(IJavaElement[] elements, IJavaElement[] destinations, IJavaElement[] siblings, String[] names, boolean force, boolean checkDelta, IProgressMonitor monitor) throws JavaModelException {
	// if forcing, ensure that a name collision exists
	int i;
	if (force) {
		for (i = 0; i < elements.length; i++) {
			IJavaElement e = elements[i];
			IJavaElement collision = null;
			if (names == null) {
				collision = generateHandle(e, null, destinations[i]);
			} else {
				collision = generateHandle(e, names[i], destinations[i]);
			}
			assertTrue("Collision does not exist", collision.exists());
		}
	}
	DeltaListener listener = new DeltaListener();
	try {
		if(checkDelta)	startDeltas(listener);

		// move
		getJavaModel().move(elements, destinations, siblings, names, force, monitor);
		for (i = 0; i < elements.length; i++) {
			IJavaElement element = elements[i];
			IJavaElement moved = null;
			if (names == null) {
				moved = generateHandle(element, null, destinations[i]);
			} else {
				moved = generateHandle(element, names[i], destinations[i]);
			}
			// ensure the original element no longer exists, unless moving within the same container
			if (!destinations[i].equals(element.getParent())) {
				if (element.getElementType() == IJavaElement.PACKAGE_FRAGMENT) {
					//moving a package fragment does not necessary mean that it no longer exists
					//it could have members that are not part of the Java Model
					try {
						IResource[] members = ((IContainer) element.getUnderlyingResource()).members();
						if (members.length == 0) {
							assertTrue("The original element must not exist", !element.exists());
						}
					} catch (CoreException ce) {
						throw new JavaModelException(ce);
					}
				} else {
					assertTrue("The original element must not exist", !element.exists());
				}
			}
			assertTrue("Moved element should exist", moved.exists());

			//ensure correct position
			if (element.getElementType() > IJavaElement.COMPILATION_UNIT) {
				if (siblings != null && siblings.length > 0) {
					ensureCorrectPositioning((IParent) moved.getParent(), siblings[i], moved);
				}
			} else {
				IJavaElement container = destinations[i];
				if (container.getElementType() == IJavaElement.PACKAGE_FRAGMENT_ROOT) {
				} else { // ensure package name is correct

					if (container.getElementName().equals("")) {
						// default package - should be no package decl
						IJavaElement[] children = ((ICompilationUnit) moved).getChildren();
						boolean found = false;
						for (int j = 0; j < children.length; j++) {
							if (children[j] instanceof IPackageDeclaration) {
								found = true;
								break;
							}
						}
						assertTrue("Should not find package decl", !found);
					} else {
						IJavaElement[] children = ((ICompilationUnit) moved).getChildren();
						boolean found = false;
						for (int j = 0; j < children.length; j++) {
							if (children[j] instanceof IPackageDeclaration) {
								assertTrue("package declaration incorrect", ((IPackageDeclaration) children[j]).getElementName().equals(container.getElementName()));
								found = true;
								break;
							}
						}
						assertTrue("Did not find package decl", found);
					}
				}
			}
			if(checkDelta) {
				IJavaElementDelta destDelta = null;
				if (isMainType(element, destinations[i]) && names != null && names[i] != null) { //moved/renamed main type to same cu
					destDelta = listener.getDeltaFor(moved.getParent());
					if(destDelta == null) {
						fail("No delta found for " + moved.getParent() + ", all deltas: " + listener.getAllDeltas());
					}
					assertTrue("Renamed compilation unit as result of main type not added", destDelta.getKind() == IJavaElementDelta.ADDED);
					assertTrue("flag should be F_MOVED_FROM", (destDelta.getFlags() & IJavaElementDelta.F_MOVED_FROM) > 0);
					assertTrue("moved from handle should be original", destDelta.getMovedFromElement().equals(element.getParent()));
				} else {
					destDelta = listener.getDeltaFor(destinations[i], true);
					assertTrue("Destination container not changed", destDelta != null && destDelta.getKind() == IJavaElementDelta.CHANGED);
					IJavaElementDelta[] deltas = destDelta.getAddedChildren();
					assertTrue("Added children not correct for element copy", deltas[i].getElement().equals(moved));
					assertTrue("should be K_ADDED", deltas[i].getKind() == IJavaElementDelta.ADDED);
					IJavaElementDelta sourceDelta= listener.getDeltaFor(element, false);
					assertTrue("should be K_REMOVED", sourceDelta.getKind() == IJavaElementDelta.REMOVED);
				}
			}
		}
	} catch (AssertionFailedError afe) {
		System.err.println(listener.stackTraces());
		throw afe;
	} finally {
		if(checkDelta)	stopDeltas(listener);
	}
}
}
