/*******************************************************************************
 * Copyright (c) 2007, 2011 Symbian Software Limited 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:
 * Bala Torati (Symbian) - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.managedbuilder.templateengine.processes;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.cdt.core.templateengine.TemplateCore;
import org.eclipse.cdt.core.templateengine.process.ProcessArgument;
import org.eclipse.cdt.core.templateengine.process.ProcessFailureException;
import org.eclipse.cdt.core.templateengine.process.ProcessRunner;
import org.eclipse.cdt.core.templateengine.process.processes.Messages;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo;
import org.eclipse.cdt.managedbuilder.core.IManagedProject;
import org.eclipse.cdt.managedbuilder.core.IResourceConfiguration;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;

/**
 * Exclude Resources from a CDT project. This takes three arguments
 * <ul>
 *   <li> <b>projectName</b> the name of the project to exclude resources for. Must be a CDT Managed project.
 *   <li> <b>configIdPattern</b> a regular expression in java.util.regex.Pattern syntax for matching against the project configuration ids. The resources that
 *   match any of the regular expressions given in the <i>filePatterns</i> argument will be excluded for all matching project configurations.
 *   <li> <b>filePatterns</b> an array of regular expressions in java.util.regex.Pattern syntax for matching against project resources. The paths that
 *   will be matched against are workspace relative (include the project folder) and use forward slash as the file separator. That this argument is an
 *   array is purely to allow logically separate patterns to be given separately )rather than as one big string). If any of the regular expressions matches
 *   then the resource in question will be excluded for the matching configuration(s).
 *   <li> <b>invertConfigMatching</b> if this is set to "true" then the set of configurations for which resources matching any of the specified file patterns will
 *   be inverted. This enables you to specify which resources the files should not be excluded for without having to know what other configurations may exist.
 * </ul>
 *
 * @noextend This class is not intended to be subclassed by clients.
 * @noinstantiate This class is not intended to be instantiated by clients.
 */
public class ExcludeResources extends ProcessRunner {

	@Override
	public void process(TemplateCore template, final ProcessArgument[] args, String processId, IProgressMonitor monitor)
			throws ProcessFailureException {
		String projectName = args[0].getSimpleValue();
		String configIdPattern = args[1].getSimpleValue();
		final String[] filePatterns = args[2].getSimpleArrayValue();
		String invertConfigMatching = args[3].getSimpleValue();

		boolean invert = Boolean.valueOf(invertConfigMatching).booleanValue();

		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);

		IManagedBuildInfo info = ManagedBuildManager.getBuildInfo(project);
		if (info == null) {
			throw new ProcessFailureException(Messages.getString("ExcludeResources.0")); //$NON-NLS-1$
		}
		IManagedProject managedProject = info.getManagedProject();

		/*
		 * Determine which configurations to exclude for
		 */
		IConfiguration[] allConfigs = managedProject.getConfigurations();
		List<IConfiguration> matchingConfigs = new ArrayList<>();
		for (int i = 0; i < allConfigs.length; i++) {
			IConfiguration config = allConfigs[i];
			if (config.getId().matches(configIdPattern)) {
				matchingConfigs.add(config);
			}
		}

		if (invert) {
			List<IConfiguration> invertedConfigs = new ArrayList<>(Arrays.asList(allConfigs));
			invertedConfigs.removeAll(matchingConfigs);
			matchingConfigs = invertedConfigs;
		}

		/*
		 * Visit project resources and exclude them if they match any pattern
		 */
		final List<IConfiguration> configsToProcess = matchingConfigs;
		IResourceProxyVisitor visitor = new IResourceProxyVisitor() {
			@Override
			public boolean visit(IResourceProxy proxy) throws CoreException {
				IPath lPath = proxy.requestFullPath();

				if (proxy.getType() == IResource.FILE) { /* CDT does not support directory resource configurations */
					boolean isDerived = false;
					for (IResource res = proxy.requestResource(); res != null; res = res.getParent()) {
						isDerived |= res.isDerived();
					}

					if (!isDerived) {
						for (IConfiguration config : configsToProcess) {
							IResourceConfiguration resourceConfig = config.getResourceConfiguration(lPath.toString());
							// Only add a resource configuration if the file pattern matches something that
							//is actually supposed to be excluded. Adding a resrouce configuration for all files
							// regardless of wheter they need it or not mess up the makefile generation.
							for (String filePattern : filePatterns) {
								if (lPath.toString().matches(filePattern)) {
									if (resourceConfig == null) {
										IFile file = (IFile) proxy.requestResource();
										resourceConfig = config.createResourceConfiguration(file);
									}

									if (resourceConfig != null) {
										resourceConfig.setExclude(true);
									}

									break;

								}
							}

						}
					}
				}
				return true;
			}
		};

		try {
			project.accept(visitor, IResource.DEPTH_INFINITE);
			if (info.isDirty()) {
				ManagedBuildManager.saveBuildInfo(project, true);
			}
		} catch (CoreException ce) {
			throw new ProcessFailureException(ce);
		}
	}
}
