/*******************************************************************************
 * Copyright (c) 2007, 2012 Wind River Systems and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.dsf.debug.ui.viewmodel.expression;

import java.util.Map;

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.ui.viewmodel.DefaultVMModelProxyStrategy;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.VMDelta;
import org.eclipse.debug.core.model.IExpression;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.jface.viewers.TreePath;

/**
 * The IModelProxy implementation to be used with an expression
 * view model provider.
 *
 * @see ExpressionVMProvider
 */
public class ExpressionVMProviderModelProxyStrategy extends DefaultVMModelProxyStrategy {

	public ExpressionVMProviderModelProxyStrategy(ExpressionVMProvider provider, Object rootElement) {
		super(provider, rootElement);
	}

	private ExpressionVMProvider getExpressionVMProvider() {
		return (ExpressionVMProvider) getVMProvider();
	}

	public int getDeltaFlagsForExpression(IExpression expression, Object event) {
		final IExpressionVMNode matchingNode = getExpressionVMProvider().findNodeToParseExpression(null, expression);

		if (matchingNode != null) {
			return getNodeDeltaFlagsForExpression(matchingNode, expression, event);
		}
		return IModelDelta.NO_CHANGE;
	}

	private int getNodeDeltaFlagsForExpression(IExpressionVMNode node, IExpression expression, Object event) {
		int flags = node.getDeltaFlagsForExpression(expression, event);

		IExpressionVMNode matchingNode = getExpressionVMProvider().findNodeToParseExpression(node, expression);
		if (matchingNode != null && !matchingNode.equals(node)) {
			flags = flags | getNodeDeltaFlagsForExpression(matchingNode, expression, event);
		} else {
			// Check the child nodes of this expression node for additional
			// delta flags.
			for (IVMNode childNode : getVMProvider().getChildVMNodes(node)) {
				if (!childNode.equals(node)) {
					int childNodeDeltaFlags = getDeltaFlags(childNode, null, event);
					if ((childNodeDeltaFlags & IModelDelta.CONTENT) != 0) {
						childNodeDeltaFlags &= ~IModelDelta.CONTENT;
						childNodeDeltaFlags |= IModelDelta.STATE;
					}
					flags |= childNodeDeltaFlags;
				}
			}
		}
		return flags;
	}

	public void buildDeltaForExpression(IExpression expression, int expressionElementIdx, Object event,
			VMDelta parentDelta, TreePath path, RequestMonitor rm) {
		final IExpressionVMNode matchingNode = getExpressionVMProvider().findNodeToParseExpression(null, expression);

		if (matchingNode != null) {
			buildNodeDeltaForExpression(matchingNode, expression, expressionElementIdx, event, parentDelta, path, rm);
		} else {
			rm.done();
		}
	}

	private void buildNodeDeltaForExpression(final IExpressionVMNode node, final IExpression expression,
			final int expressionElementIdx, final Object event, final VMDelta parentDelta, final TreePath path,
			final RequestMonitor rm) {
		node.buildDeltaForExpression(expression, expressionElementIdx, event, parentDelta, path,
				new RequestMonitor(getVMProvider().getExecutor(), rm) {
					@Override
					protected void handleSuccess() {
						final IExpressionVMNode matchingNode = getExpressionVMProvider().findNodeToParseExpression(node,
								expression);
						if (matchingNode != null && !matchingNode.equals(node)) {
							buildNodeDeltaForExpression(matchingNode, expression, expressionElementIdx, event,
									parentDelta, path, rm);
						} else {
							getExpressionVMProvider().update(new VMExpressionUpdate(parentDelta,
									getVMProvider().getPresentationContext(), expression,
									new DataRequestMonitor<Object>(getVMProvider().getExecutor(), rm) {
										@Override
										protected void handleSuccess() {
											buildDeltaForExpressionElement(node, expression, getData(),
													expressionElementIdx, event, parentDelta, path, rm);
										}

										@Override
										protected void handleErrorOrWarning() {
											// Avoid propagating the error to avoid processing the delta by
											// all nodes.
											rm.done();
										}
									}));
						}
					}
				});
	}

	private void buildDeltaForExpressionElement(IExpressionVMNode node, IExpression expression,
			Object expressionElement, int expressionElementIdx, Object event, VMDelta parentDelta, TreePath path,
			RequestMonitor rm) {
		CountingRequestMonitor multiRm = new CountingRequestMonitor(getVMProvider().getExecutor(), rm);
		int multiRmCount = 0;

		node.buildDeltaForExpressionElement(expressionElement, expressionElementIdx, event, parentDelta, multiRm);
		multiRmCount++;

		// Find the child nodes that have deltas for the given event.
		Map<IVMNode, Integer> childNodesWithDeltaFlags = getChildNodesWithDeltaFlags(node, parentDelta, event);

		// If no child layout nodes have deltas we can stop here.
		if (childNodesWithDeltaFlags.size() != 0) {
			callChildNodesToBuildDelta(node, childNodesWithDeltaFlags,
					parentDelta.addNode(expressionElement, expressionElementIdx, IModelDelta.NO_CHANGE), event,
					multiRm);
			multiRmCount++;
		}

		if (event instanceof ExpressionsChangedEvent) {
			buildDeltaForExpressionsChangedEvent(expressionElement, expressionElementIdx,
					(ExpressionsChangedEvent) event, parentDelta, multiRm);
			multiRmCount++;
		}

		multiRm.setDoneCount(multiRmCount);
	}

	private void buildDeltaForExpressionsChangedEvent(Object element, int elementIdx, ExpressionsChangedEvent event,
			VMDelta parentDelta, final RequestMonitor rm) {
		switch (event.getType()) {
		case ADDED:
			// New expressions are always added one element before last.
			// Last element is the "Add new expression" element.
			// Note: Index should be calculated when the IExpressionListener.expressionsAdded
			// is called.  If it's not, try to calculate it here based on parent count.
			int parentCount = parentDelta.getChildCount();
			if (elementIdx == -1 && parentCount > 1) {
				elementIdx = parentCount - 2;
			}
			parentDelta.addNode(element, elementIdx, IModelDelta.ADDED);
			break;
		case CHANGED:
			parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.CONTENT);
			break;
		case MOVED:
			parentDelta.addNode(element, -1, IModelDelta.REMOVED);
			parentDelta.addNode(element, elementIdx, IModelDelta.INSERTED);
			break;
		case REMOVED:
			parentDelta.addNode(element, -1, IModelDelta.REMOVED);
			break;
		case INSERTED:
			parentDelta.addNode(element, elementIdx, IModelDelta.INSERTED);
			break;
		default:
			break;
		}
		rm.done();
	}
}
