/*******************************************************************************
 * Copyright (c) 2006, 2010 Wind River Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     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<IExecutionDMContext, Integer>();

    /**
     * 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.length() > 0 &&
                        	   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.length() > 0 &&
                            	   (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.length() > 0 && function.contains(")") && //$NON-NLS-1$
                        	   module != null && module.length() > 0;
                    };
                },
                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.length() > 0 && !function.contains(")") && //$NON-NLS-1$
                            	   module != null && module.length() > 0;
                        };
                    },
                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.length() > 0;
                    };
                },
                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.length() > 0 && 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.length() > 0 && !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;
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.ui.viewmodel.datamodel.AbstractDMVMNode#updateHasElementsInSessionThread(org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate)
     */
    @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();
                }
            });
    }
    
    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.ui.viewmodel.datamodel.AbstractDMVMNode#updateElementsInSessionThread(org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate)
     */
    @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 = (SteppingController) 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" + dmData.getAddress().toString(16)); //$NON-NLS-1$
        }
            
        // Set the label to the result listener
        update.setLabel(label.toString(), 0);
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.ui.viewmodel.datamodel.AbstractDMVMNode#getContextsForEvent(org.eclipse.cdt.dsf.ui.viewmodel.VMDelta, java.lang.Object, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
     */
    @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);
    }
    
    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.ui.viewmodel.IVMNode#getDeltaFlags(java.lang.Object)
     */
    @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) {
        	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;
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.ui.viewmodel.IVMNode#buildDelta(java.lang.Object, org.eclipse.cdt.dsf.ui.viewmodel.VMDelta, int, org.eclipse.cdt.dsf.concurrent.RequestMonitor)
     */
    @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) {
            IExecutionDMContext execDmc = ((FullStackRefreshEvent)e).getDMContext();
            buildDeltaForFullStackRefreshEvent(execDmc, execDmc, parent, nodeOffset, rm);
        } 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();
                        }
                    })
                );
        }
    }
    
	/**
	 * 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$
    	}
    }
    
    /*
     * (non-Javadoc)
     * @see org.eclipse.debug.internal.ui.viewers.model.provisional.IElementMementoProvider#compareElements(org.eclipse.debug.internal.ui.viewers.model.provisional.IElementCompareRequest[])
     */
    @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();
        }
    }
    
    /*
     * (non-Javadoc)
     * @see org.eclipse.debug.internal.ui.viewers.model.provisional.IElementMementoProvider#encodeElements(org.eclipse.debug.internal.ui.viewers.model.provisional.IElementMementoRequest[])
     */
    @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);
		}
	}
}
