/*******************************************************************************
 * Copyright (c) 2000, 2012 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *     Sergey Prigogin (Google)
 *******************************************************************************/
package org.eclipse.cdt.internal.corext.refactoring.code.flow;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.IVariable;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCatchHandler;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTryBlockStatement;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
import org.eclipse.core.runtime.Assert;

public class FlowContext extends LocalVariableIndex {
	private static class ComputeMode {
	}

	public static final ComputeMode MERGE = new ComputeMode();
	public static final ComputeMode ARGUMENTS = new ComputeMode();
	public static final ComputeMode RETURN_VALUES = new ComputeMode();

	private boolean fConsiderAccessMode;
	private boolean fLoopReentranceMode;
	private ComputeMode fComputeMode;
	private IVariable[] fLocals;
	private final List<ICPPASTCatchHandler[]> fExceptionStack;

	public FlowContext(IASTFunctionDefinition functionDefinition) {
		super(functionDefinition);
		fExceptionStack = new ArrayList<>(3);
	}

	public void setConsiderAccessMode(boolean b) {
		fConsiderAccessMode = b;
	}

	public void setComputeMode(ComputeMode mode) {
		fComputeMode = mode;
	}

	void setLoopReentranceMode(boolean b) {
		fLoopReentranceMode = b;
	}

	int getArrayLength() {
		return getNumLocalVariables();
	}

	boolean considerAccessMode() {
		return fConsiderAccessMode;
	}

	boolean isLoopReentranceMode() {
		return fLoopReentranceMode;
	}

	boolean computeMerge() {
		return fComputeMode == MERGE;
	}

	boolean computeArguments() {
		return fComputeMode == ARGUMENTS;
	}

	boolean computeReturnValues() {
		return fComputeMode == RETURN_VALUES;
	}

	public IVariable getLocalFromIndex(int index) {
		if (fLocals == null || index > fLocals.length)
			return null;
		return fLocals[index];
	}

	/**
	 * Adds a local variable to the context.
	 * @param localVariable the local variable to manage.
	 */
	void manageLocal(IVariable localVariable) {
		int index = getIndexFromLocal(localVariable);
		if (index >= 0) {
			if (fLocals == null)
				fLocals = new IVariable[getNumLocalVariables()];
			fLocals[index] = localVariable;
		}
	}

	//---- Exception handling --------------------------------------------------------

	void pushExceptions(ICPPASTTryBlockStatement node) {
		ICPPASTCatchHandler[] catchHandlers = node.getCatchHandlers();
		fExceptionStack.add(catchHandlers);
	}

	void popExceptions() {
		Assert.isTrue(fExceptionStack.size() > 0);
		fExceptionStack.remove(fExceptionStack.size() - 1);
	}

	boolean isExceptionCaught(IType exceptionType) {
		for (ICPPASTCatchHandler[] catchHandlers : fExceptionStack) {
			for (ICPPASTCatchHandler catchHandler : catchHandlers) {
				if (catchHandler.isCatchAll())
					return true;
				IASTDeclaration caughtException = catchHandler.getDeclaration();
				if (caughtException instanceof IASTSimpleDeclaration) {
					IASTDeclarator[] declarators = ((IASTSimpleDeclaration) caughtException).getDeclarators();
					IType caughtType = CPPVisitor.createType(declarators[0]);
					while (caughtType != null) {
						// 15.3
						if (caughtType.isSameType(exceptionType))
							return true;
						// TODO(sprigogin): Implement the rest of 15.3 matching logic.
					}
				}
			}
		}
		return false;
	}
}
