/*******************************************************************************
 * Copyright (c) 2008, 2010 Wind River Systems, 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:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.dsf.debug.ui.viewmodel.launch;

import java.util.Map;

import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
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.IFrameDMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.update.IElementUpdateTester;
import org.eclipse.cdt.dsf.ui.viewmodel.update.IVMUpdatePolicy;
import org.eclipse.cdt.dsf.ui.viewmodel.update.UpdatePolicyDecorator;
import org.eclipse.jface.viewers.TreePath;

/**
 * An update strategy decorator specialized for delayed stack frame refresh. The
 * strategy flushes only the cached top stack frame in case of an normal {@link ISuspendedDMEvent},
 * while in in case of a special {@link FullStackRefreshEvent} everything is invalidated.
 * 
 * <p>
 * The underlying base update policy is considered for container contexts only.
 * In other cases the cache data is always flushed.
 * </p>
 * 
 * @since 1.1
 */
public class DelayedStackRefreshUpdatePolicy extends UpdatePolicyDecorator {

	private static final class DelayedStackRefreshUpdateTester implements IElementUpdateTester {

	    private final IElementUpdateTester fBaseTester;
	    
        /** Indicates whether only the top stack frame should be updated */
        private final boolean fLazyStackFrameMode;

		DelayedStackRefreshUpdateTester(IElementUpdateTester baseTester, boolean lazyStackFrameMode) {
		    fBaseTester = baseTester;
		    fLazyStackFrameMode = lazyStackFrameMode;
		}
		@Override
		public int getUpdateFlags(Object viewerInput, TreePath path) {
            Object element = path.getSegmentCount() != 0 ? path.getLastSegment() : viewerInput;
			if (element instanceof IDMVMContext) {
				IDMContext dmc = ((IDMVMContext) element).getDMContext();
				if (fLazyStackFrameMode) {
					if (dmc instanceof IFrameDMContext) {
						if (((IFrameDMContext) dmc).getLevel() == 0) {
							return FLUSH;
						}
					} else if (dmc instanceof IExecutionDMContext) {
						return fBaseTester.getUpdateFlags(viewerInput, path);
					}
					return DIRTY;
				} else if (dmc instanceof IContainerDMContext) {
					return fBaseTester.getUpdateFlags(viewerInput, path);
				}
			}
			return FLUSH;
		}

		@Override
		public boolean includes(IElementUpdateTester tester) {
		    // A non-lazy tester includes a lazy tester, but not vice versa.  
		    // This allows entries that were marked as dirty by a flush with
		    // the lazy mode to be superseded by a non-lazy update which 
		    // actually clears the entries that were marked as dirty.
			if (tester instanceof DelayedStackRefreshUpdateTester) {
			    DelayedStackRefreshUpdateTester sfTester = (DelayedStackRefreshUpdateTester)tester;
			    if (fLazyStackFrameMode) {
			    	if (sfTester.fLazyStackFrameMode) {
			    	    return fBaseTester.includes(sfTester.fBaseTester);
			    	}
			    } else {
			    	if (!sfTester.fLazyStackFrameMode) {
			    	    return fBaseTester.includes(sfTester.fBaseTester);
			    	}
			    	// non-lazy includes lazy
			        return true;
			    }
			}
			return false;
		}
		
        @Override
        public String toString() {
            return "Delayed stack refresh (lazy = " + fLazyStackFrameMode + ", base = " + fBaseTester + ") update tester"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }

	}

	private static final class ThreadsUpdateTester implements IElementUpdateTester {

        private final IElementUpdateTester fBaseTester;
        
        private final boolean fRefreshAll;
        
        ThreadsUpdateTester(IElementUpdateTester baseTester, boolean refreshAll) {
            fBaseTester = baseTester;
            fRefreshAll = refreshAll;
        }

        @Override
		public int getUpdateFlags(Object viewerInput, TreePath path) {
            Object element = path.getSegmentCount() != 0 ? path.getLastSegment() : viewerInput;
            
            if (!fRefreshAll && element instanceof IDMVMContext) {
                IDMContext dmc = ((IDMVMContext) element).getDMContext();
                if (dmc instanceof IContainerDMContext) {
                    return fBaseTester.getUpdateFlags(viewerInput, path);
                }
            }
            
            // If the element is not a container or if the flush all flag is set, 
            // always flush it.
            return FLUSH;
        }
        
        @Override
		public boolean includes(IElementUpdateTester tester) {
            // A refresh-all tester includes a non-refresh-all tester, but not 
            // vice versa. This allows entries that were marked as dirty by 
            // a flush with
            // the non-refresh-all to be superseded by a refresh-all update which 
            // actually clears the entries that were marked as dirty.
            if (tester instanceof ThreadsUpdateTester) {
                ThreadsUpdateTester threadsTester = (ThreadsUpdateTester)tester;
                if (fRefreshAll) {
                    if (threadsTester.fRefreshAll) {
                        return fBaseTester.includes(threadsTester.fBaseTester);
                    }
                    // refresh-all includes the non-refresh-all
                    return true;
                } else {
                    if (!threadsTester.fRefreshAll) {
                        return fBaseTester.includes(threadsTester.fBaseTester);
                    }
                }
            }
            return false;
        }
        
        @Override
        public String toString() {
            return "Threads update tester (base = " + fBaseTester + ") update tester"; //$NON-NLS-1$ //$NON-NLS-2$ 
        }
	}

	
	public DelayedStackRefreshUpdatePolicy(IVMUpdatePolicy base) {
		super(base);
	}

	@Override
	public IElementUpdateTester getElementUpdateTester(Object event) {
		if (event instanceof ISuspendedDMEvent) {
			return new DelayedStackRefreshUpdateTester(getBaseUpdatePolicy().getElementUpdateTester(event), true);
		} else if (event instanceof FullStackRefreshEvent) {
            return new DelayedStackRefreshUpdateTester(getBaseUpdatePolicy().getElementUpdateTester(event), false);
		} else if (event instanceof IExitedDMEvent &&
		           ((IExitedDMEvent)event).getDMContext() instanceof IContainerDMContext) 
		{
            // container exit should always trigger a refresh
            return new ThreadsUpdateTester(super.getElementUpdateTester(event), true);
		} else {
		    return new ThreadsUpdateTester(super.getElementUpdateTester(event), false);
		}
	}
	
	@Override
	public Object[] getInitialRootElementChildren(Object rootElement) {
	    return null;
	}

    @Override
	public Map<String, Object> getInitialRootElementProperties(Object rootElement) {
        return null;
    }
}
