/*******************************************************************************
 * Copyright (c) 2005, 2007 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)
 *******************************************************************************/
package org.eclipse.cdt.core.testplugin;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.zip.ZipFile;

import junit.framework.Assert;

import org.eclipse.cdt.core.CCProjectNature;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.CProjectNature;
import org.eclipse.cdt.core.ICExtensionReference;
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.ISourceRoot;
import org.eclipse.cdt.core.model.ITranslationUnit;
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.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.osgi.framework.Bundle;
/**
 * Helper methods to set up a ICProject.
 */
public class CProjectHelper {

	private final static IOverwriteQuery OVERWRITE_QUERY= new IOverwriteQuery() {
		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() {

			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_ALL_FILES, "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];
	}

	/**
	 * Add the default binary parser if no binary parser configured.
	 * 
	 * @param project
	 * @throws CoreException 
	 */
	public static boolean addDefaultBinaryParser(IProject project) throws CoreException {
		ICExtensionReference[] binaryParsers= CCorePlugin.getDefault().getBinaryParserExtensions(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 createNewStileCProject(final String projectName, final String indexerID) throws CoreException {
		return createNewStileCProject(projectName, indexerID, false);
	}

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

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

	/**
	 * Creates a ICProject.
	 */
	public static ICProject createNewStileCProject(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() {

			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_ALL_FILES, "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)) {
					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() {

			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.length() == 0) {
			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);
	}

	/**
	 * Attempts to find an archive with the given name in the workspace
	 */
	public static IArchive findArchive(ICProject testProject, String name) throws CModelException {
		int x;
		IArchive[] myArchives;
		IArchiveContainer archCont;
		/***************************************************************************************************************************
		 * 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.
		 */
		archCont = testProject.getArchiveContainer();
		myArchives = archCont.getArchives();
		if (myArchives.length < 1)
			return (null);
		for (x = 0; x < myArchives.length; x++) {
			if (myArchives[x].getElementName().equals(name))
				return (myArchives[x]);
		}
		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;
		int x;
		IBinary[] myBinaries;
		binCont = testProject.getBinaryContainer();
		myBinaries = binCont.getBinaries();
		if (myBinaries.length < 1)
			return (null);
		for (x = 0; x < myBinaries.length; x++) {
			if (myBinaries[x].getElementName().equals(name))
				return (myBinaries[x]);
		}
		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 (int i = 0; i < sourceRoots.length; i++) {
			ISourceRoot root = (ISourceRoot) sourceRoots[i];
			ICElement[] myElements = root.getChildren();
			for (int x = 0; x < myElements.length; x++) {
				if (myElements[x].getElementName().equals(name)) {
					if (myElements[x] instanceof IBinary) {
						return ((IBinary) myElements[x]);
					}
				}
			}
		}
		return null;
	}

	/**
	 * Attempts to find a TranslationUnit with the given name in the workspace
	 * @throws InterruptedException 
	 */
	public static ITranslationUnit findTranslationUnit(ICProject testProject, String name) throws CModelException, InterruptedException {
		for (int j=0; j<20; j++) {
			ICElement[] sourceRoots = testProject.getChildren();
			for (int i = 0; i < sourceRoots.length; i++) {
				ISourceRoot root = (ISourceRoot) sourceRoots[i];
				ICElement[] myElements = root.getChildren();
				for (int x = 0; x < myElements.length; x++) {
					if (myElements[x].getElementName().equals(name)) {
						if (myElements[x] instanceof ITranslationUnit) {
							return ((ITranslationUnit) myElements[x]);
						}
					}
				}
			}
			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 (int i = 0; i < sourceRoots.length; i++) {
			ISourceRoot root = (ISourceRoot) sourceRoots[i];
			ICElement[] myElements = root.getChildren();
			for (int x = 0; x < myElements.length; x++) {
				if (myElements[x].getElementName().equals(name)) {
					return myElements[x];
				}
			}
		}
		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 the systems temp. area.
	 * @return the location of a newly created directory in the systems temp. area
	 * @throws IOException
	 */
	public static File freshDir() throws IOException {
		File tempDir = new File(System.getProperty("java.io.tmpdir")).getCanonicalFile();		
		for(int i=0; i<Integer.MAX_VALUE; i++) {
			File candidate = new File(tempDir, "__testData/"+i);
			if(!candidate.exists()) {
				candidate.mkdirs();
				return candidate;
			}
		}
		return null;
	}
}