/*******************************************************************************
 * Copyright (c) 2009, 2018 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.pde.api.tools.ui.internal.refactoring;

import java.util.Collections;
import java.util.HashSet;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblemFilter;

/**
 * Helper class to create API tools refactoring changes
 *
 * @since 1.0.1
 */
public class RefactoringUtils {

	/**
	 * Creates the {@link Change} for updating the filter store when a type is
	 * deleted
	 *
	 * @param type the type being deleted
	 * @return the change to the filter store
	 */
	static Change createDeleteFilterChanges(IType type) {
		try {
			IResource resource = type.getUnderlyingResource();
			if (resource != null) {
				IApiFilterStore store = resolveFilterStore(resource.getProject());
				if (store == null) {
					return null;
				}
				IApiProblemFilter[] filters = store.getFilters(resource);
				if (filters.length != 0) {
					CompositeChange cchange = new CompositeChange(RefactoringMessages.RefactoringUtils_remove_usused_filters);
					for (IApiProblemFilter filter : filters) {
						cchange.add(new TypeFilterChange(store, filter, null, null, FilterChange.DELETE));
					}
					return cchange;
				}
			}
		} catch (CoreException ce) {
		}
		return null;
	}

	/**
	 * Creates the {@link Change} for updating a filter store when a package
	 * fragment is deleted
	 *
	 * @param fragment the fragment that has been deleted
	 * @return the {@link Change} for the package fragment deletion
	 */
	static Change createDeleteFilterChanges(IPackageFragment fragment) {
		try {
			IResource resource = fragment.getUnderlyingResource();
			if (resource != null) {
				IApiFilterStore store = resolveFilterStore(resource.getProject());
				if (store == null) {
					return null;
				}
				IApiProblemFilter[] filters = collectAllAffectedFilters(store, collectAffectedTypes(fragment));
				if (filters.length != 0) {
					CompositeChange cchange = new CompositeChange(RefactoringMessages.RefactoringUtils_remove_usused_filters);
					for (IApiProblemFilter filter : filters) {
						cchange.add(new TypeFilterChange(store, filter, null, null, FilterChange.DELETE));
					}
					return cchange;
				}
			}
		} catch (CoreException ce) {
		}
		return null;
	}

	/**
	 * Collects the complete set of {@link IApiProblemFilter}s for the given
	 * collection of {@link IType}s
	 *
	 * @return the complete collection of {@link IApiProblemFilter}s or an empty
	 *         list, never <code>null</code>
	 */
	static IApiProblemFilter[] collectAllAffectedFilters(IApiFilterStore store, IType[] types) {
		HashSet<IApiProblemFilter> filters = new HashSet<>();
		IApiProblemFilter[] fs = null;
		IResource resource = null;
		for (IType type : types) {
			try {
				resource = type.getUnderlyingResource();
				if (resource == null) {
					continue;
				}
				fs = store.getFilters(resource);
				Collections.addAll(filters, fs);
			} catch (JavaModelException jme) {
				// do nothing
			}
		}
		return filters.toArray(new IApiProblemFilter[filters.size()]);
	}

	/**
	 * Collects the complete list of {@link IType}s affected from within the
	 * {@link IPackageFragment}
	 *
	 * @return the complete collection of affected {@link IType}s or an empty
	 *         list, never <code>null</code>
	 */
	static IType[] collectAffectedTypes(IPackageFragment fragment) {
		HashSet<IType> types = new HashSet<>();
		try {
			if (fragment.containsJavaResources()) {
				ICompilationUnit[] cunits = fragment.getCompilationUnits();
				IType type = null;
				for (ICompilationUnit cunit : cunits) {
					type = cunit.findPrimaryType();
					if (type == null) {
						continue;
					}
					types.add(type);
				}
			}
		} catch (JavaModelException jme) {
			// do nothing
		}
		return types.toArray(new IType[types.size()]);
	}

	/**
	 * Creates the {@link Change} for updating {@link IApiProblemFilter}s that
	 * are affected by the rename of a type
	 *
	 * @param type the type being renamed
	 * @return the rename type change for updating filters
	 */
	static Change createRenameFilterChanges(IType type) {
		return createDeleteFilterChanges(type);
	}

	/**
	 * Creates the {@link Change} for {@link IApiProblemFilter}s affected by a
	 * package fragment rename
	 *
	 * @return the change for the package fragment rename
	 */
	static Change createRenameFilterChanges(IPackageFragment fragment) {
		return createDeleteFilterChanges(fragment);
	}

	/**
	 * Gets the {@link IApiFilterStore} for the given {@link IProject}
	 *
	 * @return the filter store for the given project or <code>null</code>
	 */
	static IApiFilterStore resolveFilterStore(IProject project) throws CoreException {
		IApiComponent component = ApiPlugin.getDefault().getApiBaselineManager().getWorkspaceBaseline().getApiComponent(project);
		if (component != null) {
			return component.getFilterStore();
		}
		return null;
	}

	/**
	 * Returns the new fully qualified name for the filter
	 */
	static String getNewQualifiedName(IType type, String newname) {
		IType dtype = type.getDeclaringType();
		String newqname = newname;
		if (dtype == null) {
			IPackageFragment packageFragment = type.getPackageFragment();
			if (!packageFragment.isDefaultPackage()) {
				newqname = packageFragment.getElementName() + '.' + newname;
			}
		} else {
			newqname = dtype.getFullyQualifiedName() + '$' + newname;
		}
		return newqname;
	}

	/**
	 * Returns a new path string given the type name. This method basically
	 * removes the last segment of the path, appends the new name and resets the
	 * extension
	 *
	 * @return the new resource path to use
	 */
	static String getNewResourcePath(IPath oldpath, String typename) {
		if (typename.indexOf('$') < 0) {
			String ext = oldpath.getFileExtension();
			IPath newpath = oldpath.removeLastSegments(1).append(typename);
			if (ext != null) {
				return newpath.addFileExtension(ext).toString();
			}
			return newpath.toString();
		}
		return oldpath.toString();
	}
}
