/*******************************************************************************
 * Copyright (c) 2005, 2015 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Andrew Ferguson (Symbian)
 *     Markus Schorn (Wind River Systems)
 *     Anton Leherbauer (Wind River Systems)
 *     Sergey Prigogin (Google)
 *******************************************************************************/
package org.eclipse.cdt.core.testplugin;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipFile;

import org.eclipse.cdt.core.CCProjectNature;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.CProjectNature;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.IArchive;
import org.eclipse.cdt.core.model.IArchiveContainer;
import org.eclipse.cdt.core.model.IBinary;
import org.eclipse.cdt.core.model.IBinaryContainer;
import org.eclipse.cdt.core.model.ICContainer;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IPathEntry;
import org.eclipse.cdt.core.model.ISourceRoot;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.core.settings.model.ICConfigExtensionReference;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionManager;
import org.eclipse.cdt.core.settings.model.TestCfgDataProvider;
import org.eclipse.cdt.core.settings.model.util.CDataUtil;
import org.eclipse.cdt.internal.core.model.InternalCoreModelUtil;
import org.eclipse.cdt.internal.core.pdom.indexer.IndexerPreferences;
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.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
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.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.ui.dialogs.IOverwriteQuery;
import org.eclipse.ui.wizards.datatransfer.FileSystemStructureProvider;
import org.eclipse.ui.wizards.datatransfer.ImportOperation;
import org.eclipse.ui.wizards.datatransfer.ZipFileStructureProvider;
import org.junit.Assert;
import org.osgi.framework.Bundle;

/**
 * Helper methods to set up a ICProject.
 */
public class CProjectHelper {

	private final static IOverwriteQuery OVERWRITE_QUERY= new IOverwriteQuery() {
		@Override
		public String queryOverwrite(String file) {
			return ALL;
		}
	};

	public static ICProject createCProject(final String projectName, String binFolderName) throws CoreException {
		return createCCProject(projectName, binFolderName, null);
	}
	
	/**
	 * Creates a ICProject.
	 */
	public static ICProject createCProject(final String projectName, String binFolderName, final String indexerID) 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 {
				IWorkspaceRoot root = ws.getRoot();
				IProject project = root.getProject(projectName);
				if (indexerID != null) {
					IndexerPreferences.set(project, IndexerPreferences.KEY_INDEX_UNUSED_HEADERS_WITH_DEFAULT_LANG, "true");
					IndexerPreferences.set(project, IndexerPreferences.KEY_INDEXER_ID, indexerID);
				}
				if (!project.exists()) {
					project.create(null);
				} else {
					project.refreshLocal(IResource.DEPTH_INFINITE, null);
				}
				if (!project.isOpen()) {
					project.open(null);
				}
				if (!project.hasNature(CProjectNature.C_NATURE_ID)) {
					String projectId = CTestPlugin.PLUGIN_ID + ".TestProject";
					addNatureToProject(project, CProjectNature.C_NATURE_ID, null);
					CCorePlugin.getDefault().mapCProjectOwner(project, projectId, false);
				}
				addDefaultBinaryParser(project);
				newProject[0] = CCorePlugin.getDefault().getCoreModel().create(project);
			}
		}, null);

		return newProject[0];
	}

	/**
	 * Adds the default binary parser if no binary parser configured.
	 * 
	 * @param project
	 * @throws CoreException 
	 */
	public static boolean addDefaultBinaryParser(IProject project) throws CoreException {
		ICConfigExtensionReference[] binaryParsers= CCorePlugin.getDefault().getDefaultBinaryParserExtensions(project);
		if (binaryParsers == null || binaryParsers.length == 0) {
			ICProjectDescription desc= CCorePlugin.getDefault().getProjectDescription(project);
			if (desc == null) {
				return false;
			}
			
			desc.getDefaultSettingConfiguration().create(CCorePlugin.BINARY_PARSER_UNIQ_ID, CCorePlugin.DEFAULT_BINARY_PARSER_UNIQ_ID);
			CCorePlugin.getDefault().setProjectDescription(project, desc);
		}
		return true;
	}

	/**
	 * Creates a ICProject.
	 */
	public static ICProject createNewStyleCProject(final String projectName, final String indexerID) throws CoreException {
		return createNewStyleCProject(projectName, indexerID, false);
	}

	/**
	 * Creates a ICProject.
	 */
	public static ICProject createNewStyleCProject(final String projectName, String providerId, final String indexerID) throws CoreException {
		return createNewStyleCProject(projectName, providerId, indexerID, false);
	}

	/**
	 * Creates a ICProject.
	 */
	public static ICProject createNewStyleCProject(final String projectName, final String indexerID, boolean markCreating) throws CoreException {
		return createNewStyleCProject(projectName, null, indexerID, markCreating);
	}

	/**
	 * Creates a ICProject.
	 */
	public static ICProject createNewStyleCProject(final String projectName, String cfgProviderId, final String indexerID, final boolean markCreating) throws CoreException {
		final IWorkspace ws = ResourcesPlugin.getWorkspace();
		final ICProject newProject[] = new ICProject[1];
		if(cfgProviderId == null)
			cfgProviderId = TestCfgDataProvider.PROVIDER_ID;
		
		final String finalCfgProviderId = cfgProviderId;
		ws.run(new IWorkspaceRunnable() {
			@Override
			public void run(IProgressMonitor monitor) throws CoreException {
				IWorkspaceRoot root = ws.getRoot();
				IProject project = root.getProject(projectName);
				if (indexerID != null) {
					IndexerPreferences.set(project, IndexerPreferences.KEY_INDEXER_ID, indexerID);
				}
				if (!project.exists()) {
					project.create(null);
				} else {
					project.refreshLocal(IResource.DEPTH_INFINITE, null);
				}
				if (!project.isOpen()) {
					project.open(null);
				}
				if (!project.hasNature(CProjectNature.C_NATURE_ID)) {
					addNatureToProject(project, CProjectNature.C_NATURE_ID, null);
					ICConfigurationDescription prefCfg = CCorePlugin.getDefault().getPreferenceConfiguration(finalCfgProviderId);
					ICProjectDescriptionManager mngr = CCorePlugin.getDefault().getProjectDescriptionManager();
					ICProjectDescription projDes = mngr.createProjectDescription(project, false, markCreating);
					projDes.createConfiguration(CDataUtil.genId(null), CDataUtil.genId("test"), prefCfg);
					mngr.setProjectDescription(project, projDes);
//					CCorePlugin.getDefault().mapCProjectOwner(project, projectId, false);
				}
				addDefaultBinaryParser(project);
				newProject[0] = CCorePlugin.getDefault().getCoreModel().create(project);
			}
		}, null);

		return newProject[0];
	}

	
	private static String getMessage(IStatus status) {
		StringBuffer message = new StringBuffer("[");
		message.append(status.getMessage());
		if (status.isMultiStatus()) {
			IStatus children[] = status.getChildren();
			for( int i = 0; i < children.length; i++) {
				message.append(getMessage(children[i]));
			}
		}
		message.append("]");
		return message.toString();
	}

	public static ICProject createCCProject(final String projectName, final String binFolderName) throws CoreException {
		return createCCProject(projectName, binFolderName, null);
	}

	public static ICProject createCCProject(final String projectName, final String binFolderName, final String indexerID) 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 {
				ICProject cproject = createCProject(projectName, binFolderName, indexerID);
				if (!cproject.getProject().hasNature(CCProjectNature.CC_NATURE_ID)) {
					addNatureToProject(cproject.getProject(), CCProjectNature.CC_NATURE_ID, null);
				}
				newProject[0] = cproject;
			}
		}, null);
		return newProject[0];
	}

	/**
	 * Removes a ICProject.
	 */
	public static void delete(ICProject cproject) {
		try {
			cproject.getProject().delete(true, true, null);
		} catch (CoreException e) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e1) {
			} finally {
				try {
					System.gc();
					System.runFinalization();
					cproject.getProject().delete(true, true, null);
				} catch (CoreException e2) {
					Assert.fail(getMessage(e2.getStatus()));
				}
			}
		}
	}

	/**
	 * Adds a folder container to a ICProject.
	 */
	public static ICContainer addCContainer(ICProject cproject, String containerName) throws CoreException {
		IProject project = cproject.getProject();
		ICContainer container = null;
		if (containerName == null || containerName.isEmpty()) {
			ICContainer[] conts = cproject.getSourceRoots();
			if (conts.length > 0) {
				container = conts[0];
			}
		} else {
			IFolder folder = project.getFolder(containerName);
			if (!folder.exists()) {
				folder.create(false, true, null);
			}
			container = CoreModel.getDefault().create(folder);
		}
		return container;
	}

	/**
	 * Adds a folder container to a ICProject and imports all files contained in the given zip file.
	 */
	public static ICContainer addCContainerWithImport(ICProject cproject, String containerName, ZipFile zipFile)
			throws InvocationTargetException, CoreException {
		ICContainer root = addCContainer(cproject, containerName);
		importFilesFromZip(zipFile, root.getPath(), null);
		return root;
	}

	/**
	 * Removes a folder from a ICProject.
	 */
	public static void removeCContainer(ICProject cproject, String containerName) throws CoreException {
		IFolder folder = cproject.getProject().getFolder(containerName);
		folder.delete(true, null);
	}

	/**
	 * Adds a source root to a C/C++ project.
	 *
	 * @param cproject the project to add the source root to
	 * @param rootName the relative path of the source root
	 */
	public static void addSourceRoot(ICProject cproject, String rootName) throws CoreException {
		IProject project = cproject.getProject();
		IFolder rootFolder = project.getFolder(rootName);
		if (!rootFolder.exists()) {
			rootFolder.create(false, true, null);
		}
		IPath rootPath = rootFolder.getFullPath();

		if (!CCorePlugin.getDefault().isNewStyleProject(project)) {
			InternalCoreModelUtil.addSourceEntry(project, rootFolder, false, null);
		} else {
			IPathEntry[] entries = cproject.getRawPathEntries();
			ArrayList<IPathEntry> newEntries= new ArrayList<>(entries.length + 1);
			
			for (IPathEntry entry : entries) {
				if (entry.getEntryKind() == IPathEntry.CDT_SOURCE && rootPath.equals(entry.getPath())) {
					return; // The source root exists already.
				}
				newEntries.add(entry);
			}
			
			IPathEntry newEntry= CoreModel.newSourceEntry(rootPath);
			
			Set<IPathEntry> modified= new HashSet<>();				
			InternalCoreModelUtil.addExclusionPatterns(newEntry, newEntries, modified);
			newEntries.add(CoreModel.newSourceEntry(rootPath));
				
			cproject.setRawPathEntries(newEntries.toArray(new IPathEntry[newEntries.size()]), null);
		}
	}

	/**
	 * Attempts to find an archive with the given name in the workspace
	 */
	public static IArchive findArchive(ICProject testProject, String name) throws CModelException {
		// Since ArchiveContainer.getArchives does not wait until all the archives in the project
		// have been parsed before returning the list, we have to do a sync
		// ArchiveContainer.getChildren first to make sure we find all the archives.
		IArchiveContainer archCont = testProject.getArchiveContainer();
		IArchive[] myArchives = archCont.getArchives();
		for (IArchive archive : myArchives) {
			if (archive.getElementName().equals(name))
				return archive;
		}
		return null;
	}

	/**
	 * Attempts to find a binary with the given name in the workspace
	 */
	public static IBinary findBinary(ICProject testProject, String name) throws CModelException {
		IBinaryContainer binCont = testProject.getBinaryContainer();
		for (IBinary binary : binCont.getBinaries()) {
			if (binary.getElementName().equals(name))
				return binary;
		}
		return null;
	}

	/**
	 * Attempts to find an object with the given name in the workspace
	 */
	public static IBinary findObject(ICProject testProject, String name) throws CModelException {
		ICElement[] sourceRoots = testProject.getChildren();
		for (ICElement root : sourceRoots) {
			for (ICElement element : ((ISourceRoot) root).getChildren()) {
				if (element.getElementName().equals(name) && element instanceof IBinary) {
					return ((IBinary) element);
				}
			}
		}
		return null;
	}

	/**
	 * Attempts to find a TranslationUnit with the given name in the workspace.
	 */
	public static ITranslationUnit findTranslationUnit(ICProject testProject, String name)
			throws CModelException, InterruptedException {
		for (int j = 0; j < 20; j++) {
			ICElement[] sourceRoots = testProject.getChildren();
			for (ICElement root : sourceRoots) {
				for (ICElement element : ((ISourceRoot) root).getChildren()) {
					if (element.getElementName().equals(name) && element instanceof ITranslationUnit) {
						return ((ITranslationUnit) element);
					}
				}
			}
			Thread.sleep(100);
		}
		return null;
	}

	/**
	 * Attempts to find an element with the given name in the workspace.
	 */
	public static ICElement findElement(ICProject testProject, String name) throws CModelException {
		ICElement[] sourceRoots = testProject.getChildren();
		for (ICElement root : sourceRoots) {
			for (ICElement element : ((ISourceRoot) root).getChildren()) {
				if (element.getElementName().equals(name)) {
					return element;
				}
			}
		}
		return null;
	}

	public static void addNatureToProject(IProject proj, String natureId, IProgressMonitor monitor) throws CoreException {
		IProjectDescription description = proj.getDescription();
		String[] prevNatures = description.getNatureIds();
		String[] newNatures = new String[prevNatures.length + 1];
		System.arraycopy(prevNatures, 0, newNatures, 0, prevNatures.length);
		newNatures[prevNatures.length] = natureId;
		description.setNatureIds(newNatures);
		proj.setDescription(description, monitor);
	}

	private static void importFilesFromZip(ZipFile srcZipFile, IPath destPath, IProgressMonitor monitor)
			throws InvocationTargetException {
		ZipFileStructureProvider structureProvider = new ZipFileStructureProvider(srcZipFile);
		try {
			ImportOperation op = new ImportOperation(destPath, structureProvider.getRoot(), structureProvider,
					OVERWRITE_QUERY);
			op.run(monitor);
		} catch (InterruptedException e) {
			// Should not happen.
		}
	}

	public static void importSourcesFromPlugin(ICProject project, Bundle bundle, String sources) throws CoreException {
		try {
			String baseDir= FileLocator.toFileURL(FileLocator.find(bundle, new Path(sources), null)).getFile();
			ImportOperation importOp = new ImportOperation(project.getProject().getFullPath(),
					new File(baseDir), FileSystemStructureProvider.INSTANCE, OVERWRITE_QUERY);
			importOp.setCreateContainerStructure(false);
			importOp.run(new NullProgressMonitor());
		} catch (Exception e) {
			throw new CoreException(new Status(IStatus.ERROR, CTestPlugin.PLUGIN_ID, 0, "Import Interrupted", e));
		}
	}

	/**
	 * Returns the location of a newly created directory in a temporary area.
	 * Note that cleanup should be done with {@link ResourceHelper#cleanUp()}.
	 */
	public static File freshDir() throws IOException, CoreException {
		IPath folderPath = ResourceHelper.createTemporaryFolder();
		File folder = folderPath.toFile();
		Assert.assertTrue(folder.exists());
		Assert.assertTrue(folder.isDirectory());
		Assert.assertTrue(folder.canWrite());
		
		return folder;
	}
}