/*******************************************************************************
 * Copyright (c) 2007, 2014 QNX Software Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Ericsson - initial API and implementation this class is based on
 *     QNX Software Systems - Initial implementation for Jtag debugging
 *     Sage Electronic Engineering, LLC - bug 305943
 *              - API generalization to become transport-independent (allow
 *                connections via serial ports and pipes).
 *     John Dallaway - Wrong groupId during initialization (Bug 349736)    
 *     Marc Khouzam (Ericsson) - Updated to extend FinalLaunchSequence instead of copying it (bug 324101)
 *     William Riley (Renesas) - Memory viewing broken (Bug 413483)
 *     Marc Khouzam (Ericsson) - Cannot disable Delay command (bug 413437)
 *******************************************************************************/
package org.eclipse.cdt.debug.gdbjtag.core;

/**
 * @author Andy Jin
 *
 */

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.eclipse.cdt.debug.core.CDebugUtils;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.debug.gdbjtag.core.jtagdevice.GDBJtagDeviceContribution;
import org.eclipse.cdt.debug.gdbjtag.core.jtagdevice.GDBJtagDeviceContributionFactory;
import org.eclipse.cdt.debug.gdbjtag.core.jtagdevice.IGDBJtagDevice;
import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfExecutor;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitorWithProgress;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext;
import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext;
import org.eclipse.cdt.dsf.gdb.launching.FinalLaunchSequence;
import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch;
import org.eclipse.cdt.dsf.gdb.service.IGDBBackend;
import org.eclipse.cdt.dsf.gdb.service.IGDBMemory;
import org.eclipse.cdt.dsf.gdb.service.SessionType;
import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl;
import org.eclipse.cdt.dsf.mi.service.IMIContainerDMContext;
import org.eclipse.cdt.dsf.mi.service.IMIProcesses;
import org.eclipse.cdt.dsf.mi.service.MIBreakpointsManager;
import org.eclipse.cdt.dsf.mi.service.MIProcesses;
import org.eclipse.cdt.dsf.mi.service.command.commands.CLICommand;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.utils.CommandLineUtil;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.variables.VariablesPlugin;

/**
 * The final launch sequence for the Jtag hardware debugging using the
 * DSF/GDB debugger framework.
 * <p>
 * This class is based on the implementation of the standard DSF/GDB debugging
 * <code>org.eclipse.cdt.dsf.gdb.launching.FinalLaunchSequence</code>
 * <p>
 * It adds Jtag hardware debugging specific steps to initialize remote target
 * and start the remote Jtag debugging.
 * <p>
 * @since 7.0
 */
public class GDBJtagDSFFinalLaunchSequence extends FinalLaunchSequence {

	/** utility method; cuts down on clutter */
	private void queueCommands(List<String> commands, RequestMonitor rm) {
		if (!commands.isEmpty()) { 
			fCommandControl.queueCommand(
					new CLICommand<MIInfo>(fCommandControl.getContext(), composeCommand(commands)),
					new DataRequestMonitor<MIInfo>(getExecutor(), rm));
		}
		else {
			rm.done();
		}
	}

	private IGDBControl fCommandControl;
	private IGDBBackend	fGDBBackend;
	private IMIProcesses fProcService;
	private IGDBJtagDevice fGdbJtagDevice;

	private DsfServicesTracker fTracker;
	private IMIContainerDMContext fContainerCtx;
	
	/**
	 * @since 8.2
	 */
	public GDBJtagDSFFinalLaunchSequence(DsfSession session, Map<String, Object> attributes, RequestMonitorWithProgress rm) {
		super(session, attributes, rm);
	}

	public GDBJtagDSFFinalLaunchSequence(DsfExecutor executor, GdbLaunch launch, SessionType sessionType, boolean attach, RequestMonitorWithProgress rm) {
		this(launch.getSession(), getAttributes(launch), rm);
    }

	@SuppressWarnings("unchecked")
	private static Map<String, Object> getAttributes(GdbLaunch launch) {
		try {
			return launch.getLaunchConfiguration().getAttributes();
		} catch (CoreException e) {
		}
		return new HashMap<String, Object>();
	}
    
	/** @since 8.2 */
	protected IMIContainerDMContext getContainerContext() {
		return fContainerCtx;
	}

	/** @since 8.2 */
	protected void setContainerContext(IMIContainerDMContext ctx) {
		fContainerCtx = ctx;
	}

	/** @since 8.2 */
	protected static final String GROUP_JTAG = "GROUP_JTAG";

	@Override
	protected String[] getExecutionOrder(String group) {
		if (GROUP_TOP_LEVEL.equals(group)) {
			// Initialize the list with the base class' steps
			// We need to create a list that we can modify, which is why we create our own ArrayList.
			List<String> orderList = new ArrayList<String>(Arrays.asList(super.getExecutionOrder(GROUP_TOP_LEVEL)));

			// First, remove all steps of the base class that we don't want to use.
			orderList.removeAll(Arrays.asList(new String[] { 
					"stepNewProcess",   //$NON-NLS-1$
			}));

			// Now insert our steps before the data model initialized event is sent
			orderList.add(orderList.indexOf("stepDataModelInitializationComplete"), GROUP_JTAG);

			return orderList.toArray(new String[orderList.size()]);
		}

		// Finally, deal with our groups and their steps.
		if (GROUP_JTAG.equals(group)) {
			return new String[] {
					"stepInitializeJTAGFinalLaunchSequence",
					"stepRetrieveJTAGDevice",   //$NON-NLS-1$
					"stepLoadSymbols",   //$NON-NLS-1$
					"stepConnectToTarget",   //$NON-NLS-1$
					"stepResetBoard",   //$NON-NLS-1$
					"stepDelayStartup",   //$NON-NLS-1$
					"stepHaltBoard",   //$NON-NLS-1$
					"stepUserInitCommands",   //$NON-NLS-1$
					"stepLoadImage",   //$NON-NLS-1$
					
					"stepUpdateContainer",   //$NON-NLS-1$
					
					"stepInitializeMemory",   //$NON-NLS-1$
					"stepSetArguments",   //$NON-NLS-1$
					"stepSetEnvironmentVariables",   //$NON-NLS-1$
					"stepStartTrackingBreakpoints",   //$NON-NLS-1$
					
					"stepSetProgramCounter",   //$NON-NLS-1$
					"stepStopScript",   //$NON-NLS-1$
					"stepResumeScript",   //$NON-NLS-1$
					"stepUserDebugCommands",   //$NON-NLS-1$
					"stepJTAGCleanup",   //$NON-NLS-1$
			};
		}
		
		// For any subgroups of the base class
		return super.getExecutionOrder(group);
	}

	/** 
	 * Initialize the members of the class.
	 * This step is mandatory for the rest of the sequence to complete.
	 * @since 8.2
	 */
	@Execute
	public void stepInitializeJTAGFinalLaunchSequence(RequestMonitor rm) {
		fTracker = new DsfServicesTracker(Activator.getBundleContext(), getSession().getId());
		fGDBBackend = fTracker.getService(IGDBBackend.class);
		if (fGDBBackend == null) {
			rm.done(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot obtain GDBBackend service", null)); //$NON-NLS-1$
			return;
		}

		fCommandControl = fTracker.getService(IGDBControl.class);
		if (fCommandControl == null) {
			rm.done(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot obtain control service", null)); //$NON-NLS-1$
			return;
		}

		fProcService = fTracker.getService(IMIProcesses.class);
		if (fProcService == null) {
			rm.done(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot obtain process service", null)); //$NON-NLS-1$
			return;
		}
		
        // When we are starting to debug a new process, the container is the default process used by GDB.
        // We don't have a pid yet, so we can simply create the container with the UNIQUE_GROUP_ID
        setContainerContext(fProcService.createContainerContextFromGroupId(fCommandControl.getContext(), MIProcesses.UNIQUE_GROUP_ID));

		rm.done();
	}

	/** 
	 * Rollback method for {@link #stepInitializeJTAGFinalLaunchSequence()}
	 * @since 4.0 
	 */
	@RollBack("stepInitializeJTAGFinalLaunchSequence")
	public void rollBackInitializeFinalLaunchSequence(RequestMonitor rm) {
		if (fTracker != null) fTracker.dispose();
		fTracker = null;
		rm.done();
	}
	
	/*
	 * Retrieve the IGDBJtagDevice instance
	 */
	/** @since 8.2 */
	@Execute
	public void stepRetrieveJTAGDevice(final RequestMonitor rm) {
		Exception exception = null;
		try {
			fGdbJtagDevice = getGDBJtagDevice();
		} catch (NullPointerException e) {
			exception = e;
		}
		if (fGdbJtagDevice == null) {
			// Abort the launch
			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot get Jtag device", exception)); //$NON-NLS-1$
		}
		rm.done();
	}
	
	/*
	 * Execute symbol loading
	 */
	/** @since 8.2 */
	@Execute
	public void stepLoadSymbols(final RequestMonitor rm) {
		try {
			if (CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_LOAD_SYMBOLS, IGDBJtagConstants.DEFAULT_LOAD_SYMBOLS)) {
				String symbolsFileName = null;

				// New setting in Helios. Default is true. Check for existence
				// in order to support older launch configs
				if (getAttributes().containsKey(IGDBJtagConstants.ATTR_USE_PROJ_BINARY_FOR_SYMBOLS) &&
						CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_USE_PROJ_BINARY_FOR_SYMBOLS, IGDBJtagConstants.DEFAULT_USE_PROJ_BINARY_FOR_SYMBOLS)) {
					IPath programFile = fGDBBackend.getProgramPath();
					if (programFile != null) {
						symbolsFileName = programFile.toOSString();
					}
				}
				else {
					symbolsFileName = CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_SYMBOLS_FILE_NAME, IGDBJtagConstants.DEFAULT_SYMBOLS_FILE_NAME);
					if (symbolsFileName.length() > 0) {
						symbolsFileName = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(symbolsFileName);
					} else {
						symbolsFileName = null;
					}
				}

				if (symbolsFileName == null) {
					rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, Messages.getString("GDBJtagDebugger.err_no_img_file"), null)); //$NON-NLS-1$
					rm.done();
					return;
				}

				// Escape windows path separator characters TWICE, once for Java and once for GDB.						
				symbolsFileName = symbolsFileName.replace("\\", "\\\\"); //$NON-NLS-1$ //$NON-NLS-2$

				String symbolsOffset = CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_SYMBOLS_OFFSET, IGDBJtagConstants.DEFAULT_SYMBOLS_OFFSET);
				if (symbolsOffset.length() > 0) {
					symbolsOffset = "0x" + symbolsOffset;					
				}
				List<String> commands = new ArrayList<String>();
				fGdbJtagDevice.doLoadSymbol(symbolsFileName, symbolsOffset, commands);
				queueCommands(commands, rm);									

			} else {
				rm.done();
			}
		} catch (CoreException e) {
			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot load symbol", e)); //$NON-NLS-1$
			rm.done();
		}
	}

	/*
	 * Hook up to remote target
	 */
	/** @since 8.2 */
	@SuppressWarnings("deprecation")
	@Execute
	public void stepConnectToTarget(final RequestMonitor rm) {
		try {
			if (CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_USE_REMOTE_TARGET, IGDBJtagConstants.DEFAULT_USE_REMOTE_TARGET)) {
				List<String> commands = new ArrayList<String>();
				if (fGdbJtagDevice instanceof IGDBJtagConnection) {
					URI	uri = new URI(CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_CONNECTION, IGDBJtagConstants.DEFAULT_CONNECTION));
					IGDBJtagConnection device = (IGDBJtagConnection)fGdbJtagDevice;
					device.doRemote(uri.getSchemeSpecificPart(), commands);
				} else {
					// Handle legacy network device contributions that don't understand URIs
					String ipAddress = CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_IP_ADDRESS, IGDBJtagConstants.DEFAULT_IP_ADDRESS);
					int portNumber = CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_PORT_NUMBER, IGDBJtagConstants.DEFAULT_PORT_NUMBER);
					fGdbJtagDevice.doRemote(ipAddress, portNumber, commands);
				}
				queueCommands(commands, rm);
			} else {
				rm.done();
			}
		} catch (URISyntaxException e) {
			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Invalid remote target connection syntax", e)); //$NON-NLS-1$
			rm.done();
		}
	}
	
	/*
	 * Run device-specific code to reset the board
	 */
	/** @since 8.2 */
	@Execute
	public void stepResetBoard(final RequestMonitor rm) {
			if (CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_DO_RESET, IGDBJtagConstants.DEFAULT_DO_RESET)) {
				List<String> commands = new ArrayList<String>();
				fGdbJtagDevice.doReset(commands);
				queueCommands(commands, rm);
			} else {
				rm.done();
			}
	}
	
	/*
	 * Run device-specific code to delay the startup
	 */
	/** @since 8.2 */
	@Execute
	public void stepDelayStartup(final RequestMonitor rm) {
		// The delay is also controlled by the RESET attribute.
		if (CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_DO_RESET, IGDBJtagConstants.DEFAULT_DO_RESET)) {
			int defaultDelay = fGdbJtagDevice.getDefaultDelay();
			List<String> commands = new ArrayList<String>();
			fGdbJtagDevice.doDelay(CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_DELAY, defaultDelay), commands);
			queueCommands(commands, rm);
		} else {
			rm.done();
		}						
	}
	
	/*
	 * Run device-specific code to halt the board
	 */
	/** @since 8.2 */
	@Execute
	public void stepHaltBoard(final RequestMonitor rm) {
			if (CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_DO_HALT, IGDBJtagConstants.DEFAULT_DO_HALT)) {
				List<String> commands = new ArrayList<String>();
				fGdbJtagDevice.doHalt(commands);
				queueCommands(commands, rm);								
			} else {
				rm.done();
			}
	}
	
	/*
	 * Execute any user defined init commands
	 */
	/** @since 8.2 */
	@Execute
	public void stepUserInitCommands(final RequestMonitor rm) {
		try {
			String userCmd = CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_INIT_COMMANDS, IGDBJtagConstants.DEFAULT_INIT_COMMANDS);
			userCmd = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(userCmd);
			if (userCmd.length() > 0) {
				String[] commands = userCmd.split("\\r?\\n"); //$NON-NLS-1$

				CountingRequestMonitor crm = new CountingRequestMonitor(getExecutor(), rm);
				crm.setDoneCount(commands.length);
				for (int i = 0; i < commands.length; ++i) {
					fCommandControl.queueCommand(
							new CLICommand<MIInfo>(fCommandControl.getContext(), commands[i]),
							new DataRequestMonitor<MIInfo>(getExecutor(), crm));
				}
			}
			else {
				rm.done();
			}
		} catch (CoreException e) {
			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot run user defined init commands", e)); //$NON-NLS-1$
			rm.done();
		}
	}
	
	/*
	 * Execute image loading
	 */
	/** @since 8.2 */
	@Execute
	public void stepLoadImage(final RequestMonitor rm) {
		try {
			String imageFileName = null;
			if (CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_LOAD_IMAGE, IGDBJtagConstants.DEFAULT_LOAD_IMAGE)) {
				// New setting in Helios. Default is true. Check for existence
				// in order to support older launch configs
				if (getAttributes().containsKey(IGDBJtagConstants.ATTR_USE_PROJ_BINARY_FOR_IMAGE) &&
						CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_USE_PROJ_BINARY_FOR_IMAGE, IGDBJtagConstants.DEFAULT_USE_PROJ_BINARY_FOR_IMAGE)) {
					IPath programFile = fGDBBackend.getProgramPath();
					if (programFile != null) {
						imageFileName = programFile.toOSString();
					}
				}
				else {
					imageFileName = CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_IMAGE_FILE_NAME, IGDBJtagConstants.DEFAULT_IMAGE_FILE_NAME); 
					if (imageFileName.length() > 0) {
						imageFileName = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(imageFileName);
					} else {
						imageFileName = null;
					}
				}

				if (imageFileName == null) {
					rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, Messages.getString("GDBJtagDebugger.err_no_img_file"), null)); //$NON-NLS-1$
					rm.done();
					return;
				}

				// Escape windows path separator characters TWICE, once for Java and once for GDB.						
				imageFileName = imageFileName.replace("\\", "\\\\"); //$NON-NLS-1$ //$NON-NLS-2$

				String imageOffset = CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_IMAGE_OFFSET, IGDBJtagConstants.DEFAULT_IMAGE_OFFSET);
				if (imageOffset.length() > 0) {
					imageOffset = (imageFileName.endsWith(".elf")) ? "" : "0x" + CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_IMAGE_OFFSET, IGDBJtagConstants.DEFAULT_IMAGE_OFFSET); //$NON-NLS-2$ 
				}
				List<String> commands = new ArrayList<String>();
				fGdbJtagDevice.doLoadImage(imageFileName, imageOffset, commands);
				queueCommands(commands, rm);									
			} 
			else {
				rm.done();
			}
		} catch (CoreException e) {
			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot load image", e)); //$NON-NLS-1$
			rm.done();
		}
	}
	
	/**
	 * Now that we are connected to the target, we should update
	 * our container to properly fill in its pid.
	 * @since 8.2
	 */
	@Execute
	public void stepUpdateContainer(RequestMonitor rm) {
		String groupId = getContainerContext().getGroupId();
        setContainerContext(fProcService.createContainerContextFromGroupId(fCommandControl.getContext(), groupId));
		rm.done();
	}
	
	/**
	 * Specify the arguments to the program that will be run.
	 * @since 8.2
	 */
	@Execute
	public void stepSetArguments(RequestMonitor rm) {
		try {
			String args = CDebugUtils.getAttribute(
					getAttributes(),
					ICDTLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,
					""); //$NON-NLS-1$

			if (args.length() != 0) {
				args = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(args);
				String[] argArray = CommandLineUtil.argumentsToArray(args);
				fCommandControl.queueCommand(
						fCommandControl.getCommandFactory().createMIGDBSetArgs(getContainerContext(), argArray), 
						new ImmediateDataRequestMonitor<MIInfo>(rm));
			} else {
				rm.done();
			}
		} catch (CoreException e) {
			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot get inferior arguments", e)); //$NON-NLS-1$
			rm.done();
		}    		
	}
	
	/**
	 * Specify environment variables if needed
	 * @since 8.2
	 */
	@Execute
	public void stepSetEnvironmentVariables(RequestMonitor rm) {
		boolean clear = false;
		Properties properties = new Properties();
		try {
			// here we need to pass the proper container context
			clear = fGDBBackend.getClearEnvironment();
			properties = fGDBBackend.getEnvironmentVariables();
		} catch (CoreException e) {
			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot get environment information", e)); //$NON-NLS-1$
			rm.done();
			return;
		}

		if (clear == true || properties.size() > 0) {
			fCommandControl.setEnvironment(properties, clear, rm);
		} else {
			rm.done();
		}
	}
	
	/* 
	 * Start tracking the breakpoints once we know we are connected to the target (necessary for remote debugging) 
	 */
	/** @since 8.2 */
	@Execute
	public void stepStartTrackingBreakpoints(final RequestMonitor rm) {
		MIBreakpointsManager bpmService = fTracker.getService(MIBreakpointsManager.class);
		IBreakpointsTargetDMContext bpTargetDmc = DMContexts.getAncestorOfType(getContainerContext(), IBreakpointsTargetDMContext.class);
		bpmService.startTrackingBreakpoints(bpTargetDmc, rm);
	}
	
	/*
	 * Set the program counter
	 */
	/** @since 8.2 */
	@Execute
	public void stepSetProgramCounter(final RequestMonitor rm) {
			if (CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_SET_PC_REGISTER, IGDBJtagConstants.DEFAULT_SET_PC_REGISTER)) {
				String pcRegister = CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_PC_REGISTER, CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_IMAGE_OFFSET, IGDBJtagConstants.DEFAULT_PC_REGISTER)); 
				List<String> commands = new ArrayList<String>();
				fGdbJtagDevice.doSetPC(pcRegister, commands);
				queueCommands(commands, rm);								
			} else {
				rm.done();
			}
	}
	
	/*
	 * Execute the stop script
	 */
	/** @since 8.2 */
	@Execute
	public void stepStopScript(final RequestMonitor rm) {
			if (CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_SET_STOP_AT, IGDBJtagConstants.DEFAULT_SET_STOP_AT)) {
				String stopAt = CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_STOP_AT, IGDBJtagConstants.DEFAULT_STOP_AT); 
				List<String> commands = new ArrayList<String>();
				fGdbJtagDevice.doStopAt(stopAt, commands);
				queueCommands(commands, rm);								
			} else {
				rm.done();
			}
	}

	/*
	 * Execute the resume script
	 */
	/** @since 8.2 */
	@Execute
	public void stepResumeScript(final RequestMonitor rm) {
			if (CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_SET_RESUME, IGDBJtagConstants.DEFAULT_SET_RESUME)) {
				List<String> commands = new ArrayList<String>();
				fGdbJtagDevice.doContinue(commands);
				queueCommands(commands, rm);									
			} else {
				rm.done();
			}
	}
	
	/*
	 * Run any user defined commands to start debugging
	 */
	/** @since 8.2 */
	@Execute
	public void stepUserDebugCommands(final RequestMonitor rm) {
		try {
			String userCmd = CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_RUN_COMMANDS, IGDBJtagConstants.DEFAULT_RUN_COMMANDS); 
			if (userCmd.length() > 0) {
				userCmd = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(userCmd);
				String[] commands = userCmd.split("\\r?\\n"); //$NON-NLS-1$

				CountingRequestMonitor crm = new CountingRequestMonitor(getExecutor(), rm);
				crm.setDoneCount(commands.length);
				for (int i = 0; i < commands.length; ++i) {
					fCommandControl.queueCommand(
							new CLICommand<MIInfo>(fCommandControl.getContext(), commands[i]),
							new DataRequestMonitor<MIInfo>(getExecutor(), crm));
				}
			}
			else {
				rm.done();
			}
		} catch (CoreException e) {
			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot run user defined run commands", e)); //$NON-NLS-1$
			rm.done();
		}
	}

	private IGDBJtagDevice getGDBJtagDevice () {
		IGDBJtagDevice gdbJtagDevice = null;
		String jtagDeviceName = CDebugUtils.getAttribute(getAttributes(), IGDBJtagConstants.ATTR_JTAG_DEVICE, IGDBJtagConstants.DEFAULT_JTAG_DEVICE); 
		GDBJtagDeviceContribution[] availableDevices = GDBJtagDeviceContributionFactory.getInstance().getGDBJtagDeviceContribution();
		for (GDBJtagDeviceContribution availableDevice : availableDevices) {
			if (jtagDeviceName.equals(availableDevice.getDeviceName())) {
				gdbJtagDevice = availableDevice.getDevice();
				break;
			}
		}
		return gdbJtagDevice;
	}

	/**
	 * @param commands
	 * @return String commands in String format
	 */
	private String composeCommand(Collection<String> commands) {
		if (commands.isEmpty())
			return null;
		StringBuffer sb = new StringBuffer();
		Iterator<String> it = commands.iterator();
		while (it.hasNext()) {
			sb.append(it.next());
		}
		return sb.toString();
	}
	
	/**
	 * Cleanup now that the sequence has been run.
	 * @since 8.2
	 */
	@Execute
	public void stepJTAGCleanup(final RequestMonitor requestMonitor) {
		fTracker.dispose();
		fTracker = null;
		requestMonitor.done();
	}

	/**
	 * Initialize the memory service with the data for given process.
	 * @since 8.3
	 */
	@Execute
	public void stepInitializeMemory(final RequestMonitor rm) {
		IGDBMemory memory = fTracker.getService(IGDBMemory.class);
		IMemoryDMContext memContext = DMContexts.getAncestorOfType(getContainerContext(), IMemoryDMContext.class);
		if (memory == null || memContext == null) {
			rm.done();
			return;
		}
		memory.initializeMemoryData(memContext, rm);
	}
}
