/*******************************************************************************
 *  Copyright (c) 2005, 2011 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *     Wind River Systems - adapted to use with DSF
 *     Dobrin Alexiev (Texas Instruments) - user groups support  (bug 240208)   
 *******************************************************************************/
package org.eclipse.cdt.dsf.ui.viewmodel;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

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.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDeltaVisitor;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxy;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.Viewer;


/**
 * This is the default implementation of {@link IModelProxy} interface for 
 * use by a view model provider.  It implements an algorithm to walk the
 * tree hierarchy of nodes configured with a provider in order to compose
 * an {@link IModelDelta} for a given data model event.
 * <p/>
 * This class is closely linked with a view model provider which is required
 * for the constructor.  The view model provider is used to access the correct
 * executor and the node hierarchy. 
 * 
 * @since 1.0
 */
@ConfinedToDsfExecutor("#getProvider()#getExecutor()")
@SuppressWarnings("restriction")
public class DefaultVMModelProxyStrategy implements IVMModelProxy {

    private final AbstractVMProvider fProvider;
    private final Object fRootElement;
    private IPresentationContext fContext;
    private Viewer fViewer;
    private boolean fDisposed = false;
    private ListenerList fListeners = new ListenerList();
	private IDoubleClickListener fDoubleClickListener;
	private boolean fAllowRecursiveVMNodes = false;
    
    /**
     * Creates this model proxy strategy for the given provider. 
     */
    public DefaultVMModelProxyStrategy(AbstractVMProvider provider, Object rootElement) {
        fProvider = provider;
        fRootElement = rootElement;
    }
    
    @Override
    public boolean isDeltaEvent(Object event) {
        return getEventDeltaFlags(event) != IModelDelta.NO_CHANGE; 
    }

    @Override
    public int getEventDeltaFlags(Object event) {
        IRootVMNode rootNode = getVMProvider().getRootVMNode();
        if (rootNode != null && 
            rootNode.isDeltaEvent(getRootElement(), event)) 
        {
            return getDeltaFlags(rootNode, null, event);
        }
        return IModelDelta.NO_CHANGE; 
    }

    
    /**
     * Returns the view model provider that this strategy is configured for.
     * @return
     */
    protected AbstractVMProvider getVMProvider() {
        return fProvider;
    }
    
    
    private Object[] getListeners() {
        return fListeners.getListeners();
    }

    @Override
    public void addModelChangedListener(IModelChangedListener listener) {
        fListeners.add(listener);
    }

    @Override
    public void removeModelChangedListener(IModelChangedListener listener) {
        fListeners.remove(listener);
    }

    @Override
    public Object getRootElement() {
        return fRootElement;
    }
    
    /** @since 1.1 */
    @Override
    public Object getViewerInput() {
        return fRootElement;
    }
    
    /** @since 1.1 */
    @Override
    public TreePath getRootPath() {
        return TreePath.EMPTY;
    }
    
    /**
     * Notifies registered listeners of the given delta.
     * 
     * @param delta model delta to broadcast
     */
    @Override
    public void fireModelChanged(IModelDelta delta) {
        final IModelDelta root = getRootDelta(delta);
        Object[] listeners = getListeners();
        for (int i = 0; i < listeners.length; i++) {
            final IModelChangedListener listener = (IModelChangedListener) listeners[i];
            ISafeRunnable safeRunnable = new ISafeRunnable() {
                @Override
                public void handleException(Throwable exception) {
                    DebugUIPlugin.log(exception);
                }

                @Override
                public void run() throws Exception {
                    listener.modelChanged(root, DefaultVMModelProxyStrategy.this);
                }

            };
            SafeRunner.run(safeRunnable);
        }
    }
    
    /**
     * Convenience method that returns the root node of the given delta.
     * 
     * @param delta delta node
     * @return returns the root of the given delta
     */
    protected IModelDelta getRootDelta(IModelDelta delta) {
        IModelDelta parent = delta.getParentDelta();
        while (parent != null) {
            delta = parent;
            parent = delta.getParentDelta();
        }
        return delta;
    }

    /* (non-Javadoc)
     * @see org.eclipse.debug.internal.ui.viewers.IModelProxy#dispose()
     */
    @Override
    public void dispose() {
        fDisposed = true;
        if (fViewer instanceof StructuredViewer && fDoubleClickListener != null) {
        	((StructuredViewer) fViewer).removeDoubleClickListener(fDoubleClickListener);
        	fDoubleClickListener= null;
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.debug.internal.ui.viewers.IModelProxy#init(org.eclipse.debug.internal.ui.viewers.IPresentationContext)
     */
    @Override
    public void init(IPresentationContext context) {
        fDisposed = false;
        fContext = context;
    }
    
    /**
     * Returns the context this model proxy is installed in.
     * 
     * @return presentation context, or <code>null</code> if this
     *  model proxy has been disposed
     */
    public IPresentationContext getPresentationContext() {
        return fContext;
    }

    /* (non-Javadoc)
     * 
     * Subclasses should override as required.
     * 
     * @see org.eclipse.debug.internal.ui.viewers.provisional.IModelProxy#installed(org.eclipse.jface.viewers.Viewer)
     */
    @Override
    public void installed(final Viewer viewer) {  
        fViewer = viewer;
        getVMProvider().getExecutor().execute( new DsfRunnable() {
            @Override
            public void run() {
                fProvider.handleEvent(new ModelProxyInstalledEvent(DefaultVMModelProxyStrategy.this, viewer, fRootElement));
            }
        });
        if (fViewer instanceof StructuredViewer && fDoubleClickListener == null) {
        	((StructuredViewer) fViewer).addDoubleClickListener(fDoubleClickListener= new IDoubleClickListener() {
        	    @Override
        		public void doubleClick(DoubleClickEvent e) {
        			handleDoubleClick(e);
        		}
        	});
        }
    }
    
    /**
     * Handle viewer double click.
     * 
	 * @param e  the event
	 * 
	 * @since 1.1
	 */
	protected void handleDoubleClick(final DoubleClickEvent e) {
		final AbstractVMProvider vmProvider= getVMProvider();
		if (!vmProvider.isDisposed()) {
	        ISelection selection = e.getSelection();
			if (!selection.isEmpty() && selection instanceof ITreeSelection) {
	            final TreePath path = ((ITreeSelection)selection).getPaths()[0];
	    	    final Object input = e.getViewer().getInput();
	    	    
	            vmProvider.getExecutor().execute( new DsfRunnable() {
	                @Override
	                public void run() {
	                    Object rootElement = getRootElement();
	                    boolean eventContainsRootElement = rootElement.equals(input);
	                    for (int i = 0; !eventContainsRootElement && i < path.getSegmentCount(); i++) {
	                        eventContainsRootElement = rootElement.equals(path.getSegment(i));
	                    }

	                    if (eventContainsRootElement) {
	                        vmProvider.handleEvent(e);
	                    }
	                }
	            });
	        }
		}
	}

	/**
     * Returns the viewer this proxy is installed in.
     * 
     * @return viewer or <code>null</code> if not installed
     * 
     * @since 1.1
     */
    @Override
    public Viewer getViewer() {
        return fViewer;
    }
    
    /* (non-Javadoc)
     * @see org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxy#isDisposed()
     */
    @Override
    public boolean isDisposed() {
        return fDisposed;
    }

	/**
	 * Recursively calls the VM nodes in the hierarchy of the given node to
	 * determine how elements of those types may be (or are) affected by
	 * [event], the answer being a collection of IModelDelta flags.
	 * 
	 * A response of IModeDelta.CONTENT has a special meaning. If we return that
	 * flag for an IVMNode, it means the <i>collection</i> of elements of that
	 * type are affected. It is not a statement on the elements themselves.
	 * 
	 * Optimization 1: If the first-level child node does not have
	 * <code>IModelDelta.CONTENT</code> but one of its descendants does, then
	 * for optimization reasons we return <code>IModelDelta.STATE</code>
	 * instead. See {@link DefaultVMModelProxyStrategy#buildChildDeltasForAllContexts(IVMNode, Object, VMDelta, int, RequestMonitor)}
	 * 
	 * Optimization 2: If the parent delta contains
	 * <code>IModelDelta.CONTENT</code>, we do not need to specify it for its
	 * children. This can shorten delta processing considerably.
	 * 
	 * @param node
	 *            the IVMNode whose delta flags (and those of its descendants)
	 *            are being queried
	 * @param parentDelta
	 *            the base portion of the delta the caller is trying to
	 *            construct; this delta node is specifically the deepest node in
	 *            that chain (i.e., has no children, but may have ancestors)
	 * @param event
	 *            the event the caller is trying to produce a delta for
	 * @return the collective set of IModelDelta flags that reflect how [node]
	 *         and its descendants may be (or are) affected by [event]
	 */
    protected int getDeltaFlags(IVMNode node, ModelDelta parentDelta, Object event) {
        int flags = node.getDeltaFlags(event);
        for (IVMNode childNode : getVMProvider().getChildVMNodes(node)) {
            if (!childNode.equals(node)) {
                int childNodeDeltaFlags = getDeltaFlags(childNode, parentDelta, event);
                
                // optimization 1; see above
                if ((childNodeDeltaFlags & IModelDelta.CONTENT) != 0) {
                    childNodeDeltaFlags &= ~IModelDelta.CONTENT;
                    childNodeDeltaFlags |= IModelDelta.STATE;
                }
                
                flags |= childNodeDeltaFlags;
            }
        }
        
        // optimization 2; see above
        while (parentDelta != null) {
            if ( (parentDelta.getFlags() & IModelDelta.CONTENT) != 0 ) {
                flags = flags & ~IModelDelta.CONTENT & ~IModelDelta.STATE;
                break;
            }
            parentDelta = (ModelDelta)parentDelta.getParentDelta();
        }
        return flags;
    }
    
    /**
     * Default implementation creates a delta assuming that the root node
     * is the input object into the view.  
     */
    @Override
    public void createDelta(final Object event, final DataRequestMonitor<IModelDelta> rm) {
        final IRootVMNode rootNode = getVMProvider().getRootVMNode(); 
        
        // Always create the rootDelta, no matter what delta flags the child nodes have.
        rootNode.createRootDelta(
            getRootElement(), event, 
            new DataRequestMonitor<VMDelta>(getVMProvider().getExecutor(), rm) {
                @Override
                protected void handleSuccess() {
					// The resulting delta will have parents if our
					// VMProvider is registered to populate only a sub-tree
					// of the viewer. Get the root node of the chain--i.e.,
					// the delta for the root element of the entire viewer.
                    final IModelDelta viewRootDelta = getRootDelta(getData());
                    
                    // Find the child nodes that (may) have deltas for the given event. 
                    final Map<IVMNode,Integer> childNodesWithDeltaFlags = getChildNodesWithDeltaFlags(rootNode, getData(), event);

                    // If no child nodes have deltas we can stop here. 
                    if (childNodesWithDeltaFlags.size() == 0) {
                        rm.setData(viewRootDelta);
                        rm.done();
                        return;
                    }            
                    
                    callChildNodesToBuildDelta(
                        rootNode, 
                        childNodesWithDeltaFlags, getData(), event, 
                        new RequestMonitor(getVMProvider().getExecutor(), rm) {
                            @Override
                            protected void handleSuccess() {
                                // Get rid of redundant CONTENT and STATE flags in delta and prune 
                                // nodes without flags
                                rm.setData(pruneDelta((VMDelta)viewRootDelta));
                                rm.done();
                            }
                        });
                }
            });
    }

    protected VMDelta pruneDelta(VMDelta delta) {
        delta.accept(new IModelDeltaVisitor() {
            @Override
            public boolean visit(IModelDelta deltaNode, int depth) {
                if ((deltaNode.getFlags() & (IModelDelta.CONTENT | IModelDelta.STATE)) != 0) {
                    VMDelta parent = (VMDelta)deltaNode.getParentDelta();
                    while (parent != null) {
                        if ((parent.getFlags() & IModelDelta.CONTENT) != 0) {
                            ((VMDelta)deltaNode).setFlags(deltaNode.getFlags() & ~(IModelDelta.CONTENT | IModelDelta.STATE));
                            break;
                        }
                        parent = parent.getParentDelta();
                    }
                }
                return true;
            }
        });
        return delta;
    }
    
    /** 
     * Base implementation that handles calling child nodes to build 
     * the model delta.  This method delegates to two other methods:
     * {@link #buildChildDeltasForEventContext(IVMContext[], IVMNode, Object, VMDelta, int, RequestMonitor)} 
     * and {@link #buildChildDeltasForAllContexts(IVMNode, Object, VMDelta, int, RequestMonitor)}, 
     * depending on the result of calling{@link IVMNode#getContextsForEvent(VMDelta, Object, DataRequestMonitor)}.
     * @see IVMNode#buildDelta(Object, ModelDelta, int, RequestMonitor)
     */
    protected void buildChildDeltas(final IVMNode node, final Object event, final VMDelta parentDelta, 
        final int nodeOffset, final RequestMonitor rm) 
    {
        node.getContextsForEvent(
            parentDelta,
            event, 
            new DataRequestMonitor<IVMContext[]>(getVMProvider().getExecutor(), rm) {
                @Override
                protected void handleCompleted() {
                    if (isSuccess()) {
                    	assert getData() != null;
                    	buildChildDeltasForEventContext(getData(), node, event, parentDelta, nodeOffset, rm);
                    } 
                    else if (getStatus().getCode() == IDsfStatusConstants.NOT_SUPPORTED) {
                    	// The DMC for this node was not found in the event.  Call the 
                    	// super-class to resort to the default behavior which will add a 
                    	// delta for every element in this node.
                    	buildChildDeltasForAllContexts(node, event, parentDelta, nodeOffset, rm);
                    } 
                    else {
                        super.handleCompleted();
                    }
                }
            });
    }
    
    /** 
     * Base implementation that handles calling child nodes to build 
     * the model delta.  This method is called with the context obtained  
     * by calling{@link IVMNode#getContextsForEvent(VMDelta, Object, DataRequestMonitor)}.
     * @see IVMNode#buildDelta(Object, ModelDelta, int, RequestMonitor)
     */
    protected void buildChildDeltasForEventContext(final IVMContext[] vmcs, final IVMNode node, final Object event, 
        final VMDelta parentDelta, final int nodeOffset, final RequestMonitor rm) 
    {
        final Map<IVMNode,Integer> childNodesWithDeltaFlags = getChildNodesWithDeltaFlags(node, parentDelta, event);
        if (childNodesWithDeltaFlags.size() == 0) {
            // There are no child nodes with deltas, just return to parent.
            rm.done();
            return;
        }            
        
        // Check if any of the child nodes are will generate IModelDelta.SELECT  or 
        // IModelDelta.EXPAND flags.  If so, we must calculate the index for this 
        // VMC.
        boolean calculateIndex = false;
        if (nodeOffset >= 0) {
            for (int childDelta : childNodesWithDeltaFlags.values()) {
                if ( (childDelta & (IModelDelta.SELECT | IModelDelta.EXPAND)) != 0 ) {
                    calculateIndex = true;
                    break;
                }
            }
        }
        
        if (calculateIndex) {
            // Calculate the index of this node by retrieving all the 
            // elements and then finding the DMC that the event is for.  
            getVMProvider().updateNode(
                node, 
                new VMChildrenUpdate(
                    parentDelta, getVMProvider().getPresentationContext(), -1, -1,
                    new DataRequestMonitor<List<Object>>(getVMProvider().getExecutor(), rm) {
                        @Override
                        protected void handleSuccess() {
                            // Check for an empty list of elements.  If it's empty then we 
                            // don't have to call the children nodes, so return here.
                            // No need to propagate error, there's no means or need to display it.
                            if (getData().isEmpty()) {
                                rm.done();
                                return;
                            }

                            CountingRequestMonitor countingRm = 
                                new CountingRequestMonitor(getVMProvider().getExecutor(), rm);
                            
                            int count = 0;
                            for (IVMContext vmc : vmcs) {
                                // Find the index of the vmc in the full list of elements.
                                int i;
                                for (i = 0; i < getData().size(); i++) {
                                    if (vmc.equals(getData().get(i))) break;
                                }                            
                                if (i == getData().size()) {
                                    // Element not found, no need to generate the delta.
                                    continue;
                                }
                                
                                // Optimization: Try to find a delta with a matching element, if found use it.  
                                // Otherwise create a new delta for the event element.
                                int elementIndex = nodeOffset + i;
                                VMDelta delta = parentDelta.getChildDelta(vmc);
                                if (delta == null || delta.getIndex() != elementIndex) {
                                    delta = parentDelta.addNode(vmc, elementIndex, IModelDelta.NO_CHANGE);
                                }
                                
                                callChildNodesToBuildDelta(
                                    node, childNodesWithDeltaFlags, delta, event, countingRm);
                                count++;
                            }
                            countingRm.setDoneCount(count);
                        }
                    }));
        } else {
            CountingRequestMonitor countingRm = 
                new CountingRequestMonitor(getVMProvider().getExecutor(), rm);
            int count = 0;
            for (IVMContext vmc : vmcs) {
                // Optimization: Try to find a delta with a matching element, if found use it.  
                // Otherwise create a new delta for the event element.    
                VMDelta delta = parentDelta.getChildDelta(vmc);
                if (delta == null) {
                    delta = parentDelta.addNode(vmc, IModelDelta.NO_CHANGE);
                }
                callChildNodesToBuildDelta(node, childNodesWithDeltaFlags, delta, event, rm);
                count++;
            }
            countingRm.setDoneCount(count);
        }            
    }

    /** 
     * Base implementation that handles calling child nodes to build 
     * the model delta.  The child nodes are called with all the elements 
     * in this node, which could be very inefficient.  This method is only 
     * called if {@link IVMNode#getContextsForEvent(VMDelta, Object, DataRequestMonitor)}
     * is not supported by the node for the given element.
     * @see IVMNode#buildDelta(Object, ModelDelta, int, RequestMonitor)
     */
    protected void buildChildDeltasForAllContexts(final IVMNode node, final Object event, final VMDelta parentDelta,
        final int nodeOffset, final RequestMonitor rm) 
    {
        final Map<IVMNode,Integer> childNodesWithDeltaFlags = getChildNodesWithDeltaFlags(node, parentDelta, event);
        if (childNodesWithDeltaFlags.size() == 0) {
            // There are no child nodes with deltas, just return to parent.
            rm.done();
            return;
        }            

		// Check if the child delta only has an IModelDelta.STATE flag. If
		// that's the case, we can skip creating a delta for this node, because
		// the Debug Platform's handling of that flag does not require ancestor
		// deltas (doesn't need to know the path to the element)
        //
		// We can skip the delta for this node even if a deeper IVMNode child
		// calls for an IModelDelta.CONTENT flag, since what we do in that case
		// is have the CONTENT flag applied to the first ancestor delta that has
		// something other than IModelDelta.STATE.
        //
        // The main benefit of this optimization is that the viewer is left 
        // to retrieve the elements that need to be refreshed, rather 
        // than having this proxy do it.  Since the viewer is lazy loading
        // it may not need to retrieve as many elements in the hierarchy.
        //
        // For example: suppose the model looks like:
        // A-
        // |-1
        //   |-I
        //   | |-a
        //   | |-b
        //   |-II
        //   | |-c
        //   |-III
        //     |-d
        //
		// And if VM Node responsible for element a, b, c, d needs a CONTENT
		// update, then the delta may look like this:
        //
        // Element: A
        //        Flags: CONTENT
        //        Index: 0 Child Count:-1
        //
        // Instead of:
        //
        //    Element: A
        //        Flags: NO_CHANGE
        //        Index: 0 Child Count: 1
        //        Element: 1
        //            Flags: NO_CHANGE
        //            Index: 0 Child Count: 3
        //            Element: I
        //                Flags: CONTENT
        //                Index: 0 Child Count: 2
        //            Element: II
        //                Flags: CONTENT
        //                Index: 1 Child Count: 1
        //            Element: III
        //                Flags: CONTENT
        //                Index: 2 Child Count: 1
        boolean mustGetElements = false;
        boolean _updateFlagsOnly = true;
        for (int childDelta : childNodesWithDeltaFlags.values()) {
            if ((childDelta & ~IModelDelta.STATE) != 0) {
                mustGetElements = true;
            }
            if ((childDelta & ~(IModelDelta.STATE | IModelDelta.CONTENT)) != 0) {
                _updateFlagsOnly = false;
            }
        }
        final boolean updateFlagsOnly = _updateFlagsOnly; 

        if (!mustGetElements) {
            callChildNodesToBuildDelta(
                node, childNodesWithDeltaFlags, parentDelta, event, 
                new RequestMonitor(getVMProvider().getExecutor(), rm) {
                    @Override
                    protected void handleError() {
                        super.handleError();
                    }
                });
        } else {
            // The given child nodes have deltas potentially for all elements
            // from this node.  Retrieve all elements and call the child nodes with
            // each element as the parent of their delta.
            getVMProvider().updateNode(
                node, 
                new VMChildrenUpdate(
                    parentDelta, getVMProvider().getPresentationContext(), -1, -1,
                    new DataRequestMonitor<List<Object>>(getVMProvider().getExecutor(), rm) {
                        @Override
                        protected void handleCompleted() {
                            if (fDisposed) return;
                            
                            final List<Object> childElements = getData();
                            
                            // Check for an empty list of elements.  If the list of elements is empty
                            // still call the child nodes using the parent delta only.  Do this only if
                            // an optimization was used to build the delta, so that the child node can  
                            // adds the optimized delta flags without the full delta (bug 280770).
                            if (childElements == null || childElements.size() == 0) {
                                if (updateFlagsOnly) {
                                    callChildNodesToBuildDelta(
                                        node, childNodesWithDeltaFlags, parentDelta, event, rm);
                                } else {
                                    rm.done();
                                    return;
                                }
                            } else {
                                final CountingRequestMonitor countingRM = new CountingRequestMonitor(getVMProvider().getExecutor(), rm);
                                int rmCount = 0;
        
                                // For each element from this node, create a new delta, 
                                // and then call all the child nodes to build their delta. 
                                for (int i = 0; i < childElements.size(); i++) {
                                    int elementIndex = nodeOffset >= 0 ? nodeOffset + i : -1;
                                    VMDelta delta= parentDelta.getChildDelta(childElements.get(i));
                                    if (delta == null) {
                                        delta= parentDelta.addNode(childElements.get(i), elementIndex, IModelDelta.NO_CHANGE);
                                    }
                                    callChildNodesToBuildDelta(node, childNodesWithDeltaFlags, delta, event, countingRM);
                                    rmCount++;
                                }
                                countingRM.setDoneCount(rmCount);
                            }
                        }
                    })
                );
        }
    }

	/**
	 * Calls the specified child nodes (of [node]) to build the delta for the
	 * given event.
	 * 
	 * @param childNodes
	 *            Map of nodes to be invoked, and the corresponding delta flags
	 *            that they may (or will) generate. This map is generated with a
	 *            call to {@link #getChildNodesWithDeltaFlags(Object)}.
	 * @param delta
	 *            The delta object to build on. This delta should have been
	 *            generated by this node, unless the full delta path is not
	 *            being calculated due to an optimization.
	 * @param event
	 *            The event object that the delta is being built for.
	 * @param rm
	 *            The result monitor to invoke when the delta is completed.
	 */
    protected void callChildNodesToBuildDelta(final IVMNode node, final Map<IVMNode,Integer> childNodes, final VMDelta delta, 
        final Object event, final RequestMonitor rm) 
    {
        assert childNodes.size() != 0;

		// Check if any of the child nodes might generate a delta that requires
		// us to calculate the index for this VMC.
        boolean calculateOffsets = false;
        for (int childDelta : childNodes.values()) {
            if ( (childDelta & (IModelDelta.SELECT | IModelDelta.EXPAND | IModelDelta.INSERTED | IModelDelta.REMOVED)) != 0 ) {
                calculateOffsets = true;
                break;
            }
        }

        getChildNodesElementOffsets(
            node, delta, calculateOffsets, 
            new DataRequestMonitor<Map<IVMNode, Integer>>(getVMProvider().getExecutor(), rm) {
                @Override
                protected void handleSuccess() {
                    final CountingRequestMonitor multiRm = new CountingRequestMonitor(getVMProvider().getExecutor(), rm);
                    int multiRmCount = 0;
                    
                    // Set the total count of number of children in the parent delta.
                    delta.setChildCount(getData().get(null));
                    
                    for (final IVMNode childNode : childNodes.keySet()) {

                    	if (node.equals(childNode)) {
                    		
                        	// Avoid descending into recursive node hierarchy's when calculating the delta.
                        	// if recursive nodes are not allowed. 
                    		if( !allowRecursiveVMNodes())
                    			continue;

                    		// get into recursion to build the delta only if the recursive context is added.
                    		// 
                    		// We user current assumption that recursive container can be added as first children 
                    		// if the list of VMNodes. If we decide to make the patch more generic ( allow recursive 
                    		// node to be at different index) we need to remove this simplification.
                    		//
                    		if( isDeltaElementOfType(delta, childNode)) {
                    			childNode.buildDelta(
                					event, delta, 0,   
                					new RequestMonitor(getVMProvider().getExecutor(), multiRm) {
                						@Override
                						protected void handleSuccess() {
                							buildChildDeltas( 
                								childNode, event, delta, 0,  
                								new RequestMonitor(getVMProvider().getExecutor(), multiRm) );
                						}
                        		});
                        		multiRmCount++;
                    		}
                    		
                    		continue;
                    	}
                        
                        final int nodeOffset = getData().get(childNode);
                        childNode.buildDelta(
                            event, delta, nodeOffset, 
                            new RequestMonitor(getVMProvider().getExecutor(), multiRm) {
                                @Override
                                protected void handleSuccess() {
                                    buildChildDeltas( 
                                        childNode, event, delta, nodeOffset, 
                                        new RequestMonitor(getVMProvider().getExecutor(), multiRm) );
                                }
                            });
                        multiRmCount++;
                    }
                    multiRm.setDoneCount(multiRmCount);
                }
            });
    }

    /**
     * Calculates the indexes at which the elements of each of the child 
     * nodes begin.  These indexes are necessary to correctly 
     * calculate the deltas for elements in the child nodes.
     * @param delta The delta object to build on.  This delta should have been 
     * generated by this node, unless the full delta path is not being calculated
     * due to an optimization.
     * @param doCalculdateOffsets If true, the method calls each node to get its
     * element count. If false, it causes this method to fill the return data
     * structure with dummy values.  The dummy values indicate that the indexes
     * are not known and are acceptable in the delta if the delta flags being 
     * generated do not require full index information. 
     * @param rm Return token containing the results.  The result data is a 
     * mapping between the child nodes and the indexes at which the child nodes' 
     * elements begin.  There is a special value in the map with a <code>null</code>
     * key, which contains the full element count for all the nodes. 
     */
    private void getChildNodesElementOffsets(IVMNode node, IModelDelta delta, boolean calculdateOffsets, final DataRequestMonitor<Map<IVMNode, Integer>> rm) {
        final IVMNode[] childNodes = getVMProvider().getChildVMNodes(node); 
        assert childNodes.length != 0;
        
        if (calculdateOffsets) {
            final Integer[] counts = new Integer[childNodes.length]; 
            final CountingRequestMonitor crm = new CountingRequestMonitor(getVMProvider().getExecutor(), rm) { 
                    @Override
                    protected void handleSuccess() {
                        Map<IVMNode, Integer> data = new HashMap<IVMNode, Integer>();
                        int offset = 0;
                        for (int i = 0; i < childNodes.length; i++) {
                            data.put(childNodes[i], offset);
                            offset += counts[i];
                        }
                        // As the final value, put the total count in the return map, with null key.
                        data.put(null, offset);
                        rm.setData(data);
                        rm.done();
                    }
            };
            int countRM = 0;
            
            for (int i = 0; i < childNodes.length; i++) {
                final int nodeIndex = i;
                getVMProvider().updateNode(
                    childNodes[i], 
                    new VMChildrenCountUpdate(
                        delta, getVMProvider().getPresentationContext(),
                            new DataRequestMonitor<Integer>(getVMProvider().getExecutor(), crm) {
                                @Override
                                protected void handleCompleted() {
                                    counts[nodeIndex] = getData();
                                    crm.done();
                                }
                            } 
                        )
                    );
                countRM++;
            }
            crm.setDoneCount(countRM);
        } else {
            Map<IVMNode, Integer> data = new HashMap<IVMNode, Integer>();
            for (int i = 0; i < childNodes.length; i++) {
                data.put(childNodes[i], -1);
            }
            data.put(null, -1);
            rm.setData(data);
            rm.done();
        }            
    }

	/**
	 * Convenience method that returns what each of the child nodes returns from
	 * {@link #getDeltaFlags(IVMNode, ModelDelta, Object)}. Children that return
	 * IModelDelta.NO_CHANGE are omitted.
	 */
    protected Map<IVMNode, Integer> getChildNodesWithDeltaFlags(IVMNode node, ModelDelta parentDelta, Object e) {
        Map<IVMNode, Integer> nodes = new HashMap<IVMNode, Integer>(); 
        for (final IVMNode childNode : getVMProvider().getChildVMNodes(node)) {
            if (!childNode.equals(node) || allowRecursiveVMNodes()) {
                int delta = getDeltaFlags(childNode, parentDelta, e);
                if (delta != IModelDelta.NO_CHANGE) {
                    nodes.put(childNode, delta);
                }
            }
        }
        return nodes;
    }

    /**
     * Returns whether DefaultVMModelProxyStrategy allows to handles recursive VMNdoes hierarchy.
     *  
     * @see setAllowRecursiveVMNodes()
     * @return true if this DefaultVMModelProxyStrategy allows recursive containers.  
     */
    public boolean allowRecursiveVMNodes() { 
    	return fAllowRecursiveVMNodes;
    }
    
    /**
     * Allow DefaultVMModelProxyStrategy to handles recursive VMNdoes hierarchy.
     * 	
     * For example if the client wants the debug view to display container nodes that  
     * have containers this flag has to be set.  
     * 
     * 	Launch1 
     * 		Container-1
     *			Container-1.1
     *			Thread-1	
     *				Container-1.1.1
     *				Thread-2
     *				Thread-2
     * 
     * This will allow the client to setup a VMNode to be in the list of its children. 
     * 		addChildNodes(containerNode, new IVMNode[] { containerNode, threadsNode });
     * 
     * The client also need to make sure the recursive VMNodes and their immediate children: 
     * 1. Handles buildDelta() by building one level at a time by examining the delta passed as parameter.   
     * 2. Returns the correct level container inside getContextsForEvent() based on the delta passed. 
     * 
     * See org.eclipse.cdt.dsf.gdb.internal.ui.viewmodel.launch.ContainerVMNode for sample implementation. 
     * 
     * @param allow - whether to allow or not recursive containment of VMNodes. 
     */
    public void setAllowRecursiveVMNodes( boolean allow) {
    	fAllowRecursiveVMNodes = allow;
    }
    
    /**
     * Compares if the VMNode of element of the provided delta is the same as the provided IVMNode.  
     * 		
     * @param delta - delta for which to compare the IDMVMContext 
     * @param node - the IVMNode we want to compare to.
     * @return if the VMNode of element of the provided delta is the same as the provided IVMNode.
     */
	protected boolean isDeltaElementOfType( VMDelta delta, IVMNode node) {
		if( delta.getElement() instanceof IDMVMContext) {
			IDMVMContext dmvmContext = (IDMVMContext)delta.getElement();
			return dmvmContext.getVMNode().equals(node);
		}
		return false;
	}
}
