/*******************************************************************************
 * 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.expression;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.IDebugVMConstants;
import org.eclipse.cdt.dsf.ui.concurrent.ViewerCountingRequestMonitor;
import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.VMDelta;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IExpressionManager;
import org.eclipse.debug.core.model.IExpression;
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.IElementEditor;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider;
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.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.widgets.Composite;

/**
 * This is the top-level view model node in the expressions view.  Its job is to:
 * <li>
 *   <ol> retrieve the {@link IExpression} objects from the global {@link IExpressionManager},</ol>
 *   <ol> retrieve the expression string from the <code>IExpression</code> object,</ol>
 *   <ol> then to call the configured expression nodes to parse the expression string.</ol>
 * </li>
 * <p>
 * This node is not intended to have any standard child nodes, therefore 
 * the implementation of {@link #setChildNodes(IVMNode[])} throws an exception.  
 * Instead users should call {@link #setExpressionNodes(IExpressionVMNode[])}
 * to configure the nodes that this node will delegate to when processing expressions.
 * </p> 
 */
public class ExpressionManagerVMNode extends AbstractVMNode
    implements IElementLabelProvider, IElementEditor
{
    /**
     * VMC for a new expression object to be added.  When user clicks on this node to 
     * edit it, he will create a new expression.
     */
    public class NewExpressionVMC extends AbstractVMContext {
        public NewExpressionVMC() {
            super(ExpressionManagerVMNode.this);
        }
        
        @Override
        @SuppressWarnings("rawtypes") 
        public Object getAdapter(Class adapter) {
            return super.getAdapter(adapter);
        }
        
        @Override
        public boolean equals(Object obj) {
            return obj instanceof NewExpressionVMC;
        }
        
        @Override
        public int hashCode() {
            return getClass().hashCode();
        }
    }

    /** Local reference to the global expression manager */ 
    private IExpressionManager fManager = DebugPlugin.getDefault().getExpressionManager();
    
    /** Cached reference to a cell modifier for editing expression strings of invalid expressions */
    private ICellModifier fWatchExpressionCellModifier = null;
    
    /**
     * @since 2.1
     * 
     * @return The cell modifier to be used when editing. If you need to provide
     *         a custom cell editor you would override this method.
     */
    protected ICellModifier createCellModifier() {
    	return new WatchExpressionCellModifier();
    }
    
    public ExpressionManagerVMNode(ExpressionVMProvider provider) {
        super(provider);
    }

    @Override
    public String toString() {
        return "ExpressionManagerVMNode";  //$NON-NLS-1$ 
    }
    
    private ExpressionVMProvider getExpressionVMProvider() {
        return (ExpressionVMProvider)getVMProvider();
    }

    @Override
    public void update(IHasChildrenUpdate[] updates) {
        // Test availability of children based on whether there are any expressions 
        // in the manager.  We assume that the getExpressions() will just read 
        // local state data, so we don't bother using a job to perform this 
        // operation.
        for (int i = 0; i < updates.length; i++) {
            boolean hasChildren = fManager.getExpressions().length != 0;
            if (!hasChildren && updates[i].getPresentationContext().getColumns() != null) {
                hasChildren = true;
            }
            
            updates[i].setHasChilren(hasChildren);
            updates[i].done();
        }
    }
    
    @Override
    public void update(IChildrenCountUpdate[] updates) {
        for (IChildrenCountUpdate update : updates) {
            if (!checkUpdate(update)) continue;

            // We assume that the getExpressions() will just read local state data,
            // so we don't bother using a job to perform this operation.
            int count = fManager.getExpressions().length;
            
            // Account for "Add New Expression" element
            if (update.getPresentationContext().getColumns() != null) {
                count += 1;  
            }
            
            update.setChildCount(count);
            update.done();
        }
    }
    
    @Override
    public void update(final IChildrenUpdate[] updates) {
        for (IChildrenUpdate update : updates) {
            doUpdateChildren(update);
        }        
    }
    
    public void doUpdateChildren(final IChildrenUpdate update) {
        final IExpression[] expressions = fManager.getExpressions();
        
        // For each (expression) element in update, find the layout node that can 
        // parse it.  And for each expression that has a corresponding layout node, 
        // call IExpressionLayoutNode#getElementForExpression to generate a VMC.
        // Since the last is an async call, we need to create a multi-RM to wait
        // for all the calls to complete.
        final CountingRequestMonitor multiRm = new ViewerCountingRequestMonitor(getVMProvider().getExecutor(), update);
        int multiRmCount = 0;
        
        int lowOffset= update.getOffset();
        if (lowOffset < 0) {
        	lowOffset = 0;
        }
		int length= update.getLength();
		if (length <= 0) {
			length = expressions.length;
		}
		final int highOffset= lowOffset + length;
        // If columns are present, add the "Add New Expression" element.
		int expressionsLength = expressions.length;
		if (update.getPresentationContext().getColumns() != null) {
		    expressionsLength += 1;
		}
		for (int i = lowOffset; i < highOffset && i < expressionsLength; i++) {
            if (i < expressions.length) {
                multiRmCount++;
                final int childIndex = i;
                final IExpression expression = expressions[i];
                // getElementForExpression() accepts a IElementsUpdate as an argument.
                // Construct an instance of VMElementsUpdate which will call a 
                // the request monitor when it is finished.  The request monitor
                // will in turn set the element in the update argument in this method. 
                ((ExpressionVMProvider)getVMProvider()).update(
                    new VMExpressionUpdate(
                        update, expression,
                        new DataRequestMonitor<Object>(getVMProvider().getExecutor(), multiRm) {
                            @Override
                            protected void handleSuccess() {
                                update.setChild(getData(), childIndex);
                                multiRm.done();
                            } 
                            
                            @Override
                            protected void handleError() {
                                update.setChild(new InvalidExpressionVMContext(ExpressionManagerVMNode.this, expression), childIndex);                                
                                multiRm.done();
                            }
                        })
                    );
            } else {
                // Last element in the list of expressions is the "add new expression"
                // dummy entry.
                update.setChild(new NewExpressionVMC(), i);
            }
        }

        // If no expressions were parsed, we're finished.
        // Set the count to the counting RM.
        multiRm.setDoneCount(multiRmCount);
    }

    @Override
	public void update(ILabelUpdate[] updates) {
        // The label update handler only handles labels for the invalid expression VMCs.
        // The expression layout nodes are responsible for supplying label providers 
        // for their VMCs.
        for (ILabelUpdate update : updates) {
            if (update.getElement() instanceof NewExpressionVMC) {
                updateNewExpressionVMCLabel(update, (NewExpressionVMC) update.getElement());
            } else {
                update.done();
            }
        }
    }

    /**
     * Updates the label for the NewExpressionVMC.
     */
    private void updateNewExpressionVMCLabel(ILabelUpdate update, NewExpressionVMC vmc) {
        String[] columnIds = update.getColumnIds() != null ? 
            update.getColumnIds() : new String[0];
            
        for (int i = 0; i < columnIds.length; i++) {
            // Bug 373468: show "Add New Expression" label in name column if 
            // expression column is not shown.
            if ( IDebugVMConstants.COLUMN_ID__EXPRESSION.equals(columnIds[i]) ||
                 (IDebugVMConstants.COLUMN_ID__NAME.equals(columnIds[i]) && 
                  !Arrays.asList(columnIds).contains(IDebugVMConstants.COLUMN_ID__EXPRESSION)) ) 
            {
                update.setLabel(MessagesForExpressionVM.ExpressionManagerLayoutNode__newExpression_label, i);
                update.setImageDescriptor(DebugUITools.getImageDescriptor(IDebugUIConstants.IMG_LCL_ADD), i);
                FontData fontData = JFaceResources.getFontDescriptor(IDebugUIConstants.PREF_VARIABLE_TEXT_FONT).getFontData()[0];
                // Bugzilla 287598: Distinguish 'Add new expression' entry from actual expressions
                fontData.setStyle(SWT.ITALIC);                  
                update.setFontData(fontData, i);
            } else {
                update.setLabel("", i); //$NON-NLS-1$
            }
        }
        
        update.done();
    }

    @Override
	public int getDeltaFlags(Object event) {
        int retVal = 0;
        
        // Add a flag if the list of expressions in the global expression manager has changed.
        if (event instanceof ExpressionsChangedEvent) {
            retVal |= IModelDelta.ADDED | IModelDelta.REMOVED | IModelDelta.INSERTED | IModelDelta.CONTENT ;
        }

        if ( event instanceof PropertyChangeEvent &&
             IPresentationContext.PROPERTY_COLUMNS.equals(((PropertyChangeEvent)event).getProperty()) ) 
        {
            retVal |= IModelDelta.CONTENT;
        }
        
        for (IExpression expression : fManager.getExpressions()) {
            retVal |= getExpressionVMProvider().getDeltaFlagsForExpression(expression, event);
        }
        
        return retVal;
    }

    @Override
	public void buildDelta(final Object event, final VMDelta parentDelta, final int nodeOffset, final RequestMonitor requestMonitor) {
        if (event instanceof ExpressionsChangedEvent) {
            buildDeltaForExpressionsChangedEvent((ExpressionsChangedEvent)event, parentDelta, nodeOffset, requestMonitor);
        } 
        else if ( event instanceof PropertyChangeEvent &&
            IPresentationContext.PROPERTY_COLUMNS.equals(((PropertyChangeEvent)event).getProperty()) ) 
        {
           parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.CONTENT);
           requestMonitor.done();
        }
        else {
        
            // For each expression, find its corresponding node and ask that
            // layout node for its delta flags for given event.  If there are delta flags to be 
            // generated, call the asynchronous method to do so.
            CountingRequestMonitor multiRm = new CountingRequestMonitor(getExecutor(), requestMonitor);
            
            int buildDeltaForExpressionCallCount = 0;
            
            IExpression[] expressions = fManager.getExpressions();
            for (int i = 0; i < expressions.length; i++ ) {
                int flags = getExpressionVMProvider().getDeltaFlagsForExpression(expressions[i], event);
                // If the given expression has no delta flags, skip it.
                if (flags == IModelDelta.NO_CHANGE) continue;
    
                int elementOffset = nodeOffset >= 0 ? nodeOffset + i : -1;
                getExpressionVMProvider().buildDeltaForExpression(
                    expressions[i], elementOffset, event, parentDelta, getTreePathFromDelta(parentDelta), 
                    new RequestMonitor(getExecutor(), multiRm));
                buildDeltaForExpressionCallCount++;
            }
            
            multiRm.setDoneCount(buildDeltaForExpressionCallCount);
        }
    }
    
    private void buildDeltaForExpressionsChangedEvent(ExpressionsChangedEvent event, VMDelta parentDelta, 
        int nodeOffset, RequestMonitor requestMonitor) 
    {
        CountingRequestMonitor multiRm = new CountingRequestMonitor(getExecutor(), requestMonitor);
        for (int i = 0; i < event.getExpressions().length; i++) {
            int expIndex = event.getIndex() != -1 
                ? nodeOffset + event.getIndex() + i 
                : -1; 
            getExpressionVMProvider().buildDeltaForExpression(
                event.getExpressions()[i], expIndex, event, parentDelta, getTreePathFromDelta(parentDelta), 
                new RequestMonitor(getExecutor(), multiRm));                
        }
        multiRm.setDoneCount(event.getExpressions().length);
    }
        
    private TreePath getTreePathFromDelta(IModelDelta delta) {
        List<Object> elementList = new LinkedList<Object>();
        IModelDelta listDelta = delta;
        elementList.add(0, listDelta.getElement());
        while (listDelta.getParentDelta() != null) {
            elementList.add(0, listDelta.getElement());
            listDelta = listDelta.getParentDelta();
        }
        return new TreePath(elementList.toArray());
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.debug.internal.ui.viewers.model.provisional.IElementEditor#getCellEditor(org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext, java.lang.String, java.lang.Object, org.eclipse.swt.widgets.Composite)
     */
    @Override
	public CellEditor getCellEditor(IPresentationContext context, String columnId, Object element, Composite parent) {
        if (IDebugVMConstants.COLUMN_ID__EXPRESSION.equals(columnId) ||
            (IDebugVMConstants.COLUMN_ID__NAME.equals(columnId) && 
             !Arrays.asList(context.getColumns()).contains(IDebugVMConstants.COLUMN_ID__EXPRESSION)) ) 
        {
            return new TextCellEditor(parent);
        } 
        return null;
    }
    
    /*
     * (non-Javadoc)
     * @see org.eclipse.debug.internal.ui.viewers.model.provisional.IElementEditor#getCellModifier(org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext, java.lang.Object)
     */
    @Override
	public ICellModifier getCellModifier(IPresentationContext context, Object element) {
        if ( fWatchExpressionCellModifier == null ) {
        	fWatchExpressionCellModifier = createCellModifier();
        }
        return fWatchExpressionCellModifier;
    }
}
