/*******************************************************************************
 * Copyright (c) 2008, 2015 Wind River Systems 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:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.examples.dsf.pda.service;

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

import org.eclipse.cdt.debug.core.model.ICLineBreakpoint;
import org.eclipse.cdt.debug.core.model.ICWatchpoint;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.debug.service.BreakpointsMediator;
import org.eclipse.cdt.dsf.debug.service.BreakpointsMediator2;
import org.eclipse.cdt.dsf.debug.service.BreakpointsMediator2.BreakpointEventType;
import org.eclipse.cdt.dsf.debug.service.BreakpointsMediator2.ITargetBreakpointInfo;
import org.eclipse.cdt.dsf.debug.service.IBreakpointAttributeTranslator2;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext;
import org.eclipse.cdt.examples.dsf.pda.PDAPlugin;
import org.eclipse.cdt.examples.dsf.pda.breakpoints.PDALineBreakpoint;
import org.eclipse.cdt.examples.dsf.pda.breakpoints.PDAWatchpoint;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IBreakpoint;

/**
 * Translator between {@link PDALineBreakpoint} object attributes and
 * attributes used by the {@link PDABreakpoints} service.
 * <p>
 * The attribute translator is used by the standard {@link BreakpointsMediator}
 * service to map between platform breakpoint attributes and target-side DSF
 * breakpoint attributes.  Thus, this object encapsulates the model-specific
 * functionality of synchronizing target side and IDE-side breakpoint objects.
 * </p>
 */
public class PDABreakpointAttributeTranslator implements IBreakpointAttributeTranslator2 {

	// Arrays of common attributes between the two breakpoint types.  These
	// attributes can be copied directly without translation.
	private static final String[] fgPDALineBreakpointAttributes = { IBreakpoint.ENABLED, IMarker.LINE_NUMBER, };
	private static final String[] fgPDAWatchpointAttributes = { IBreakpoint.ENABLED, PDAWatchpoint.FUNCTION_NAME,
			PDAWatchpoint.VAR_NAME, PDAWatchpoint.ACCESS, PDAWatchpoint.MODIFICATION };

	private static final String[] fgCDTWatchpointAttributes = { IBreakpoint.ENABLED, PDAWatchpoint.FUNCTION_NAME, };

	// PDA breakpoints translator doesn't keep any state and it doesn't
	// need to initialize or clean up.
	@Override
	public void initialize(BreakpointsMediator2 mediator) {
	}

	@Override
	public void dispose() {
	}

	private List<Map<String, Object>> getBreakpointAttributes(IBreakpoint bp, boolean bpManagerEnabled)
			throws CoreException {
		if (bp instanceof ICLineBreakpoint) {
			return getCBreakpointAttributes((ICLineBreakpoint) bp, bpManagerEnabled);
		} else if (bp instanceof ICWatchpoint) {
			return getCWatchpointAttributes((ICWatchpoint) bp, bpManagerEnabled);
		} else {
			return getPDABreakpointAttributes(bp, bpManagerEnabled);
		}
	}

	private List<Map<String, Object>> getCBreakpointAttributes(ICLineBreakpoint bp, boolean bpManagerEnabled)
			throws CoreException {
		Map<String, Object> attrs = new HashMap<>();

		// Check that the marker exists and retrieve its attributes.
		// Due to accepted race conditions, the breakpiont marker may become null
		// while this method is being invoked.  In this case throw an exception
		// and let the caller handle it.
		IMarker marker = bp.getMarker();
		if (marker == null || !marker.exists()) {
			throw new DebugException(new Status(IStatus.ERROR, PDAPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED,
					"Breakpoint marker does not exist", null));
		}
		// Suppress cast warning: platform is still on Java 1.3
		Map<String, Object> platformBpAttrs = marker.getAttributes();

		// Copy breakpoint attributes.
		attrs.put(PDABreakpoints.ATTR_BREAKPOINT_TYPE, PDABreakpoints.PDA_LINE_BREAKPOINT);
		attrs.put(PDABreakpoints.ATTR_PROGRAM_PATH, bp.getFileName());

		copyAttributes(platformBpAttrs, attrs, fgPDALineBreakpointAttributes);

		// If the breakpoint manager is disabled, override the enabled attribute.
		if (!bpManagerEnabled) {
			attrs.put(IBreakpoint.ENABLED, false);
		}

		// The breakpoint mediator allows for multiple target-side breakpoints
		// to be created for each IDE breakpoint.  Although in case of PDA this
		// feature is never used, we still have to return a list of attributes.
		List<Map<String, Object>> retVal = new ArrayList<>(1);
		retVal.add(attrs);
		return retVal;
	}

	private List<Map<String, Object>> getCWatchpointAttributes(ICWatchpoint bp, boolean bpManagerEnabled)
			throws CoreException {
		Map<String, Object> attrs = new HashMap<>();

		// Check that the marker exists and retrieve its attributes.
		// Due to accepted race conditions, the breakpiont marker may become null
		// while this method is being invoked.  In this case throw an exception
		// and let the caller handle it.
		IMarker marker = bp.getMarker();
		if (marker == null || !marker.exists()) {
			throw new DebugException(new Status(IStatus.ERROR, PDAPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED,
					"Breakpoint marker does not exist", null));
		}
		// Suppress cast warning: platform is still on Java 1.3
		Map<String, Object> platformBpAttrs = marker.getAttributes();

		// Copy breakpoint attributes.
		attrs.put(PDABreakpoints.ATTR_BREAKPOINT_TYPE, PDABreakpoints.PDA_WATCHPOINT);
		attrs.put(PDABreakpoints.ATTR_BREAKPOINT_TYPE, PDABreakpoints.PDA_WATCHPOINT);
		attrs.put(PDAWatchpoint.VAR_NAME, platformBpAttrs.get(ICWatchpoint.EXPRESSION));
		attrs.put(PDAWatchpoint.ACCESS, platformBpAttrs.get(ICWatchpoint.READ));
		attrs.put(PDAWatchpoint.MODIFICATION, platformBpAttrs.get(ICWatchpoint.WRITE));

		copyAttributes(platformBpAttrs, attrs, fgCDTWatchpointAttributes);

		// If the breakpoint manager is disabled, override the enabled attribute.
		if (!bpManagerEnabled) {
			attrs.put(IBreakpoint.ENABLED, false);
		}

		// The breakpoint mediator allows for multiple target-side breakpoints
		// to be created for each IDE breakpoint.  Although in case of PDA this
		// feature is never used, we still have to return a list of attributes.
		List<Map<String, Object>> retVal = new ArrayList<>(1);
		retVal.add(attrs);
		return retVal;
	}

	private List<Map<String, Object>> getPDABreakpointAttributes(IBreakpoint bp, boolean bpManagerEnabled)
			throws CoreException {
		Map<String, Object> attrs = new HashMap<>();

		// Check that the marker exists and retrieve its attributes.
		// Due to accepted race conditions, the breakpiont marker may become null
		// while this method is being invoked.  In this case throw an exception
		// and let the caller handle it.
		IMarker marker = bp.getMarker();
		if (marker == null || !marker.exists()) {
			throw new DebugException(new Status(IStatus.ERROR, PDAPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED,
					"Breakpoint marker does not exist", null));
		}
		// Suppress cast warning: platform is still on Java 1.3
		Map<String, Object> platformBpAttrs = marker.getAttributes();

		// Copy breakpoint attributes.
		if (bp instanceof PDAWatchpoint) {
			attrs.put(PDABreakpoints.ATTR_BREAKPOINT_TYPE, PDABreakpoints.PDA_WATCHPOINT);

			copyAttributes(platformBpAttrs, attrs, fgPDAWatchpointAttributes);
		} else if (bp instanceof PDALineBreakpoint) {
			attrs.put(PDABreakpoints.ATTR_BREAKPOINT_TYPE, PDABreakpoints.PDA_LINE_BREAKPOINT);
			attrs.put(PDABreakpoints.ATTR_PROGRAM_PATH, marker.getResource().getFullPath().toString());

			copyAttributes(platformBpAttrs, attrs, fgPDALineBreakpointAttributes);
		}

		// If the breakpoint manager is disabled, override the enabled attribute.
		if (!bpManagerEnabled) {
			attrs.put(IBreakpoint.ENABLED, false);
		}

		// The breakpoint mediator allows for multiple target-side breakpoints
		// to be created for each IDE breakpoint.  Although in case of PDA this
		// feature is never used, we still have to return a list of attributes.
		List<Map<String, Object>> retVal = new ArrayList<>(1);
		retVal.add(attrs);
		return retVal;
	}

	private void copyAttributes(Map<String, Object> srcMap, Map<String, Object> destMap, String[] attrs) {
		for (String attr : attrs) {
			if (srcMap.containsKey(attr)) {
				destMap.put(attr, srcMap.get(attr));
			}
		}
	}

	@Override
	public boolean canUpdateAttributes(IBreakpoint bp, IBreakpointsTargetDMContext context,
			Map<String, Object> attributes) {
		// PDA debugger only allows updating of the action property of the watchpoint.
		// All other breakpoint updates will require a re-installation.
		if (bp instanceof PDAWatchpoint) {
			Map<String, Object> deltaCopy = new HashMap<>(attributes);
			deltaCopy.remove(PDAWatchpoint.ACCESS);
			deltaCopy.remove(PDAWatchpoint.MODIFICATION);
			return !deltaCopy.isEmpty();
		}
		return false;
	}

	@Override
	public boolean supportsBreakpoint(IBreakpoint bp) {
		return bp.getModelIdentifier().equals(PDAPlugin.ID_PDA_DEBUG_MODEL) || bp instanceof ICLineBreakpoint
				|| bp instanceof ICWatchpoint;
	}

	@Override
	public void updateBreakpointsStatus(
			Map<IBreakpoint, Map<IBreakpointsTargetDMContext, ITargetBreakpointInfo[]>> bpsInfo,
			BreakpointEventType eventType) {
		for (IBreakpoint bp : bpsInfo.keySet()) {
			if (!(bp instanceof ICLineBreakpoint)) {
				continue;
			}
			ICLineBreakpoint cbp = (ICLineBreakpoint) bp;
			try {
				if (eventType == BreakpointEventType.ADDED) {
					cbp.incrementInstallCount();
					// Testing for Bug 360280 - [breakpoints] Reposition breakpoints when planted on invalid line
					//                    if (cbp instanceof ICLineBreakpoint2) {
					//                        ICLineBreakpoint2 lbp2 = (ICLineBreakpoint2)cbp;
					//                        lbp2.setInstalledLineNumber(lbp2.getRequestedLine() + 1);
					//                    }
				} else if (eventType == BreakpointEventType.REMOVED) {
					cbp.decrementInstallCount();
				}
			} catch (CoreException e) {
			}
		}
	}

	@Override
	public void resolveBreakpoint(IBreakpointsTargetDMContext context, IBreakpoint breakpoint,
			Map<String, Object> bpAttributes, DataRequestMonitor<List<Map<String, Object>>> drm) {
		try {
			drm.setData(getBreakpointAttributes(breakpoint, true));
		} catch (CoreException e) {
			drm.setStatus(e.getStatus());
		}
		drm.done();
	}

	@Override
	public Map<String, Object> getAllBreakpointAttributes(IBreakpoint platformBP, boolean bpManagerEnabled)
			throws CoreException {
		IMarker marker = platformBP.getMarker();
		if (marker == null) {
			throw new DebugException(
					new Status(IStatus.ERROR, PDAPlugin.PLUGIN_ID, "Null marker for breakpoint: " + platformBP));
		}
		return marker.getAttributes();
	}

	@Override
	public Map<String, Object> convertAttributes(Map<String, Object> platformBPAttr) {
		return platformBPAttr;
	}

}
