/*******************************************************************************
 * Copyright (c) 2007, 2015 Ecliptical Software 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:
 *     Ecliptical Software Inc. - initial API and implementation
 *     IBM Canada - review initial contribution and commit
 *******************************************************************************/
package org.eclipse.jdt.internal.debug.ui.search;

import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.ILaunchGroup;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.ui.search.ElementQuerySpecification;
import org.eclipse.jdt.ui.search.IMatchPresentation;
import org.eclipse.jdt.ui.search.IQueryParticipant;
import org.eclipse.jdt.ui.search.ISearchRequestor;
import org.eclipse.jdt.ui.search.PatternQuerySpecification;
import org.eclipse.jdt.ui.search.QuerySpecification;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.search.ui.text.Match;
import org.eclipse.ui.PartInitException;

/**
 * This class provides a search participant to find class references in the
 * {@linkplain IJavaLaunchConfigurationConstants#ATTR_MAIN_TYPE_NAME} attribute
 * of Java launch configurations
 *
 * @since 3.4.0
 */
public class LaunchConfigurationQueryParticipant implements IQueryParticipant {

	/**
	 * Singleton instance of the UI participant for the configuration
	 * search matches
	 */
	private UIParticipant uiParticipant;

	/* (non-Javadoc)
	 * @see org.eclipse.jdt.ui.search.IQueryParticipant#estimateTicks(org.eclipse.jdt.ui.search.QuerySpecification)
	 */
	@Override
	public int estimateTicks(QuerySpecification query) {
		if (isValid(query)) {
			return 50;
		}
		return 0;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jdt.ui.search.IQueryParticipant#getUIParticipant()
	 */
	@Override
	public synchronized IMatchPresentation getUIParticipant() {
		if (uiParticipant == null) {
			uiParticipant = new UIParticipant();
		}
		return uiParticipant;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jdt.ui.search.IQueryParticipant#search(org.eclipse.jdt.ui.search.ISearchRequestor, org.eclipse.jdt.ui.search.QuerySpecification, org.eclipse.core.runtime.IProgressMonitor)
	 */
	@Override
	public void search(ISearchRequestor requestor, QuerySpecification query, IProgressMonitor monitor) throws CoreException {
		if (!isValid(query)) {
			return;
		}
		if(monitor == null) {
			monitor = new NullProgressMonitor();
		}
		monitor.beginTask("", 11); //$NON-NLS-1$
		try {
			Pattern pattern = null;
			if (query instanceof ElementQuerySpecification) {
				ElementQuerySpecification elementQuery = (ElementQuerySpecification) query;
				IJavaElement element = elementQuery.getElement();
				if(element instanceof IMember) {
					IMember member = (IMember) element;
					IType type = null;
					if(member.getElementType() == IJavaElement.TYPE) {
						type = (IType) member;
					}
					else if(member.getElementType() == IJavaElement.METHOD) {
						if(((IMethod)member).isMainMethod()) {
							type = member.getDeclaringType();
						}
					}
					if(type != null) {
						pattern = Pattern.compile(quotePattern(type.getFullyQualifiedName('$')));
					}
					else {
						return;
					}
				}
				else {
					return;
				}
			}
			if (query instanceof PatternQuerySpecification) {
				PatternQuerySpecification patternQuery = (PatternQuerySpecification) query;
				int flags = patternQuery.isCaseSensitive() ? 0
						: Pattern.CASE_INSENSITIVE;
				String quotedPattern = quotePattern(patternQuery.getPattern());
				pattern = Pattern.compile(quotedPattern, flags);
			}
			if(monitor.isCanceled()) {
				return;
			}
			monitor.worked(1);
			searchLaunchConfigurations(query.getScope(), requestor, pattern, new SubProgressMonitor(monitor, 7));
		} finally {
			monitor.done();
		}
	}

	/**
	 * Creates an adjusted pattern from the specified quote pattern
	 * @param pattern the search pattern to manipulate
	 * @return the original pattern adjusted to escape special chars in Java,and change Eclipse
	 * search chars to Java RegEx chars
	 */
	private String quotePattern(String pattern) {
		StringTokenizer t = new StringTokenizer(pattern, ".?*$()", true); //$NON-NLS-1$
		StringBuilder buf = new StringBuilder();
		String token = null;
		while (t.hasMoreTokens()) {
			token = t.nextToken();
			switch (token.charAt(0)) {
				case '.': {
					buf.append('\\');
					break;
				}
				case '?':
				case '*': {
					buf.append('.');
					break;
				}
				case '$': {
					buf.append('\\');
					break;
				}
				case '(': {
					buf.append('\\');
					break;
				}
				case ')': {
					buf.append('\\');
					break;
				}
			}
			buf.append(token);
		}
		return buf.toString();
	}

	/**
	 * Determines if the current query should be considered or not
	 * @param query the current query
	 * @return true if the query should be considered, false otherwise
	 */
	private boolean isValid(QuerySpecification query) {
		switch(query.getLimitTo()) {
			case IJavaSearchConstants.REFERENCES:
			case IJavaSearchConstants.ALL_OCCURRENCES: {
				break;
			}
			default: {
				return false;
			}
		}
		if (query instanceof ElementQuerySpecification) {
			IJavaElement element = ((ElementQuerySpecification) query).getElement();
			return element.getElementType() == IJavaElement.TYPE || element.getElementType() == IJavaElement.METHOD;
		}
		if (query instanceof PatternQuerySpecification) {
			PatternQuerySpecification patternQuery = (PatternQuerySpecification) query;
			switch (patternQuery.getSearchFor()) {
				case IJavaSearchConstants.UNKNOWN:
				case IJavaSearchConstants.TYPE:
				case IJavaSearchConstants.CLASS:
				case IJavaSearchConstants.CLASS_AND_INTERFACE:
				case IJavaSearchConstants.CLASS_AND_ENUM: {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Returns the success of the the pattern matching
	 * @param scope the search scope
	 * @param config the backing {@link ILaunchConfiguration}
	 * @param pattern the search pattern
	 * @return true if there were matching elements for the pattern, false otherwise
	 * @throws CoreException if an exception occurs
	 */
	private boolean matches(IJavaSearchScope scope, ILaunchConfiguration config, Pattern pattern) throws CoreException {
		if(!config.exists() || !config.getType().isPublic() || !DebugUIPlugin.doLaunchConfigurationFiltering(config)) {
			return false;
		}
		String mainTypeName = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, (String) null);
		if (mainTypeName == null) {
			return false;
		}
		try {
			mainTypeName = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(mainTypeName);
		}
		catch(CoreException ce) {
			return false;
		}
		if (! pattern.matcher(mainTypeName).matches()) {
			return false;
		}
		IResource[] resources = config.getMappedResources();
		if (resources != null) {
			for (int i = 0; i < resources.length; i++) {
				if (scope.encloses(resources[i].getFullPath().toString())) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Searches for configurations matching the specified pattern
	 * @param scope the search scope
	 * @param requestor the requester
	 * @param pattern the search pattern
	 * @param monitor for progress monitoring
	 * @throws CoreException if an exception occurs
	 */
	private void searchLaunchConfigurations(IJavaSearchScope scope, ISearchRequestor requestor, Pattern pattern, IProgressMonitor monitor) throws CoreException {
		ILaunchConfiguration[] configs = DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurations();
		monitor.beginTask("Searching for launch configurations", configs.length); //$NON-NLS-1$
		try {
			for (int i = 0; i < configs.length; ++i) {
				if (monitor.isCanceled()) {
					return;
				}
				monitor.worked(1);
				if (matches(scope, configs[i], pattern)) {
					requestor.reportMatch(new Match(configs[i], 0, 0));
				}
			}
		} finally {
			monitor.done();
		}
	}

	/**
	 * The UI participant for showing configuration matches in the search view
	 *
	 * @since 3.4.0
	 */
	private static class UIParticipant implements IMatchPresentation {

		/* (non-Javadoc)
		 * @see org.eclipse.jdt.ui.search.IMatchPresentation#createLabelProvider()
		 */
		@Override
		public ILabelProvider createLabelProvider() {
			return DebugUITools.newDebugModelPresentation();
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jdt.ui.search.IMatchPresentation#showMatch(org.eclipse.search.ui.text.Match, int, int, boolean)
		 */
		@Override
		public void showMatch(Match match, int currentOffset, int currentLength, boolean activate) throws PartInitException {
			Object o = match.getElement();
			if (o instanceof ILaunchConfiguration) {
				if (activate) {
					try {
						ILaunchConfiguration config = (ILaunchConfiguration) o;
						Set<Set<String>> modes =  config.getType().getSupportedModeCombinations();
						ILaunchGroup group = null;
						Set<String> mode = null;
						for (Iterator<Set<String>> iter = modes.iterator(); iter.hasNext();) {
							mode = iter.next();
							if(mode.size() == 1) {
								group = DebugUITools.getLaunchGroup(config, mode.iterator().next());
								if (group != null) {
									break;
								}
							}
						}
						if (group == null || !config.exists()) {
							return;
						}
						DebugUITools.openLaunchConfigurationDialog(JDIDebugUIPlugin.getActiveWorkbenchShell(), config, group.getIdentifier(), Status.OK_STATUS);
					}
					catch(CoreException ce) {
						JDIDebugUIPlugin.log(ce);
					}
				}
			}
		}
	}
}
