/*******************************************************************************
 * Copyright (c) 2006, 2016 Intel Corporation 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:
 * Intel Corporation - Initial API and implementation
 * IBM Corporation
 *******************************************************************************/
package org.eclipse.cdt.managedbuilder.makegen.gnu;

import java.util.Vector;

import org.eclipse.cdt.managedbuilder.core.BuildException;
import org.eclipse.cdt.managedbuilder.core.IBuildObject;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.IFolderInfo;
import org.eclipse.cdt.managedbuilder.core.IManagedCommandLineGenerator;
import org.eclipse.cdt.managedbuilder.core.IManagedCommandLineInfo;
import org.eclipse.cdt.managedbuilder.core.IResourceConfiguration;
import org.eclipse.cdt.managedbuilder.core.IResourceInfo;
import org.eclipse.cdt.managedbuilder.core.ITool;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.cdt.managedbuilder.internal.macros.BuildMacroProvider;
import org.eclipse.cdt.managedbuilder.internal.macros.FileContextData;
import org.eclipse.cdt.managedbuilder.macros.BuildMacroException;
import org.eclipse.cdt.managedbuilder.macros.IBuildMacroProvider;
import org.eclipse.cdt.managedbuilder.makegen.IManagedBuilderMakefileGenerator;
import org.eclipse.cdt.managedbuilder.makegen.IManagedDependencyPreBuild;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;

/**
 * This dependency calculator uses the GCC -MM -MF -MP -MT options in order to
 * generate .d files as separate step prior to the source compilations.
 *
 * This dependency calculator uses the class DefaultGCCDependencyCalculatorPreBuildCommands
 * which implements the per-source command information
 *
 * This class is used with DefaultGCCDependencyCalculatorPreBuild.
 *
 * @since 3.1
 * @noextend This class is not intended to be subclassed by clients.
 * @noinstantiate This class is not intended to be instantiated by clients.
 */
public class DefaultGCCDependencyCalculatorPreBuildCommands implements IManagedDependencyPreBuild {

	private static final String EMPTY_STRING = ""; //$NON-NLS-1$

	//  Member variables set by the constructor
	IPath source;
	IResource resource;
	IBuildObject buildContext;
	ITool tool;
	IPath topBuildDirectory;

	//  Other Member variables
	IProject project;
	IPath sourceLocation;
	IPath outputLocation;
	boolean needExplicitRuleForFile;
	Boolean genericCommands = null;

	/**
	 * Constructor
	 *
	 * @param source  The source file for which dependencies should be calculated
	 *    The IPath can be either relative to the project directory, or absolute in the file system.
	 * @param buildContext  The IConfiguration or IResourceConfiguration that
	 *   contains the context in which the source file will be built
	 * @param tool  The tool associated with the source file
	 * @param topBuildDirectory  The top build directory of the configuration.  This is
	 *   the working directory for the tool.  This IPath is relative to the project directory.
	 */
	public DefaultGCCDependencyCalculatorPreBuildCommands(IPath source, IResource resource, IBuildObject buildContext,
			ITool tool, IPath topBuildDirectory) {
		this.source = source;
		this.resource = resource;
		this.buildContext = buildContext;
		this.tool = tool;
		this.topBuildDirectory = topBuildDirectory;

		//  Compute the project
		if (buildContext instanceof IConfiguration) {
			IConfiguration config = (IConfiguration) buildContext;
			project = (IProject) config.getOwner();
		} else if (buildContext instanceof IResourceInfo) {
			IResourceInfo resInfo = (IResourceInfo) buildContext;
			project = (IProject) resInfo.getParent().getOwner();
		}

		sourceLocation = (source.isAbsolute() ? source : project.getLocation().append(source));
		outputLocation = project.getLocation().append(topBuildDirectory).append(getDependencyFiles()[0]);

		// A separate rule is needed for the resource in the case where explicit file-specific macros
		// are referenced, or if the resource contains special characters in its path (e.g., whitespace)

		/* fix for 137674
		 *
		 * We only need an explicit rule if one of the following is true:
		 * - The resource is linked, and its full path to its real location contains special characters
		 * - The resource is not linked, but its project relative path contains special characters
		*/
		boolean resourceNameRequiresExplicitRule = true;

		if (resource != null) {
			resourceNameRequiresExplicitRule = (resource.isLinked()
					&& GnuMakefileGenerator.containsSpecialCharacters(sourceLocation.toString()))
					|| (!resource.isLinked() && GnuMakefileGenerator
							.containsSpecialCharacters(resource.getProjectRelativePath().toString()));
		}

		needExplicitRuleForFile = resourceNameRequiresExplicitRule
				|| BuildMacroProvider.getReferencedExplitFileMacros(tool).length > 0
				|| BuildMacroProvider.getReferencedExplitFileMacros(tool.getToolCommand(),
						IBuildMacroProvider.CONTEXT_FILE,
						new FileContextData(sourceLocation, outputLocation, null, tool)).length > 0;

		if (needExplicitRuleForFile)
			genericCommands = false;
	}

	/**
	 * Constructor. This constructor calls
	 * DefaultGCCDependencyCalculatorPreBuildCommands(IPath source, IResource resource,
	 * IBuildObject buildContext, ITool tool, IPath topBuildDirectory) with a
	 * null resource. The net result of this is that dependency rules will
	 * always be explicit and will never use pattern rules, as it is impossible
	 * for the calculator to know whether the resource is linked or not.
	 *
	 * @param source
	 *            The source file for which dependencies should be calculated
	 *            The IPath can be either relative to the project directory, or
	 *            absolute in the file system.
	 * @param buildContext
	 *            The IConfiguration or IResourceConfiguration that contains the
	 *            context in which the source file will be built
	 * @param tool
	 *            The tool associated with the source file
	 * @param topBuildDirectory
	 *            The top build directory of the configuration. This is the
	 *            working directory for the tool. This IPath is relative to the
	 *            project directory.
	 *
	 * @see #DefaultGCCDependencyCalculatorPreBuildCommands(IPath source, IResource resource, IBuildObject buildContext, ITool tool, IPath topBuildDirectory)
	 */
	public DefaultGCCDependencyCalculatorPreBuildCommands(IPath source, IBuildObject buildContext, ITool tool,
			IPath topBuildDirectory) {
		this(source, (IResource) null, buildContext, tool, topBuildDirectory);
	}

	@Override
	public boolean areCommandsGeneric() {
		if (genericCommands != null)
			return genericCommands.booleanValue();
		//  If the context is a Configuration, yes
		if (buildContext instanceof IConfiguration || buildContext instanceof IFolderInfo) {
			genericCommands = true;
			return true;
		}
		//  If the context is a Resource Configuration, determine if it overrides any
		//  of its parent configuration's options that would affect dependency file
		//  generation.
		// TODO
		genericCommands = false;
		return false;
	}

	@Override
	public String getBuildStepName() {
		return "GCC_DEPENDS"; //$NON-NLS-1$
	}

	@Override
	public String[] getDependencyCommands() {

		String[] commands = new String[1];
		String depCmd = EMPTY_STRING;
		IBuildMacroProvider provider = ManagedBuildManager.getBuildMacroProvider();

		// Get and resolve the command
		String cmd = tool.getToolCommand();
		try {
			String resolvedCommand = null;
			if (!needExplicitRuleForFile) {
				resolvedCommand = provider.resolveValueToMakefileFormat(cmd, EMPTY_STRING,
						IManagedBuilderMakefileGenerator.WHITESPACE, IBuildMacroProvider.CONTEXT_FILE,
						new FileContextData(sourceLocation, outputLocation, null, tool));
			} else {
				// if we need an explicit rule then don't use any builder
				// variables, resolve everything
				// to explicit strings
				resolvedCommand = provider.resolveValue(cmd, EMPTY_STRING, IManagedBuilderMakefileGenerator.WHITESPACE,
						IBuildMacroProvider.CONTEXT_FILE,
						new FileContextData(sourceLocation, outputLocation, null, tool));
			}

			if ((resolvedCommand = resolvedCommand.trim()).length() > 0)
				cmd = resolvedCommand;

		} catch (BuildMacroException e) {
		}

		IManagedCommandLineInfo cmdLInfo = null;

		//  Set up the command line options that will generate the dependency file
		Vector<String> options = new Vector<>();
		// -w
		options.add("-w"); //$NON-NLS-1$
		// -MM
		options.add("-MM"); //$NON-NLS-1$
		// -MP
		options.add("-MP"); //$NON-NLS-1$

		String optTxt;

		if (buildContext instanceof IResourceConfiguration || needExplicitRuleForFile) {
			IPath outPath = getDependencyFiles()[0];
			// -MT"dependecy-file-name"
			optTxt = "-MT\""; //$NON-NLS-1$
			optTxt += GnuMakefileGenerator.escapeWhitespaces(outPath.toString()) + "\""; //$NON-NLS-1$
			options.add(optTxt);
			// -MT"object-file-filename"
			optTxt = "-MT\""; //$NON-NLS-1$
			optTxt += GnuMakefileGenerator.escapeWhitespaces((outPath.removeFileExtension()).toString());
			String outExt = tool.getOutputExtension(source.getFileExtension());
			if (outExt != null)
				optTxt += "." + outExt; //$NON-NLS-1$
			optTxt += "\""; //$NON-NLS-1$
			options.add(optTxt);
		} else {
			// -MT"$@"
			options.add("-MT\"$@\""); //$NON-NLS-1$
			// -MT'$(@:%.d=%.o)'
			optTxt = "-MT\"$(@:%.d=%.o)\""; //$NON-NLS-1$
			//optTxt = "-MT\"${OutputDirRelPath}${OutputFileBaseName}";
			//if (outExt != null) optTxt += "." + outExt;
			//optTxt += "\""; 						//$NON-NLS-1$
			options.add(optTxt);
		}

		// Save the -I, -D, -U options and discard the rest
		try {
			String[] allFlags = tool.getToolCommandFlags(sourceLocation, outputLocation);
			for (String flag : allFlags) {
				if (flag.startsWith("-I") || //$NON-NLS-1$
						flag.startsWith("-D") || //$NON-NLS-1$
						flag.startsWith("-U")) { //$NON-NLS-1$
					options.add(flag);
				}
			}
		} catch (BuildException ex) {
		}

		// Call the command line generator
		IManagedCommandLineGenerator cmdLGen = tool.getCommandLineGenerator();
		String[] flags = options.toArray(new String[options.size()]);
		String[] inputs = new String[1];
		inputs[0] = IManagedBuilderMakefileGenerator.IN_MACRO;
		cmdLInfo = cmdLGen.generateCommandLineInfo(tool, cmd, flags, "-MF", EMPTY_STRING, //$NON-NLS-1$
				IManagedBuilderMakefileGenerator.OUT_MACRO, inputs, tool.getCommandLinePattern());

		// The command to build
		if (cmdLInfo != null) {
			depCmd = cmdLInfo.getCommandLine();

			// resolve any remaining macros in the command after it has been
			// generated
			try {
				String resolvedCommand;
				if (!needExplicitRuleForFile) {
					resolvedCommand = provider.resolveValueToMakefileFormat(depCmd, EMPTY_STRING,
							IManagedBuilderMakefileGenerator.WHITESPACE, IBuildMacroProvider.CONTEXT_FILE,
							new FileContextData(sourceLocation, outputLocation, null, tool));
				} else {
					// if we need an explicit rule then don't use any builder
					// variables, resolve everything to explicit strings
					resolvedCommand = provider.resolveValue(depCmd, EMPTY_STRING,
							IManagedBuilderMakefileGenerator.WHITESPACE, IBuildMacroProvider.CONTEXT_FILE,
							new FileContextData(sourceLocation, outputLocation, null, tool));
				}

				if ((resolvedCommand = resolvedCommand.trim()).length() > 0)
					depCmd = resolvedCommand;

			} catch (BuildMacroException e) {
			}
		}

		commands[0] = depCmd;
		return commands;
	}

	@Override
	public IPath[] getDependencyFiles() {
		//  The source file is project relative and the dependency file is top build directory relative
		//  Remove the source extension and add the dependency extension
		IPath depFilePath = source.removeFileExtension().addFileExtension(IManagedBuilderMakefileGenerator.DEP_EXT);
		//  Remember that the source folder hierarchy and the build output folder hierarchy are the same
		//  but if this is a generated resource, then it may already be under the top build directory
		if (!depFilePath.isAbsolute()) {
			if (topBuildDirectory.isPrefixOf(depFilePath)) {
				depFilePath = depFilePath.removeFirstSegments(1);
			}
		}
		IPath[] paths = new IPath[1];
		paths[0] = depFilePath;
		return paths;
	}

	@Override
	public IBuildObject getBuildContext() {
		return buildContext;
	}

	@Override
	public IPath getSource() {
		return source;
	}

	@Override
	public ITool getTool() {
		return tool;
	}

	@Override
	public IPath getTopBuildDirectory() {
		return topBuildDirectory;
	}

}
