/*******************************************************************************
 * Copyright (c) 2000, 2011 IBM 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.text.correction;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

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.ExpressionTagNames;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaModelMarker;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;

import org.eclipse.jdt.internal.corext.util.JavaModelUtil;

import org.eclipse.jdt.ui.JavaUI;

import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.dialogs.StatusInfo;

public final class ContributedProcessorDescriptor {

	private final IConfigurationElement fConfigurationElement;
	private Object fProcessorInstance;
	private Boolean fStatus;
	private String fRequiredSourceLevel;
	private final Set<String> fHandledMarkerTypes;

	private static final String ID= "id"; //$NON-NLS-1$
	private static final String CLASS= "class"; //$NON-NLS-1$

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

	private static final String HANDLED_MARKER_TYPES= "handledMarkerTypes"; //$NON-NLS-1$
	private static final String MARKER_TYPE= "markerType"; //$NON-NLS-1$

	public ContributedProcessorDescriptor(IConfigurationElement element, boolean testMarkerTypes) {
		fConfigurationElement= element;
		fProcessorInstance= null;
		fStatus= null; // undefined
		if (fConfigurationElement.getChildren(ExpressionTagNames.ENABLEMENT).length == 0) {
			fStatus= Boolean.TRUE;
		}
		fRequiredSourceLevel= element.getAttribute(REQUIRED_SOURCE_LEVEL);
		fHandledMarkerTypes= testMarkerTypes ? getHandledMarkerTypes(element) : null;
	}

	private Set<String> getHandledMarkerTypes(IConfigurationElement element) {
		HashSet<String> map= new HashSet<>(7);
		for (IConfigurationElement c : element.getChildren(HANDLED_MARKER_TYPES)) {
			for (IConfigurationElement type : c.getChildren(MARKER_TYPE)) {
				String attribute= type.getAttribute(ID);
				if (attribute != null) {
					map.add(attribute);
				}
			}
		}
		if (map.isEmpty()) {
			map.add(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
			map.add(IJavaModelMarker.BUILDPATH_PROBLEM_MARKER);
			map.add(IJavaModelMarker.TASK_MARKER);
		}
		return map;
	}

	public IStatus checkSyntax() {
		IConfigurationElement[] children= fConfigurationElement.getChildren(ExpressionTagNames.ENABLEMENT);
		if (children.length > 1) {
			String id= fConfigurationElement.getAttribute(ID);
			return new StatusInfo(IStatus.ERROR, "Only one < enablement > element allowed. Disabling " + id); //$NON-NLS-1$
		}
		return new StatusInfo(IStatus.OK, "Syntactically correct quick assist/fix processor"); //$NON-NLS-1$
	}

	private boolean matches(ICompilationUnit cunit) {
		if (fRequiredSourceLevel != null) {
			String current= cunit.getJavaProject().getOption(JavaCore.COMPILER_SOURCE, true);
			if (JavaModelUtil.isVersionLessThan(current, fRequiredSourceLevel)) {
				return false;
			}
		}

		if (fStatus != null) {
			return fStatus;
		}

		IConfigurationElement[] children= fConfigurationElement.getChildren(ExpressionTagNames.ENABLEMENT);
		if (children.length == 1) {
			try {
				ExpressionConverter parser= ExpressionConverter.getDefault();
				Expression expression= parser.perform(children[0]);
				EvaluationContext evalContext= new EvaluationContext(null, cunit);
				evalContext.addVariable("compilationUnit", cunit); //$NON-NLS-1$
				IJavaProject javaProject= cunit.getJavaProject();
				String[] natures= javaProject.getProject().getDescription().getNatureIds();
				evalContext.addVariable("projectNatures", Arrays.asList(natures)); //$NON-NLS-1$
				evalContext.addVariable("sourceLevel", javaProject.getOption(JavaCore.COMPILER_SOURCE, true)); //$NON-NLS-1$
				return expression.evaluate(evalContext) == EvaluationResult.TRUE;
			} catch (CoreException e) {
				JavaPlugin.log(e);
			}
			return false;
		}
		fStatus= Boolean.FALSE;
		return false;
	}

	public Object getProcessor(ICompilationUnit cunit, Class<?> expectedType) {
		if (matches(cunit)) {
			if (fProcessorInstance == null) {
				try {
					Object extension= fConfigurationElement.createExecutableExtension(CLASS);
					if (expectedType.isInstance(extension)) {
						fProcessorInstance= extension;
					} else {
						String message= "Invalid extension to " + fConfigurationElement.getName() //$NON-NLS-1$
						+ ". Must extends '" + expectedType.getName() + "'." + fConfigurationElement.getContributor().getName(); //$NON-NLS-1$ //$NON-NLS-2$
						JavaPlugin.log(new Status(IStatus.ERROR, JavaUI.ID_PLUGIN, message));
						fStatus= Boolean.FALSE;
						return null;
					}
				} catch (CoreException e) {
					JavaPlugin.log(e);
					fStatus= Boolean.FALSE;
					return null;
				}
			}
			return fProcessorInstance;
		}
		return null;
	}

	public boolean canHandleMarkerType(String markerType) {
		return fHandledMarkerTypes == null || fHandledMarkerTypes.contains(markerType);
	}

}
