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

import java.util.regex.Pattern;

import org.eclipse.cdt.core.dom.ILinkage;
import org.eclipse.cdt.core.dom.ast.IASTCompletionContext;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNameOwner;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IProblemBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNameSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.internal.core.dom.Linkage;
import org.eclipse.cdt.internal.core.dom.parser.ASTNode;
import org.eclipse.cdt.internal.core.dom.parser.IASTInternalNameOwner;
import org.eclipse.cdt.internal.core.dom.parser.IRecursionResolvingBinding;
import org.eclipse.cdt.internal.core.dom.parser.ProblemBinding;
import org.eclipse.core.runtime.Assert;

/**
 * Common base class for all sorts of c++ names: unqualified, qualified, operator and conversion
 * names plus template-ids
 */
public abstract class CPPASTNameBase extends ASTNode implements ICPPASTName {
	protected static final Pattern WHITESPACE_SEQ = Pattern.compile("\\s+"); //$NON-NLS-1$

	/**
	 * For test-purposes, only.
	 */
	public static boolean sAllowRecursionBindings = true;
	public static boolean sAllowNameComputation = true;
	private static final byte MAX_RESOLUTION_DEPTH = 6;

	protected final static class RecursionResolvingBinding extends ProblemBinding
			implements IRecursionResolvingBinding {
		public RecursionResolvingBinding(IASTName node, char[] arg) {
			super(node, IProblemBinding.SEMANTIC_RECURSION_IN_LOOKUP, arg);
			Assert.isTrue(sAllowRecursionBindings, getMessage());
		}
	}

	private RecursionResolvingBinding createRecursionResolvingBinding() {
		// We create a recursion resolving binding when the resolution depth
		// exceeds MAX_RESOLUTION_DEPTH. If the resolution depth exceeds
		// MAX_RESOLUTION_DEPTH + 1, it means that attempting to create the
		// recursion resolving binding has led us back to trying to resolve
		// the binding for this name again, so the recursion isn't broken.
		// This can happen because the constructor of RecursionResolvingBinding
		// calls ProblemBinding.getMessage(), which can try to do name
		// resolution to build an argument string if one wasn't provided in the
		// ProblemBinding constructor. To break the recursion in a case
		// like, this we provide the argument string "(unknown)" instead.
		char[] args = (fResolutionDepth > MAX_RESOLUTION_DEPTH + 1) ? "(unknown)".toCharArray() : null; //$NON-NLS-1$
		return new RecursionResolvingBinding(this, args);
	}

	private IBinding fBinding;
	private byte fResolutionDepth;
	private boolean fIsFinal;

	public final void incResolutionDepth() {
		if (fBinding == null && ++fResolutionDepth > MAX_RESOLUTION_DEPTH) {
			setBinding(createRecursionResolvingBinding());
		}
	}

	/**
	 * Called to perform the binding resolution. Subclasses may return lazy bindings that
	 * will not be exposed within public API.
	 */
	protected abstract IBinding createIntermediateBinding();

	/**
	 * Resolves the name at least up to the intermediate binding and returns it.
	 * @see ICPPTwoPhaseBinding
	 */
	@Override
	public IBinding resolvePreBinding() {
		if (fBinding == null) {
			if (++fResolutionDepth > MAX_RESOLUTION_DEPTH) {
				setBinding(createRecursionResolvingBinding());
			} else {
				setBinding(createIntermediateBinding());
			}
		}
		return fBinding;
	}

	@Override
	public IBinding resolveBinding() {
		if (fBinding == null) {
			if (++fResolutionDepth > MAX_RESOLUTION_DEPTH) {
				setBinding(createRecursionResolvingBinding());
			} else {
				fIsFinal = false;
				final IBinding b = createIntermediateBinding();
				if (b instanceof ProblemBinding) {
					ProblemBinding pb = (ProblemBinding) b;
					final IASTNode node = pb.getASTNode();
					if (node == null || node.getParent() == null) {
						pb.setASTNode(this);
					}
				}
				setBinding(b);
			}
		}
		if (!fIsFinal)
			resolveFinalBinding(this);

		return fBinding;
	}

	/**
	 * If this name has not yet been resolved at all, <code>null</code> will be returned.
	 * Otherwise the intermediate or final binding for this name is returned.
	 * @see ICPPTwoPhaseBinding
	 */
	@Override
	public IBinding getPreBinding() {
		return fBinding;
	}

	/**
	 * If this name has not yet been resolved at all, <code>null</code> will be returned.
	 * Otherwise the final binding for this name is returned.
	 * @see ICPPTwoPhaseBinding
	 */
	@Override
	public IBinding getBinding() {
		final IBinding cand = fBinding;
		if (cand == null)
			return null;

		if (!fIsFinal)
			resolveFinalBinding(this);

		return fBinding;
	}

	private void resolveFinalBinding(CPPASTNameBase astName) {
		if (fBinding instanceof ICPPTwoPhaseBinding) {
			ICPPTwoPhaseBinding intermediateBinding = (ICPPTwoPhaseBinding) fBinding;
			if (++fResolutionDepth > MAX_RESOLUTION_DEPTH) {
				setBinding(createRecursionResolvingBinding());
			} else {
				setBinding(intermediateBinding.resolveFinalBinding(astName));
			}
		}
		fIsFinal = true;
	}

	@Override
	public void setBinding(IBinding binding) {
		fBinding = binding;
		fResolutionDepth = 0;
	}

	@Override
	public IASTName getLastName() {
		return this;
	}

	@Override
	public boolean isQualified() {
		IASTNode parent = getParent();
		if (parent instanceof ICPPASTQualifiedName) {
			ICPPASTQualifiedName qn = (ICPPASTQualifiedName) parent;
			if (qn.isFullyQualified())
				return true;
			ICPPASTNameSpecifier[] qualifier = qn.getQualifier();
			if (qualifier.length > 0 && qualifier[0] == this)
				return false;
			return true;
		}
		return false;
	}

	@Override
	public final String toString() {
		return new String(toCharArray());
	}

	@Override
	public IASTCompletionContext getCompletionContext() {
		IASTNode node = getParent();
		while (node != null) {
			if (node instanceof IASTCompletionContext) {
				return (IASTCompletionContext) node;
			}
			node = node.getParent();
		}

		return null;
	}

	@Override
	public int getRoleOfName(boolean allowResolution) {
		IASTNode parent = getParent();
		if (parent instanceof IASTInternalNameOwner) {
			return ((IASTInternalNameOwner) parent).getRoleForName(this, allowResolution);
		}
		if (parent instanceof IASTNameOwner) {
			return ((IASTNameOwner) parent).getRoleForName(this);
		}
		return IASTNameOwner.r_unclear;
	}

	@Override
	public boolean isDeclaration() {
		IASTNode parent = getParent();
		if (parent instanceof IASTNameOwner) {
			int role = ((IASTNameOwner) parent).getRoleForName(this);
			switch (role) {
			case IASTNameOwner.r_reference:
			case IASTNameOwner.r_unclear:
				return false;
			default:
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean isReference() {
		IASTNode parent = getParent();
		if (parent instanceof IASTNameOwner) {
			int role = ((IASTNameOwner) parent).getRoleForName(this);
			return role == IASTNameOwner.r_reference;
		}
		return false;
	}

	@Override
	public boolean isDefinition() {
		IASTNode parent = getParent();
		if (parent instanceof IASTNameOwner) {
			int role = ((IASTNameOwner) parent).getRoleForName(this);
			return role == IASTNameOwner.r_definition;
		}
		return false;
	}

	@Override
	public ILinkage getLinkage() {
		return Linkage.CPP_LINKAGE;
	}
}
