/*
 * $Id$
 *
 * SARL is an general-purpose agent programming language.
 * More details on http://www.sarl.io
 *
 * Copyright (C) 2014-2026 SARL.io, the original authors and main authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.sarl.eclipse.tests.api;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceDescription;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.xtext.resource.FileExtensionProvider;
import org.eclipse.xtext.ui.util.PluginProjectFactory;
import org.eclipse.xtext.util.JavaVersion;
import org.eclipse.xtext.util.StringInputStream;
import org.junit.jupiter.api.AfterEach;
import org.osgi.framework.Bundle;

import io.sarl.lang.ui.preferences.SARLPreferences;

/** Provides tools for setting up and managing the Eclipse workspace.
 * This class was adapted from the Xtend test suite.
 *
 * @author $Author: sgalland$
 * @author $Author: ngaud$
 * @author Jan Koehnlein - Xtend contributor
 * @version $FullVersion$
 * @mavengroupid $GroupId$
 * @mavenartifactid $ArtifactId$
 */
@Singleton
@SuppressWarnings("static-method")
public class WorkbenchTestHelper {

	/** Default Java version for tests.
	 *
	 * @since 0.13
	 */
	public static final JavaVersion DEFAULT_TEST_JAVA_VERSION = JavaVersion.JAVA17; 
	
//	/** Logger.
//	 */
//	public static final Logger log = Logger.getLogger(WorkbenchTestHelper.class.getName());

	/** Name of the project that is generated during unit tests.
	 */
	public static final String TESTPROJECT_NAME = "test.project"; //$NON-NLS-1$

	/**
	 * ID of this nature.
	 */
	public static final String NATURE_ID = "io.sarl.eclipse.SARLProjectNature"; //$NON-NLS-1$

	/** List of the bundles that are required for created a testing project.
	 */
	public static final ImmutableList<String> DEFAULT_REQ_BUNDLES = ImmutableList.of(
			"com.google.inject", //$NON-NLS-1$
			"org.eclipse.xtend.lib", //$NON-NLS-1$
			"org.eclipse.xtext.xbase.lib", //$NON-NLS-1$
			"javax.inject", //$NON-NLS-1$
			"wrapper.io.sarl.lang.compiler", //$NON-NLS-1$
			"wrapper.io.sarl.lang.core", //$NON-NLS-1$
			"io.sarl.lang.ui", //$NON-NLS-1$
			"wrapper.io.sarl.baseutils.tests.api", //$NON-NLS-1$
			"wrapper.io.sarl.lang.compiler.tests", //$NON-NLS-1$
			"junit-jupiter-api"); //$NON-NLS-1$

	private Set<IFile> files = new HashSet<>();

//	@Inject
//	private XtextEditorInfo editorInfo;

	@Inject
	private Injector injector;

	@Inject
	private FileExtensionProvider fileExtensionProvider;

	private IWorkspace workspace;

	private IWorkbench workbench;

	//	@Inject
//	private IResourceSetProvider resourceSetProvider;
	
	@Inject
	private ProjectCreator projectCreator;

	private boolean isLazyCreatedProject = false;
	
	private String lastProjectName = null;

//	/** Init the helper for the unit test.
//	 * 
//	 * @throws Exception
//	 */
//	public void setUp() throws Exception {
//		//
//	}
	
	/** Dispose the helper for the unit test.
	 * 
	 * @throws Exception
	 */
	@AfterEach
	public void tearDown() throws Exception {
		this.lastProjectName = null;
		// Close the editors safely.
		final IWorkbench wb = getWorkbench();
		if (wb != null) {
			try {
				final IWorkbenchWindow window = wb.getActiveWorkbenchWindow();
				if (window != null) {
					window.getActivePage().closeAllEditors(false);
				}
			} catch (Throwable e) {
				//
			}
		}
		new WorkspaceModifyOperation() {

			@Override
			protected void execute(IProgressMonitor monitor) throws CoreException,
			InvocationTargetException, InterruptedException {
				for (IFile file : getFiles()) {
					try {
						file.delete(true, null);
					} catch (Exception exc) {
						// Be silent because it is outside the scope of the tests
					}
				}
				getFiles().clear();
				IFolder binFolder = getProject(false).getFolder("bin"); //$NON-NLS-1$
				if (binFolder.exists()) {
					for (IResource binMember : binFolder.members()) {
						try {
							binMember.delete(true, null);
						} catch (Exception exc) {
							// Be silent because it is outside the scope of the tests
						}
					}
				}
				if (WorkbenchTestHelper.this.isLazyCreatedProject) {
					deleteProject(getProject(false));
					WorkbenchTestHelper.this.isLazyCreatedProject = false;
				}
			}

		}.run(null);
		awaitAutoBuild();
	}

	/** Replies the workspace.
	 *
	 * @return the workspace.
	 */
	public IWorkspace getWorkspace() {
		if (this.workspace == null) {
			this.workspace = this.injector.getInstance(IWorkspace.class);
		}
		return this.workspace;
	}

	/** Replies the workbench.
	 *
	 * @return the workbench.
	 */
	public IWorkbench getWorkbench() {
		if (this.workbench == null) {
			this.workbench = this.injector.getInstance(IWorkbench.class);
		}
		return this.workbench;
	}

	//	private static void deleteProjects(IProject[] projects) throws CoreException {
//		for (IProject iProject : projects) {
//			if (iProject.exists()) {
//				iProject.delete(true,true, new NullProgressMonitor());
//			}
//		}
//	}
//	
//	/** Clear the workspace.
//	 */
//	public void clearWorkspace() {
//		try {
//			IProject[] visibleProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
//			deleteProjects(visibleProjects);
//			IProject[] hiddenProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects(org.eclipse.core.resources.IContainer.INCLUDE_HIDDEN);
//			deleteProjects(hiddenProjects);
//		} catch (Exception e) {
//			throw new RuntimeException(e);
//		}
//	}
	
	/** Replies the created files.
	 *
	 * @return the created files.
	 */
	public Set<IFile> getFiles() {
		return this.files;
	}

	/** Replies the current project.
	 * 
	 * @return the project.
	 */
	public IProject getProject() {
		return getProject(true);
	}
	
	/** Replies the current project.
	 * 
	 * @param createOnDemand create the project if it does not exist yet.
	 * @return the project.
	 */
	public IProject getProject(boolean createOnDemand) {
		return getProject(this.lastProjectName, createOnDemand);
	}

//	/** Replies the current Java project.
//	 * 
//	 * @return the Java project.
//	 */
//	public IJavaProject getJavaProject() {
//		return JavaCore.create(getProject());
//	}
//
//	/** Replies the Java project for the given resource.
//	 *
//	 * @param resource the resource to search for.
//	 * @return the Java project.
//	 */
//	public IJavaProject getJavaProject(Resource resource) {
//		return JavaCore.create(getProject(resource, true));
//	}

	/** Replies the project with the given name.
	 * 
	 * @param projectName the name of the project.
	 * @param createOnDemand create the project if it does not exist yet.
	 * @return the project.
	 */
	public IProject getProject(String projectName, boolean createOnDemand) {
		String prjName = projectName;
		if (Strings.isNullOrEmpty(prjName)) {
			prjName = TESTPROJECT_NAME;
		}
		IProject project = getWorkspace().getRoot().getProject(prjName);
		if (createOnDemand && !project.exists()) {
			try {
				this.isLazyCreatedProject = true;
				project = createPluginProject(prjName, this.projectCreator);
			} catch (CoreException e) {
				throw new RuntimeException(e);
			}
		}
		return project;
	}

//	/** Replies the project for the given resource.
//	 * 
//	 * @param resource the resource to search for.
//	 * @param createOnDemand create the project if it does not exist yet.
//	 * @return the project.
//	 */
//	public IProject getProject(Resource resource, boolean createOnDemand) {
//		if (resource != null) {
//			final URI uri = resource.getURI();
//			final String platformString = uri.toPlatformString(true);
//			final IPath resourcePath = new Path(platformString);
//			final IFile file = this.workspace.getRoot().getFile(resourcePath);
//			if (file != null) {
//				final IProject project = file.getProject();
//				if (project != null && project.exists()) {
//					return project;
//				}
//			}
//		}
//		return getProject(createOnDemand);
//	}
//
//	/** Open the Xtext editor.
//	 * 
//	 * @param fileName the name of the file to open.
//	 * @param content the content of the file.
//	 * @return the editor.
//	 * @throws Exception
//	 */
//	public XtextEditor openEditor(String fileName, String content) throws Exception {
//		int cursor = content.indexOf('|');
//		IFile file = createFile(fileName, content.replace("|", ""));  //$NON-NLS-1$//$NON-NLS-2$
//		XtextEditor editor = openEditor(file);
//		editor.getInternalSourceViewer().setSelectedRange(cursor, 0);
//		editor.getInternalSourceViewer().getTextWidget().setFocus();
//		return editor;
//	}
//
//	/** Create a file in the source folder.
//	 * 
//	 * @param fileName the name of the file to create.
//	 * @param content the content of the file.
//	 * @return the file.
//	 * @throws Exception
//	 * see {@link #getFullFileNameInSources(String)}
//	 */
//	public IFile createFile(String fileName, String content) throws Exception {
//		String fullFileName = getFullFileNameInSources(fileName);
//		return createFileImpl(fullFileName, content);
//	}

	private static IProject createProject(IProject project) throws CoreException {
		if (!project.exists()) {
			project.create(new NullProgressMonitor());
		}
		project.open(new NullProgressMonitor());
		return project;
	}

	/** Create the container tree.
	 *
	 * @param container the container
	 * @throws InvocationTargetException in case of error.
	 * @throws InterruptedException in case of errir.
	 */
	public static void create(org.eclipse.core.resources.IContainer container)
			throws InvocationTargetException, InterruptedException {
		new WorkspaceModifyOperation() {

			@Override
			protected void execute(IProgressMonitor monitor)
					throws CoreException, InvocationTargetException,
					InterruptedException {
				if (!container.exists()) {
					create(container.getParent());
					if (container instanceof IFolder) {
						((IFolder) container).create(true, true, new NullProgressMonitor());
					} else {
						IProject iProject = (IProject) container;
						createProject(iProject);
					}
				}
			}
		}.run(new NullProgressMonitor());
	}

	/** Create a file.
	 * 
	 * @param fullFileName the name of the file to create.
	 * @param content the content of the file.
	 * @return the file.
	 * @throws Exception
	 */
	public IFile createFileImpl(String fullFileName, String content) throws Exception {
		final IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(fullFileName));
		new WorkspaceModifyOperation() {
			@Override
			protected void execute(IProgressMonitor monitor)
					throws CoreException, InvocationTargetException,
					InterruptedException {
				create(file.getParent());
				file.delete(true, new NullProgressMonitor());
				try (final InputStream is = new StringInputStream(content)) {
					file.create(is, true, new NullProgressMonitor());
				} catch (Exception exception) {
					//
				}
			}

		}.run(new NullProgressMonitor());
		getFiles().add(file);
		return file;
	}

	/** Create a file.
	 * 
	 * @param file the name of the file to create.
	 * @param content the content of the file.
	 * @return the file.
	 * @throws Exception
	 */
	public IFile createFileImpl(IFile file, InputStream content) throws Exception {
		new WorkspaceModifyOperation() {
			@Override
			protected void execute(IProgressMonitor monitor)
					throws CoreException, InvocationTargetException,
					InterruptedException {
				create(file.getParent());
				file.delete(true, new NullProgressMonitor());
				file.create(content, true, new NullProgressMonitor());
			}

		}.run(new NullProgressMonitor());
		getFiles().add(file);
		return file;
	}

//	/** Replies the file with the given name.
//	 * 
//	 * @param fileName the name without the file extension.
//	 * @return the file.
//	 */
//	public IFile getFile(String fileName) {
//		return getWorkspace().getRoot().getFile(new Path(getFullFileNameInSources(fileName)));
//	}
//
	/** Compute the full filename.
	 * 
	 * @param fileName the name with the file extension.
	 * @return the full filename.
	 * @see #getFullFileNameInProject(String)
	 */
	public String getFullFileNameInSources(String fileName) {
		final String extension = (fileName.indexOf(".") != -1) ? "" : "." + getFileExtension(); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
		final String fullFileName = getProject().getName() + "/src/" + fileName + extension; //$NON-NLS-1$
		return fullFileName;
	}

//	/** Replies the file with the given name.
//	 * 
//	 * @param fileName the name.
//	 * @return the file.
//	 */
//	public IFile getFileInProject(String fileName) {
//		return getFileInRoot(getProject().getName(), fileName);
//	}
//
//	/** Replies the file with the given name.
//	 * 
//	 * @param projectName the name of the project
//	 * @param fileName the name.
//	 * @return the file.
//	 */
//	public IFile getFileInRoot(String projectName, String fileName) {
//		String fullFileName = projectName + "/" + fileName; //$NON-NLS-1$
//		return getFile(new Path(fullFileName));
//	}
//
//	/** Replies the file with the given name.
//	 * 
//	 * @param path the name of the file.
//	 * @return the file.
//	 */
//	public IFile getFile(IPath path) {
//		return this.workspace.getRoot().getFile(path);
//	}
//
//	/** Replies the folder with the given name.
//	 * 
//	 * @param path the name of the folder.
//	 * @return the folder.
//	 */
//	public IFolder getFolder(IPath path) {
//		return this.workspace.getRoot().getFolder(path);
//	}
//
	/** Compute the full filename inside the proejct root directory.
	 * 
	 * @param fileName the name without the file extension.
	 * @return the full filename.
	 * @see #getFullFileNameInSources(String)
	 */
	public String getFullFileNameInProject(String fileName) {
		String extension = (fileName.indexOf(".") != -1) ? "" : "." + getFileExtension(); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
		String fullFileName = getProject().getName() + "/" + fileName + extension; //$NON-NLS-1$
		return fullFileName;
	}

	/** Replies the file extension.
	 *
	 * @return the file extension.
	 */
	public String getFileExtension() {
		return this.fileExtensionProvider.getFileExtensions().iterator().next();
	}

//	/** Compute the URI of the given file.
//	 *
//	 * @param file the file.
//	 * @return the uri.
//	 */
//	public URI uri(IFile file) {
//		return URI.createPlatformResourceURI(file.getFullPath().toString(), true);
//	}
//
//	/** Create a SARL file.
//	 *
//	 * @param fileName the filename.
//	 * @param content the content.
//	 * @return the SARL script.
//	 * @throws Exception
//	 */
//	public SarlScript sarlScript(String fileName, String content) throws Exception {
//		return sarlScript(fileName, content, true);
//	}
//
//	/** Create a SARL file.
//	 *
//	 * @param fileName the filename.
//	 * @param content the content.
//	 * @param assertNoError indicates if this function asserts no error in the content.
//	 * If {@code true} this function may fail. Otherwise, it never fails on syntax errors.
//	 * @return the SARL script.
//	 * @throws Exception
//	 */
//	public SarlScript sarlScript(String fileName, String content, boolean assertNoError) throws Exception {
//		IFile file = createFile(fileName, content);
//		Resource resource = getResourceSet().createResource(uri(file));
//		try (InputStream is = new StringInputStream(content)) {
//			resource.load(is, null);
//			if (assertNoError) {
//				assertEquals(0, resource.getErrors().size(), resource.getErrors().toString());
//			}
//			SarlScript xtendFile = (SarlScript) resource.getContents().get(0);
//			return xtendFile;
//		}
//	}
//
//	/** Create a SARL file.
//	 *
//	 * @param file the file.
//	 * @return the SARL script.
//	 * @throws Exception
//	 */
//	public SarlScript sarlScript(IFile file) throws Exception {
//		Resource resource = getResourceSet().createResource(uri(file));
//		resource.load(null);
//		return (SarlScript) resource.getContents().get(0);
//	}
//
//	/** Create a SARL file.
//	 *
//	 * @param project the project.
//	 * @param fileName the filename in the project.
//	 * @param content the content.
//	 * @return the SARL script.
//	 * @throws Exception
//	 */
//	public SarlScript sarlScript(IProject project, String fileName, String content) throws Exception {
//		IFile file = createFileImpl(project.getName() + "/src/" + fileName, content); //$NON-NLS-1$
//		Resource resource = this.resourceSetProvider.get(project).createResource(uri(file));
//		try (InputStream is = new StringInputStream(content)) {
//			resource.load(is, null);
//			assertEquals(0, resource.getErrors().size(), resource.getErrors().toString());
//			SarlScript xtendFile = (SarlScript) resource.getContents().get(0);
//			return xtendFile;
//		}
//	}
//	
//	/** Replies the resource associated to the given file in the given project.
//	 *
//	 * @param file the file in the project.
//	 * @return the resource.
//	 */
//	public Resource getResourceFor(IFile file) {
//		return getResourceFor(getProject(), file);
//	}
//
//	/** Replies the resource associated to the given file in the given project.
//	 *
//	 * @param project the project.
//	 * @param file the file in the project.
//	 * @return the resource.
//	 */
//	public Resource getResourceFor(IProject project, IFile file) {
//		URI uri = uri(file);
//		ResourceSet resourceSet = this.resourceSetProvider.get(project);
//		Resource resource = resourceSet.getResource(uri, false);
//		if (resource == null) {
//			resource = resourceSet.createResource(uri);
//		}
//		return resource;
//	}
//
//	/** Create a SARL file with a SARL agent inside.
//	 *
//	 * @param fileName the filename.
//	 * @param type the type of the expected element.
//	 * @param content the content.
//	 * @return the SARL script.
//	 * @throws Exception
//	 */
//	public <T extends XtendTypeDeclaration> T sarlTypeDeclaration(
//			String fileName, Class<T> type,
//			String content) throws Exception {
//		SarlScript script = sarlScript(fileName, content);
//		Object latest = null;
//		for (XtendTypeDeclaration declaration : script.getXtendTypes()) {
//			if (type.isInstance(declaration)) {
//				latest = declaration;
//			}
//		}
//		if (latest != null) {
//			return type.cast(latest);
//		}
//		throw new NoSuchElementException();
//	}
//
//	/** Create a SARL file with a SARL agent inside.
//	 *
//	 * @param type the type of the expected element.
//	 * @param content the content.
//	 * @return the SARL script.
//	 * @throws Exception
//	 */
//	public <T extends XtendTypeDeclaration> T sarlTypeDeclaration(
//			Class<T> type,
//			String content) throws Exception {
//		return sarlTypeDeclaration(generateFilename(), type, content);
//	}
//
//	/** Replies the resource set for the testing project.
//	 *
//	 * @return the resource set.
//	 */
//	public ResourceSet getResourceSet() {
//		return this.resourceSetProvider.get(getProject());
//	}
//
//	/** Replies the identifier of the editor.
//	 *
//	 * @return the identifier of the editor.
//	 */
//	public String getEditorID() {
//		return this.editorInfo.getEditorId();
//	}
//
//	/** Replies the content of the given file.
//	 *
//	 * @param file the name of the file.
//	 * @return the content of the file.
//	 * @throws Exception
//	 */
//	public String getContents(IFile file) throws Exception {
//		try (InputStream inputStream = file.getContents()) {
//			byte[] buffer = new byte[2048];
//			int bytesRead = 0;
//			StringBuffer b = new StringBuffer();
//			do {
//				bytesRead = inputStream.read(buffer);
//				if (bytesRead != -1)
//					b.append(new String(buffer, 0, bytesRead));
//			} while (bytesRead != -1);
//			return b.toString();
//		}
//	}
//
//	/** Close the welcome page.
//	 *
//	 * @throws InterruptedException
//	 */
//	public void closeWelcomePage() throws InterruptedException {
//		if (PlatformUI.getWorkbench().getIntroManager().getIntro() != null) {
//			PlatformUI.getWorkbench().getIntroManager().closeIntro(
//					PlatformUI.getWorkbench().getIntroManager().getIntro());
//		}
//	}

	/** Replies the JDT Java version that corresponds to the given Xtext JAva version.
	 *
	 * @param version the Xtext Java version.
	 * @return the JDT Java version.
	 * @since 0.13
	 */
	public static String toJavaCoreVersion(JavaVersion version) {
		final JavaVersion jVersion =  version == null ? DEFAULT_TEST_JAVA_VERSION : version;
		switch (jVersion) {
		case JAVA17:
			return JavaCore.VERSION_17;
		case JAVA11:
			return JavaCore.VERSION_11;
		case JAVA10:
			return JavaCore.VERSION_10;
		case JAVA9:
			return JavaCore.VERSION_9;
		case JAVA8:
			return JavaCore.VERSION_1_8;
		case JAVA7:
			return JavaCore.VERSION_1_7;
		case JAVA6:
			return JavaCore.VERSION_1_6;
		case JAVA5:
			return JavaCore.VERSION_1_5;
		default:
			throw new IllegalStateException();
		}
	}
	
	/** Create a project.
	 *
	 * @param name the name of the project.
	 * @param creator the creator of project.
	 * @param requiredBundles the bundles to add into the build path.
	 * @return the project.
	 * @throws CoreException
	 */
	public IProject createPluginProject(String name, ProjectCreator creator, String... requiredBundles) throws CoreException {
		// If no required bundle is given, use the defaults.
		String[] bundleDependencies;
		if (requiredBundles == null || requiredBundles.length == 0) {
			bundleDependencies = new String[WorkbenchTestHelper.DEFAULT_REQ_BUNDLES.size()];
			WorkbenchTestHelper.DEFAULT_REQ_BUNDLES.toArray(bundleDependencies);
		} else {
			bundleDependencies = requiredBundles;
		}
		
		// Disable "Build automatically"
		try {
			IWorkspace workspace = ResourcesPlugin.getWorkspace();
			IWorkspaceDescription workspaceDescription = workspace.getDescription();
			workspaceDescription.setAutoBuilding(false);
			workspace.setDescription(workspaceDescription);
		} catch (Throwable exception) {
			throw new CoreException(new Status(IStatus.ERROR, "io.sarl.tests.api", //$NON-NLS-1$
					exception.getLocalizedMessage(), exception));
		}
		
		
		PluginProjectFactory projectFactory = creator.getProjectFactory();
		projectFactory.setProjectName(name);
		this.lastProjectName = name;
		final JavaVersion jVersion = creator.getJavaVersion();
		final String jseVersion = toJavaCoreVersion(jVersion);
		projectFactory.setBreeToUse("JavaSE-" + jseVersion); //$NON-NLS-1$
		projectFactory.addFolders(creator.getSourceFolders());
		IPath srcGenFolder = Path.fromPortableString(creator.getGenerationFolder());
		projectFactory.addBuilderIds(creator.getBuilderIds());
		projectFactory.addProjectNatures(creator.getNatures());
		IProject result = projectFactory.createProject(null, null);
		IJavaProject javaProject = JavaCore.create(result);
		makeCompliantFor(javaProject, jVersion);
		creator.addJreClasspathEntry(javaProject);

		IPath workspaceRoot;
		IPath platformLocation;

		try {
			workspaceRoot = ResourcesPlugin.getWorkspace().getRoot().getLocation();
			Location location = Platform.getInstallLocation();
			java.net.URI uri = URIUtil.toURI(location.getURL());
			File file = URIUtil.toFile(uri);
			platformLocation = Path.fromOSString(file.getAbsolutePath());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
		// Retrieve the bundles
		List<IClasspathEntry> classpathEntries = new ArrayList<>(bundleDependencies.length);
		for(String bundleName : bundleDependencies) {
			URL url;
			try {
				url = new URL(bundleName);
			} catch (Throwable exception) {
				url = null;
			}
			if (url != null) {
				URL newUrl;
				try {
					newUrl = FileLocator.toFileURL(url);
				} catch (Exception exception) {
					throw new RuntimeException("Reference library not found: " + url, exception); //$NON-NLS-1$
				}
				if (newUrl == null) {
					throw new RuntimeException("Reference library not found: " + url); //$NON-NLS-1$
				}
				
				IPath path = Path.fromPortableString(newUrl.getPath());

				IClasspathEntry classPathEntry = JavaCore.newLibraryEntry(
						path,
						null,
						null);
				classpathEntries.add(classPathEntry);
			} else {
				Bundle bundle = Platform.getBundle(bundleName);
				if (bundle == null) {
					throw new RuntimeException("Reference library not found: " + bundleName); //$NON-NLS-1$
				}
	
				IPath bundlePath = computeBundlePath(bundle, workspaceRoot, platformLocation);
				if (bundlePath == null) {
					throw new RuntimeException("Reference library not found: " + bundleName); //$NON-NLS-1$
				}
	
				IPath binPath = computeBinaryPathForBundle(bundlePath, workspaceRoot);
				if (binPath == null) {
					throw new RuntimeException("Reference library not found: " + bundleName); //$NON-NLS-1$
				}
	
				IClasspathEntry classPathEntry = JavaCore.newLibraryEntry(
						binPath,
						null,
						null);
				classpathEntries.add(classPathEntry);
			}
		}

		if (!classpathEntries.isEmpty()) {
			creator.addToClasspath(javaProject, false, classpathEntries);
		}

		// Configure SARL source folder
		SARLPreferences.setSpecificSARLConfigurationFor(result, srcGenFolder, null);

		// Enable "Build automatically"
		try {
			IWorkspace workspace = ResourcesPlugin.getWorkspace();
			IWorkspaceDescription workspaceDescription = workspace.getDescription();
			workspaceDescription.setAutoBuilding(true);
			workspace.setDescription(workspaceDescription);
		} catch (Throwable exception) {
			throw new CoreException(new Status(IStatus.ERROR, "io.sarl.tests.api", //$NON-NLS-1$
					exception.getLocalizedMessage(), exception));
		}
		
		awaitAutoBuild();
		
		return result;
	}
	
	private static IPath computeBundlePath(Bundle bundle, IPath workspaceRoot, IPath platformLocation) {
		IPath bundlePath;
		final Optional<File> fileOpt = FileLocator.getBundleFileLocation(bundle);
		if (fileOpt.isEmpty()) {
			throw new RuntimeException(new FileNotFoundException());
		}
		bundlePath = Path.fromOSString(fileOpt.get().getPath());

		// Ensure that the bundle path is absolute (mandatory for beeing a classpath entry)
		if (!bundlePath.isAbsolute()) {
			IPath newBundlePath = workspaceRoot.append(bundlePath);
			if (!newBundlePath.toFile().exists()) {
				newBundlePath = platformLocation.append(bundlePath);
			}
			bundlePath = newBundlePath;
		}
		assert (bundlePath.isAbsolute()) : "The bundle path is not absolute: " + bundlePath; //$NON-NLS-1$
		return bundlePath;
	}

	private static IPath computeBinaryPathForBundle(IPath bundlePath, IPath workspaceRoot) {
		// Determine the path from the output folders of the Java projects in the current workspace.
		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(bundlePath.lastSegment());
		IPath newBundlePath = null;
		try {
			if (project != null && project.hasNature(JavaCore.NATURE_ID)) {
				IJavaProject javaProject = JavaCore.create(project);
				newBundlePath = javaProject.getOutputLocation();
				if (newBundlePath != null) {
					newBundlePath = workspaceRoot.append(newBundlePath);
					// Test if the bundle path exists
					if (newBundlePath != null && !newBundlePath.toFile().exists()) {
						newBundlePath = null;
					}
				}
			}
		} catch (Exception e) {
			// Ignore the exceptions since they are not useful (hopefully)
		}

		if (newBundlePath != null) {
			assert (newBundlePath.isAbsolute()) : "The bundle path is not absolute: " + newBundlePath; //$NON-NLS-1$
			return newBundlePath;
		}

		// Detect the binary folder in the bundle.
		//
		// TODO: Replace by a dynamic detection based on Jdt API.
		File localFile = bundlePath.toFile();
		File binFolder = new File(new File(localFile, "target"), "classes"); //$NON-NLS-1$//$NON-NLS-2$
		if (binFolder.exists()) {
			newBundlePath = bundlePath.append("target").append("classes"); //$NON-NLS-1$//$NON-NLS-2$
		} else {
			binFolder = new File(localFile, "bin"); //$NON-NLS-1$
			if (binFolder.exists()) {
				newBundlePath = bundlePath.append("bin"); //$NON-NLS-1$
			} else {
				newBundlePath = bundlePath;
			}
		}

		assert (newBundlePath.isAbsolute()) : "The bundle path is not absolute: " + bundlePath; //$NON-NLS-1$
		return newBundlePath;
	}

	/** Make the given project compliant for the given version.
	 *
	 * @param javaProject the project.
	 * @param javaVersion the Java version.
	 */
	public static void makeCompliantFor(IJavaProject javaProject, JavaVersion javaVersion) {
		final Map<String, String> options = javaProject.getOptions(false);
		final String jreLevel = toJavaCoreVersion(javaVersion);
		options.put(JavaCore.COMPILER_COMPLIANCE, jreLevel);
		options.put(JavaCore.COMPILER_SOURCE, jreLevel);
		options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, jreLevel);
		options.put(JavaCore.COMPILER_PB_ASSERT_IDENTIFIER, JavaCore.ERROR);
		options.put(JavaCore.COMPILER_PB_ENUM_IDENTIFIER, JavaCore.ERROR);
		options.put(JavaCore.COMPILER_CODEGEN_INLINE_JSR_BYTECODE, JavaCore.ENABLED);
		options.put(JavaCore.COMPILER_LOCAL_VARIABLE_ATTR, JavaCore.GENERATE);
		options.put(JavaCore.COMPILER_LINE_NUMBER_ATTR, JavaCore.GENERATE);
		options.put(JavaCore.COMPILER_SOURCE_FILE_ATTR, JavaCore.GENERATE);
		options.put(JavaCore.COMPILER_CODEGEN_UNUSED_LOCAL, JavaCore.PRESERVE);
		javaProject.setOptions(options);
	}

//	/** Add exported packages into the project.
//	 *
//	 * @param project the project.
//	 * @param exportedPackages the exported packages.
//	 * @throws Exception
//	 */
//	@SuppressWarnings("resource")
//	public static void addExportedPackages(IProject project, String ... exportedPackages) throws Exception{
//		IFile manifest = project.getFile("META-INF/MANIFEST.MF"); //$NON-NLS-1$
//		Manifest mf = new Manifest(manifest.getContents());
//		String value = mf.getMainAttributes().getValue("Export-Package"); //$NON-NLS-1$
//		for (String exported : exportedPackages) {
//			if (value == null) {
//				value = exported;
//			} else {
//				value += ","+exported; //$NON-NLS-1$
//			}
//		}
//		mf.getMainAttributes().putValue("Export-Package", value); //$NON-NLS-1$
//		ByteArrayOutputStream stream = new ByteArrayOutputStream();
//		mf.write(stream);
//		manifest.setContents(new ByteArrayInputStream(stream.toByteArray()), true, true, null);
//	}
//	
//	/** Add required plugin into the project.
//	 *
//	 * @param project the project.
//	 * @param requiredBundles the required bundles.
//	 * @throws Exception
//	 */
//	@SuppressWarnings("resource")
//	public static void addRequiredBundle(IProject project, Iterable<String> requiredBundles) throws Exception{
//		IFile manifest = project.getFile("META-INF/MANIFEST.MF"); //$NON-NLS-1$
//		Manifest mf = new Manifest(manifest.getContents());
//		String value = mf.getMainAttributes().getValue("Required-Bundles"); //$NON-NLS-1$
//		for (String required : requiredBundles) {
//			if (value == null) {
//				value = required;
//			} else {
//				value += ","+required; //$NON-NLS-1$
//			}
//		}
//		mf.getMainAttributes().putValue("Required-Bundles", value); //$NON-NLS-1$
//		ByteArrayOutputStream stream = new ByteArrayOutputStream();
//		mf.write(stream);
