/*******************************************************************************
 * Copyright (c) 2014-2016 Red Hat Inc., 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:
 *     Mickael Istria (Red Hat Inc.) - initial API and implementation
 ******************************************************************************/
package org.eclipse.ui.internal.wizards.datatransfer;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.expressions.ElementHandler;
import org.eclipse.core.expressions.EvaluationContext;
import org.eclipse.core.expressions.EvaluationResult;
import org.eclipse.core.expressions.Expression;
import org.eclipse.core.expressions.ExpressionConverter;
import org.eclipse.core.expressions.IEvaluationContext;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.wizards.datatransfer.expressions.FileExpressionHandler;
import org.eclipse.ui.wizards.datatransfer.ProjectConfigurator;
import org.osgi.framework.Bundle;

/**
 * Manages and requests the active {@link ProjectConfigurator} extensions for an
 * {@link SmartImportJob} execution.
 *
 * @since 3.12
 *
 */
public class ProjectConfiguratorExtensionManager {

	private static final String EXTENSION_POINT_ID = IDEWorkbenchPlugin.IDE_WORKBENCH + ".projectConfigurators"; //$NON-NLS-1$

	private IConfigurationElement[] extensions;
	private ExpressionConverter expressionConverter;
	private Map<IConfigurationElement, ProjectConfigurator> configuratorsByExtension = new HashMap<>();

	/**
	 * Each instance of this class will have it's own internal registry, that will load (maximum) once each extension class,
	 * depending on whether the extension has been active for one case handled by this Manager.
	 */
	public ProjectConfiguratorExtensionManager() {
		this.extensions = Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_POINT_ID);
		// Force Eclipse configurator to be 1st
		int eclipseConfiguratorIndex = 0;
		while (eclipseConfiguratorIndex < this.extensions.length && !this.extensions[eclipseConfiguratorIndex]
				.getAttribute("class").equals(EclipseProjectConfigurator.class.getName())) { //$NON-NLS-1$
			eclipseConfiguratorIndex++;
		}
		if (eclipseConfiguratorIndex != 0 && eclipseConfiguratorIndex < this.extensions.length) {
			// swap
			IConfigurationElement tmp = this.extensions[eclipseConfiguratorIndex];
			this.extensions[eclipseConfiguratorIndex] = this.extensions[0];
			this.extensions[0] = tmp;
		}
		this.expressionConverter = new ExpressionConverter(new ElementHandler[] {
			ElementHandler.getDefault(),
			new FileExpressionHandler()
		});
	}

	/**
	 *
	 * @param container
	 * @return The active connectors for given container, order is important: top-priority are 1st
	 */
	private List<ProjectConfigurator> getAllActiveProjectConfiguratorsUntyped(Object container) {
		List<ProjectConfigurator> res = new ArrayList<>();
		for (IConfigurationElement extension : this.extensions) {
			boolean addIt = false;
			String bundleOrFragmentId = extension.getContributor().getName();
			Bundle contributingBundle = Platform.getBundle(bundleOrFragmentId);
			if (contributingBundle.getState() == Bundle.ACTIVE) {
				addIt = true;
			} else {
				// Else, only load class and activate bundle if necessary (checked by activeWhen)
				IConfigurationElement[] activeWhenElements = extension.getChildren("activeWhen"); //$NON-NLS-1$
				switch (activeWhenElements.length) {
				case 0:
					// by default, if no activeWhen, enable extension
					addIt = true;
					break;
				case 1:
					IConfigurationElement activeWhen = activeWhenElements[0];
					IConfigurationElement[] activeWhenChildren = activeWhen.getChildren();
					if (activeWhenChildren.length == 1) {
						try {
							Expression expression = this.expressionConverter.perform(activeWhen.getChildren()[0]);
							IEvaluationContext context = new EvaluationContext(null, container);
							addIt = expression.evaluate(context).equals(EvaluationResult.TRUE);
						} catch (CoreException ex) {
							IDEWorkbenchPlugin.log(
									"Could not evaluate expression for " + extension.getContributor().getName(), ex); //$NON-NLS-1$
						}
					} else {
						IDEWorkbenchPlugin
						.log("Could not evaluate xpression for " + extension.getContributor().getName() //$NON-NLS-1$
								+ ": there must be exactly one child of 'activeWhen'"); //$NON-NLS-1$
					}
					break;
				default:
					IDEWorkbenchPlugin.log("Only one 'activeWhen' is authorized on extension point " //$NON-NLS-1$
							+ EXTENSION_POINT_ID + ", for extension contributed by " + //$NON-NLS-1$
							extension.getContributor().getName());
					break;
				}
			}
			if (addIt) {
				ProjectConfigurator configurator = getConfigurator(extension);
				if (configurator instanceof EclipseProjectConfigurator) {
					// give priority
					res.add(0, configurator);
				} else {
					res.add(configurator);
				}
			}
		}
		return res;
	}

	/**
	 *
	 * @param container
	 * @return The active connectors for given container, order is important: top-priority are 1st
	 */
	public List<ProjectConfigurator> getAllActiveProjectConfigurators(IContainer container) {
		return this.getAllActiveProjectConfiguratorsUntyped(container);
	}

	/**
	 *
	 * @param folder
	 * @return The active connectors for given folder, order is important: top-priority are 1st
	 */
	public List<ProjectConfigurator> getAllActiveProjectConfigurators(File folder) {
		Assert.isTrue(folder.isDirectory(), folder.getAbsolutePath());
		return this.getAllActiveProjectConfiguratorsUntyped(folder);
	}

	private ProjectConfigurator getConfigurator(IConfigurationElement extension) {
		if (!this.configuratorsByExtension.containsKey(extension)) {
			try {
				ProjectConfigurator configurator = (ProjectConfigurator) extension.createExecutableExtension("class"); //$NON-NLS-1$
				this.configuratorsByExtension.put(extension, configurator);
				return configurator;
			} catch (CoreException ex) {
				IDEWorkbenchPlugin.log(ex.getMessage(), ex);
				return null;
			}
		}
		return this.configuratorsByExtension.get(extension);
	}

	/**
	 *
	 * @return the label of all known {@link ProjectConfigurator}s
	 */
	public static List<String> getAllExtensionLabels() {
		IConfigurationElement[] extensions = Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_POINT_ID);
		List<String> res = new ArrayList<>(extensions.length);
		for (IConfigurationElement extension : extensions) {
			res.add(extension.getAttribute("label")); //$NON-NLS-1$
		}
		return res;
	}

	/**
	 *
	 * @param configurator
	 * @return the internationalized label for the provided configurator
	 */
	public static String getLabel(ProjectConfigurator configurator) {
		IConfigurationElement[] extensions = Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_POINT_ID);
		for (IConfigurationElement extension : extensions) {
			if (configurator.getClass().getName().equals(extension.getAttribute("class"))) { //$NON-NLS-1$
				return extension.getAttribute("label"); //$NON-NLS-1$
			}
		}
		return "Missing label for " + configurator.getClass().getName(); //$NON-NLS-1$
	}

}
