/*******************************************************************************
 * Copyright (c) 2000, 2012 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.debug.core.breakpoints;

import java.text.MessageFormat;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugException;
import org.eclipse.jdt.debug.core.IJavaPatternBreakpoint;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;

import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.VirtualMachine;

@SuppressWarnings("deprecation")
public class JavaPatternBreakpoint extends JavaLineBreakpoint implements
		IJavaPatternBreakpoint {

	private static final String PATTERN_BREAKPOINT = "org.eclipse.jdt.debug.javaPatternBreakpointMarker"; //$NON-NLS-1$

	/**
	 * Breakpoint attribute storing the pattern identifier of the source file in
	 * which a breakpoint is created (value
	 * <code>"org.eclipse.jdt.debug.core.pattern"</code>). This attribute is a
	 * <code>String</code>.
	 */
	protected static final String PATTERN = "org.eclipse.jdt.debug.core.pattern"; //$NON-NLS-1$

	public JavaPatternBreakpoint() {
	}

	/**
	 * @see JDIDebugModel#createPatternBreakpoint(IResource, String, int, int,
	 *      int, int, boolean, Map)
	 */
	public JavaPatternBreakpoint(IResource resource, String sourceName,
			String pattern, int lineNumber, int charStart, int charEnd,
			int hitCount, boolean add, Map<String, Object> attributes) throws DebugException {
		this(resource, sourceName, pattern, lineNumber, charStart, charEnd,
				hitCount, add, attributes, PATTERN_BREAKPOINT);
	}

	public JavaPatternBreakpoint(final IResource resource,
			final String sourceName, final String pattern,
			final int lineNumber, final int charStart, final int charEnd,
			final int hitCount, final boolean add, final Map<String, Object> attributes,
			final String markerType) throws DebugException {
		IWorkspaceRunnable wr = monitor -> {

			// add attributes
			addLineBreakpointAttributes(attributes, getModelIdentifier(),
					true, lineNumber, charStart, charEnd);
			addPatternAndHitCount(attributes, sourceName, pattern, hitCount);
			// set attributes
			attributes.put(SUSPEND_POLICY, Integer.valueOf(getDefaultSuspendPolicy()));
			// create the marker
			setMarker(resource.createMarker(markerType, attributes));

			register(add);
		};
		run(getMarkerRule(resource), wr);
	}

	/**
	 * @see JavaBreakpoint#getReferenceTypeName()
	 */
	protected String getReferenceTypeName() {
		String name = ""; //$NON-NLS-1$
		try {
			name = getPattern();
		} catch (CoreException ce) {
			JDIDebugPlugin.log(ce);
		}
		return name;
	}

	/**
	 * @see JavaBreakpoint#installableReferenceType(ReferenceType)
	 */
	@Override
	protected boolean installableReferenceType(ReferenceType type,
			JDIDebugTarget target) throws CoreException {
		// if the source name attribute is specified, check for a match with the
		// debug attribute (if available)
		if (getSourceName() != null) {
			String sourceName = null;
			try {
				sourceName = type.sourceName();
			} catch (AbsentInformationException e) {
				// unable to compare
			} catch (VMDisconnectedException e) {
				if (!target.isAvailable()) {
					return false;
				}
				target.targetRequestFailed(
						MessageFormat.format(JDIDebugBreakpointMessages.JavaPatternBreakpoint_exception_source_name, e.toString(), type.name()), e);
				// execution will not reach this line, as
				// #targetRequestFailed will throw an exception
				return false;
			} catch (RuntimeException e) {
				target.targetRequestFailed(
						MessageFormat.format(JDIDebugBreakpointMessages.JavaPatternBreakpoint_exception_source_name, e.toString(), type.name()), e);
				// execution will not reach this line, as
				// #targetRequestFailed will throw an exception
				return false;
			}

			// if the debug attribute matches the source name, attempt
			// installation
			if (sourceName != null) {
				if (!getSourceName().equalsIgnoreCase(sourceName)) {
					return false;
				}
			}
		}

		String pattern = getPattern();
		String queriedType = type.name();
		if (pattern == null || queriedType == null) {
			return false;
		}
		if (queriedType.startsWith(pattern)) {
			// query registered listeners to see if this pattern breakpoint
			// should
			// be installed in the given target
			return queryInstallListeners(target, type);
		}
		return false;
	}

	/**
	 * Adds the class name pattern and hit count attributes to the given map.
	 */
	protected void addPatternAndHitCount(Map<String, Object> attributes, String sourceName,
			String pattern, int hitCount) {
		attributes.put(PATTERN, pattern);
		if (sourceName != null) {
			attributes.put(SOURCE_NAME, sourceName);
		}
		if (hitCount > 0) {
			attributes.put(HIT_COUNT, Integer.valueOf(hitCount));
			attributes.put(EXPIRED, Boolean.FALSE);
		}
	}

	/**
	 * @see IJavaPatternBreakpoint#getPattern()
	 */
	@Override
	public String getPattern() throws CoreException {
		return (String) ensureMarker().getAttribute(PATTERN);
	}

	/**
	 * @see IJavaPatternBreakpoint#getSourceName()
	 */
	@Override
	public String getSourceName() throws CoreException {
		return (String) ensureMarker().getAttribute(SOURCE_NAME);
	}

	@Override
	protected void createRequests(JDIDebugTarget target) throws CoreException {
		if (target.isTerminated() || shouldSkipBreakpoint()) {
			return;
		}
		String referenceTypeName = getReferenceTypeName();
		if (referenceTypeName == null) {
			return;
		}

		String classPrepareTypeName = referenceTypeName;
		// create request to listen to class loads
		// name may only be partially resolved
		if (!referenceTypeName.endsWith("*")) { //$NON-NLS-1$
			classPrepareTypeName = classPrepareTypeName + '*';
		}
		registerRequest(target.createClassPrepareRequest(classPrepareTypeName),
				target);

		// create breakpoint requests for each class currently loaded
		VirtualMachine vm = target.getVM();
		if (vm == null) {
			target.requestFailed(
					JDIDebugBreakpointMessages.JavaPatternBreakpoint_Unable_to_add_breakpoint___VM_disconnected__1,
					new VMDisconnectedException());
		}
		List<ReferenceType> classes = null;
		try {
			classes = vm.allClasses();
		} catch (RuntimeException e) {
			target.targetRequestFailed(
					JDIDebugBreakpointMessages.JavaPatternBreakpoint_0, e);
		}
		if (classes != null) {
			Iterator<ReferenceType> iter = classes.iterator();
			String typeName = null;
			ReferenceType type = null;
			while (iter.hasNext()) {
				type = iter.next();
				typeName = type.name();
				if (typeName != null && typeName.startsWith(referenceTypeName)) {
					createRequest(target, type);
				}
			}
		}
	}
}
