/*******************************************************************************
 * Copyright (c) 2009, 2015 Ericsson 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:
 *     Ericsson - initial API and implementation
 *     Alvaro Sanchez-Leon (Ericsson) - Make Registers View specific to a frame (Bug 323552)
 *     Alvaro Sanchez-Leon (Ericsson) - Allow user to edit the register groups (Bug 235747)
 *     Simon Marchi (Ericsson) - Adapt test code to thread platform compatibility layer.
 *******************************************************************************/
package org.eclipse.cdt.tests.dsf.gdb.tests;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
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.ImmediateCountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.datamodel.CompositeDMContext;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMContext;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMData;
import org.eclipse.cdt.dsf.debug.service.IRegisters;
import org.eclipse.cdt.dsf.debug.service.IRegisters.IRegisterDMContext;
import org.eclipse.cdt.dsf.debug.service.IRegisters.IRegisterDMData;
import org.eclipse.cdt.dsf.debug.service.IRegisters.IRegisterGroupDMContext;
import org.eclipse.cdt.dsf.debug.service.IRegisters.IRegisterGroupDMData;
import org.eclipse.cdt.dsf.debug.service.IRegisters.IRegistersChangedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRegisters2;
import org.eclipse.cdt.dsf.debug.service.IRunControl;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.StepType;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
import org.eclipse.cdt.dsf.gdb.service.GDBRegisters;
import org.eclipse.cdt.dsf.mi.service.MIRegisters.MIRegisterDMC;
import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.tests.dsf.gdb.framework.BaseParametrizedTestCase;
import org.eclipse.cdt.tests.dsf.gdb.framework.ServiceEventWaitor;
import org.eclipse.cdt.tests.dsf.gdb.framework.SyncUtil;
import org.eclipse.cdt.tests.dsf.gdb.launching.TestsPlugin;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class MIRegistersTest extends BaseParametrizedTestCase {
	protected List<String> getRegistersFromGdb() throws Throwable {
		return SyncUtil.getRegistersFromGdb(getGdbVersion(), fCompositeDmc);
	}

	/*
	 * Name of the executable
	 */
	private static final String EXEC_NAME = "MultiThread.exe";
	private static final String SOURCE_NAME = "MultiThread.cc";

	private static final String GROUP_X = "GroupX";
	private static final String GROUP_Y = "GroupY";
	private static final String PROPOSE_GROUP_NAME_BASE = "Group_";

	private DsfSession fSession;
	private DsfServicesTracker fServicesTracker;
	private IDMContext fCompositeDmc;
	private IRegisters2 fRegService;
	private IRunControl fRunControl;
	private Integer fGroupNameSuffix;

	@Override
	public void doBeforeTest() throws Exception {
		super.doBeforeTest();

		fSession = getGDBLaunch().getSession();
		resolveLineTagLocations(SOURCE_NAME, MIRunControlTest.LINE_TAGS);

		Runnable runnable = () -> {
			// We obtain the services we need after the new
			// launch has been performed
			fServicesTracker = new DsfServicesTracker(TestsPlugin.getBundleContext(), fSession.getId());

			fRegService = (IRegisters2) fServicesTracker.getService(IRegisters.class);
			fRunControl = fServicesTracker.getService(IRunControl.class);
		};

		fSession.getExecutor().submit(runnable).get();

		//resolve the execution context
		IFrameDMContext frameDmc = SyncUtil.getStackFrame(getInitialStoppedEvent().getDMContext(), 0);

		//resolve the container context
		IContainerDMContext containerDmc = DMContexts.getAncestorOfType(getInitialStoppedEvent().getDMContext(),
				IContainerDMContext.class);
		//The container dmc is expected to contain the frame and container context
		fCompositeDmc = new CompositeDMContext(new IDMContext[] { containerDmc, frameDmc });
		fGroupNameSuffix = 0;
	}

	@Override
	protected void setLaunchAttributes() {
		super.setLaunchAttributes();

		setLaunchAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, EXEC_PATH + EXEC_NAME);

	}

	@Override
	public void doAfterTest() throws Exception {
		super.doAfterTest();

		if (fServicesTracker != null)
			fServicesTracker.dispose();
		fRegService = null;
	}

	/*
	 * This is a common support method which gets the Register Group Information from target
	 */
	private IRegisterGroupDMContext getTargetRegisterGroup() throws Throwable {
		//Get all the registers from the Container (Process)
		IRegisterDMContext[] registers = getRegisters(fCompositeDmc);
		assertTrue(registers.length > 0);

		//Get the register group from any of the register contexts
		IRegisterGroupDMContext regGroupsDMC = DMContexts.getAncestorOfType(registers[0],
				IRegisterGroupDMContext.class);
		assertNotNull(regGroupsDMC);

		return regGroupsDMC;
	}

	/*
	 * This is a common support method which gets the Registers names.
	 */
	private IRegisterDMContext[] getAllRegisters(final IFrameDMContext frameDmc) throws Throwable {

		Query<IRegisterDMContext[]> queryRegisters = new Query<IRegisterDMContext[]>() {
			@Override
			protected void execute(DataRequestMonitor<IRegisterDMContext[]> rm) {
				fRegService.getRegisters(new CompositeDMContext(new IDMContext[] { fCompositeDmc, frameDmc }), rm);
			}
		};

		fSession.getExecutor().execute(queryRegisters);

		IRegisterDMContext[] regContexts = queryRegisters.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);

		assertEquals("Wrong number of registers", getRegistersFromGdb().size(), regContexts.length);

		return regContexts;
	}

	/*
	 *  Get the Registers for the specified composite context
	 */
	private IRegisterDMContext[] getRegisters(final IDMContext dmc) throws Throwable {

		Query<IRegisterDMContext[]> queryRegistersDmc = new Query<IRegisterDMContext[]>() {
			@Override
			protected void execute(DataRequestMonitor<IRegisterDMContext[]> rm) {
				fRegService.getRegisters(dmc, rm);
			}
		};

		fRegService.getExecutor().submit(queryRegistersDmc);
		IRegisterDMContext[] regContexts = queryRegistersDmc.get(TestsPlugin.massageTimeout(500),
				TimeUnit.MILLISECONDS);

		return (regContexts);
	}

	/*
	 *  This is a common support method which gets the Register context of root group over the specified frame context
	 */
	private IRegisterDMContext[] getTargetRegisters(final IFrameDMContext frameDmc) throws Throwable {
		IRegisterDMContext[] regContexts = getRegisters(
				new CompositeDMContext(new IDMContext[] { fCompositeDmc, frameDmc }));
		assertEquals("Wrong number of registers", getRegistersFromGdb().size(), regContexts.length);

		return regContexts;
	}

	/*************************************************************************
	 *
	 * The tests for the register service.
	 *
	 *************************************************************************/
	@Test
	public void resolveTargetRegisterGroup() throws Throwable {
		final IRegisterGroupDMContext regGroupsDMC = getTargetRegisterGroup();
		IRegisterGroupDMData data = getRegisterGroupData(regGroupsDMC);

		assertEquals("Main register group's name", "General Registers", data.getName());
	}

	@Test
	public void resolveTargetRegistersLength() throws Throwable {
		MIStoppedEvent stoppedEvent = getInitialStoppedEvent();
		IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);
		final IRegisterDMContext[] regDMCs = getAllRegisters(frameDmc);
		assertEquals("Wrong number of registers", getRegistersFromGdb().size(), regDMCs.length);
	}

	@Test
	public void getRegisters() throws Throwable {
		MIStoppedEvent stoppedEvent = getInitialStoppedEvent();
		IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);
		final IRegisterDMContext[] regDMCs = getAllRegisters(frameDmc);
		List<String> regNames = getRegistersFromGdb();

		IRegisterDMData[] datas = getRegistersData(regDMCs);

		for (IRegisterDMData data : datas) {
			String regName = data.getName();
			assertTrue("GDB does not support register name: " + regName, regNames.contains(regName));
		}
	}

	private IRegisterDMData[] getRegistersData(final IRegisterDMContext[] regDMCs)
			throws InterruptedException, ExecutionException {

		Query<IRegisterDMData[]> query = new Query<IRegisterDMData[]>() {
			@Override
			protected void execute(DataRequestMonitor<IRegisterDMData[]> rm) {
				final IRegisterDMData[] datas = new IRegisterDMData[regDMCs.length];
				rm.setData(datas);
				final CountingRequestMonitor countingRm = new ImmediateCountingRequestMonitor(rm);
				countingRm.setDoneCount(regDMCs.length);
				for (int i = 0; i < regDMCs.length; i++) {
					final int index = i;
					fRegService.getRegisterData(regDMCs[index],
							new ImmediateDataRequestMonitor<IRegisterDMData>(countingRm) {
								@Override
								protected void handleSuccess() {
									datas[index] = getData();
									countingRm.done();
								}
							});
				}

			}
		};

		fSession.getExecutor().execute(query);

		return query.get();
	}

	private String getModelDataForRegisterDataValue(IFrameDMContext frameDmc, String format, int regNo)
			throws Throwable {
		final IRegisterDMContext[] regDMCs = getAllRegisters(frameDmc);
		return getModelDataForRegisterDataValue(regDMCs[regNo], format);
	}

	private String getModelDataForRegisterDataValue(IRegisterDMContext registerDmc, String format) throws Throwable {

		final FormattedValueDMContext valueDmc = fRegService.getFormattedValueContext(registerDmc, format);

		Query<FormattedValueDMData> queryFormattedData = new Query<FormattedValueDMData>() {
			@Override
			protected void execute(DataRequestMonitor<FormattedValueDMData> rm) {
				fRegService.getFormattedExpressionValue(valueDmc, rm);
			}
		};

		fRegService.getExecutor().submit(queryFormattedData);

		FormattedValueDMData data = queryFormattedData.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
		return data.getFormattedValue();
	}

	@Test
	public void getModelDataForRegisterDataValueInDifferentNumberFormats() throws Throwable {
		MIStoppedEvent stoppedEvent = getInitialStoppedEvent();
		IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);

		// Make sure register #0 contains a known value.
		writeRegister(frameDmc, 0, "0x1234", IFormattedValues.HEX_FORMAT);

		String val = getModelDataForRegisterDataValue(frameDmc, IFormattedValues.NATURAL_FORMAT, 0);
		assertThat(Long.parseLong(val), equalTo(0x1234L));

		val = getModelDataForRegisterDataValue(frameDmc, IFormattedValues.HEX_FORMAT, 0);
		assertTrue("Register Value is not in HEX_FORMAT: " + val, val.startsWith("0x"));
		assertThat(Long.parseLong(val.substring(2), 16), equalTo(0x1234L));

		val = getModelDataForRegisterDataValue(frameDmc, IFormattedValues.BINARY_FORMAT, 0);
		assertThat(Long.parseLong(val, 2), equalTo(0x1234L));

		val = getModelDataForRegisterDataValue(frameDmc, IFormattedValues.DECIMAL_FORMAT, 0);
		assertThat(Long.parseLong(val), equalTo(0x1234L));

		val = getModelDataForRegisterDataValue(frameDmc, IFormattedValues.OCTAL_FORMAT, 0);
		assertTrue("Register Value is not in OCTAL_FORMAT: " + val, val.startsWith("0"));
		assertThat(Long.parseLong(val.substring(1), 8), equalTo(0x1234L));
	}

	@Test
	public void compareRegisterForMultipleExecutionContexts() throws Throwable {
		MIStoppedEvent stoppedEvent = SyncUtil
				.runToLocation(SOURCE_NAME + ':' + getLineForTag("LINE_MAIN_ALL_THREADS_STARTED"));

		// Get the thread IDs
		final IContainerDMContext containerDmc = DMContexts.getAncestorOfType(stoppedEvent.getDMContext(),
				IContainerDMContext.class);

		Query<IExecutionDMContext[]> queryExecutionContexts = new Query<IExecutionDMContext[]>() {
			@Override
			protected void execute(DataRequestMonitor<IExecutionDMContext[]> rm) {
				fRunControl.getExecutionContexts(containerDmc, rm);
			}
		};

		fRegService.getExecutor().submit(queryExecutionContexts);

		IExecutionDMContext[] ctxts = queryExecutionContexts.get(TestsPlugin.massageTimeout(500),
				TimeUnit.MILLISECONDS);

		assertNotNull(ctxts);
		assertTrue(ctxts.length > 1);

		// Get stack frame for thread 2
		IFrameDMContext frameDmc2 = SyncUtil.getStackFrame(ctxts[1], 0);

		String thread2RegVal0 = getModelDataForRegisterDataValue(frameDmc2, IFormattedValues.NATURAL_FORMAT, 0);
		String thread2RegVal1 = getModelDataForRegisterDataValue(frameDmc2, IFormattedValues.NATURAL_FORMAT, 1);
		String thread2RegVal2 = getModelDataForRegisterDataValue(frameDmc2, IFormattedValues.NATURAL_FORMAT, 2);
		String thread2RegVal3 = getModelDataForRegisterDataValue(frameDmc2, IFormattedValues.NATURAL_FORMAT, 3);
		String thread2RegVal4 = getModelDataForRegisterDataValue(frameDmc2, IFormattedValues.NATURAL_FORMAT, 4);
		String thread2RegVal5 = getModelDataForRegisterDataValue(frameDmc2, IFormattedValues.NATURAL_FORMAT, 5);

		// Get stack frame for thread 1
		IFrameDMContext frameDmc1 = SyncUtil.getStackFrame(ctxts[0], 0);

		getModelDataForRegisterDataValue(frameDmc1, IFormattedValues.NATURAL_FORMAT, 0);

		// Re-set the execution context to 2 and Fetch from the Cache
		String dupliThread2RegVal0 = getModelDataForRegisterDataValue(frameDmc2, IFormattedValues.NATURAL_FORMAT, 0);
		String dupliThread2RegVal1 = getModelDataForRegisterDataValue(frameDmc2, IFormattedValues.NATURAL_FORMAT, 1);
		String dupliThread2RegVal2 = getModelDataForRegisterDataValue(frameDmc2, IFormattedValues.NATURAL_FORMAT, 2);
		String dupliThread2RegVal3 = getModelDataForRegisterDataValue(frameDmc2, IFormattedValues.NATURAL_FORMAT, 3);
		String dupliThread2RegVal4 = getModelDataForRegisterDataValue(frameDmc2, IFormattedValues.NATURAL_FORMAT, 4);
		String dupliThread2RegVal5 = getModelDataForRegisterDataValue(frameDmc2, IFormattedValues.NATURAL_FORMAT, 5);

		// If Values not equal , then context haven't been re-set properly
		assertEquals("Multiple context not working. Execution Context is not reset to 2", thread2RegVal0,
				dupliThread2RegVal0);
		assertEquals("Multiple context not working. Execution Context is not reset to 2", thread2RegVal1,
				dupliThread2RegVal1);
		assertEquals("Multiple context not working. Execution Context is not reset to 2", thread2RegVal2,
				dupliThread2RegVal2);
		assertEquals("Multiple context not working. Execution Context is not reset to 2", thread2RegVal3,
				dupliThread2RegVal3);
		assertEquals("Multiple context not working. Execution Context is not reset to 2", thread2RegVal4,
				dupliThread2RegVal4);
		assertEquals("Multiple context not working. Execution Context is not reset to 2", thread2RegVal5,
				dupliThread2RegVal5);

	}

	private void writeRegister(IFrameDMContext frameDmc, final int regIndex, final String regValue,
			final String formatId) throws Throwable {
		final IRegisterDMContext[] regDMCs = getAllRegisters(frameDmc);
		writeRegister(regDMCs[regIndex], regValue, formatId);
	}

	private void writeRegister(final IRegisterDMContext registerDmc, final String regValue, final String formatId)
			throws Throwable {

		Query<Object> queryAction = new Query<Object>() {
			@Override
			protected void execute(DataRequestMonitor<Object> rm) {
				fRegService.writeRegister(registerDmc, regValue, formatId, rm);
			}
		};

		fRegService.getExecutor().submit(queryAction);
		queryAction.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
	}

	/**
	 * Waits for IRegistersChangedDMEvent(s) during a time interval, collects them and returns them after timeout
	 */
	private List<IRegistersChangedDMEvent> writeRegisterWaitNotication(final IRegisterDMContext registerDmc,
			final String regValue, final String formatId) throws Throwable {
		ServiceEventWaitor<IRegistersChangedDMEvent> eventWaitor = new ServiceEventWaitor<>(fSession,
				IRegistersChangedDMEvent.class);

		writeRegister(registerDmc, regValue, formatId);

		/*
		 * Wait long enough to find out if too few or too many events arrive. On
		 * my testing the expected events arrived within 10 ms, so this timeout
		 * allows 10x that amount of time for unexpected extra events to arrive.
		 */
		return eventWaitor.waitForEvents(TestsPlugin.massageTimeout(100));
	}

	@Test
	public void writeRegisterNaturalFormat() throws Throwable {
		MIStoppedEvent stoppedEvent = getInitialStoppedEvent();
		IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);
		String regValue = "10";
		int regIndex = 3;
		writeRegister(frameDmc, 3, regValue, IFormattedValues.NATURAL_FORMAT);
		String val = getModelDataForRegisterDataValue(frameDmc, IFormattedValues.NATURAL_FORMAT, regIndex);
		assertEquals("Failed writing register", regValue, val);
	}

	@Test
	public void writeRegisterHEXFormat() throws Throwable {
		MIStoppedEvent stoppedEvent = getInitialStoppedEvent();
		IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);
		String regValue = "0x10";
		int regIndex = 3;
		writeRegister(frameDmc, 3, regValue, IFormattedValues.HEX_FORMAT);
		String val = getModelDataForRegisterDataValue(frameDmc, IFormattedValues.HEX_FORMAT, regIndex);
		assertEquals("Failed writing register", regValue, val);
	}

	@Test
	public void writeRegisterBinaryFormat() throws Throwable {
		MIStoppedEvent stoppedEvent = getInitialStoppedEvent();
		IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);
		String regValue = "100101001";
		int regIndex = 3;
		writeRegister(frameDmc, 3, regValue, IFormattedValues.BINARY_FORMAT);
		String val = getModelDataForRegisterDataValue(frameDmc, IFormattedValues.BINARY_FORMAT, regIndex);
		assertEquals("Failed writing register", regValue, val);
	}

	@Test
	public void writeRegisterOctalFormat() throws Throwable {
		MIStoppedEvent stoppedEvent = getInitialStoppedEvent();
		IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);
		// String regValue = "10";
		String regValue = "012";
		int regIndex = 3;
		writeRegister(frameDmc, 3, regValue, IFormattedValues.OCTAL_FORMAT);
		String val = getModelDataForRegisterDataValue(frameDmc, IFormattedValues.OCTAL_FORMAT, regIndex);
		assertEquals("Failed writing register", regValue, val);
	}

	/**
	 * This test validates retrieval of different values for the same register used on different frames
	 */
	@Test
	public void frameSpecificValues() throws Throwable {
		// Step to a multi-level stack to be able to test different stack frames
		SyncUtil.runToLocation("PrintHello");
		MIStoppedEvent stoppedEvent = SyncUtil.step(StepType.STEP_OVER);
		int depth = SyncUtil.getStackDepth(stoppedEvent.getDMContext());

		// we need at least 2 levels of stack frame to continue this test.
		// note: the depth is glibc-version-dependent
		assertTrue(depth >= 2);

		// Resolve the register name of the stack pointer
		String sp_name = resolveStackPointerName();
		assertNotNull(sp_name);

		// Get the stack pointer value for frame0
		IFrameDMContext frame0 = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);
		IRegisterDMContext[] registers_f0 = getTargetRegisters(frame0);
		MIRegisterDMC sp_reg_f0 = (MIRegisterDMC) findStackPointerRegister(sp_name, registers_f0);
		assertNotNull(sp_reg_f0);
		String sp_f0_str = getModelDataForRegisterDataValue(frame0, IFormattedValues.HEX_FORMAT, sp_reg_f0.getRegNo());

		// Get the stack pointer value for frame1
		IFrameDMContext frame1 = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 1);
		IRegisterDMContext[] registers_f1 = getTargetRegisters(frame1);
		MIRegisterDMC sp_reg_f1 = (MIRegisterDMC) findStackPointerRegister(sp_name, registers_f1);
		assertNotNull(sp_reg_f1);
		String sp_f1_str = getModelDataForRegisterDataValue(frame1, IFormattedValues.HEX_FORMAT, sp_reg_f1.getRegNo());

		//The stack pointer's are not expected to be the same among frames
		assertFalse("Stack pointers shall be different among frames", sp_f0_str.equals(sp_f1_str));
	}

	private IRegisterDMContext findStackPointerRegister(String sp_name, IRegisterDMContext[] registerDMCs)
			throws InterruptedException, ExecutionException {
		IRegisterDMData[] registersData = getRegistersData(registerDMCs);
		for (int i = 0; i < registersData.length; i++) {
			IRegisterDMData registerData = registersData[i];

			if (registerData.getName().equals(sp_name)) {
				return registerDMCs[i];
			}
		}

		return null;
	}

	private String resolveStackPointerName() throws Throwable {
		List<String> regNames = getRegistersFromGdb();

		// for 64 bits
		String sp_name = "rsp";
		if (regNames.contains(sp_name)) {
			return sp_name;
		}

		// for 32 bits
		sp_name = "esp";
		if (regNames.contains(sp_name)) {
			return sp_name;
		}

		// for 16 bits
		sp_name = "sp";
		if (regNames.contains(sp_name)) {
			return sp_name;
		}

		return null;
	}

	@Test
	public void getRegisterGroupsData() throws Throwable {
		int grpsIncrement = 3;

		//Group name to Group description
		Map<String, String> groupNameToDescMap = new HashMap<>();
		groupNameToDescMap.put("Group_1", "");
		groupNameToDescMap.put("Group_2", "");
		groupNameToDescMap.put("Group_3", "");
		groupNameToDescMap.put("General Registers", "General Purpose and FPU Register Group");

		//Tracking groups found
		Set<String> groupsFound = new HashSet<>();

		addRegisterGroups(grpsIncrement);
		final IRegisterDMContext[] regInGroup = getRegisters(0, 4);

		IRegisterGroupDMData[] groupsData = getRegisterGroupsData(regInGroup[0]);
		//increment + root
		assertEquals(grpsIncrement + 1, groupsData.length);
		for (IRegisterGroupDMData grpData : groupsData) {
			// Validate group name
			assertTrue(groupNameToDescMap.containsKey(grpData.getName()));
			String grpDataDesc = grpData.getDescription();
			String expectedName = groupNameToDescMap.get(grpData.getName());

			//Validate group description
			assertEquals(expectedName, grpDataDesc);
			groupsFound.add(grpData.getName());
		}

		//Make sure all expected groups were found
		assertEquals(groupNameToDescMap.size(), groupsFound.size());
	}

	@Test
	public void canAddRegisterGroup() throws Throwable {
		// only root group expected
		final IRegisterGroupDMContext[] groups = getRegisterGroups(1);
		assertEquals("Unexpected groups present, only root was expected", 1, groups.length);
		assertTrue("Can not Add register groups", canAddRegisterGroup(groups[0]));
	}

	@Test
	public void canNotEditRootRegisterGroup() throws Throwable {
		// only root group expected
		final IRegisterGroupDMContext[] groups = getRegisterGroups(1);
		assertEquals("Unexpected groups present, only root was expected", 1, groups.length);

		assertFalse("Not expected to allow the editing of the root register group", canEditRegisterGroup(groups[0]));
	}

	@Test
	public void canNotEditUnknownRegisterGroup() throws Throwable {
		// only root group expected
		final IRegisterGroupDMContext group = new IRegisterGroupDMContext() {

			@Override
			public String getSessionId() {
				return "session";
			}

			@Override
			public IDMContext[] getParents() {
				return new IDMContext[0];
			}

			@Override
			public <T> T getAdapter(Class<T> adapter) {
				return null;
			}
		};

		assertFalse("Not expected to allow editing of a non registered group", canEditRegisterGroup(group));
	}

	@Test
	public void canEditRegisterGroup() throws Throwable {
		// only root group expected
		final IRegisterGroupDMContext[] groups = getRegisterGroups(1);
		assertEquals("Unexpected groups present, only root was expected", 1, groups.length);

		IRegisterGroupDMContext group = addDefaultUserGroup();
		assertTrue("Was not allowed to edit register group", canEditRegisterGroup(group));
	}

	/**
	 * @throws Throwable
	 */
	@Test
	public void addRegisterGroups() throws Throwable {
		// Define a subset of registers to create a register group (from, to)
		final IRegisterDMContext[] regInGroup = getRegisters(0, 4);

		// Adding three groups with default names
		String groupName = proposeGroupName();
		addGroup(groupName, regInGroup);
		// Resolve the new group's  sequence number from the pattern "Group_sequenceN"
		int starting_sequence = resolveGroupNameSequence(groupName);

		groupName = proposeGroupName();
		addGroup(groupName, regInGroup);

		groupName = proposeGroupName();
		addGroup(groupName, regInGroup);

		// Retrieve the existing groups, expected and validated to root + 3
		IRegisterGroupDMContext[] groups = getRegisterGroups(4);

		// The groups are returned in reversed order to present latest created
		// first i.e. index 0
		// The newest group shall be at the top then i.e. 0

		//Add a valid execution context to resolve the register values
		IFrameDMContext frameDmc = DMContexts.getAncestorOfType(regInGroup[0], IFrameDMContext.class);
		CompositeDMContext compositeDmc = new CompositeDMContext(new IDMContext[] { frameDmc, groups[0] });
		IRegisterDMContext[] readRegisters = getRegisters(compositeDmc);

		// Same order, same data and same values are expected, although different instances to different parents
		assertTrue(sameData(regInGroup, readRegisters, false));

		// Assert the last created group name
		IRegisterGroupDMData groupData = getRegisterGroupData(groups[0]);
		// 2 additional groups after creation of the base group in this test case
		assertEquals("Group_" + (starting_sequence + 2), groupData.getName());
	}

	@Test
	public void editRegisterGroup() throws Throwable {
		// Get Register Groups
		IRegisterGroupDMContext[] groups = getRegisterGroups(1);
		assertEquals("unexpected groups present", 1, groups.length);

		IRegisterGroupDMContext group = addDefaultUserGroup();

		IRegisterDMContext[] origRegisters = getRegisters(group);

		// Assert the default group name
		IRegisterGroupDMData groupData = getRegisterGroupData(group);
		assertEquals(GROUP_X, groupData.getName());

		// Registers to associate to exiting default group
		IRegisterDMContext[] newRegisters = getRegisters(5, 9);

		// A different set of registers being assigned to the group
		assertFalse((sameData(origRegisters, newRegisters, false)));

		// Modify the name and associated registers of the default group
		editGroup(group, GROUP_Y, newRegisters);

		groupData = getRegisterGroupData(group);
		assertEquals(GROUP_Y, groupData.getName());

		IFrameDMContext frameDmc = DMContexts.getAncestorOfType(newRegisters[0], IFrameDMContext.class);

		//Read the context with a valid execution context
		CompositeDMContext compositeDmc = new CompositeDMContext(new IDMContext[] { group, frameDmc });
		IRegisterDMContext[] readRegisters = getRegisters(compositeDmc);

		//Same data but not from the same parent group, newRegisters from root, readRegisters from GroupY
		assertTrue(sameData(newRegisters, readRegisters, false));

	}

	@Test
	public void canRemoveRegisterGroup() throws Throwable {
		// only root group expected
		IRegisterGroupDMContext[] groups = getRegisterGroups(1);
		assertFalse("Removal of root register group shall not be allowed", canRemoveRegisterGroups(groups));

		//Add another two groups
		// Define a subset of registers to create a register group (from, to)
		final IRegisterDMContext[] regInGroup = getRegisters(0, 4);

		// Adding three groups with default names
		String groupName = proposeGroupName();
		addGroup(groupName, regInGroup);

		groupName = proposeGroupName();
		addGroup(groupName, regInGroup);

		groupName = proposeGroupName();
		addGroup(groupName, regInGroup);

		// Retrieve the existing groups, expected and validated to root + 3
		groups = getRegisterGroups(4);

		//Remove the root group from the list, so the remaining can be validated
		//as can be removed
		groups = Arrays.copyOfRange(groups, 0, groups.length - 1);
		assertTrue("Not allowing removal of groups", canRemoveRegisterGroups(groups));

		//remove the non root groups and validate the result
		removeGroups(groups);
		groups = getRegisterGroups(1);
	}

	@Test
	public void removeRegisterGroups() throws Throwable {
		int grpsIncrement = 3;
		addRegisterGroups(grpsIncrement);

		// Retrieve the existing groups, expected and validated to 1 root + 3
		IRegisterGroupDMContext[] groups = getRegisterGroups(1 + grpsIncrement);

		// remove one and assert the new size
		IRegisterGroupDMContext[] remGroups = new IRegisterGroupDMContext[] { groups[0] };
		removeGroups(remGroups);
		getRegisterGroups(grpsIncrement); // assert this to root + 2

		// remove two more and assert the new size
		remGroups = new IRegisterGroupDMContext[] { groups[1], groups[2] };
		removeGroups(remGroups);
		getRegisterGroups(1); // assert this to only one i.e. root

		// attempt to remove root -- Shall not be allowed
		remGroups = new IRegisterGroupDMContext[] { groups[3] };
		removeGroups(remGroups);
		getRegisterGroups(1); // assert this to only one i.e. root
	}

	/**
	 * The root group shall not be deleted i.e. ignore and preserved
	 */
	@Test
	public void removeRegisterGroupsWithRoot() throws Throwable {
		int grpsIncrement = 3;
		addRegisterGroups(grpsIncrement);

		// Retrieve the existing groups, expected and validated to 1 root + 3
		IRegisterGroupDMContext[] groups = getRegisterGroups(1 + grpsIncrement);

		// Attempt to remove all i.e. root + user defined register groups
		removeGroups(groups);
		getRegisterGroups(1); // assert root is preserved
	}

	@Test
	public void canRestoreRegisterGroups() throws Throwable {
		int grpsIncrement = 3;
		addRegisterGroups(grpsIncrement);
		//Always able to restore to default
		assertTrue(canRestoreDefaultGroups());
	}

	@Test
	public void restoreRegisterGroups() throws Throwable {
		int grpsIncrement = 3;
		addRegisterGroups(grpsIncrement);

		restoreDefaultGroups();

		// assert all groups are gone except root
		getRegisterGroups(1);
	}

	@Test
	public void getRegisterNames() throws Throwable {
		int grpsIncrement = 2;
		addRegisterGroups(grpsIncrement);
		IRegisterGroupDMContext[] groups = getRegisterGroups(3); // root +
																	// increment

		IRegisterDMContext[] regDmcs1 = getRegisters(groups[0]);
		IRegisterDMContext[] regDmcs2 = getRegisters(groups[1]);

		IRegisterDMData[] regNames1 = getRegistersData(regDmcs1);
		IRegisterDMData[] regNames2 = getRegistersData(regDmcs2);

		// assert the register names match on both groups
		assertTrue(sameRegisterNames(regNames1, regNames2));
	}

	@Test
	public void saveAndReadRegisterGroupData() throws Throwable {
		//Only the default group is expected
		int starting_sequence = 0;

		int grpsIncrement = 2;
		addRegisterGroups(grpsIncrement);
		getRegisterGroups(3); // root + 2

		//The two steps below would ideally use a shutdown and the start of a new launch configuration within the same case.
		//However the approach below accomplishes verification of saving and reading with out over complicating the current base test case structure.

		//trigger groups saving
		saveRegGroups();

		//trigger group reading from launch configuration
		resetRegService();

		IRegisterGroupDMContext[] groups = getRegisterGroups(3); // root + 2
		// Assert the last created group name
		IRegisterGroupDMData groupData = getRegisterGroupData(groups[0]);
		// 2 additional groups after creation of the base group in this test case
		assertEquals("Group_" + (starting_sequence + grpsIncrement), groupData.getName());
	}

	/**
	 * All groups shall be able to write / update register values, These new value shall be propagated to any other
	 * group(s) containing this register
	 */
	@Test
	public void writeRegisterFromUserGroup() throws Throwable {
		// Define a subset of registers common to other register groups
		// indexes (from, to)
		final IRegisterDMContext[] regInRootGroup = getRegisters(0, 4);
		String oirigVal = getModelDataForRegisterDataValue(regInRootGroup[0], IFormattedValues.NATURAL_FORMAT);

		// Get a handle to the execution contexts, same frame execution context for all actions
		IFrameDMContext frameDmc = DMContexts.getAncestorOfType(regInRootGroup[0], IFrameDMContext.class);

		// create two user groups containing the same registers (new register instances based on root)
		String groupNameOne = proposeGroupName();
		addGroup(groupNameOne, regInRootGroup);

		String groupNameTwo = proposeGroupName();
		addGroup(groupNameTwo, regInRootGroup);

		// Retrieve the existing groups, expected and validated to root + 2
		IRegisterGroupDMContext[] groups = getRegisterGroups(3);

		// Read registers from group one
		// index 0 -> root group; index1 -> group one, index2 -> group Two
		CompositeDMContext compositeDmc = new CompositeDMContext(new IDMContext[] { frameDmc, groups[1] });
		IRegisterDMContext[] groupOneRegisters = getRegisters(compositeDmc);

		// write a register value from register group one, register index 0
		Long iWrittenVal = Long.valueOf(oirigVal) + Long.valueOf(5);
		String writtenValue = iWrittenVal.toString();
		List<IRegistersChangedDMEvent> eventNotifications = writeRegisterWaitNotication(groupOneRegisters[0],
				writtenValue, IFormattedValues.NATURAL_FORMAT);

		// Validate IRegistersChangedDMEvent event notifications, one notification per group to trigger UI refresh
		assertNotNull("No IRegistersChangedDMEvent were generated from the register value update", eventNotifications);
		assertEquals("Incorrect number of IRegistersChangedDMEvent notifications, expecting one per group", 3,
				eventNotifications.size());

		// read the register value from user group two
		compositeDmc = new CompositeDMContext(new IDMContext[] { frameDmc, groups[2] });
		IRegisterDMContext[] groupTwoRegisters = getRegisters(compositeDmc);
		String readVal = getModelDataForRegisterDataValue(groupTwoRegisters[0], IFormattedValues.NATURAL_FORMAT);

		// assert the value from group two has been updated
		assertEquals(
				"Register[0] Value read from group two does not correspond to updated value written from group one",
				writtenValue, readVal);

		// read the register value from the root register group
		readVal = getModelDataForRegisterDataValue(regInRootGroup[0], IFormattedValues.NATURAL_FORMAT);

		// assert the value from root group has also been updated
		assertEquals(
				"Register[0] Value read from root group does not correspond to updated value written from group one",
				writtenValue, readVal);

	}

	/**
	 * Get an array with all available register groups
	 */
	private IRegisterGroupDMContext[] getRegisterGroups() throws Throwable {

		Query<IRegisterGroupDMContext[]> queryGroupsCtx = new Query<IRegisterGroupDMContext[]>() {
			@Override
			protected void execute(DataRequestMonitor<IRegisterGroupDMContext[]> rm) {
				fRegService.getRegisterGroups(fCompositeDmc, rm);
			}
		};

		fRegService.getExecutor().execute(queryGroupsCtx);

		IRegisterGroupDMContext[] regGroupsDMCs = queryGroupsCtx.get(TestsPlugin.massageTimeout(500),
				TimeUnit.MILLISECONDS);

		return (regGroupsDMCs);
	}

	/**
	 * Request the existing groups and validate an expected count
	 */
	private IRegisterGroupDMContext[] getRegisterGroups(int expectedCount) throws Throwable {
		IRegisterGroupDMContext[] regGroupsDMCs = getRegisterGroups();
		assertEquals("Number of groups present", //$NON-NLS-1$
				expectedCount, regGroupsDMCs.length);

		return regGroupsDMCs;
	}

	private String proposeGroupName() throws Throwable {
		return PROPOSE_GROUP_NAME_BASE + ++fGroupNameSuffix;
	}

	private boolean canAddRegisterGroup(final IDMContext context) throws Throwable {
		Query<Boolean> queryAction = new Query<Boolean>() {
			@Override
			protected void execute(DataRequestMonitor<Boolean> rm) {
				fRegService.canAddRegisterGroup(context, rm);
			}
		};

		fRegService.getExecutor().submit(queryAction);
		return queryAction.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
	}

	private boolean canEditRegisterGroup(final IRegisterGroupDMContext context) throws Throwable {
		Query<Boolean> queryAction = new Query<Boolean>() {
			@Override
			protected void execute(DataRequestMonitor<Boolean> rm) {
				fRegService.canEditRegisterGroup(context, rm);
			}
		};

		fRegService.getExecutor().submit(queryAction);
		return queryAction.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
	}

	private boolean canRemoveRegisterGroups(final IRegisterGroupDMContext[] groupsContext) throws Throwable {
		Query<Boolean> queryAction = new Query<Boolean>() {
			@Override
			protected void execute(DataRequestMonitor<Boolean> rm) {
				fRegService.canRemoveRegisterGroups(groupsContext, rm);
			}
		};

		fRegService.getExecutor().submit(queryAction);
		return queryAction.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
	}

	private void addGroup(final String groupName, final IRegisterDMContext[] regIndexes) throws Throwable {

		if (regIndexes == null || regIndexes.length < 1) {
			fail("Invalid argument regIndexes");
			return;
		}

		Query<Object> queryAction = new Query<Object>() {
			@Override
			protected void execute(DataRequestMonitor<Object> rm) {
				IContainerDMContext contDmc = DMContexts.getAncestorOfType(regIndexes[0], IContainerDMContext.class);
				fRegService.addRegisterGroup(contDmc, groupName, regIndexes, rm);
			}
		};

		fRegService.getExecutor().submit(queryAction);
		queryAction.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
	}

	private void editGroup(final IRegisterGroupDMContext group, final String newGroupName,
			final IRegisterDMContext[] regIndexes) throws Throwable {

		Query<Object> queryAction = new Query<Object>() {
			@Override
			protected void execute(DataRequestMonitor<Object> rm) {
				fRegService.editRegisterGroup(group, newGroupName, regIndexes, rm);
			}
		};

		fRegService.getExecutor().submit(queryAction);
		queryAction.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
	}

	private void removeGroups(final IRegisterGroupDMContext[] groups) throws Throwable {

		Query<Object> queryAction = new Query<Object>() {
			@Override
			protected void execute(DataRequestMonitor<Object> rm) {
				fRegService.removeRegisterGroups(groups, rm);
			}
		};

		fRegService.getExecutor().submit(queryAction);
		queryAction.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
	}

	private boolean canRestoreDefaultGroups() throws Throwable {
		Query<Boolean> queryCanRestore = new Query<Boolean>() {

			@Override
			protected void execute(DataRequestMonitor<Boolean> rm) {
				//selection context not used for the time being
				fRegService.canRestoreDefaultGroups(null, rm);
			}

		};

		fRegService.getExecutor().submit(queryCanRestore);

		//Validate, we can always restore to defaults
		return queryCanRestore.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
	}

	private void restoreDefaultGroups() throws Throwable {
		Query<Object> queryRestore = new Query<Object>() {

			@Override
			protected void execute(DataRequestMonitor<Object> rm) {
				//selection context not used for the time being
				fRegService.restoreDefaultGroups(null, rm);
			}

		};

		fRegService.getExecutor().submit(queryRestore);

		queryRestore.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
	}

	private void resetRegService() throws Throwable {
		assert (fRegService instanceof GDBRegisters);
		final GDBRegisters regManager = (GDBRegisters) fRegService;
		Query<Object> queryReset = new Query<Object>() {

			@Override
			protected void execute(DataRequestMonitor<Object> rm) {
				regManager.reset(rm);
			}

		};

		regManager.getExecutor().submit(queryReset);

		queryReset.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
	}

	private void saveRegGroups() throws Throwable {
		assert (fRegService instanceof GDBRegisters);
		final GDBRegisters regManager = (GDBRegisters) fRegService;
		Query<Object> querySave = new Query<Object>() {

			@Override
			protected void execute(DataRequestMonitor<Object> rm) {
				regManager.save();
				rm.done();
			}

		};

		regManager.getExecutor().submit(querySave);

		querySave.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
	}

	private IRegisterDMData getRegisterData(final IRegisterDMContext registerDmc) throws Throwable {
		Query<IRegisterDMData> registerDataQ = new Query<IRegisterDMData>() {
			@Override
			protected void execute(DataRequestMonitor<IRegisterDMData> rm) {
				fRegService.getRegisterData(registerDmc, rm);
			}
		};

		fRegService.getExecutor().submit(registerDataQ);
		IRegisterDMData registerData = registerDataQ.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
		assertNotNull(registerData);

		return registerData;
	}

	private FormattedValueDMData getRegisterValue(final IRegisterDMContext registerDmc) throws Throwable {
		Query<FormattedValueDMData> registerValueQ = new Query<FormattedValueDMData>() {
			@Override
			protected void execute(DataRequestMonitor<FormattedValueDMData> rm) {
				FormattedValueDMContext valueDmc = new FormattedValueDMContext(fRegService, registerDmc,
						IFormattedValues.NATURAL_FORMAT);
				fRegService.getFormattedExpressionValue(valueDmc, rm);
			}
		};

		fRegService.getExecutor().submit(registerValueQ);
		FormattedValueDMData registerValue = registerValueQ.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
		assertNotNull(registerValue);

		return registerValue;
	}

	private IRegisterGroupDMData getRegisterGroupData(final IRegisterGroupDMContext groupDmc) throws Throwable {
		Query<IRegisterGroupDMData> groupDataQ = new Query<IRegisterGroupDMData>() {
			@Override
			protected void execute(DataRequestMonitor<IRegisterGroupDMData> rm) {
				fRegService.getRegisterGroupData(groupDmc, rm);
			}
		};

		fRegService.getExecutor().submit(groupDataQ);
		IRegisterGroupDMData groupData = groupDataQ.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
		assertNotNull(groupData);

		return groupData;
	}

	private IRegisterGroupDMData[] getRegisterGroupsData(final IDMContext dmc) throws Throwable {
		Query<IRegisterGroupDMData[]> groupDataQ = new Query<IRegisterGroupDMData[]>() {
			@Override
			protected void execute(DataRequestMonitor<IRegisterGroupDMData[]> rm) {
				getRegisterGroupsData(dmc, rm);
			}
		};

		fRegService.getExecutor().submit(groupDataQ);
		IRegisterGroupDMData[] groupsData = groupDataQ.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS);
		assertNotNull(groupsData);

		return groupsData;
	}

	private void getRegisterGroupsData(final IDMContext dmc, final DataRequestMonitor<IRegisterGroupDMData[]> rm) {
		assert (dmc != null);
		final DsfExecutor executor = fRegService.getExecutor();
		// First get all register group contexts, any register context can be used to resolve the container context
		fRegService.getRegisterGroups(dmc, new DataRequestMonitor<IRegisterGroupDMContext[]>(executor, rm) {
			@Override
			protected void handleSuccess() {
				final IRegisterGroupDMContext[] groupsCtx = getData();
				assert (groupsCtx != null);

				final IRegisterGroupDMData[] groupsData = new IRegisterGroupDMData[groupsCtx.length];

				final CountingRequestMonitor crm = new CountingRequestMonitor(executor, rm) {
					@Override
					protected void handleCompleted() {
						rm.setData(groupsData);
						rm.done();
					}
				};

				// Resolve all register group data
				for (int i = 0; i < groupsCtx.length; i++) {
					final int index = i;
					fRegService.getRegisterGroupData(groupsCtx[index],
							new DataRequestMonitor<IRegisterGroupDMData>(executor, crm) {
								@Override
								protected void handleSuccess() {
									groupsData[index] = getData();
									crm.done();
								}
							});
				}

				crm.setDoneCount(groupsCtx.length);
			}
		});
	}

	private IRegisterDMContext[] getRegisters(int from, int to) throws Throwable {
		MIStoppedEvent stoppedEvent = getInitialStoppedEvent();
		IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);
		final IRegisterDMContext[] regDMCs = getAllRegisters(frameDmc);

		// Shall not ask for more than is available
		assertTrue(regDMCs.length > to);

		// Retrieve the register range
		final IRegisterDMContext[] regRange = Arrays.copyOfRange(regDMCs, from, to);

		return regRange;
	}

	private IRegisterGroupDMContext addDefaultUserGroup() throws Throwable {
		// Define a subset of registers to create a register group
		final IRegisterDMContext[] regInGroup = getRegisters(0, 4);
		// Request the addition of the new group
		addGroup(GROUP_X, regInGroup);

		// Retrieve the existing groups, expected and validated to 2
		IRegisterGroupDMContext[] groups = getRegisterGroups(2);

		// The groups are returned in reversed order to present latest created
		// first i.e. index 0
		// Our new group shall be at the top then i.e. 0
		IFrameDMContext frameDmc = DMContexts.getAncestorOfType(regInGroup[0], IFrameDMContext.class);
		CompositeDMContext compositeDmc = new CompositeDMContext(new IDMContext[] { groups[0], frameDmc });
		IRegisterDMContext[] readRegisters = getRegisters(compositeDmc);

		// Same objects and same order expected, although different parents
		assertTrue(sameData(regInGroup, readRegisters, false));
		return groups[0];
	}

	private IRegisterGroupDMContext[] addRegisterGroups(int numberOfNewGroups) throws Throwable {
		// Define a subset of registers to associate to the new register groups
		final IRegisterDMContext[] regInGroup = getRegisters(0, 4);
		for (int i = 0; i < numberOfNewGroups; i++) {
			String groupName = proposeGroupName();
			addGroup(groupName, regInGroup);
		}

		// Expected number of groups = Root + numberofNewGroups
		return getRegisterGroups(1 + numberOfNewGroups);
	}

	/**
	 * Check the register Data entry names are in the same order
	 */
	private boolean sameRegisterNames(IRegisterDMData[] regNames1, IRegisterDMData[] regNames2) {
		boolean same = false;
		if (regNames1.length == regNames2.length) {
			for (int i = 0; i < regNames1.length; i++) {
				if (regNames1[i].getName().equals(regNames2[i].getName())) {
					continue;
				} else {
					// Found a different name, Not the same !!
					return false;
				}
			}

			// All names matched !!
			return true;
		}

		return same;
	}

	private boolean sameData(IRegisterDMContext[] regArrOne, IRegisterDMContext[] regArrTwo, boolean sameParentGroup)
			throws Throwable {
		if (regArrOne.length != regArrTwo.length) {
			return false;
		}

		for (int i = 0; i < regArrOne.length; i++) {
			if (sameParentGroup) {
				//same group parent expected
				final IRegisterGroupDMContext parentGroupOne = DMContexts.getAncestorOfType(regArrOne[i],
						IRegisterGroupDMContext.class);
				final IRegisterGroupDMContext parentGroupTwo = DMContexts.getAncestorOfType(regArrTwo[i],
						IRegisterGroupDMContext.class);
				if (!parentGroupOne.equals(parentGroupTwo)) {
					return false;
				}
			}

			//same data
			IRegisterDMData dataOne = getRegisterData(regArrOne[i]);
			IRegisterDMData dataTwo = getRegisterData(regArrTwo[i]);

			if (!dataOne.getName().equals(dataTwo.getName())) {
				return false;
			}

			//same value
			FormattedValueDMData valueOne = getRegisterValue(regArrOne[i]);
			FormattedValueDMData valueTwo = getRegisterValue(regArrTwo[i]);
			if (!valueOne.getFormattedValue().equals(valueTwo.getFormattedValue())) {
				return false;
			}
		}

		//All data is the same
		return true;
	}

	/**
	 * Resolve from proposed name to group sequence number e.g Group_5 -> 5
	 */
	private int resolveGroupNameSequence(String groupName) {
		int sequence = 0;
		String[] strSequence = groupName.split("_");
		assertEquals(2, strSequence.length);
		sequence = Integer.parseInt(strSequence[1]);
		return sequence;
	}

}
