/*******************************************************************************
 * Copyright (c) 2007, 2014 Symbian Software Systems 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:
 *    Andrew Ferguson (Symbian) - Initial implementation
 *******************************************************************************/
package org.eclipse.cdt.core.index.export;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.IPDOMManager;
import org.eclipse.cdt.core.index.IIndexLocationConverter;
import org.eclipse.cdt.core.index.ResourceContainerRelativeLocationConverter;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IPathEntry;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.extension.impl.CDefaultConfigurationData;
import org.eclipse.cdt.internal.core.index.IIndexFragment;
import org.eclipse.cdt.internal.core.pdom.indexer.IndexerPreferences;
import org.eclipse.core.resources.FileInfoMatcherDescription;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceFilterDescription;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;

import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.MessageFormat;

/**
 * An IExportProjectProvider suitable for indexing an external folder. The arguments understood by this provider
 * are
 * <ul>
 * <li>-source what will become the root of the indexed content
 * <li>-include any preinclude files to configure the parser with
 * <li>-id the id to write to the produce fragment
 * </ul>
 */
public class ExternalExportProjectProvider extends AbstractExportProjectProvider {
	private static final String PREBUILT_PROJECT_OWNER = "org.eclipse.cdt.core.index.export.prebuiltOwner"; //$NON-NLS-1$
	private static final String ORG_ECLIPSE_CDT_CORE_INDEX_EXPORT_DATESTAMP = "org.eclipse.cdt.core.index.export.datestamp"; //$NON-NLS-1$
	private static final String CONTENT = "content"; //$NON-NLS-1$
	public static final String OPT_SOURCE = "-source"; //$NON-NLS-1$
	public static final String OPT_INCLUDE = "-include"; //$NON-NLS-1$
	/** @since 5.7 */
	public static final String OPT_EXCLUDE = "-exclude"; //$NON-NLS-1$
	public static final String OPT_FRAGMENT_ID = "-id"; //$NON-NLS-1$

	private IFolder content;
	private String fragmentId;

	public ExternalExportProjectProvider() {
		super();
	}

	@Override
	public ICProject createProject() throws CoreException {
		// -source
		File source = new File(getSingleString(OPT_SOURCE));
		if (!source.exists()) {
			fail(MessageFormat.format(Messages.ExternalContentPEM_LocationToIndexNonExistent, new Object[] { source }));
		}

		// -include
		List<String> includeFiles = new ArrayList<>();
		if (isPresent(OPT_INCLUDE)) {
			includeFiles.addAll(getParameters(OPT_INCLUDE));
		}

		// -exclude
		List<String> excludeFiles = new ArrayList<>();
		if (isPresent(OPT_EXCLUDE)) {
			excludeFiles.addAll(getParameters(OPT_EXCLUDE));
		}

		// -id
		fragmentId = getSingleString(OPT_FRAGMENT_ID);

		return createCCProject("__" + System.currentTimeMillis(), source, includeFiles, excludeFiles); //$NON-NLS-1$
	}

	/**
	 * Returns the project folder the external content is stored in
	 * @return the project folder the external content is stored in
	 */
	protected IFolder getContentFolder() {
		return content;
	}

	/**
	 * Convenience method for creating a cproject
	 * @param projectName the name for the new project
	 * @param location the absolute path of some external content
	 * @param includeFiles a list of include paths to add to the project scanner
	 * @return a new project
	 * @throws CoreException
	 */
	private ICProject createCCProject(final String projectName, final File location, final List<String> includeFiles,
			final List<String> excludeFiles) throws CoreException {
		final IWorkspace ws = ResourcesPlugin.getWorkspace();
		final ICProject newProject[] = new ICProject[1];

		ws.run(new IWorkspaceRunnable() {
			@Override
			public void run(IProgressMonitor monitor) throws CoreException {
				IWorkspace workspace = ResourcesPlugin.getWorkspace();
				IProject project = workspace.getRoot()
						.getProject("__prebuilt_index_temp__" + System.currentTimeMillis()); //$NON-NLS-1$
				IProjectDescription description = workspace.newProjectDescription(project.getName());
				CCorePlugin.getDefault().createCProject(description, project, NPM, PREBUILT_PROJECT_OWNER);
				CCorePlugin.getDefault().convertProjectFromCtoCC(project, NPM);
				ICProjectDescription pd = CCorePlugin.getDefault().getProjectDescription(project, true);
				newCfg(pd, project.getName(), "config"); //$NON-NLS-1$

				CoreModel.getDefault().setProjectDescription(project, pd, true, new NullProgressMonitor());

				// Add in exclude filters
				for (String excludeFile : excludeFiles) {
					FileInfoMatcherDescription matcherDescription = ExportIndexFileInfoMatcher
							.getDescription(excludeFile);
					project.createFilter(IResourceFilterDescription.EXCLUDE_ALL | IResourceFilterDescription.FOLDERS
							| IResourceFilterDescription.INHERITABLE, matcherDescription, 0, NPM);
				}

				ICProject cproject = CCorePlugin.getDefault().getCoreModel().create(project);

				// External content appears under a linked folder
				content = project.getFolder(CONTENT);
				content.createLink(new Path(location.getAbsolutePath()), IResource.NONE, null);

				// Setup path entries
				List<IPathEntry> entries = new ArrayList<>(Arrays.asList(CoreModel.getRawPathEntries(cproject)));

				// pre-include files
				for (String path : includeFiles) {
					entries.add(CoreModel.newIncludeFileEntry(project.getFullPath(), new Path(path)));
				}

				// content directory is a source root
				entries.add(CoreModel.newSourceEntry(content.getProjectRelativePath()));

				// any additional entries
				entries.addAll(getAdditionalRawEntries());

				cproject.setRawPathEntries(entries.toArray(new IPathEntry[entries.size()]), new NullProgressMonitor());

				newProject[0] = cproject;

				IndexerPreferences.set(newProject[0].getProject(), IndexerPreferences.KEY_INDEXER_ID,
						IPDOMManager.ID_NO_INDEXER);
				IndexerPreferences.set(newProject[0].getProject(), IndexerPreferences.KEY_INDEX_ALL_FILES,
						Boolean.TRUE.toString());
				IndexerPreferences.set(newProject[0].getProject(),
						IndexerPreferences.KEY_INDEX_UNUSED_HEADERS_WITH_DEFAULT_LANG, Boolean.TRUE.toString());
			}
		}, null);

		return newProject[0];
	}

	/**
	 * Get additional raw entries (above those added as part of the ExternalExportProjectProvider functionality)
	 * @return a list of additional entries to add to the project
	 */
	protected List<IPathEntry> getAdditionalRawEntries() {
		List<IPathEntry> entries = new ArrayList<>();
		entries.add(CoreModel.newIncludeEntry(content.getProjectRelativePath(), null, content.getLocation(), true));
		return entries;
	}

	private ICConfigurationDescription newCfg(ICProjectDescription des, String project, String config)
			throws CoreException {
		CDefaultConfigurationData data = new CDefaultConfigurationData(project + "." + config, //$NON-NLS-1$
				project + " " + config + " name", null); //$NON-NLS-1$ //$NON-NLS-2$
		data.initEmptyData();
		return des.createConfiguration(CCorePlugin.DEFAULT_PROVIDER_ID, data);
	}

	/*
	 * @see org.eclipse.cdt.core.index.export.IExportProjectProvider#getLocationConverter(org.eclipse.cdt.core.model.ICProject)
	 */
	@Override
	public IIndexLocationConverter getLocationConverter(final ICProject cproject) {
		return new ResourceContainerRelativeLocationConverter(content);
	}

	/*
	 * @see org.eclipse.cdt.core.index.export.IExportProjectProvider#getExportProperties()
	 */
	@Override
	public Map<String, String> getExportProperties() {
		Map<String, String> properties = new HashMap<>();
		Date now = Calendar.getInstance().getTime();
		properties.put(ORG_ECLIPSE_CDT_CORE_INDEX_EXPORT_DATESTAMP,
				DateFormat.getDateInstance().format(now) + " " + DateFormat.getTimeInstance().format(now)); //$NON-NLS-1$
		properties.put(IIndexFragment.PROPERTY_FRAGMENT_ID, fragmentId);
		return properties;
	}
}
