/*******************************************************************************
 * 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.internal.debug.ui.sourcelookup;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.debug.core.sourcelookup.ISourceContainer;
import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
import org.eclipse.debug.ui.sourcelookup.AbstractSourceContainerBrowser;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.debug.ui.IJavaDebugUIConstants;
import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
import org.eclipse.jdt.internal.debug.ui.actions.ProjectSelectionDialog;
import org.eclipse.jdt.launching.sourcelookup.containers.ClasspathContainerSourceContainer;
import org.eclipse.jdt.launching.sourcelookup.containers.ClasspathVariableSourceContainer;
import org.eclipse.jdt.launching.sourcelookup.containers.JavaProjectSourceContainer;
import org.eclipse.jdt.launching.sourcelookup.containers.PackageFragmentRootSourceContainer;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;

/**
 * Browser to select Java projects to add to the source lookup path.
 *
 * @since 3.0
 */
public class JavaProjectSourceContainerBrowser extends AbstractSourceContainerBrowser {

	class ContentProvider implements IStructuredContentProvider {

		private List<IJavaProject> fProjects;

		public ContentProvider(List<IJavaProject> projects) {
			fProjects = projects;
		}

		/**
		 * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
		 */
		@Override
		public Object[] getElements(Object inputElement) {
			return fProjects.toArray();
		}

		/**
		 * @see org.eclipse.jface.viewers.IContentProvider#dispose()
		 */
		@Override
		public void dispose() {
		}

		/**
		 * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
		 */
		@Override
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		}

	}
	/* (non-Javadoc)
	 * @see org.eclipse.debug.internal.ui.sourcelookup.ISourceContainerBrowser#createSourceContainers(org.eclipse.swt.widgets.Shell, org.eclipse.debug.core.ILaunchConfiguration)
	 */
	@Override
	public ISourceContainer[] addSourceContainers(Shell shell, ISourceLookupDirector director) {
		List<IJavaProject> projects = getPossibleAdditions(director);
		ProjectSelectionDialog dialog= new ProjectSelectionDialog(shell, projects);
		dialog.setTitle(SourceLookupMessages.JavaProjectSourceContainerBrowser_1);
		MultiStatus status = new MultiStatus(JDIDebugUIPlugin.getUniqueIdentifier(), IJavaDebugUIConstants.INTERNAL_ERROR, "Failed to add project(s)", null);  //$NON-NLS-1$

		List<ISourceContainer> sourceContainers = new ArrayList<>();
		if (dialog.open() == Window.OK) {
			Object[] selections = dialog.getResult();
			List<IJavaProject> additions = new ArrayList<>(selections.length);
			try {
				for (Object selection : selections) {
					IJavaProject jp = (IJavaProject) selection;
					if (dialog.isAddRequiredProjects()) {
						collectRequiredProjects(jp, additions);
					} else {
						additions.add(jp);
					}
				}
			} catch (JavaModelException e) {
				status.add(e.getStatus());
			}

			Iterator<IJavaProject> iter = additions.iterator();
			while (iter.hasNext()) {
				IJavaProject jp = iter.next();
				sourceContainers.add(new JavaProjectSourceContainer(jp));
				if (dialog.isAddExportedEntries()) {
					try {
						collectExportedEntries(jp, sourceContainers);
					} catch (CoreException e) {
						status.add(e.getStatus());
					}
				}
			}
		}

		if (!status.isOK()) {
			JDIDebugUIPlugin.statusDialog(status);
		}
		return sourceContainers.toArray(new ISourceContainer[sourceContainers.size()]);
	}

	/**
	 * Returns the possible projects that can be added
	 *
	 * @param director the source lookup director currently being edited
	 * @return the listing of {@link IJavaProject}s to add
	 */
	protected List<IJavaProject> getPossibleAdditions(ISourceLookupDirector director) {
		IJavaProject[] projects;
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		try {
			projects= JavaCore.create(root).getJavaProjects();
		} catch (JavaModelException e) {
			JDIDebugUIPlugin.log(e);
			projects= new IJavaProject[0];
		}
		List<IJavaProject> remaining = new ArrayList<>();
		for (IJavaProject project : projects) {
			remaining.add(project);
		}
		List<IJavaProject> alreadySelected = new ArrayList<>();
		for (ISourceContainer container : director.getSourceContainers()) {
			if (container.getType().getId().equals(JavaProjectSourceContainer.TYPE_ID)) {
				alreadySelected.add(((JavaProjectSourceContainer)container).getJavaProject());
			}
		}
		remaining.removeAll(alreadySelected);
		return remaining;
	}

	/**
	 * Adds all projects required by <code>proj</code> to the list
	 * <code>res</code>
	 *
	 * @param proj the project for which to compute required
	 *  projects
	 * @param res the list to add all required projects too
	 * @throws JavaModelException if there is an exception accessing the Java model
	 */
	protected void collectRequiredProjects(IJavaProject proj, List<IJavaProject> res) throws JavaModelException {
		if (!res.contains(proj)) {
			res.add(proj);

			IJavaModel model= proj.getJavaModel();

			for (IClasspathEntry curr : proj.getRawClasspath()) {
				if (curr.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
					IJavaProject ref= model.getJavaProject(curr.getPath().segment(0));
					if (ref.exists()) {
						collectRequiredProjects(ref, res);
					}
				}
			}
		}
	}

	/**
	 * Adds all exported entries defined by <code>proj</code> to the list
	 * <code>list</code>.
	 *
	 * @param proj the project to inspect
	 * @param list the listing of containers
	 * @throws CoreException if an exception occurs
	 */
	protected void collectExportedEntries(IJavaProject proj, List<ISourceContainer> list) throws CoreException {
		for (IClasspathEntry entry : proj.getRawClasspath()) {
			ISourceContainer sourceContainer = null;
			if (entry.isExported()) {
				switch (entry.getEntryKind()) {
					case IClasspathEntry.CPE_CONTAINER:
						IClasspathContainer container = JavaCore.getClasspathContainer(entry.getPath(), proj);
						sourceContainer = new ClasspathContainerSourceContainer(container.getPath());
						break;
					case IClasspathEntry.CPE_LIBRARY:
						IPackageFragmentRoot[] roots = proj.findPackageFragmentRoots(entry);
						if (roots != null && roots.length > 0) {
							sourceContainer = new PackageFragmentRootSourceContainer(roots[0]);
						}
						break;
					case IClasspathEntry.CPE_PROJECT:
						String name = entry.getPath().segment(0);
						IProject p = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
						if (p.exists()) {
							IJavaProject jp = JavaCore.create(p);
							if (jp.exists()) {
								sourceContainer = new JavaProjectSourceContainer(jp);
							}
						}
						break;
					case IClasspathEntry.CPE_VARIABLE:
						sourceContainer = new ClasspathVariableSourceContainer(entry.getPath());
						break;
					default:
						break;
				}
				if (sourceContainer != null) {
					if (!list.contains(sourceContainer)) {
						list.add(sourceContainer);
					}
				}
			}
		}
	}
}
