/*******************************************************************************
 * Copyright (c) 2004, 2011 QNX Software Systems 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:
 * QNX Software Systems - Initial API and implementation
 * Alex Collins (Broadcom Corp.) - choose build config automatically
 * James Blackburn (Broadcom Corp.)
 *******************************************************************************/
package org.eclipse.cdt.launch;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.IBinaryParser;
import org.eclipse.cdt.core.IBinaryParser.IBinaryObject;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.CoreModelUtil;
import org.eclipse.cdt.core.settings.model.ICConfigExtensionReference;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICOutputEntry;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.extension.CBuildData;
import org.eclipse.cdt.core.settings.model.extension.CConfigurationData;
import org.eclipse.cdt.core.settings.model.util.CDataUtil;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.internal.core.resources.ResourceLookup;
import org.eclipse.cdt.utils.CommandLineUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.activities.IActivityManager;
import org.eclipse.ui.activities.IWorkbenchActivitySupport;

/**
 * Utility methods.
 */
public class LaunchUtils {

	/**
	 * A specialised WrapperProgressMonitor which doesn't let cancellation of the
	 * child build task cause cancellation of our top-level launch task.
	 */
	static class BuildProgressMonitor extends SubProgressMonitor {
		private boolean cancelled;

		public BuildProgressMonitor(IProgressMonitor monitor, int ticks, int style) {
			super(monitor, ticks, style);
		}
		
		public BuildProgressMonitor(IProgressMonitor monitor, int ticks) {
			this(monitor, ticks, 0);
		}

		@Override
		public void setCanceled(boolean b) {
			// Only cancel this operation, not the top-level launch.
			cancelled = b;
		}

		@Override
		public boolean isCanceled() {
			// Canceled if this monitor has been explicitly canceled
			//  || parent has been canceled.
			return cancelled || super.isCanceled();
		}
	}

	/**
	 * For given launch configuration returns the program arguments as 
	 * an array of individual arguments.
	 */
	public static String[] getProgramArgumentsArray(ILaunchConfiguration config) throws CoreException {
		return parseArguments(getProgramArguments(config));
	}

	/**
	 * Returns the program arguments as a String.
	 */
	public static String getProgramArguments(ILaunchConfiguration config) throws CoreException {
		String args = config.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, (String) null);
		if (args != null) {
			args = getStringVariableManager().performStringSubstitution(args);
		}
		return args;
	}
	
	/**
	 * @since 6.0
	 */
	public static IBinaryObject getBinary(IProject project, IPath exePath) throws CoreException {
		ICConfigExtensionReference[] parserRef = CCorePlugin.getDefault().getDefaultBinaryParserExtensions(project);
		for (int i = 0; i < parserRef.length; i++) {
			try {
				IBinaryParser parser = CoreModelUtil.getBinaryParser(parserRef[i]);
				IBinaryObject exe = (IBinaryObject)parser.getBinary(exePath);				
				if (exe != null) {
					return exe;
				}
			} catch (ClassCastException e) {
			} catch (IOException e) {
			}
		}
		IBinaryParser parser = CCorePlugin.getDefault().getDefaultBinaryParser();
		try {
			IBinaryObject exe = (IBinaryObject)parser.getBinary(exePath);
			return exe;
		} catch (ClassCastException e) {
		} catch (IOException e) {
		}
		return null;
	}
	
	/**
	 * @since 6.0
	 */
	public static IBinaryObject getBinary(String programName, String projectName)
		throws CoreException
	{
		if (programName != null ) {
			IPath exePath = new Path(programName);
			IProject project = null;
			if (projectName != null && !projectName.equals("")) { //$NON-NLS-1$
				project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
				if (project == null || project.getLocation() == null)
				{
					return null;
				}
				if (!exePath.isAbsolute()) {
					exePath = project.getLocation().append(exePath);
				}
			}		
			return getBinary(project, exePath);
		}
		return null;
	}

	
	/**
	 * Convenience method.
	 */
	public static IStringVariableManager getStringVariableManager() {
		return VariablesPlugin.getDefault().getStringVariableManager();
	}

	private static String[] parseArguments(String args) {
		return CommandLineUtil.argumentsToArray(args);
	}
	
	/**
	 * @since 6.1
	 */
	@SuppressWarnings("unchecked")
	public static void enableActivity(final String activityID, final boolean enableit)
	{
		if (PlatformUI.isWorkbenchRunning())
		{
			PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
				@Override
				public void run() {
					IWorkbenchActivitySupport workbenchActivitySupport = PlatformUI.getWorkbench().getActivitySupport();
					IActivityManager activityManager = workbenchActivitySupport.getActivityManager();
					Set<String> enabledActivityIds = new HashSet<String>(activityManager.getEnabledActivityIds());
					boolean changed = false;
					if (enableit)
						changed = enabledActivityIds.add(activityID);		
					else
						changed = enabledActivityIds.remove(activityID);		
					if (changed)
						workbenchActivitySupport.setEnabledActivityIds(enabledActivityIds);
				}
			});
		}
	}

	/**
	 * Get the build configuration that most likely builds the given program path.
	 * The build configuration is chosen as the one that outputs to a directory that contains
	 * the given program.
	 * 
	 * @param projectDesc The description for the project in which to search for the configuration.
	 * @param programPath The path to the program to search the build configurations for
	 * @return The build configuration that builds programName; or null if none or more than one were found.
	 * @since 7.0
	 */
	public static ICConfigurationDescription getBuildConfigByProgramPath(IProject project, String programPath) {
		if (project == null || programPath == null)
			return null;
		ICProjectDescription projectDesc = CoreModel.getDefault().getProjectDescription(project, false);
		if (projectDesc == null)
			return null;

		// If the program path is relative, it must be relative to the projects root
		IPath path = new Path(programPath);
		if (!path.isAbsolute()) {
			IPath projLocation = project.getLocation();
			if (projLocation == null)
				return null;
			path = projLocation.append(path);
		}

		// Get all possible files that the program path could refer to
		IFile[] files = ResourceLookup.findFilesForLocation(path);

		// Find the build config whose output directory matches one of the possible files
		ICConfigurationDescription buildConfig = null;
		findCfg: for (ICConfigurationDescription cfgDes : projectDesc.getConfigurations()) {
			CConfigurationData cfgData = cfgDes.getConfigurationData();
			if (cfgData == null)
				continue;
			CBuildData buildData = cfgData.getBuildData();
			if (buildData == null)
				continue;
			for (ICOutputEntry dir : buildData.getOutputDirectories()) {
				ICOutputEntry absoluteDir = CDataUtil.makeAbsolute(project, dir);
				if (absoluteDir == null)
					continue;
				IPath dirLocation = absoluteDir.getLocation();
				if (dirLocation == null)
					continue;
				for (IFile file : files) {
					if (dirLocation.isPrefixOf(file.getLocation())) {
						if (buildConfig != null && buildConfig != cfgDes) {
							// Matched more than one, so use the active configuration
							buildConfig = null;
							break findCfg;
						}
						buildConfig = cfgDes;
					}
				}
			}
		}
		return buildConfig;
	}
}
