/*******************************************************************************
 * Copyright (c) 2014 Ericsson AB 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:
 *     Alvaro Sanchez-Leon (Ericsson) - First Implementation and API (Bug 235747)
 *******************************************************************************/
package org.eclipse.cdt.dsf.debug.internal.ui.actions;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.cdt.debug.core.model.IRegisterDescriptor;
import org.eclipse.cdt.debug.internal.core.model.IRegisterGroupDescriptor;
import org.eclipse.cdt.debug.internal.ui.actions.RegisterGroupDialog;
import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
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.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.internal.ui.Messages;
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.IRegisters2;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.core.commands.AbstractHandler;
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.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchPart;

public abstract class AbstractDsfRegisterGroupActions extends AbstractHandler {
	private static final String BLANK_STRING = ""; //$NON-NLS-1$
	private static final String REG_GROUP_ACTION_FAILED = "Register Group Action failed\n"; //$NON-NLS-1$

	private class RegisterGroupDialogRunnable implements Runnable {
		private String fGroupName = BLANK_STRING;
		private IRegisterDescriptor[] fSelectedRegisters = null;
		private final IRegisterDescriptor[] fallRegisters;
		private final Shell fShell;
		private final DataRequestMonitor<IRegisterGroupDescriptor> fMonitor;

		private RegisterGroupDialogRunnable(Shell shell, String groupName, IRegisterDescriptor[] allRegisters,
				IRegisterDescriptor[] selectedRegisters, DataRequestMonitor<IRegisterGroupDescriptor> rm) {
			fallRegisters = allRegisters;
			fSelectedRegisters = selectedRegisters;
			fShell = shell;
			fGroupName = groupName;
			fMonitor = rm;
		}

		@Override
		public void run() {
			RegisterGroupDialog dialog = new RegisterGroupDialog(fShell, fGroupName, fallRegisters, fSelectedRegisters);
			if (dialog.open() == Window.OK) {
				String groupName = dialog.getName();
				IRegisterDescriptor[] iSelectedRegisters = dialog.getDescriptors();
				IRegisterGroupDescriptor groupDescriptor = createGroupDescriptor(groupName, iSelectedRegisters);

				fMonitor.setData(groupDescriptor);
			} else {
				fMonitor.cancel();
				return;
			}

			fMonitor.done();
		}
	}

	private class RegisterDescriptor implements IRegisterDescriptor {
		private final IRegisterDMContext fRegContext;
		private String fOriginalGroupName = BLANK_STRING;
		private String fName = BLANK_STRING;

		private RegisterDescriptor(String groupName, IRegisterDMContext regContext, String name) {
			fRegContext = regContext;
			fName = name;
			// initial group Name
			fOriginalGroupName = groupName;
		}

		@Override
		public String getName() {
			return fName;
		}

		@Override
		public String getGroupName() {
			return fOriginalGroupName;
		}
	}

	private class SelectionDMContext {
		private final IDMContext fcontext;
		private final DsfSession fsession;

		private SelectionDMContext(IStructuredSelection selection) throws DebugException {
			if (!(selection.getFirstElement() instanceof IDMVMContext)) {
				abort("Unrecognized element from the provided register selection"); //$NON-NLS-1$
			}

			// Resolve the context
			IDMVMContext context = (IDMVMContext) selection.getFirstElement();
			fcontext = context.getDMContext();

			// Resolve the session
			String sessionId = fcontext.getSessionId();
			fsession = DsfSession.getSession(sessionId);

			if (fsession == null || !(fsession.isActive())) {
				abort("Sesssion inactive"); //$NON-NLS-1$
			}
		}

		/**
		 * This method has to be called under the executor's thread
		 */
		public IRegisters2 resolveService() throws DebugException {
			// Resolve the registers service
			DsfServicesTracker tracker = new DsfServicesTracker(DsfUIPlugin.getBundleContext(), fsession.getId());
			IRegisters service = tracker.getService(IRegisters.class, null);

			tracker.dispose();

			if (!(service instanceof IRegisters2)) {
				abort("Unable to resolve IRegisters2 service"); //$NON-NLS-1$
			}

			return (IRegisters2) service;
		}

		private void abort(String message) throws DebugException {
			// Interrupt on error
			IStatus status = new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IStatus.ERROR, message, null);
			throw new DebugException(status);
		}

	}

	private interface DialogRegisterProvider {
		public IRegisterDescriptor[] getAllRegisters();

		public IRegisterDescriptor[] getcheckedRegisters();
	}

	protected void addRegisterGroup(final IWorkbenchPart part, final IStructuredSelection selection) {
		try {
		final SelectionDMContext selectionContext = new SelectionDMContext(selection);
		selectionContext.fsession.getExecutor().execute(new DsfRunnable() {

			@Override
			public void run() {
				final IRegisters2 registersService;
				try {
					registersService = selectionContext.resolveService();
				} catch (CoreException e) {
					failed(e);
					return;
				}

				// continue to process
				processAddRegisterGroup(part.getSite().getShell(), selectionContext,
						resolveSelectedRegisters(selection), registersService);
			}});
		
		} catch (DebugException e) {
		}
	}

	protected boolean canAddRegisterGroup(IWorkbenchPart part, IStructuredSelection selection) {
		try {
			final SelectionDMContext selectionContext = new SelectionDMContext(selection);
			Query<Boolean> query = new Query<Boolean>() {
				@Override
				protected void execute(DataRequestMonitor<Boolean> rm) {
					IRegisters2 registersService;
					try {
						registersService = selectionContext.resolveService();
					} catch (DebugException e) {
						rm.setData(false);
						rm.done();
						return;
					}

					if (registersService != null) {
						registersService.canAddRegisterGroup(selectionContext.fcontext, rm);
					} else {
						rm.setData(false);
						rm.done();
					}
				}
			};
			selectionContext.fsession.getExecutor().execute(query);
			return query.get();
		} catch (RejectedExecutionException e) {
		} catch (InterruptedException e) {
		} catch (ExecutionException e) {
		} catch (DebugException e1) {
		}

		return false;
	}

	protected void editRegisterGroup(final IWorkbenchPart part, IStructuredSelection selection) {
		try {
		final SelectionDMContext selectionContext = new SelectionDMContext(selection);
		
		selectionContext.fsession.getExecutor().execute(new DsfRunnable() {
			@Override
			public void run() {
				// Create a services tracker
				final IRegisters2 registersService;
				try {
					registersService = selectionContext.resolveService();
				} catch (CoreException e) {
					failed(e);
					return;
				}
				
				processEditRegisterGroup(part.getSite().getShell(), selectionContext,
						registersService);
			}
			
		});
	} catch (DebugException e) {
	}
	}

	protected boolean canEditRegisterGroup(IWorkbenchPart part, IStructuredSelection selection) {
		try {
			final SelectionDMContext selectionContext = new SelectionDMContext(selection);
			final IDMContext context = selectionContext.fcontext;

			// The group to be edited needs to be selected
			if (!(context instanceof IRegisterGroupDMContext)) {
				return false;
			}

			Query<Boolean> query = new Query<Boolean>() {
				@Override
				protected void execute(final DataRequestMonitor<Boolean> rm) {
					IRegisters2 registersService;
					try {
						registersService = selectionContext.resolveService();
					} catch (DebugException e) {
						rm.setData(false);
						rm.done();
						return;
					}

					if (registersService != null) {
						registersService.canEditRegisterGroup((IRegisterGroupDMContext) context, rm);
					} else {
						rm.setData(false);
						rm.done();
					}
				}
			};
			selectionContext.fsession.getExecutor().execute(query);
			return query.get();

		} catch (RejectedExecutionException e) {
		} catch (InterruptedException e) {
		} catch (ExecutionException e) {
		} catch (DebugException e1) {
		}

		return false;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.internal.ui.actions.IRegisterGroupActionsTarget#removeRegisterGroups(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.IStructuredSelection)
	 */
	protected void removeRegisterGroups(IWorkbenchPart part, IStructuredSelection selection) {
		try {
		final SelectionDMContext selectionContext = new SelectionDMContext(selection);
		
		final IRegisterGroupDMContext[] groups = resolveSelectedGroups(selection);
		selectionContext.fsession.getExecutor().execute(new DsfRunnable() {
			@Override
			public void run() {
				IRegisters2 registersService;
				try {
					registersService = selectionContext.resolveService();
				} catch (CoreException e) {
					failed(e);
					return;
				}

				registersService.removeRegisterGroups(groups, new RequestMonitor(registersService.getExecutor(), null) {
				});
			}			
		});
		} catch (DebugException e) {
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.internal.ui.actions.IRegisterGroupActionsTarget#canRemoveRegisterGroups(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.IStructuredSelection)
	 */
	protected boolean canRemoveRegisterGroups(IWorkbenchPart part, IStructuredSelection selection) {
		final SelectionDMContext selectionContext;
		try {
			selectionContext = new SelectionDMContext(selection);
		} catch (DebugException e) {
			// No DM context present or group registers service found in the selection
			return false;
		}
		
		//resolve the selected groups
		final IRegisterGroupDMContext[] groups = resolveSelectedGroups(selection);
		if (groups == null || groups.length < 1){
			return false;
		}
		
		//Prepare to Query the service and check if the selected groups can be removed
		Query<Boolean> query = new Query<Boolean>() {
			@Override
			protected void execute(DataRequestMonitor<Boolean> rm) {
				IRegisters2 regService;
				try {
					regService = selectionContext.resolveService();
				} catch (DebugException e) {
					// Unable to resolve the registers service
					rm.setData(false);
					rm.done();
					return;
				}
				
				regService.canRemoveRegisterGroups(groups, rm);
			}
		};
		
		//Execute the query
		selectionContext.fsession.getExecutor().execute(query);

		try {
			// return the answer from the service
			return query.get();
		} catch (InterruptedException e) {
		} catch (ExecutionException e) {
		}
		
		// No positive answer from the service
		return false;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.internal.ui.actions.IRegisterGroupActionsTarget#restoreDefaultGroups(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.IStructuredSelection)
	 */
	protected void restoreDefaultGroups(IWorkbenchPart part, IStructuredSelection selection) {
		if (!restoreConfirmed()) {
			return;
		}

		try {
			final SelectionDMContext selectionContext = new SelectionDMContext(selection);

			selectionContext.fsession.getExecutor().execute(new DsfRunnable() {
				@Override
				public void run() {
					IRegisters2 registersService;
					try {
						registersService = selectionContext.resolveService();
					} catch (CoreException e) {
						failed(e);
						return;
					}

					// no success handler needed
					registersService.restoreDefaultGroups(null, new RequestMonitor(registersService.getExecutor(), null));
				}
			});
		} catch (DebugException e) {
			failed(e);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.internal.ui.actions.IRegisterGroupActionsTarget#canRestoreDefaultGroups(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.IStructuredSelection)
	 */
	protected boolean canRestoreDefaultGroups(IWorkbenchPart part, IStructuredSelection selection) {
		final SelectionDMContext selectionContext;
		try {
			selectionContext = new SelectionDMContext(selection);
		} catch (DebugException e) {
			// No DM context present or group registers service found in the selection
			return false;
		}
		
		//Prepare to Query the service
		Query<Boolean> query = new Query<Boolean>() {
			@Override
			protected void execute(DataRequestMonitor<Boolean> rm) {
				IRegisters2 regService;
				try {
					regService = selectionContext.resolveService();
				} catch (DebugException e) {
					// Unable to resolve the registers service
					rm.setData(false);
					rm.done();
					return;
				}
				
				regService.canRestoreDefaultGroups(selectionContext.fcontext, rm);
			}
		};
		
		//Execute the query
		selectionContext.fsession.getExecutor().execute(query);

		try {
			// return the answer from the service
			return query.get();
		} catch (InterruptedException e) {
		} catch (ExecutionException e) {
		}
		
		// No positive answer from the service
		return false;
	}

	@ConfinedToDsfExecutor("selectionContext.fsession.getExecutor()")
	private void processAddRegisterGroup(final Shell shell, final SelectionDMContext selectionContext, final IRegisterDMContext[] selectedRegisters,
			final IRegisters2 regServiceManager) {

		final DsfSession session = selectionContext.fsession;
		final DsfExecutor executor = session.getExecutor();

		final IContainerDMContext contDmc = DMContexts.getAncestorOfType(selectionContext.fcontext, IContainerDMContext.class);
		// Using the container context to get all existing registers from the target instead of a limited set of registers for a selected group 
		regServiceManager.getRegisters(contDmc, new DataRequestMonitor<IRegisterDMContext[]>(
				executor, null) {
			
			@Override
			protected void handleSuccess() {
				// Get Register Contexts
				final IRegisterDMContext[] rootRegisters = getData();
				
				if (rootRegisters.length < 1) {
					//The target is expected to have registers, an error has happened !
					assert false;
					noRegisterGroupFoundErr("Add Register Group", this); //$NON-NLS-1$
					return;
				}
				
				//Find the root register group, containing all the registers associated to a target, from any of the root registers
				final IRegisterGroupDMContext rootGroupDmc = DMContexts.getAncestorOfType(rootRegisters[0], IRegisterGroupDMContext.class);

				// Get data for all available registers
				getRegistersData(rootRegisters, regServiceManager, new DataRequestMonitor<IRegisterDMData[]>(executor, null) {
					@Override
					protected void handleSuccess() {
						final IRegisterDMData[] rootRegistersData = getData();
						
						getRegistersData(selectedRegisters, regServiceManager, new DataRequestMonitor<IRegisterDMData[]>(executor, null) {
							@Override
							protected void handleSuccess() {
								// Get data for all selected registers i.e. selected for the new group
								final IRegisterDMData[] selectedRegistersData = getData();
								
								//Need the root group name to build register descriptors 
								regServiceManager.getRegisterGroupData(rootGroupDmc,
										new DataRequestMonitor<IRegisterGroupDMData>(executor, null) {
											@Override
											protected void handleSuccess() {
												final IRegisterGroupDMData rootGroupData = getData();
												// request for the next unused group name to propose it to the user
												proposeGroupName(rootRegisters, regServiceManager, new DataRequestMonitor<String>(executor, null) {
															@Override
															protected void handleSuccess() {
																String proposedGroupName = getData();

																String rootGroupName = (rootGroupData == null) ? BLANK_STRING
																		: rootGroupData.getName();
																// Create the Register descriptors
																DialogRegisterProvider descriptors = buildDescriptors(
																		rootGroupName, rootRegisters,
																		rootRegistersData,
																		selectedRegistersData);
																// Create Dialog Resolve selection to DSF
																// Registers
																getDialogSelection(
																		shell,
																		proposedGroupName,
																		descriptors.getAllRegisters(),
																		descriptors.getcheckedRegisters(),
																		new DataRequestMonitor<IRegisterGroupDescriptor>(
																				executor, null) {
																			@Override
																			protected void handleSuccess() {
																				try {
																					addRegisterGroup(
																							regServiceManager,
																							getData(), contDmc);
																				} catch (CoreException e) {
																					failed(e);
																				}
																			};
																		});
															}
														});

											}
										});
							}
						});
					}
				});
			}
			
		});
	}

	private void noRegisterGroupFoundErr(String msgOrigin, RequestMonitor rm) {
		String message = msgOrigin + ": Unable to resolve root Group"; //$NON-NLS-1$
		IStatus status = new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED,
				REG_GROUP_ACTION_FAILED + message, new Exception(message));
		DsfUIPlugin.log(status);
		rm.setStatus(status);
		rm.done();
	}

	private void proposeGroupName(IRegisterDMContext[] registers, final IRegisters2 regServiceManager, final DataRequestMonitor<String> rm) {
		assert(registers != null && registers.length > 0);
		
		final DsfExecutor executor = regServiceManager.getExecutor();
		if (registers != null && registers.length > 0) {
			//First get all register group contexts, any register context can be used to resolve the container context
			regServiceManager.getRegisterGroups(registers[0], new DataRequestMonitor<IRegisterGroupDMContext[]>(executor, null) {
				@Override
				protected void handleSuccess() {
					IRegisterGroupDMContext[] groupsCtx = getData();
					assert (groupsCtx != null); 

					final IRegisterGroupDMData[] groupsData = new IRegisterGroupDMData[groupsCtx.length];
					
					final CountingRequestMonitor crm = new CountingRequestMonitor(executor, rm) {
						@Override
						protected void handleCompleted() {
							//GroupsData is resolved now
							//Select an unused name
							String unusedGroupName = Messages.ProposeGroupNameRoot + (resolveGroupNameWaterMark(groupsData) + 1);
							rm.setData(unusedGroupName);
							rm.done();
						}
					};
					
					//Resolve all register group data
					for (int i=0; i < groupsCtx.length; i++) { 
						final int index = i;
						regServiceManager.getRegisterGroupData(groupsCtx[index], new DataRequestMonitor<IRegisterGroupDMData>(executor, crm){
							@Override
							protected void handleSuccess() {
								groupsData[index] = getData();
								crm.done();
							}
						});
					}
					
	                crm.setDoneCount(groupsCtx.length);
				}
			});
		} else {
			//Should not happen
			rm.setData(Messages.DefaultRegistersGroupName);
			rm.done();
		}
	}
	
	// Adjust water mark suffix used to suggest register group names
	private Integer resolveGroupNameWaterMark(IRegisterGroupDMData[] groupsData) {
		// check only for this name pattern
		Pattern pattern = Pattern.compile("^group_(\\d*)$"); //$NON-NLS-1$
		Matcher matcher = pattern.matcher(""); //$NON-NLS-1$
		int water_mark = 0;
		for (IRegisterGroupDMData groupData : groupsData) {
			// Normalize the name to lower case comparison
			String name = groupData.getName().trim().toLowerCase();
			// tracking proposed group names e.d. Group_1, Group_2, etc..,
			// otherwise no need to update the water mark
			if (matcher.reset(name).matches()) {
				// Obtain the numerical suffix
				String number = matcher.replaceAll("$1"); //$NON-NLS-1$
				try {
					int nameSequence = Integer.valueOf(number).intValue();
					if (nameSequence > water_mark) {
						// The new value is bigger so lets move up the water mark
						water_mark = nameSequence;
					}
				} catch (NumberFormatException e) {
					// Quite unlikely and only causing a possibility to
					// propose a group name that already exists.
				}
			}	
		}
		
		return Integer.valueOf(water_mark);
	}
	
	@ConfinedToDsfExecutor("selectionContext.fsession.getExecutor()")
	private void processEditRegisterGroup(final Shell shell, final SelectionDMContext selectionContext, final IRegisters2 regServiceManager) {

		final DsfSession session = selectionContext.fsession;
		final DsfExecutor executor = session.getExecutor();

		// Get a handle to the context of the group being edited
		final IRegisterGroupDMContext groupDmc = DMContexts.getAncestorOfType(selectionContext.fcontext,
				IRegisterGroupDMContext.class);

		// Getting the children of the selected group
		regServiceManager.getRegisters(selectionContext.fcontext,
				new DataRequestMonitor<IRegisterDMContext[]>(executor, null) {
					@Override
					protected void handleSuccess() {
						// Get children Register Contexts
						final IRegisterDMContext[] childRegisters = getData();
						
						final IContainerDMContext contDmc = DMContexts.getAncestorOfType(selectionContext.fcontext, IContainerDMContext.class);
						// Using the container context to get all existing registers from the target instead of a limited set of registers for a selected group 
						// This is needed to populate the dialog with all available registers to pick from
						regServiceManager.getRegisters(contDmc, new DataRequestMonitor<IRegisterDMContext[]>(
								executor, null) {
							@Override
							protected void handleSuccess() {
								final IRegisterDMContext[] rootRegisters = getData();
								
								if (rootRegisters.length < 1) {
									//The target is expected to have a root register group and associated registers, an error has happened !
									assert false;
									noRegisterGroupFoundErr("Edit Register Group", this); //$NON-NLS-1$
									return;
								}
								
								// We need to resolve the names for all root registers
								getRegistersData(rootRegisters, regServiceManager, new DataRequestMonitor<IRegisterDMData[]>(executor, null) {
									@Override
									protected void handleSuccess() {
										final IRegisterDMData[] rootRegistersData = getData();

										getRegistersData(childRegisters, regServiceManager, new DataRequestMonitor<IRegisterDMData[]>(executor, null) {
											@Override
											protected void handleSuccess() {
												// Get register data for all selected registers i.e. selected for the new group
												final IRegisterDMData[] childRegisterData = getData();

												// Need to get the parent group name. Used on the register descriptors
												final IRegisterGroupDMContext rootGroupDmc = DMContexts.getAncestorOfType(rootRegisters[0], IRegisterGroupDMContext.class);
												regServiceManager.getRegisterGroupData(rootGroupDmc,
														new DataRequestMonitor<IRegisterGroupDMData>(
																executor, null) {
															@Override
															protected void handleSuccess() {
																IRegisterGroupDMData rootGroupData = getData();
																final String rootGroupName = (rootGroupData == null) ? BLANK_STRING
																		: rootGroupData.getName();
																
																regServiceManager.getRegisterGroupData(groupDmc,
																		new DataRequestMonitor<IRegisterGroupDMData>(
																				executor, null) {
																			@Override
																			protected void handleSuccess() {
																				// Resolve the name of the selected group being edited
																				String selGroupName = getData().getName();
																				// Create the Register descriptors to
																				// access all children registers
																				DialogRegisterProvider descriptors = buildDescriptors(
																						rootGroupName, rootRegisters,
																						rootRegistersData,
																						childRegisterData);

																				// Create Dialog to Resolve new user
																				// selection of group name and registers
																				getDialogSelection(
																						shell,
																						selGroupName,
																						descriptors.getAllRegisters(),
																						descriptors
																								.getcheckedRegisters(),
																						new DataRequestMonitor<IRegisterGroupDescriptor>(
																								executor, null) {
																							@Override
																							protected void handleSuccess() {
																								try {
																									editRegisterGroup(
																											groupDmc,
																											regServiceManager,
																											getData());
																								} catch (CoreException e) {
																									failed(e);
																								}
																							};
																						});
																			}
																		});
															};

														});
											}
										});

									}
								});
							}
						});
					}
				});
	}

	private IRegisterGroupDMContext[] resolveSelectedGroups(IStructuredSelection selection) {
		IRegisterGroupDMContext[] selectedGroups = null;
		List<IRegisterGroupDMContext> groupList = new ArrayList<IRegisterGroupDMContext>();
		if (selection != null && !selection.isEmpty()) {
			for (Iterator<?> iterator = selection.iterator(); iterator.hasNext();) {
				Object element = iterator.next();
				if (element instanceof IDMVMContext) {
					IDMContext dmContext = ((IDMVMContext) element).getDMContext();
					// Make sure this selection is a group
					if (dmContext instanceof IRegisterGroupDMContext) {
						IRegisterGroupDMContext groupDmc = (IRegisterGroupDMContext) dmContext;
						groupList.add(groupDmc);
					}
				}
			}

		}

		selectedGroups = groupList.toArray(new IRegisterGroupDMContext[groupList.size()]);
		return selectedGroups;
	}

	@ConfinedToDsfExecutor("selectionContext.fsession.getExecutor()")
	private IRegisterDMContext[] resolveSelectedRegisters(IStructuredSelection selection) {
		List<IRegisterDMContext> selectedRegistersList = new ArrayList<IRegisterDMContext>();
		for (Iterator<?> iterator = selection.iterator(); iterator.hasNext();) {
			Object element = iterator.next();
			IDMVMContext regContext = null;
			if (element instanceof IDMVMContext) {
				regContext = (IDMVMContext) element;
				IRegisterDMContext registerDmc = DMContexts.getAncestorOfType(regContext.getDMContext(),
						IRegisterDMContext.class);
				if (registerDmc != null) {
					selectedRegistersList.add(registerDmc);
				}
			}
		}

		IRegisterDMContext[] selectedRegisters = selectedRegistersList
				.toArray(new IRegisterDMContext[selectedRegistersList.size()]);
		return selectedRegisters;
	}

	@ConfinedToDsfExecutor("selectionContext.fsession.getExecutor()")
	private IRegisterDMContext[] getRegisterContexts(IRegisterDescriptor[] registerDescriptors) throws CoreException {
		IRegisterDMContext[] regContexts = new IRegisterDMContext[registerDescriptors.length];
		for (int i = 0; i < registerDescriptors.length; i++) {
			if (registerDescriptors[i] instanceof RegisterDescriptor) {
				regContexts[i] = ((RegisterDescriptor) registerDescriptors[i]).fRegContext;
			} else {
				// Interrupt on error
				IStatus status = new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IStatus.ERROR,
						"Unexpected IRegisterDescription instance type", null); //$NON-NLS-1$
				throw new CoreException(status);
			}
		}

		return regContexts;
	}

	@ConfinedToDsfExecutor("selectionContext.fsession.getExecutor()")
	private void getDialogSelection(Shell shell, String originalGroupName,
			IRegisterDescriptor[] allRegisters, IRegisterDescriptor[] checkedRegisters,
			DataRequestMonitor<IRegisterGroupDescriptor> rm) {
		RegisterGroupDialogRunnable dialog = new RegisterGroupDialogRunnable(shell, originalGroupName, allRegisters,
				checkedRegisters, rm);
		shell.getDisplay().asyncExec(dialog);
	}

	private IRegisterGroupDescriptor createGroupDescriptor(final String groupName,
			final IRegisterDescriptor[] iSelectedRegisters) {
		IRegisterGroupDescriptor groupDescriptor = new IRegisterGroupDescriptor() {

			@Override
			public boolean isEnabled() {
				return true;
			}

			@Override
			public String getName() {
				return groupName;
			}

			@Override
			public IRegisterDescriptor[] getChildren() throws CoreException {
				return iSelectedRegisters;
			}
		};

		return groupDescriptor;
	}

	/**
	 * Build descriptor adapters to dialog interface both all registers as well as registers to be pre-selected on the
	 * dialog
	 * 
	 * @param checkedRegistersData
	 */
	@ConfinedToDsfExecutor("selectionContext.fsession.getExecutor()")
	private DialogRegisterProvider buildDescriptors(String groupName, IRegisterDMContext[] registers,
			IRegisterDMData[] registerData, IRegisterDMData[] checkedRegistersData) {
		assert (registers.length == registerData.length);

		List<RegisterDescriptor> checkedDescriptorsList = new ArrayList<RegisterDescriptor>();

		final RegisterDescriptor[] regDescriptors = new RegisterDescriptor[registers.length];

		Map<String, RegisterDescriptor> mapNameToRegDescriptor = new HashMap<String, RegisterDescriptor>();

		for (int i = 0; i < registers.length; i++) {
			regDescriptors[i] = new RegisterDescriptor(groupName, registers[i], registerData[i].getName());
			mapNameToRegDescriptor.put(regDescriptors[i].getName(), regDescriptors[i]);
		}

		for (int i = 0; i < checkedRegistersData.length; i++) {
			// Resolve the descriptor by name
			RegisterDescriptor descriptor = mapNameToRegDescriptor.get(checkedRegistersData[i].getName());

			// All checked registers are expected to be part of the complete list
			assert (descriptor != null);

			// prevent duplicates or null values, duplicates are possible since the selected registers
			// may come from different groups
			if (descriptor != null && !checkedDescriptorsList.contains(descriptor)) {
				checkedDescriptorsList.add(descriptor);
			}
		}

		final RegisterDescriptor[] checkedRegDescriptors = checkedDescriptorsList
				.toArray(new RegisterDescriptor[checkedDescriptorsList.size()]);

		DialogRegisterProvider provider = new DialogRegisterProvider() {
			@Override
			public IRegisterDescriptor[] getcheckedRegisters() {
				return checkedRegDescriptors;
			}

			@Override
			public IRegisterDescriptor[] getAllRegisters() {
				return regDescriptors;
			}
		};

		return provider;
	}

	@ConfinedToDsfExecutor("selectionContext.fsession.getExecutor()")
	private void addRegisterGroup(IRegisters2 regServiceManager,
			IRegisterGroupDescriptor groupDescriptor, IContainerDMContext contDmc) throws CoreException {
		IRegisterDescriptor[] selectedRegisters = groupDescriptor.getChildren();
		if (selectedRegisters != null) {
			String groupName = groupDescriptor.getName();
			// Register the addition of the group and notify the change
			IRegisterDMContext[] registers = getRegisterContexts(selectedRegisters);
			regServiceManager.addRegisterGroup(contDmc, groupName, registers, new RequestMonitor(
					regServiceManager.getSession().getExecutor(), null) {
				@Override
				protected void handleCompleted() {
					if (getStatus() != null && getStatus().getCode() == IDsfStatusConstants.NOT_SUPPORTED) {
						// This user request is not supported, notify the user
						notifyUser(getStatus().getMessage());
					}

				};
			});
		}
	}

	@ConfinedToDsfExecutor("selectionContext.fsession.getExecutor()")
	private void editRegisterGroup(IRegisterGroupDMContext group, IRegisters2 regServiceManager,
			IRegisterGroupDescriptor groupDescriptor) throws CoreException {
		IRegisterDescriptor[] selectedRegisters = groupDescriptor.getChildren();
		if (selectedRegisters != null) {
			String groupName = groupDescriptor.getName();
			// Register the addition of the group and notify the change
			regServiceManager.editRegisterGroup(group, groupName, getRegisterContexts(selectedRegisters),
					new RequestMonitor(regServiceManager.getSession().getExecutor(), null) {
				@Override
				protected void handleCompleted() {
					if (getStatus() != null && getStatus().getCode() == IDsfStatusConstants.NOT_SUPPORTED) {
						// This user request is not supported, notify the user
						notifyUser(getStatus().getMessage());
					}
					
				};
			});
		}
	}

	private void failed(Throwable e) {
		IStatus status = new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED,
				REG_GROUP_ACTION_FAILED + e.getMessage(), e);
		DsfUIPlugin.log(status);
	}

	private void notifyUser(final String message) {
		
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				Shell parent = DsfUIPlugin.getActiveWorkbenchShell();
				if (parent != null) {
					MessageDialog.openInformation(parent, Messages.Information, Messages.RegisterGroupInfo + ": " + message); //$NON-NLS-1$
				}
			}
		};

		Display.getDefault().asyncExec(runnable);
	}

    /**
     * @return true - OK to restore
     */
    private boolean restoreConfirmed() {
    	ConfirmRestoreDialog restoreDialog = new ConfirmRestoreDialog();
		Display.getDefault().syncExec(restoreDialog);

        return restoreDialog.fRestore;
    }
	
    private class ConfirmRestoreDialog implements Runnable {

    	private Boolean fRestore = false;
    	
		@Override
		public void run() {
			Shell parent = DsfUIPlugin.getActiveWorkbenchShell();
			if (parent != null) {
		    	
	            String title = Messages.RegisterGroupConfirmRestoreTitle;
	            String message = Messages.RegisterGroupConfirmRestoreMessage;
	            String[] buttonLabels = new String[]{Messages.RegisterGroupRestore, Messages.RegisterGroupRestoreCancel,
	            };
	            MessageDialog dialog = new MessageDialog(parent, title, null, message, MessageDialog.QUESTION,
	                    buttonLabels, 0);
	            int res = dialog.open();
	            if (res == 0) { // RESTORE
	            	fRestore = true;
	            } else if (res == 1) { // CANCEL
	            	fRestore = false;
	            }
			}			
		}
    	
    }
    
	
	private void getRegistersData(IRegisterDMContext[] regDMCs, IRegisters2 regService, final DataRequestMonitor<IRegisterDMData[]> rm) {
		final IRegisterDMData[] regDataArray = new IRegisterDMData[regDMCs.length];
		final DsfExecutor executor = regService.getExecutor();
		
		final CountingRequestMonitor crm = new CountingRequestMonitor(executor, rm) {
			@Override
			protected void handleSuccess() {
				rm.setData(regDataArray);
				rm.done();
			}
		};
		
		for (int i = 0; i < regDMCs.length; i++) {
			final int index = i;
			regService.getRegisterData(regDMCs[index], new DataRequestMonitor<IRegisterDMData>(executor, crm) {
				@Override
				protected void handleSuccess() {
					regDataArray[index] = getData();
					crm.done();
				}
			});
		}
		
		crm.setDoneCount(regDMCs.length);
	}
}
