/*******************************************************************************
 * Copyright (c) 2010, 2011 Texas Instruments, Inc. 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:
 *     Dobrin Alexiev (Texas Instruments) - initial API and implementation (bug 240208)
********************************************************************************/
package org.eclipse.cdt.dsf.debug.ui.viewmodel.launch;

import java.util.ArrayList;

import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.datamodel.IDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerResumedDMEvent;
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.IResumedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.SteppingController.SteppingTimedOutEvent;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode;
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.debug.core.ILaunch;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;

/**
 * This class is a base class of AbstractThreadVMNode and AbstractContainerVMNode.
 * It contains common functionality between these classes.
 * 
 * The main reason this class is introduce is to allow the debug view to 
 * show multiple levels of execution containers and properly handle the delta generation.  
 * 
 * Longer term we would like to merge the classes AbstractContainerVMNode and 
 * AbstractThreadVMNode. That will make the implementation of both classes 
 * more generic and robust in the case of recursive containers.
 *  
 * Having this class as a base for both AbstractContainerVMNode and 
 * AbstractThreadVMNode enables us to merge them in the future.
 * 
 * Originally DefaultVMModelProxyStrategy didn't accept recursive container for 
 * generating deltas, even though they are accepted and supported by 
 * AbstractDMVMProvider  for viewing. 
 * The approach I took to support recursive container in delta generation is to have 
 * the VMNodes to generate level by level its deltas instead of one the whole delta at once. 
 * That required changes in identifying which is the correct context for each of the events. 
 *
 * See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=240208
 * 
 * @since 2.2
 * @experimental
 */
public abstract class AbstractExecutionContextVMNode extends AbstractDMVMNode
{
	/**
	 * List that keeps track of which events are considered leave events for 
	 * delta creation. 
	 */
    protected ArrayList<Class<?>> leafEventTypes = new ArrayList<Class<?>>();
    
	/**
	 * List that keeps track of which events are considered container events for 
	 * delta creation. 
	 */
    protected ArrayList<Class<?>> containerEventTypes = new ArrayList<Class<?>>();
    
    /** 
     * Constructor. 
     * Pass the parameter to the base class's constructor.
     * 
     * @param session
     * @param dmcClassType
     * 
     * @see #setChildNodes(IVMNode[])
     */
	public AbstractExecutionContextVMNode(AbstractDMVMProvider provider,
		DsfSession session, Class<? extends IDMContext> dmcClassType) {
		super(provider, session, dmcClassType);
	}

	/**
	 * Adds the events that common DSF classes relay on. 
	 */
	protected void addCommonEventTypes() {

		// non container events. 
    	addEventType(ISuspendedDMEvent.class,false);
    	addEventType(IResumedDMEvent.class, false); 
    	addEventType(FullStackRefreshEvent.class, false);
    	addEventType(SteppingTimedOutEvent.class, false);
    	addEventType(ExpandStackEvent.class, false);
		
    	// container events. 
    	addEventType(IContainerSuspendedDMEvent.class,true);
    	addEventType(IContainerResumedDMEvent.class, true);
	}

	/**
	 * When DSF debugger define custom events for which the containers and threads 
	 * nodes needs to be update they can need to register these events using this 
	 * function, so the proper recursive deltas are being created. 
	 *  
	 * @param eventClass
	 */
	protected void addEventType( Class<? extends IDMEvent<?>> eventClass, boolean containerEvent)
	{
		if( containerEvent) 
			containerEventTypes.add( eventClass);
		else 
			leafEventTypes.add( eventClass);
	}

	/**
	 * If DSF debuggers overrides the behavior of the AbstractThreadVMNode 
	 * or AbstractContainerVMNode, some event are no longer needed the derived 
	 * VMNdoe can call this method to remove some events. 
	 *  
	 * @param eventClass
	 * @param containerEvent
	 */
	protected void removeEventType( Class<?> eventClass, boolean containerEvent) {
		if( containerEvent)
			containerEventTypes.remove( eventClass);
		else
			leafEventTypes.remove( eventClass);
	}
	
	
	/**
	 * When we support recursive containers we want to make sure the immediate parent is returned only.
	 *
	 * @param parentDelta 
	 * @param e
	 * @param rm - request monitor
	 * @return true if the context is set by the method. 
	 */
    protected boolean getContextsForRecursiveVMNode(VMDelta parentDelta, Object e, final DataRequestMonitor<IVMContext[]> rm) {
    	
    	IExecutionDMContext leafContext = null;
    	if( isExecutionContainerEvent(e)) {
    		leafContext = getLeafContextForContainerEvent( e);
    	}
    	else if( isExecutionLeafEvent(e)) {
    		leafContext = getLeafContextForLeafEvent( e);
    	}
		if( leafContext != null) {
			setImmediateParentAsContexts(leafContext,parentDelta,rm);
			return true;
    	}
		return false;
    }
    
    /**
     * Make sure we build the delta for the recursive containers one level at a time.
     * 
     * @param e - the events. 
     * @param parentDelta
     * @param nodeOffset
     * @param requestMonitor
	 * @return true if the delta is built by this method. 
     */
	protected boolean buildDeltaForRecursiveVMNode(Object e, final VMDelta parentDelta, final int nodeOffset, final RequestMonitor requestMonitor) {

    	IExecutionDMContext leafContext = null;
    	if( isExecutionContainerEvent(e)) {
    		leafContext = getLeafContextForContainerEvent( e);
    	}
    	else if( isExecutionLeafEvent(e)) {
    		leafContext = getLeafContextForLeafEvent( e);
    	}
		if( leafContext != null) {
			addOneLevelToDelta( leafContext, parentDelta, requestMonitor);
			return true;
    	}
		return false;
	}
    
    /**
     * When the deltas are generated one level at a time we needs to distinguish  
     * between container and regular event to return the proper context for the event.    
     * 
     * @param event
     * @return
     */
	protected IExecutionDMContext getLeafContextForContainerEvent( Object event) {
    	
    	IExecutionDMContext leafEC =  null;
    	IExecutionDMContext[] triggeringContext = null;

    	if( isExecutionContainerEvent(event)) {
			if( event instanceof IContainerSuspendedDMEvent) {
				IContainerSuspendedDMEvent typedEvent = (IContainerSuspendedDMEvent)event;   
				triggeringContext = typedEvent.getTriggeringContexts();
			}
			if( event instanceof IContainerResumedDMEvent) {
				IContainerResumedDMEvent typedEvent = (IContainerResumedDMEvent)event;   
				triggeringContext = typedEvent.getTriggeringContexts();
			}
    	}
		
		if( triggeringContext != null && triggeringContext.length > 0){
			leafEC =  triggeringContext[0];
		}
		
		return leafEC;
    }

    /**
     * When the deltas are generated one level at a time we needs to distinguish  
     * between container and regular event to return the proper context for the event.    
     * 
     * @param event
     * @return
     */
	protected IExecutionDMContext getLeafContextForLeafEvent( Object event) {
    	
    	IExecutionDMContext leafEC =  null;
    	
    	if( event instanceof IDMEvent<?>) 
    		if( isExecutionLeafEvent( event)) {
	    		IDMEvent<?> typedEvent = (IDMEvent<?>)event;
	    		IDMContext dmContext = typedEvent.getDMContext();
	    		if( dmContext instanceof IExecutionDMContext)
	    			leafEC =  (IExecutionDMContext)dmContext;
    		}
    	
		return leafEC;
    }
    
	/**
	 * Considers the parent delta when we construct the next level. 
	 * 
	 * @param leafContext
	 * @param parentDelta
	 * @param requestMonitor
	 */
    protected void addOneLevelToDelta( IExecutionDMContext leafContext, VMDelta parentDelta, RequestMonitor requestMonitor) {
    	assert leafContext != null;
		if( parentDelta.getElement() instanceof ILaunch) {
			IContainerDMContext topContainer = 
				DMContexts.getTopMostAncestorOfType( leafContext, IContainerDMContext.class);
			
			// It is possible for a thread node to be an immediate child of a launch node
			// with no container node in between.  
			if( topContainer != null)
				parentDelta.addNode(createVMContext(topContainer), 0, IModelDelta.NO_CHANGE);
		}
		else if( parentDelta.getElement() instanceof IDMVMContext) {
			IDMVMContext vmContext = (IDMVMContext)parentDelta.getElement();
			IDMContext dmContext = vmContext.getDMContext(); 
			IExecutionDMContext current = DMContexts.getParentOfType(leafContext, IContainerDMContext.class);
			while( current != null) {
				IContainerDMContext parent = DMContexts.getParentOfType(current, IContainerDMContext.class);
				if( dmContext.equals(parent)) {
					parentDelta.addNode(createVMContext(current), 0, IModelDelta.NO_CHANGE);
					break;
				}
				current = parent;
			}
		}
		requestMonitor.done();
    }

    /**
     * Based on the event (container or not) set the proper context that is the immediate 
     * parent one level at a time. 
     * 
     * @param leafContext
     * @param parentDelta
     * @param rm
     */
    protected void setImmediateParentAsContexts( IExecutionDMContext leafContext, 
    	VMDelta parentDelta, DataRequestMonitor<IVMContext[]> rm){

    	assert leafContext != null;
    	IVMContext[] all = null;
		if( parentDelta.getElement() instanceof ILaunch) {
			IContainerDMContext topContainer = 
				DMContexts.getTopMostAncestorOfType( leafContext, IContainerDMContext.class);
			if( topContainer != null) {
				all = new IVMContext[] { createVMContext(topContainer) };
			}
			else {
				// the thread is directly a child node of the launch node (no container in the middle).
				all = new IVMContext[] { createVMContext(leafContext) };
			}
		}
		else if( parentDelta.getElement() instanceof IDMVMContext) {
			IDMVMContext vmContext = (IDMVMContext)parentDelta.getElement();
			IDMContext dmContext = vmContext.getDMContext(); 
			IExecutionDMContext current = leafContext;
			while( current != null) {
				IContainerDMContext parent = DMContexts.getParentOfType(current, IContainerDMContext.class);
				if( dmContext.equals(parent)) {
					all = new IVMContext[] { createVMContext(current)};
					break;
				}
				current = parent;
			}
		}
		if( all == null)
			all = new IVMContext[0];
		rm.setData( all );
		rm.done();
	}
	
    /**
     * Returns whether the event should be considered a container event or not. 
     * 
     * @param event
     * @return
     */
    protected boolean isExecutionContainerEvent( Object event) {
    	if( event != null)
	    	for( Class<?> clazz : containerEventTypes)
	    		if( clazz.isAssignableFrom(event.getClass()))
	    			return true;
	    return false;
	}

    /**
     * Returns whether the event should be use to generate delta for each of the levels.  
     * 
     * @param event
     * @return
     */
    protected boolean isExecutionLeafEvent( Object event) {
    	if( event != null)
	    	for( Class<?> clazz : leafEventTypes)
	    		if( clazz.isAssignableFrom(event.getClass()))
	    			return true;
	    return false;
	}
}
