/*******************************************************************************
 * Copyright (c) 2008, 2014 Wind River Systems, Inc. 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:
 *     Markus Schorn - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.core.dom.parser;

import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTBinaryExpression;
import org.eclipse.cdt.core.dom.ast.IASTCastExpression;
import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTUnaryExpression;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IProblemBinding;

/**
 * Handles the ambiguity between a binary- and a cast-expression. (type)+var versus (var)+var.
 * It also handles the impact on the grouping of the sub-expressions.
 */
public abstract class ASTAmbiguousBinaryVsCastExpression extends ASTAmbiguousNode implements IASTAmbiguousExpression {
	private final IASTBinaryExpression fBinaryExpression;
	private final IASTCastExpression fCastExpression;

	/**
	 * The binary expression must have one of the following operators: +,-,&,*. The left hand side of the binary expression
	 * must end with an expression in parenthesis (which could be read as the beginning of the cast-expression.
	 * The cast-expression must contain the type-id (corresponding to the last expression in parenthesis on the left side of the
	 * binary expression. The operand of the castExpression can be <code>null</code>, it will be computed from the binary expression.
	 */
	public ASTAmbiguousBinaryVsCastExpression(IASTBinaryExpression binaryExpression,
			IASTCastExpression castExpression) {
		fBinaryExpression = binaryExpression;
		fCastExpression = castExpression;
	}

	@Override
	public final IASTExpression copy() {
		throw new UnsupportedOperationException();
	}

	@Override
	public final IASTExpression copy(CopyStyle style) {
		throw new UnsupportedOperationException();
	}

	@Override
	public final void addExpression(IASTExpression e) {
		throw new UnsupportedOperationException();
	}

	@Override
	public final IASTNode[] getNodes() {
		return getExpressions();
	}

	@Override
	public IASTExpression[] getExpressions() {
		return new IASTExpression[] { fBinaryExpression, fCastExpression };
	}

	@Override
	protected final IASTNode doResolveAmbiguity(ASTVisitor visitor) {
		final IASTAmbiguityParent owner = (IASTAmbiguityParent) getParent();
		IASTNode nodeToReplace = this;

		// handle nested ambiguities first
		owner.replace(nodeToReplace, fCastExpression);
		nodeToReplace = fCastExpression;
		fCastExpression.getTypeId().accept(visitor);

		owner.replace(nodeToReplace, fBinaryExpression);
		nodeToReplace = fBinaryExpression;
		fBinaryExpression.accept(visitor);

		// find nested names
		final NameCollector nameCollector = new NameCollector();
		fCastExpression.getTypeId().accept(nameCollector);
		final IASTName[] names = nameCollector.getNames();

		// resolve names
		boolean hasIssue = false;
		for (IASTName name : names) {
			try {
				IBinding b = name.resolveBinding();
				if (b instanceof IProblemBinding) {
					hasIssue = true;
					break;
				}
			} catch (Exception t) {
				hasIssue = true;
				break;
			}
		}
		if (hasIssue) {
			return nodeToReplace;
		}

		final IASTExpression left = fBinaryExpression.getOperand1();
		final IASTExpression right = fBinaryExpression.getOperand2();
		left.setParent(null);
		right.setParent(null);
		IASTUnaryExpression primaryInParenthesis = findTrailingBracketedPrimaryExpression(left);
		IASTExpression leadingCastExpression = findLeadingCastExpression(right);
		IASTExpression castedUnary = fCastExpression.getOperand();
		if (primaryInParenthesis != null && leadingCastExpression != null
				&& castedUnary instanceof IASTUnaryExpression) {
			IASTExpression lp = (IASTExpression) primaryInParenthesis.getParent();
			IASTBinaryExpression rp = (IASTBinaryExpression) leadingCastExpression.getParent();
			((IASTUnaryExpression) castedUnary).setOperand(leadingCastExpression);
			setEnd(castedUnary, leadingCastExpression);
			setRange(fCastExpression, primaryInParenthesis, leadingCastExpression);
			IASTExpression root = joinExpressions(lp, fCastExpression, rp);
			if (root != null) {
				owner.replace(nodeToReplace, root);
				return root;
			}
		}
		return nodeToReplace;
	}

	private void setEnd(IASTNode node, IASTNode end) {
		final ASTNode target = (ASTNode) node;
		final ASTNode e = (ASTNode) end;
		target.setLength(e.getOffset() + e.getLength() - target.getOffset());
	}

	private void setStart(IASTNode node, IASTNode start) {
		final ASTNode target = (ASTNode) node;
		final int offset = ((ASTNode) start).getOffset();
		target.setOffsetAndLength(offset, target.getOffset() + target.getLength() - offset);
	}

	private void setRange(IASTNode node, IASTNode from, IASTNode to) {
		final int offset = ((ASTNode) from).getOffset();
		final ASTNode t = (ASTNode) to;
		((ASTNode) node).setOffsetAndLength(offset, t.getOffset() + t.getLength() - offset);
	}

	/**
	 * @param l unary, cast or binary expression to the left, all parents are unary, cast or binary expressions.
	 * @param middle initially a cast-expression, always suitable to put into l or r
	 * @param r a binary expression to the right, all parents are binary expressions.
	 */
	private IASTExpression joinExpressions(IASTExpression l, IASTExpression middle, IASTBinaryExpression r) {
		while (true) {
			if (l == null) {
				if (r == null) {
					return middle;
				}
				r.setOperand1(middle);
				setStart(r, middle);
				middle = r;
				r = (IASTBinaryExpression) r.getParent();
			} else if (l instanceof IASTCastExpression) {
				// cast binds stronger than binary operator
				((IASTCastExpression) l).setOperand(middle);
				setEnd(l, middle);
				middle = l; // middle becomes cast-expr, can be put into r (a binary-expr)
				l = (IASTExpression) l.getParent();
			} else if (l instanceof IASTUnaryExpression) { //
				// unary operator binds stronger than binary operator
				((IASTUnaryExpression) l).setOperand(middle);
				setEnd(l, middle);
				middle = l; // middle becomes unary-expr, can be put into r (a binary-expr)
				l = (IASTExpression) l.getParent();
			} else {
				if (r == null || getPrecendence((IASTBinaryExpression) l) >= getPrecendence(r)) {
					((IASTBinaryExpression) l).setOperand2(middle);
					setEnd(l, middle);
					middle = l; // middle becomes binary, can be put into r because precedence is greater or equal.
					l = (IASTExpression) l.getParent();
				} else {
					r.setOperand1(middle);
					setStart(r, middle);
					middle = r; // middle becomes binary, can be put into r because precedence is greater.
					r = (IASTBinaryExpression) r.getParent();
				}
			}
		}
	}

	private int getPrecendence(IASTBinaryExpression r) {
		switch (r.getOperator()) {
		case IASTBinaryExpression.op_ellipses:
		case IASTBinaryExpression.op_assign:
		case IASTBinaryExpression.op_binaryAndAssign:
		case IASTBinaryExpression.op_binaryOrAssign:
		case IASTBinaryExpression.op_binaryXorAssign:
		case IASTBinaryExpression.op_divideAssign:
		case IASTBinaryExpression.op_minusAssign:
		case IASTBinaryExpression.op_moduloAssign:
		case IASTBinaryExpression.op_multiplyAssign:
		case IASTBinaryExpression.op_plusAssign:
		case IASTBinaryExpression.op_shiftLeftAssign:
		case IASTBinaryExpression.op_shiftRightAssign:
			return 0;
		case IASTBinaryExpression.op_logicalOr:
			return 1;
		case IASTBinaryExpression.op_logicalAnd:
			return 2;
		case IASTBinaryExpression.op_binaryOr:
			return 3;
		case IASTBinaryExpression.op_binaryXor:
			return 4;
		case IASTBinaryExpression.op_binaryAnd:
			return 5;
		case IASTBinaryExpression.op_equals:
		case IASTBinaryExpression.op_notequals:
			return 6;
		case IASTBinaryExpression.op_greaterThan:
		case IASTBinaryExpression.op_greaterEqual:
		case IASTBinaryExpression.op_lessThan:
		case IASTBinaryExpression.op_lessEqual:
		case IASTBinaryExpression.op_max:
		case IASTBinaryExpression.op_min:
			return 7;
		case IASTBinaryExpression.op_shiftLeft:
		case IASTBinaryExpression.op_shiftRight:
			return 8;
		case IASTBinaryExpression.op_plus:
		case IASTBinaryExpression.op_minus:
			return 9;
		case IASTBinaryExpression.op_multiply:
		case IASTBinaryExpression.op_divide:
		case IASTBinaryExpression.op_modulo:
			return 10;
		case IASTBinaryExpression.op_pmarrow:
		case IASTBinaryExpression.op_pmdot:
			return 11;
		}
		assert false;
		return 0;
	}

	private IASTUnaryExpression findTrailingBracketedPrimaryExpression(IASTExpression expr) {
		while (true) {
			if (expr instanceof IASTBinaryExpression) {
				expr = ((IASTBinaryExpression) expr).getOperand2();
			} else if (expr instanceof IASTCastExpression) {
				expr = ((IASTCastExpression) expr).getOperand();
			} else if (expr instanceof IASTUnaryExpression) {
				IASTUnaryExpression u = (IASTUnaryExpression) expr;
				if (u.getOperator() == IASTUnaryExpression.op_bracketedPrimary)
					return u;
				expr = u.getOperand();
			} else {
				return null;
			}
		}
	}

	private IASTExpression findLeadingCastExpression(IASTExpression expr) {
		while (expr instanceof IASTBinaryExpression) {
			expr = ((IASTBinaryExpression) expr).getOperand1();
		}
		return expr;
	}
}
