/*******************************************************************************
 * Copyright (c) 2006, 2016 Wind River Systems and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.dsf.debug.ui.viewmodel.launch;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.RejectedExecutionException;

import org.eclipse.cdt.core.IAddress;
import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.DataModelInitializedEvent;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
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.IContainerSuspendedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExitedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IStack;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMData;
import org.eclipse.cdt.dsf.debug.ui.IDsfDebugUIConstants;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.SteppingController;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.SteppingController.SteppingTimedOutEvent;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.concurrent.ViewerDataRequestMonitor;
import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.ModelProxyInstalledEvent;
import org.eclipse.cdt.dsf.ui.viewmodel.VMChildrenUpdate;
import org.eclipse.cdt.dsf.ui.viewmodel.VMDelta;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.AbstractDMVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.AbstractDMVMProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.IElementPropertiesProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.IPropertiesUpdate;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.LabelAttribute;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.LabelColumnInfo;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.LabelImage;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.LabelText;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.PropertiesBasedLabelProvider;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementCompareRequest;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementMementoProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementMementoRequest;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IMemento;

public class StackFramesVMNode extends AbstractDMVMNode
		implements IElementLabelProvider, IElementMementoProvider, IElementPropertiesProvider {
	/**
	 * @since 2.0
	 */
	public static final String PROP_IS_INCOMPLETE_STACK_MARKER = "is_incomplete_stack_marker"; //$NON-NLS-1$

	/**
	 * View model context representing the end of an incomplete stack.
	 *
	 * @since 1.1
	 */
	public class IncompleteStackVMContext extends AbstractVMContext {
		private final int fLevel;
		private final IExecutionDMContext fDmc;

		public IncompleteStackVMContext(IExecutionDMContext dmc, int level) {
			super(StackFramesVMNode.this);
			fDmc = dmc;
			fLevel = level;
		}

		public int getLevel() {
			return fLevel;
		}

		public IExecutionDMContext getExecutionDMContext() {
			return fDmc;
		}

		@Override
		public boolean equals(Object obj) {
			return obj instanceof IncompleteStackVMContext && ((IncompleteStackVMContext) obj).fDmc.equals(fDmc);
		}

		@Override
		public int hashCode() {
			return fDmc.hashCode();
		}
	}

	/**
	 * Temporary stack frame limit to allow incremental stack updates.
	 */
	private Map<IExecutionDMContext, Integer> fTemporaryLimits = new HashMap<>();

	/**
	 * The label provider delegate.  This VM node will delegate label updates to this provider
	 * which can be created by sub-classes.
	 *
	 * @since 2.0
	 */
	private IElementLabelProvider fLabelProvider;

	public StackFramesVMNode(AbstractDMVMProvider provider, DsfSession session) {
		super(provider, session, IStack.IFrameDMContext.class);
		fLabelProvider = createLabelProvider();
	}

	@Override
	public String toString() {
		return "StackFramesVMNode(" + getSession().getId() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Creates the label provider delegate.  This VM node will delegate label
	 * updates to this provider which can be created by sub-classes.
	 *
	 * @return Returns the label provider for this node.
	 *
	 * @since 2.0
	 */
	protected IElementLabelProvider createLabelProvider() {
		PropertiesBasedLabelProvider provider = new PropertiesBasedLabelProvider();

		provider.setColumnInfo(PropertiesBasedLabelProvider.ID_COLUMN_NO_COLUMNS,
				new LabelColumnInfo(new LabelAttribute[] { new LabelText(
						MessagesForLaunchVM.StackFramesVMNode_No_columns__Incomplete_stack_marker__text_format,
						new String[] { PROP_IS_INCOMPLETE_STACK_MARKER }) {
					@Override
					public boolean isEnabled(IStatus status, Map<String, Object> properties) {
						return Boolean.TRUE.equals(properties.get(PROP_IS_INCOMPLETE_STACK_MARKER));
					}

				}, new LabelText(MessagesForLaunchVM.StackFramesVMNode_No_columns__text_format,
						new String[] { ILaunchVMConstants.PROP_FRAME_ADDRESS, ILaunchVMConstants.PROP_FRAME_FUNCTION,
								ILaunchVMConstants.PROP_FRAME_FILE, ILaunchVMConstants.PROP_FRAME_LINE,
								ILaunchVMConstants.PROP_FRAME_COLUMN, ILaunchVMConstants.PROP_FRAME_MODULE }) {
					@Override
					public boolean isEnabled(IStatus status, java.util.Map<String, Object> properties) {
						Integer line = (Integer) properties.get(ILaunchVMConstants.PROP_FRAME_LINE);
						String file = (String) properties.get(ILaunchVMConstants.PROP_FRAME_FILE);
						String function = (String) properties.get(ILaunchVMConstants.PROP_FRAME_FUNCTION);
						return line != null && line >= 0 && file != null && !file.isEmpty() && function != null
								&& function.contains(")"); //$NON-NLS-1$
					}
				}, new LabelText(MessagesForLaunchVM.StackFramesVMNode_No_columns__add_parens__text_format,
						new String[] { ILaunchVMConstants.PROP_FRAME_ADDRESS, ILaunchVMConstants.PROP_FRAME_FUNCTION,
								ILaunchVMConstants.PROP_FRAME_FILE, ILaunchVMConstants.PROP_FRAME_LINE,
								ILaunchVMConstants.PROP_FRAME_COLUMN, ILaunchVMConstants.PROP_FRAME_MODULE }) {
					@Override
					public boolean isEnabled(IStatus status, java.util.Map<String, Object> properties) {
						Integer line = (Integer) properties.get(ILaunchVMConstants.PROP_FRAME_LINE);
						String file = (String) properties.get(ILaunchVMConstants.PROP_FRAME_FILE);
						String function = (String) properties.get(ILaunchVMConstants.PROP_FRAME_FUNCTION);
						return line != null && line >= 0 && file != null && !file.isEmpty()
								&& (function == null || !function.contains(")")); //$NON-NLS-1$
					}
				}, new LabelText(MessagesForLaunchVM.StackFramesVMNode_No_columns__No_line__text_format,
						new String[] { ILaunchVMConstants.PROP_FRAME_ADDRESS, ILaunchVMConstants.PROP_FRAME_FUNCTION,
								ILaunchVMConstants.PROP_FRAME_MODULE }) {
					@Override
					public boolean isEnabled(IStatus status, java.util.Map<String, Object> properties) {
						String function = (String) properties.get(ILaunchVMConstants.PROP_FRAME_FUNCTION);
						String module = (String) properties.get(ILaunchVMConstants.PROP_FRAME_MODULE);
						return function != null && !function.isEmpty() && function.contains(")") && //$NON-NLS-1$
						module != null && !module.isEmpty();
					}
				}, new LabelText(MessagesForLaunchVM.StackFramesVMNode_No_columns__No_line__add_parens__text_format,
						new String[] { ILaunchVMConstants.PROP_FRAME_ADDRESS, ILaunchVMConstants.PROP_FRAME_FUNCTION,
								ILaunchVMConstants.PROP_FRAME_MODULE }) {
					@Override
					public boolean isEnabled(IStatus status, java.util.Map<String, Object> properties) {
						String function = (String) properties.get(ILaunchVMConstants.PROP_FRAME_FUNCTION);
						String module = (String) properties.get(ILaunchVMConstants.PROP_FRAME_MODULE);
						return function != null && !function.isEmpty() && !function.contains(")") && //$NON-NLS-1$
						module != null && !module.isEmpty();
					}
				}, new LabelText(MessagesForLaunchVM.StackFramesVMNode_No_columns__No_function__text_format,
						new String[] { ILaunchVMConstants.PROP_FRAME_ADDRESS, ILaunchVMConstants.PROP_FRAME_MODULE }) {
					@Override
					public boolean isEnabled(IStatus status, java.util.Map<String, Object> properties) {
						String module = (String) properties.get(ILaunchVMConstants.PROP_FRAME_MODULE);
						return module != null && !module.isEmpty();
					}
				}, new LabelText(MessagesForLaunchVM.StackFramesVMNode_No_columns__No_module__text_format,
						new String[] { ILaunchVMConstants.PROP_FRAME_ADDRESS,
								ILaunchVMConstants.PROP_FRAME_FUNCTION }) {
					@Override
					public boolean isEnabled(IStatus status, java.util.Map<String, Object> properties) {
						String function = (String) properties.get(ILaunchVMConstants.PROP_FRAME_FUNCTION);
						return function != null && !function.isEmpty() && function.contains(")"); //$NON-NLS-1$
					}
				}, new LabelText(MessagesForLaunchVM.StackFramesVMNode_No_columns__No_module__add_parens__text_format,
						new String[] { ILaunchVMConstants.PROP_FRAME_ADDRESS,
								ILaunchVMConstants.PROP_FRAME_FUNCTION }) {
					@Override
					public boolean isEnabled(IStatus status, java.util.Map<String, Object> properties) {
						String function = (String) properties.get(ILaunchVMConstants.PROP_FRAME_FUNCTION);
						return function != null && !function.isEmpty() && !function.contains(")"); //$NON-NLS-1$
					}
				}, new LabelText(MessagesForLaunchVM.StackFramesVMNode_No_columns__Address_only__text_format,
						new String[] { ILaunchVMConstants.PROP_FRAME_ADDRESS }),
						new LabelImage(DebugUITools.getImageDescriptor(IDebugUIConstants.IMG_OBJS_STACKFRAME_RUNNING)) {
							{
								setPropertyNames(new String[] { ILaunchVMConstants.PROP_IS_SUSPENDED });
							}

							@Override
							public boolean isEnabled(IStatus status, java.util.Map<String, Object> properties) {
								return Boolean.FALSE.equals(properties.get(ILaunchVMConstants.PROP_IS_SUSPENDED));
							}
						}, new LabelImage(DebugUITools.getImageDescriptor(IDebugUIConstants.IMG_OBJS_STACKFRAME)) }));

		return provider;
	}

	@Override
	protected void updateHasElementsInSessionThread(IHasChildrenUpdate update) {
		IRunControl runControl = getServicesTracker().getService(IRunControl.class);
		IExecutionDMContext execCtx = findDmcInPath(update.getViewerInput(), update.getElementPath(),
				IExecutionDMContext.class);
		if (runControl == null || execCtx == null) {
			handleFailedUpdate(update);
			return;
		}

		update.setHasChilren(runControl.isSuspended(execCtx) || runControl.isStepping(execCtx));
		update.done();
	}

	@Override
	protected void updateElementCountInSessionThread(final IChildrenCountUpdate update) {
		IStack stackService = getServicesTracker().getService(IStack.class);
		final IExecutionDMContext execDmc = findDmcInPath(update.getViewerInput(), update.getElementPath(),
				IExecutionDMContext.class);
		if (stackService == null || execDmc == null) {
			handleFailedUpdate(update);
			return;
		}

		final int stackFrameLimit = getStackFrameLimit(execDmc);
		stackService.getStackDepth(execDmc, stackFrameLimit == Integer.MAX_VALUE ? 0 : stackFrameLimit + 1,
				new ViewerDataRequestMonitor<Integer>(getSession().getExecutor(), update) {
					@Override
					public void handleCompleted() {
						if (!isSuccess()) {
							handleFailedUpdate(update);
							return;
						}
						int stackDepth = getData();
						if (stackFrameLimit < stackDepth) {
							stackDepth = stackFrameLimit + 1;
						}
						update.setChildCount(stackDepth);
						update.done();
					}
				});
	}

	@Override
	protected void updateElementsInSessionThread(final IChildrenUpdate update) {
		IStack stackService = getServicesTracker().getService(IStack.class);
		final IExecutionDMContext execDmc = findDmcInPath(update.getViewerInput(), update.getElementPath(),
				IExecutionDMContext.class);
		if (stackService == null || execDmc == null) {
			handleFailedUpdate(update);
			return;
		}

		final int stackFrameLimit = getStackFrameLimit(execDmc);
		final int startIndex = update.getOffset();

		if (startIndex == 0 && update.getLength() == 1) {
			// Requesting top stack frame only
			stackService.getTopFrame(execDmc,
					new ViewerDataRequestMonitor<IFrameDMContext>(getSession().getExecutor(), update) {
						@Override
						public void handleCompleted() {
							if (!isSuccess()) {
								handleFailedUpdate(update);
								return;
							}
							update.setChild(createVMContext(getData()), 0);
							update.done();
						}
					});

		} else {
			if (startIndex >= 0 && update.getLength() > 0) {
				// partial stack dump
				int endIndex = startIndex + update.getLength() - 1;
				if (startIndex < stackFrameLimit && endIndex >= stackFrameLimit) {
					endIndex = stackFrameLimit - 1;
				}
				stackService.getFrames(execDmc, startIndex, endIndex,
						new ViewerDataRequestMonitor<IFrameDMContext[]>(getSession().getExecutor(), update) {
							@Override
							public void handleCompleted() {
								if (!isSuccess()) {
									handleFailedUpdate(update);
									return;
								}
								IFrameDMContext[] frames = getData();
								fillUpdateWithVMCs(update, frames, startIndex);
								if (startIndex + update.getLength() > stackFrameLimit) {
									update.setChild(new IncompleteStackVMContext(execDmc, stackFrameLimit),
											stackFrameLimit);
								}
								update.done();
							}
						});
			} else {
				// full stack dump
				stackService.getFrames(execDmc,
						new ViewerDataRequestMonitor<IFrameDMContext[]>(getSession().getExecutor(), update) {
							@Override
							public void handleCompleted() {
								if (!isSuccess()) {
									handleFailedUpdate(update);
									return;
								}
								IFrameDMContext[] frames = getData();
								if (frames.length > stackFrameLimit) {
									IFrameDMContext[] tmpFrames = new IFrameDMContext[stackFrameLimit];
									System.arraycopy(frames, 0, tmpFrames, 0, stackFrameLimit);
									frames = tmpFrames;
									update.setChild(new IncompleteStackVMContext(execDmc, stackFrameLimit),
											stackFrameLimit);
								}
								fillUpdateWithVMCs(update, frames);
								update.done();
							}
						});
			}
		}
	}

	@Override
	public void update(final ILabelUpdate[] updates) {
		fLabelProvider.update(updates);
	}

	/**
	 * @see IElementPropertiesProvider#update(IPropertiesUpdate[])
	 *
	 * @since 2.0
	 */
	@Override
	public void update(final IPropertiesUpdate[] updates) {
		try {
			getSession().getExecutor().execute(new DsfRunnable() {
				@Override
				public void run() {
					updatePropertiesInSessionThread(updates);
				}
			});
		} catch (RejectedExecutionException e) {
			for (IPropertiesUpdate update : updates) {
				handleFailedUpdate(update);
			}
		}
	}

	/**
	 * @since 2.0
	 */
	@ConfinedToDsfExecutor("getSession().getExecutor()")
	protected void updatePropertiesInSessionThread(final IPropertiesUpdate[] updates) {
		IStack service = getServicesTracker().getService(IStack.class);

		for (final IPropertiesUpdate update : updates) {
			if (service == null) {
				handleFailedUpdate(update);
				continue;
			}

			if (update.getElement() instanceof IncompleteStackVMContext) {
				update.setProperty(PROP_IS_INCOMPLETE_STACK_MARKER, Boolean.TRUE);
				update.done();
				continue;
			}

			IFrameDMContext dmc = findDmcInPath(update.getViewerInput(), update.getElementPath(),
					IFrameDMContext.class);
			if (dmc == null) {
				handleFailedUpdate(update);
				continue;
			}

			IRunControl runControlService = getServicesTracker().getService(IRunControl.class);
			IExecutionDMContext execDmc = DMContexts.getAncestorOfType(dmc, IExecutionDMContext.class);
			if (execDmc != null && runControlService != null) {
				update.setProperty(ILaunchVMConstants.PROP_IS_SUSPENDED, runControlService.isSuspended(execDmc));
				update.setProperty(ILaunchVMConstants.PROP_IS_STEPPING, runControlService.isStepping(execDmc));
			} else {
				update.setStatus(DsfUIPlugin.newErrorStatus(IDsfStatusConstants.INVALID_HANDLE,
						"Invalid context or service not available", null)); //$NON-NLS-1$
			}

			service.getFrameData(dmc, new ViewerDataRequestMonitor<IFrameDMData>(getSession().getExecutor(), update) {
				@Override
				protected void handleSuccess() {
					fillFrameDataProperties(update, getData());
					update.done();
				}
			});
		}
	}

	protected void fillFrameDataProperties(IPropertiesUpdate update, IFrameDMData data) {
		IAddress address = data.getAddress();
		if (address != null) {
			update.setProperty(ILaunchVMConstants.PROP_FRAME_ADDRESS, "0x" + address.toString(16)); //$NON-NLS-1$
		}

		String fileName = data.getFile();
		if (fileName != null) {
			Object showFullPathPreference = getVMProvider().getPresentationContext()
					.getProperty(IDsfDebugUIConstants.DEBUG_VIEW_SHOW_FULL_PATH_PROPERTY);
			if (showFullPathPreference instanceof Boolean && (Boolean) showFullPathPreference == false) {
				fileName = new Path(fileName).lastSegment();
			}
		}
		update.setProperty(ILaunchVMConstants.PROP_FRAME_FILE, fileName);

		update.setProperty(ILaunchVMConstants.PROP_FRAME_FUNCTION, data.getFunction());
		update.setProperty(ILaunchVMConstants.PROP_FRAME_LINE, data.getLine());
		update.setProperty(ILaunchVMConstants.PROP_FRAME_COLUMN, data.getColumn());
		update.setProperty(ILaunchVMConstants.PROP_FRAME_MODULE, data.getModule());
	}

	protected void fillColumnLabel(IFrameDMContext dmContext, IFrameDMData dmData, String columnId, int idx,
			ILabelUpdate update) {
		if (idx != 0)
			return;

		final IExecutionDMContext execDmc = findDmcInPath(update.getViewerInput(), update.getElementPath(),
				IExecutionDMContext.class);
		if (execDmc == null) {
			return;
		}
		IRunControl runControlService = getServicesTracker().getService(IRunControl.class);
		SteppingController stepQueueMgr = execDmc.getAdapter(SteppingController.class);
		if (runControlService == null || stepQueueMgr == null)
			return;

		String imageKey = null;
		if (runControlService.isSuspended(execDmc)
				|| (runControlService.isStepping(execDmc) && !stepQueueMgr.isSteppingTimedOut(execDmc))) {
			imageKey = IDebugUIConstants.IMG_OBJS_STACKFRAME;
		} else {
			imageKey = IDebugUIConstants.IMG_OBJS_STACKFRAME_RUNNING;
		}
		update.setImageDescriptor(DebugUITools.getImageDescriptor(imageKey), 0);

		//
		// Finally, if all goes well, set the label.
		//
		StringBuilder label = new StringBuilder();

		// Add the function name
		if (dmData.getFunction() != null && dmData.getFunction().length() != 0) {
			label.append(" "); //$NON-NLS-1$
			label.append(dmData.getFunction());
			label.append("()"); //$NON-NLS-1$
		}

		boolean hasFileName = dmData.getFile() != null && dmData.getFile().length() != 0;

		// Add full file name
		if (hasFileName) {
			label.append(" at "); //$NON-NLS-1$
			label.append(dmData.getFile());
		}

		// Add line number
		if (dmData.getLine() >= 0) {
			label.append(":"); //$NON-NLS-1$
			label.append(dmData.getLine());
			label.append(" "); //$NON-NLS-1$
		}

		// Add module
		if (!hasFileName && (dmData.getModule() != null && dmData.getModule().length() != 0)) {
			label.append(" "); //$NON-NLS-1$
			label.append(dmData.getModule());
			label.append(" "); //$NON-NLS-1$
		}

		// Add the address
		if (dmData.getAddress() != null) {
			label.append("- 0x").append(dmData.getAddress().toString(16)); //$NON-NLS-1$
		}

		// Set the label to the result listener
		update.setLabel(label.toString(), 0);
	}

	@Override
	public void getContextsForEvent(final VMDelta parentDelta, Object e, final DataRequestMonitor<IVMContext[]> rm) {
		if (e instanceof ModelProxyInstalledEvent || e instanceof DataModelInitializedEvent) {
			// Retrieve the list of stack frames, and mark the top frame to be selected.
			getVMProvider().updateNode(this, new VMChildrenUpdate(parentDelta, getVMProvider().getPresentationContext(),
					0, 1, new DataRequestMonitor<List<Object>>(getExecutor(), rm) {
						@Override
						public void handleCompleted() {
							if (isSuccess() && getData().size() != 0) {
								rm.setData(new IVMContext[] { (IVMContext) getData().get(0) });
							} else {
								// In case of errors, return an empty set of frames.
								rm.setData(new IVMContext[0]);
							}
							rm.done();
						}
					}));
			return;
		}
		super.getContextsForEvent(parentDelta, e, rm);
	}

	@Override
	public int getDeltaFlags(Object e) {
		// This node generates delta if the timers have changed, or if the
		// label has changed.
		if (e instanceof ISuspendedDMEvent) {
			return IModelDelta.CONTENT | IModelDelta.EXPAND | IModelDelta.SELECT;
		} else if (e instanceof FullStackRefreshEvent
				&& !(((FullStackRefreshEvent) e).getTriggeringEvent() instanceof IContainerSuspendedDMEvent)) {
			return IModelDelta.CONTENT;
		} else if (e instanceof SteppingTimedOutEvent) {
			return IModelDelta.CONTENT;
		} else if (e instanceof ModelProxyInstalledEvent || e instanceof DataModelInitializedEvent) {
			return IModelDelta.SELECT | IModelDelta.EXPAND;
		} else if (e instanceof ExpandStackEvent) {
			return IModelDelta.CONTENT;
		} else if (e instanceof IExitedDMEvent) {
			// Do not generate a delta for this event, but do clear the
			// internal stack frame limit to avoid a memory leak.
			resetStackFrameLimit(((IExitedDMEvent) e).getDMContext());
			return IModelDelta.NO_CHANGE;
		} else if (e instanceof PropertyChangeEvent) {
			String property = ((PropertyChangeEvent) e).getProperty();
			if (IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT_ENABLE.equals(property)
					|| IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT.equals(property)) {
				return IModelDelta.CONTENT;
			} else if (IDsfDebugUIConstants.DEBUG_VIEW_SHOW_FULL_PATH_PROPERTY.equals(property)) {
				return IModelDelta.STATE;
			}
		} else {
		}

		return IModelDelta.NO_CHANGE;
	}

	@Override
	public void buildDelta(final Object e, final VMDelta parent, final int nodeOffset, final RequestMonitor rm) {
		if (e instanceof IContainerSuspendedDMEvent) {
			// Clear the limit on the stack frames for all stack frames under a given container.
			resetStackFrameLimit(((IContainerSuspendedDMEvent) e).getDMContext());

			IContainerSuspendedDMEvent csEvent = (IContainerSuspendedDMEvent) e;

			IExecutionDMContext triggeringCtx = csEvent.getTriggeringContexts().length != 0
					? csEvent.getTriggeringContexts()[0]
					: null;

			if (parent.getElement() instanceof IDMVMContext) {
				IExecutionDMContext threadDmc = null;
				threadDmc = DMContexts.getAncestorOfType(((IDMVMContext) parent.getElement()).getDMContext(),
						IExecutionDMContext.class);
				buildDeltaForSuspendedEvent(threadDmc, triggeringCtx, parent, nodeOffset, rm);
			} else {
				rm.done();
			}
		} else if (e instanceof FullStackRefreshEvent) {
			FullStackRefreshEvent refreshEvent = (FullStackRefreshEvent) e;
			if (!(refreshEvent.getTriggeringEvent() instanceof IContainerSuspendedDMEvent)) {
				// Don't refresh each stack frame when we get an event for the entire container
				// Bug 386175
				IExecutionDMContext execDmc = ((FullStackRefreshEvent) e).getDMContext();
				buildDeltaForFullStackRefreshEvent(execDmc, execDmc, parent, nodeOffset, rm);
			} else {
				rm.done();
			}
		} else if (e instanceof ISuspendedDMEvent) {
			resetStackFrameLimit(((ISuspendedDMEvent) e).getDMContext());
			IExecutionDMContext execDmc = ((ISuspendedDMEvent) e).getDMContext();
			buildDeltaForSuspendedEvent(execDmc, execDmc, parent, nodeOffset, rm);
		} else if (e instanceof SteppingTimedOutEvent) {
			buildDeltaForSteppingTimedOutEvent((SteppingTimedOutEvent) e, parent, nodeOffset, rm);
		} else if (e instanceof ModelProxyInstalledEvent || e instanceof DataModelInitializedEvent) {
			buildDeltaForModelProxyInstalledEvent(parent, nodeOffset, rm);
		} else if (e instanceof ExpandStackEvent) {
			IExecutionDMContext execDmc = ((ExpandStackEvent) e).getDMContext();
			buildDeltaForExpandStackEvent(execDmc, parent, rm);
		} else if (e instanceof PropertyChangeEvent) {
			String property = ((PropertyChangeEvent) e).getProperty();
			if (IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT_ENABLE.equals(property)
					|| IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT.equals(property)) {
				buildDeltaForStackFrameLimitPreferenceChangedEvent(parent, rm);
			} else if (IDsfDebugUIConstants.DEBUG_VIEW_SHOW_FULL_PATH_PROPERTY.equals(property)) {
				buildDeltaForShowFullPathPreferenceChangedEvent(parent, rm);
			} else {
				rm.done();
			}
		} else {
			rm.done();
		}
	}

	/**
	 * Builds the delta in response to a suspended event.
	 * <p>
	 * Default behavior is to expand the thread, repaint <strong>only</strong>
	 * the top stack frame and select it.  The rest of the frames will be
	 * repainted after a short delay.
	 *
	 * @since 2.1
	 */
	protected void buildDeltaForSuspendedEvent(final IExecutionDMContext executionCtx,
			final IExecutionDMContext triggeringCtx, final VMDelta parentDelta, final int nodeOffset,
			final RequestMonitor rm) {
		// Check if we are building a delta for the thread that triggered the event.
		// Only then expand the stack frames and select the top one.
		if (executionCtx.equals(triggeringCtx)) {
			// Always expand the thread node to show the stack frames.
			parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.EXPAND);

			// Retrieve the list of stack frames, and mark the top frame to be selected.
			getVMProvider().updateNode(this, new VMChildrenUpdate(parentDelta, getVMProvider().getPresentationContext(),
					0, 1, new DataRequestMonitor<List<Object>>(getExecutor(), rm) {
						@Override
						public void handleCompleted() {
							final List<Object> data = getData();
							if (data != null && data.size() != 0) {
								parentDelta.addNode(data.get(0), 0, IModelDelta.SELECT | IModelDelta.STATE);
							}
							// Even in case of errors, complete the request monitor.
							rm.done();
						}
					}));
		} else {
			rm.done();
		}
	}

	/**
	 * Builds a delta in response to automatic refresh event generated after
	 * every suspend event.
	 * <p>
	 * The default behavior is to check if the thread is
	 * still stepping or suspended and refresh the stack trace.
	 *
	 * @since 2.1
	 */
	protected void buildDeltaForFullStackRefreshEvent(final IExecutionDMContext executionCtx,
			final IExecutionDMContext triggeringCtx, final VMDelta parentDelta, final int nodeOffset,
			final RequestMonitor rm) {
		try {
			getSession().getExecutor().execute(new DsfRunnable() {
				@Override
				public void run() {
					IRunControl runControlService = getServicesTracker().getService(IRunControl.class);
					IStack stackService = getServicesTracker().getService(IStack.class);
					if (stackService == null || runControlService == null) {
						// Required services have not initialized yet.  Ignore the event.
						rm.done();
						return;
					}

					// Refresh the whole list of stack frames unless the target is already stepping the next command.  In
					// which case, the refresh will occur when the stepping sequence slows down or stops.  Trying to
					// refresh the whole stack trace with every step would slow down stepping too much.
					if (triggeringCtx == null || !runControlService.isStepping(triggeringCtx)) {
						parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.CONTENT);
					}

					rm.done();
				}
			});
		} catch (RejectedExecutionException e) {
			// Session shut down, no delta to build.
			rm.done();
		}
	}

	/**
	 * Builds the delta in response to a time-out after stepping over a long
	 * executing function.
	 * <p>
	 * The default behavior is to repainting the stack frame images to
	 * show the running symbol.
	 *
	 * @since 2.1
	 */
	protected void buildDeltaForSteppingTimedOutEvent(final SteppingTimedOutEvent e, final VMDelta parentDelta,
			final int nodeOffset, final RequestMonitor rm) {
		// Repaint the stack frame images to have the running symbol.
		//parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.CONTENT);
		rm.done();
	}

	/**
	 * Builds the delta in response the debug view being opened.
	 * <p>
	 * The default behavior is to retrieve the list of stack frames,
	 * and mark the top frame to be selected.
	 *
	 * @since 2.1
	 */
	protected void buildDeltaForModelProxyInstalledEvent(final VMDelta parentDelta, final int nodeOffset,
			final RequestMonitor rm) {
		getVMProvider().updateNode(this, new VMChildrenUpdate(parentDelta, getVMProvider().getPresentationContext(), -1,
				-1, new DataRequestMonitor<List<Object>>(getExecutor(), rm) {
					@Override
					public void handleCompleted() {
						if (isSuccess() && getData().size() != 0) {
							parentDelta.addNode(getData().get(0), 0, IModelDelta.SELECT | IModelDelta.EXPAND);
						}
						rm.done();
					}
				}));
	}

	/**
	 * Builds the delta in response to the user requesting to retrieve
	 * additional stack frames (changing the current stack frames limit).
	 * <p>
	 * The default behavior is to refresh the stack frames, and to select the
	 * first frame of the new frames that are retrieved.
	 *
	 * @since 2.1
	 */
	protected void buildDeltaForExpandStackEvent(IExecutionDMContext execDmc, final VMDelta parentDelta,
			final RequestMonitor rm) {
		parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.CONTENT);
		// Retrieve the list of new stack frames, and mark the first frame to be selected.
		final int offset = getStackFrameLimit(execDmc) / 2;
		getVMProvider().updateNode(this, new VMChildrenUpdate(parentDelta, getVMProvider().getPresentationContext(),
				offset, offset, new DataRequestMonitor<List<Object>>(getExecutor(), rm) {
					@Override
					public void handleCompleted() {
						final List<Object> data = getData();
						if (data != null && data.size() != 0) {
							parentDelta.addNode(data.get(0), offset, IModelDelta.SELECT);
						}
						rm.done();
					}
				}));
	}

	/**
	 * Builds the delta in response to the stack frame limit preference changing.
	 * <p>
	 * Default behavior is to refresh the stack frames.
	 *
	 * @since 2.1
	 */
	protected void buildDeltaForStackFrameLimitPreferenceChangedEvent(final VMDelta parentDelta,
			final RequestMonitor rm) {
		parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.CONTENT);
		rm.done();
	}

	private void buildDeltaForShowFullPathPreferenceChangedEvent(final VMDelta parentDelta, final RequestMonitor rm) {
		parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.STATE);
		rm.done();
	}

	private String produceFrameElementName(String viewName, IFrameDMContext frame) {
		/*
		 *  We are addressing Bugzilla 211490 which wants the Register View  to keep the same expanded
		 *  state for registers for stack frames within the same thread. Different  threads could have
		 *  different register sets ( e.g. one thread may have floating point & another may not ). But
		 *  within a thread we are enforcing  the assumption that the register  sets will be the same.
		 *  So we make a more convenient work flow by keeping the same expansion when selecting amount
		 *  stack frames within the same thread. We accomplish this by only differentiating by  adding
		 *  the level for the Expression/Variables view. Otherwise we do not delineate based on  which
		 *  view and this captures the Register View in its filter.
		 */
		if (viewName.startsWith(IDebugUIConstants.ID_VARIABLE_VIEW)
				|| viewName.startsWith(IDebugUIConstants.ID_EXPRESSION_VIEW)) {
			return "Frame." + frame.getLevel() + "." + frame.getSessionId(); //$NON-NLS-1$ //$NON-NLS-2$
		} else {
			return "Frame" + frame.getSessionId(); //$NON-NLS-1$
		}
	}

	@Override
	public void compareElements(IElementCompareRequest[] requests) {

		for (IElementCompareRequest request : requests) {

			Object element = request.getElement();
			IMemento memento = request.getMemento();
			String mementoName = memento.getString("STACK_FRAME_MEMENTO_NAME"); //$NON-NLS-1$

			if (mementoName != null) {
				if (element instanceof IDMVMContext) {

					IDMContext dmc = ((IDMVMContext) element).getDMContext();

					if (dmc instanceof IFrameDMContext) {

						String elementName = produceFrameElementName(request.getPresentationContext().getId(),
								(IFrameDMContext) dmc);
						request.setEqual(elementName.equals(mementoName));
					}
				}
			}
			request.done();
		}
	}

	@Override
	public void encodeElements(IElementMementoRequest[] requests) {

		for (IElementMementoRequest request : requests) {

			Object element = request.getElement();
			IMemento memento = request.getMemento();

			if (element instanceof IDMVMContext) {

				IDMContext dmc = ((IDMVMContext) element).getDMContext();

				if (dmc instanceof IFrameDMContext) {

					String elementName = produceFrameElementName(request.getPresentationContext().getId(),
							(IFrameDMContext) dmc);
					memento.putString("STACK_FRAME_MEMENTO_NAME", elementName); //$NON-NLS-1$
				}
			}
			request.done();
		}
	}

	/**
	 * Get the current active stack frame limit. If no limit is applicable {@link Integer.MAX_VALUE} is returned.
	 *
	 * @return the current stack frame limit
	 *
	 * @since 1.1
	 */
	public int getStackFrameLimit(IExecutionDMContext execCtx) {
		if (fTemporaryLimits.containsKey(execCtx)) {
			return fTemporaryLimits.get(execCtx);
		}
		Object stackDepthLimit = getVMProvider().getPresentationContext()
				.getProperty(IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT);
		if (stackDepthLimit instanceof Integer) {
			return (Integer) stackDepthLimit;
		}
		return Integer.MAX_VALUE;
	}

	/**
	 * Resets the temporary stack frame limit for the given execution context.
	 * The stack frame limit should be reset when the a thread is suspended.
	 *
	 * @param execCtx
	 *
	 * @since 2.1
	 */
	protected void resetStackFrameLimit(IExecutionDMContext execCtx) {
		if (execCtx instanceof IContainerDMContext) {
			for (Iterator<IExecutionDMContext> itr = fTemporaryLimits.keySet().iterator(); itr.hasNext();) {
				IExecutionDMContext limitCtx = itr.next();
				if (limitCtx.equals(execCtx) || DMContexts.isAncestorOf(limitCtx, execCtx)) {
					itr.remove();
				}
			}
		} else {
			fTemporaryLimits.remove(execCtx);
		}
	}

	/**
	 * Increment the stack frame limit by the default increment.
	 * This implementation doubles the current limit.
	 *
	 * @since 1.1
	 */
	public void incrementStackFrameLimit(IExecutionDMContext execCtx) {
		final int stackFrameLimit = getStackFrameLimit(execCtx);
		if (stackFrameLimit < Integer.MAX_VALUE / 2) {
			fTemporaryLimits.put(execCtx, stackFrameLimit * 2);
		} else {
			fTemporaryLimits.put(execCtx, Integer.MAX_VALUE);
		}
	}
}
