/*******************************************************************************
 * Copyright (c) 2005, 2015 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:
 *     Rational Software - initial implementation
 *     Markus Schorn (Wind River Systems)
 *     Sergey Prigogin (Google)
 *     Nathan Ridge
 *     Karsten Thoms (itemis) - Bug#471103
 *******************************************************************************/
package org.eclipse.cdt.core.dom.ast;

import java.net.URI;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.IBasicType.Kind;
import org.eclipse.cdt.core.dom.ast.c.ICArrayType;
import org.eclipse.cdt.core.dom.ast.c.ICQualifierType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBasicType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespace;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameterPackType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPPointerToMemberType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPReferenceType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateArgument;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateInstance;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateParameter;
import org.eclipse.cdt.core.dom.ast.gnu.cpp.IGPPQualifierType;
import org.eclipse.cdt.core.index.IIndexBinding;
import org.eclipse.cdt.core.index.IIndexFile;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.core.parser.GCCKeywords;
import org.eclipse.cdt.core.parser.Keywords;
import org.eclipse.cdt.core.parser.util.ArrayUtil;
import org.eclipse.cdt.internal.core.dom.parser.ASTInternal;
import org.eclipse.cdt.internal.core.dom.parser.ASTTranslationUnit;
import org.eclipse.cdt.internal.core.dom.parser.ITypeContainer;
import org.eclipse.cdt.internal.core.dom.parser.IntegralValue;
import org.eclipse.cdt.internal.core.dom.parser.c.CASTTypeId;
import org.eclipse.cdt.internal.core.dom.parser.c.CVisitor;
import org.eclipse.cdt.internal.core.dom.parser.c.ICInternalBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTTypeId;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPDeferredClassInstance;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownMemberClassInstance;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPSemantics;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.TypeOfDependentExpression;
import org.eclipse.cdt.internal.core.resources.ResourceLookup;
import org.eclipse.cdt.utils.UNCPathConverter;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;

/**
 * Collection of static methods for converting AST elements to {@link String}s corresponding to
 * the AST element's type.
 *
 * @noextend This class is not intended to be subclassed by clients.
 */
public class ASTTypeUtil {
	private static final String COMMA_SPACE = ", "; //$NON-NLS-1$
	private static final String EMPTY_STRING = ""; //$NON-NLS-1$
	private static final String SPACE = " "; //$NON-NLS-1$
	private static final int DEAULT_ITYPE_SIZE = 2;

	// A threshold above which type strings are appended to other type strings
	// "by reference" rather than by copying their contents. This avoids the
	// sizes of type strings from getting out of control for certain code patterns.
	private static final int TYPE_STRING_LENGTH_THRESHOLD = 100000;

	private static final ThreadLocal<Set<IBinding>> fSourceFileOnlyCheckInProgress = new ThreadLocal<Set<IBinding>>() {
		@Override
		protected Set<IBinding> initialValue() {
			return new HashSet<>();
		}
	};

	private ASTTypeUtil() {
	}

	/**
	 * Returns a string representation for the parameters of the given function type.
	 * The representation contains the comma-separated list of the normalized parameter
	 * type representations wrapped in parentheses.
	 */
	public static String getParameterTypeString(IFunctionType type) {
		StringBuilder result = new StringBuilder();
		appendParameterTypeString(type, result);
		return result.toString();
	}

	/**
	 * Returns a string representation for the parameters and the qualifiers of the given function type.
	 * The representation contains the comma-separated list of the normalized parameter
	 * type representations wrapped in parentheses followed by the method qualifiers, if any.
	 *
	 * @since 5.11
	 */
	public static String getParameterTypeStringAndQualifiers(IFunctionType type) {
		StringBuilder result = new StringBuilder();
		appendParameterTypeStringAndQualifiers(type, result);
		return result.toString();
	}

	private static void appendParameterTypeString(IFunctionType ft, StringBuilder result) {
		IType[] types = ft.getParameterTypes();
		result.append(Keywords.cpLPAREN);
		boolean needComma = false;
		for (IType type : types) {
			if (type != null) {
				if (needComma)
					result.append(COMMA_SPACE);
				appendType(type, true, result);
				needComma = true;
			}
		}
		if (ft instanceof ICPPFunctionType && ((ICPPFunctionType) ft).takesVarArgs()) {
			if (needComma)
				result.append(COMMA_SPACE);
			result.append(Keywords.cpELLIPSIS);
		}
		result.append(Keywords.cpRPAREN);
	}

	private static boolean appendParameterTypeStringAndQualifiers(IFunctionType ft, StringBuilder result) {
		appendParameterTypeString(ft, result);
		boolean needSpace = false;
		if (ft instanceof ICPPFunctionType) {
			ICPPFunctionType cppft = (ICPPFunctionType) ft;
			needSpace = appendCVQ(result, needSpace, cppft.isConst(), cppft.isVolatile(), false);
			if (cppft.hasRefQualifier()) {
				appendRefQualifier(result, needSpace, cppft.isRValueReference());
				needSpace = true;
			}
		}
		return needSpace;
	}

	/**
	 * Returns whether the function matching the given function binding takes parameters or not.
	 *
	 * @since 5.1
	 */
	public static boolean functionTakesParameters(IFunction function) {
		IParameter[] parameters = function.getParameters();

		if (parameters.length == 0) {
			return false;
		}
		if (parameters.length == 1 && SemanticUtil.isVoidType(parameters[0].getType())) {
			return false;
		}
		return true;
	}

	/**
	 * Returns a string representation for the type array. The representation is a comma-separated
	 * list of the normalized string representations of the provided types.
	 * @see #getTypeListString(IType[], boolean)
	 */
	public static String getTypeListString(IType[] types) {
		return getTypeListString(types, true);
	}

	/**
	 * Returns a String representation of the type array as a
	 * comma-separated list.
	 * @param types
	 * @param normalize indicates whether normalization shall be performed
	 * @return representation of the type array as a comma-separated list
	 * @since 5.1
	 */
	public static String getTypeListString(IType[] types, boolean normalize) {
		StringBuilder result = new StringBuilder();
		for (int i = 0; i < types.length; i++) {
			if (types[i] != null) {
				appendType(types[i], normalize, result);
				if (i < types.length - 1)
					result.append(COMMA_SPACE);
			}
		}
		return result.toString();
	}

	/**
	 * Returns a comma-separated list of the string representations of the arguments, enclosed
	 * in angle brackets.
	 * Optionally normalization is performed:
	 * <br> template parameter names are represented by their parameter position,
	 * <br> further normalization may be performed in future versions.
	 * @param normalize indicates whether normalization shall be performed
	 * @since 5.1
	 */
	public static String getArgumentListString(ICPPTemplateArgument[] args, boolean normalize) {
		if (args.length == 0)
			return "<>"; //$NON-NLS-1$
		StringBuilder result = new StringBuilder();
		appendArgumentList(args, normalize, result);
		return result.toString();
	}

	private static void appendArgumentList(ICPPTemplateArgument[] args, boolean normalize, StringBuilder result) {
		result.append('<');
		for (int i = 0; i < args.length; i++) {
			if (i != 0) {
				result.append(',');
			}
			appendArgument(args[i], normalize, result);
		}
		result.append('>');
	}

	/**
	 * Returns a string representation for an template argument. Optionally
	 * normalization is performed:
	 * <br> template parameter names are represented by their parameter position,
	 * <br> further normalization may be performed in future versions.
	 * @param normalize indicates whether normalization shall be performed
	 * @since 5.1
	 */
	public static String getArgumentString(ICPPTemplateArgument arg, boolean normalize) {
		StringBuilder buf = new StringBuilder();
		appendArgument(arg, normalize, buf);
		return buf.toString();
	}

	private static void appendArgument(ICPPTemplateArgument arg, boolean normalize, StringBuilder buf) {
		IValue val = arg.getNonTypeValue();
		if (val != null) {
			appendType(arg.getTypeOfNonTypeValue(), normalize, buf);
			buf.append(val.getSignature());
		} else {
			IType type = normalize ? arg.getTypeValue() : arg.getOriginalTypeValue();
			appendType(type, normalize, buf);
		}
	}

	/**
	 * Returns an array of normalized string representations for the parameter types of the given
	 * function type.
	 * @see #getType(IType, boolean)
	 */
	public static String[] getParameterTypeStringArray(IFunctionType type) {
		IType[] parms = type.getParameterTypes();
		String[] result = new String[parms.length];

		for (int i = 0; i < parms.length; i++) {
			if (parms[i] != null) {
				result[i] = getType(parms[i]);
			}
		}

		return result;
	}

	private static void appendTypeString(IType type, boolean normalize, StringBuilder result) {
		boolean needSpace = false;

		if (type instanceof IArrayType) {
			result.append(Keywords.cpLBRACKET);
			if (type instanceof ICArrayType) {
				final ICArrayType catype = (ICArrayType) type;
				if (catype.isConst()) {
					result.append(Keywords.CONST);
					needSpace = true;
				}
				if (catype.isRestrict()) {
					if (needSpace) {
						result.append(SPACE);
						needSpace = false;
					}
					result.append(Keywords.RESTRICT);
					needSpace = true;
				}
				if (catype.isStatic()) {
					if (needSpace) {
						result.append(SPACE);
						needSpace = false;
					}
					result.append(Keywords.STATIC);
					needSpace = true;
				}
				if (catype.isVolatile()) {
					if (needSpace) {
						result.append(SPACE);
						needSpace = false;
					}
					result.append(Keywords.VOLATILE);
				}
			}
			IValue val = ((IArrayType) type).getSize();
			if (val != null && val != IntegralValue.UNKNOWN) {
				if (normalize) {
					if (needSpace) {
						result.append(SPACE);
						needSpace = false;
					}
					result.append(val.getSignature());
				} else {
					Number v = val.numberValue();
					if (v != null) {
						if (needSpace) {
							result.append(SPACE);
							needSpace = false;
						}
						result.append(v.longValue());
					}
				}
			}
			result.append(Keywords.cpRBRACKET);
		} else if (type instanceof IBasicType) {
			IBasicType basicType = (IBasicType) type;
			final Kind kind = basicType.getKind();
			if (basicType.isSigned()) {
				// 3.9.1.2: signed integer types
				if (!normalize || kind == Kind.eChar) {
					result.append(Keywords.SIGNED);
					needSpace = true;
				}
			} else if (basicType.isUnsigned()) {
				if (needSpace) {
					result.append(SPACE);
					needSpace = false;
				}
				result.append(Keywords.UNSIGNED);
				needSpace = true;
			}
			if (basicType.isLong()) {
				if (needSpace) {
					result.append(SPACE);
					needSpace = false;
				}
				result.append(Keywords.LONG);
				needSpace = true;
			} else if (basicType.isShort()) {
				if (needSpace) {
					result.append(SPACE);
					needSpace = false;
				}
				result.append(Keywords.SHORT);
				needSpace = true;
			} else if (basicType.isLongLong()) {
				if (needSpace) {
					result.append(SPACE);
					needSpace = false;
				}
				result.append(Keywords.LONG_LONG);
				needSpace = true;
			}

			if (basicType.isComplex()) {
				if (needSpace) {
					result.append(SPACE);
					needSpace = false;
				}
				result.append(Keywords.c_COMPLEX);
				needSpace = true;
			}
			if ((basicType).isImaginary()) {
				if (needSpace) {
					result.append(SPACE);
					needSpace = false;
				}
				result.append(Keywords.c_IMAGINARY);
				needSpace = true;
			}

			switch (kind) {
			case eChar:
				if (needSpace)
					result.append(SPACE);
				result.append(Keywords.CHAR);
				break;
			case eDouble:
				if (needSpace)
					result.append(SPACE);
				result.append(Keywords.DOUBLE);
				break;
			case eFloat:
				if (needSpace)
					result.append(SPACE);
				result.append(Keywords.FLOAT);
				break;
			case eFloat128:
				if (needSpace)
					result.append(SPACE);
				result.append(GCCKeywords.__FLOAT128);
				break;
			case eDecimal32:
				if (needSpace)
					result.append(SPACE);
				result.append(GCCKeywords._DECIMAL32);
				break;
			case eDecimal64:
				if (needSpace)
					result.append(SPACE);
				result.append(GCCKeywords._DECIMAL64);
				break;
			case eDecimal128:
				if (needSpace)
					result.append(SPACE);
				result.append(GCCKeywords._DECIMAL128);
				break;
			case eInt:
				if (needSpace)
					result.append(SPACE);
				result.append(Keywords.INT);
				break;
			case eInt128:
				if (needSpace)
					result.append(SPACE);
				result.append(GCCKeywords.__INT128);
				break;
			case eVoid:
				if (needSpace)
					result.append(SPACE);
				result.append(Keywords.VOID);
				break;
			case eBoolean:
				if (needSpace)
					result.append(SPACE);
				if (basicType instanceof ICPPBasicType) {
					result.append(Keywords.BOOL);
				} else {
					result.append(Keywords.c_BOOL);
				}
				break;
			case eWChar:
				if (needSpace)
					result.append(SPACE);
				result.append(Keywords.WCHAR_T);
				break;
			case eChar16:
				if (needSpace)
					result.append(SPACE);
				result.append(Keywords.CHAR16_T);
				break;
			case eChar32:
				if (needSpace)
					result.append(SPACE);
				result.append(Keywords.CHAR32_T);
				break;
			case eNullPtr:
				if (needSpace)
					result.append(SPACE);
				result.append("std::nullptr_t"); //$NON-NLS-1$
				break;
			case eUnspecified:
				break;
			}
		} else if (type instanceof ICPPTemplateParameter) {
			appendCppName((ICPPTemplateParameter) type, normalize, normalize, result);
		} else if (type instanceof ICPPBinding) {
			if (type instanceof IEnumeration) {
				result.append(Keywords.ENUM);
				result.append(SPACE);
			}
			ICPPBinding binding = (ICPPBinding) type;
			boolean nested = binding.getOwner() instanceof ICPPClassType;
			boolean qualify = normalize || nested || (type instanceof ITypedef && type instanceof ICPPSpecialization);
			appendCppName(binding, normalize, qualify, result);
		} else if (type instanceof ICompositeType) {
			// Don't display class, and for consistency don't display struct/union as well (bug 101114).
			appendNameCheckAnonymous((ICompositeType) type, result);
		} else if (type instanceof ITypedef) {
			result.append(((ITypedef) type).getNameCharArray());
		} else if (type instanceof ICPPReferenceType) {
			if (((ICPPReferenceType) type).isRValueReference()) {
				result.append(Keywords.cpAND);
			} else {
				result.append(Keywords.cpAMPER);
			}
		} else if (type instanceof ICPPParameterPackType) {
			result.append(Keywords.cpELLIPSIS);
		} else if (type instanceof IEnumeration) {
			result.append(Keywords.ENUM);
			result.append(SPACE);
			appendNameCheckAnonymous((IEnumeration) type, result);
		} else if (type instanceof IFunctionType) {
			needSpace = appendParameterTypeStringAndQualifiers((IFunctionType) type, result);
		} else if (type instanceof IPointerType) {
			if (type instanceof ICPPPointerToMemberType) {
				appendTypeString(((ICPPPointerToMemberType) type).getMemberOfClass(), normalize, result);
				result.append(Keywords.cpCOLONCOLON);
			}
			result.append(Keywords.cpSTAR);
			needSpace = true;
			IPointerType pt = (IPointerType) type;
			needSpace = appendCVQ(result, needSpace, pt.isConst(), pt.isVolatile(), pt.isRestrict());
		} else if (type instanceof IQualifierType) {
			if (type instanceof ICQualifierType) {
				if (((ICQualifierType) type).isRestrict()) {
					result.append(Keywords.RESTRICT);
					needSpace = true;
				}
			} else if (type instanceof IGPPQualifierType) {
				if (((IGPPQualifierType) type).isRestrict()) {
					result.append(Keywords.RESTRICT);
					needSpace = true;
				}
			}

			IQualifierType qt = (IQualifierType) type;
			needSpace = appendCVQ(result, needSpace, qt.isConst(), qt.isVolatile(), false);
		} else if (type instanceof TypeOfDependentExpression) {
			result.append(((TypeOfDependentExpression) type).getSignature());
		} else if (type instanceof ISemanticProblem) {
			result.append('?');
		} else if (type != null) {
			result.append('@').append(type.hashCode());
		}
	}

	private static void appendTemplateParameter(ICPPTemplateParameter type, boolean normalize, StringBuilder result) {
		if (normalize) {
			result.append('#');
			result.append(Integer.toString(type.getParameterID(), 16));
			if (type.isParameterPack())
				result.append("(...)"); //$NON-NLS-1$
		} else {
			result.append(type.getName());
		}
	}

	private static boolean appendCVQ(StringBuilder target, boolean needSpace, final boolean isConst,
			final boolean isVolatile, final boolean isRestrict) {
		if (isConst) {
			if (needSpace) {
				target.append(SPACE);
			}
			target.append(Keywords.CONST);
			needSpace = true;
		}
		if (isVolatile) {
			if (needSpace) {
				target.append(SPACE);
			}
			target.append(Keywords.VOLATILE);
			needSpace = true;
		}
		if (isRestrict) {
			if (needSpace) {
				target.append(SPACE);
			}
			target.append(Keywords.RESTRICT);
			needSpace = true;
		}
		return needSpace;
	}

	private static void appendRefQualifier(StringBuilder target, boolean needSpace, boolean isRValueReference) {
		if (needSpace) {
			target.append(SPACE);
		}
		target.append(isRValueReference ? Keywords.cpAND : Keywords.cpAMPER);
	}

	/**
	 * Returns the normalized string representation of the type.
	 * @see #getType(IType, boolean)
	 */
	public static String getType(IType type) {
		return getType(type, true);
	}

	/**
	 * Returns a string representation of a type.
	 * Optionally the representation is normalized:
	 * <br> typedefs are resolved
	 * <br> template parameter names are represented by their parameter position
	 * <br> further normalization may be performed in the future.
	 * @param type a type to compute the string representation for.
	 * @param normalize whether or not normalization should be performed.
	 * @return the type representation of the IType
	 */
	public static String getType(IType type, boolean normalize) {
		StringBuilder result = new StringBuilder();
		appendType(type, normalize, result);
		return result.toString();
	}

	private static Map<IType, String> getTypeStringCache(boolean normalize) {
		IASTNode lookupPoint = CPPSemantics.getCurrentLookupPoint();
		if (lookupPoint != null) {
			IASTTranslationUnit tu = lookupPoint.getTranslationUnit();
			if (tu instanceof ASTTranslationUnit) {
				return ((ASTTranslationUnit) tu).getTypeStringCache(normalize);
			}
		}
		return null;
	}

	/**
	 * Append a string to the builder "by reference".
	 *
	 * Instead of copying the bytes of the string into the builder, it copies
	 * the string's "address", as returned by System.identityHashCode().
	 *
	 * To preserve correct semantics, the string that's passed in must
	 * be interned, to ensure that we get the same "address" if and only if
	 * we would have gotten the same contents. (However, we do not call
	 * intern() ourselves, as some of our callers already know they have
	 * an interned strings, and calling it again would be wasteful.)
	 *
	 * The motivation is to avoid strings built by ASTTypeUtil from becoming
	 * excessively long by having the contents of a long string appended
	 * repeatedly to them.
	 */
	private static void appendStringReference(String str, StringBuilder result) {
		result.append("{String@"); //$NON-NLS-1$
		result.append(System.identityHashCode(str));
		result.append("}"); //$NON-NLS-1$
	}

	/**
	 * Appends the the result of {@link #getType(IType, boolean)} to the given buffer.
	 * @since 5.3
	 */
	public static void appendType(IType type, boolean normalize, StringBuilder result) {
		// performance: check if type was appended before
		Map<IType, String> cache = getTypeStringCache(normalize);
		if (cache != null) {
			String cachedResult = cache.get(type);
			if (cachedResult != null) {
				// If the cached result is very long, append it "by reference".
				// Note that all strings longer than TYPE_STRING_LENGTH_THRESHOLD
				// stored in the cache are interned, so we are meeting that
				// requirement of appendStringReference().
				if (cachedResult.length() > TYPE_STRING_LENGTH_THRESHOLD) {
					appendStringReference(cachedResult, result);
				}
				result.append(cachedResult);
				return;
			}
		}

		// Remember type argument for caching, argument is not final and might be changed.
		IType originalType = type;
		// Remember the start offset of the appended string.
		int startOffset = result.length();
		IType[] types = new IType[DEAULT_ITYPE_SIZE];
		int numTypes = 0;

		// Push all of the types onto the stack.
		int i = 0;
		IQualifierType cvq = null;
		ICPPReferenceType ref = null;
		while (type != null && ++i < 100) {
			if (type instanceof ITypedef) {
				// If normalization was not requested, skip the typedef and proceed with its target type.
				if (!normalize) {
					// Output reference, qualifier and typedef, then stop.
					if (ref != null) {
						types = ArrayUtil.appendAt(types, numTypes++, ref);
						ref = null;
					}
					if (cvq != null) {
						types = ArrayUtil.appendAt(types, numTypes++, cvq);
						cvq = null;
					}
					types = ArrayUtil.appendAt(types, numTypes++, type);
					type = null;
				}
			} else {
				if (type instanceof ICPPReferenceType) {
					// Reference types ignore cv-qualifiers.
					cvq = null;
					// Lvalue references win over rvalue references.
					if (ref == null || ref.isRValueReference()) {
						// Delay reference to see if there are more.
						ref = (ICPPReferenceType) type;
					}
				} else {
					if (cvq != null) {
						// Merge cv qualifiers.
						if (type instanceof IQualifierType || type instanceof IPointerType) {
							type = SemanticUtil.addQualifiers(type, cvq.isConst(), cvq.isVolatile(), false);
							cvq = null;
						}
					}
					if (type instanceof IQualifierType) {
						// Delay cv qualifier to merge it with others
						cvq = (IQualifierType) type;
					} else {
						// No reference, no cv qualifier: output reference and cv-qualifier.
						if (ref != null) {
							types = ArrayUtil.appendAt(types, numTypes++, ref);
							ref = null;
						}
						if (cvq != null) {
							types = ArrayUtil.appendAt(types, numTypes++, cvq);
							cvq = null;
						}
						types = ArrayUtil.appendAt(types, numTypes++, type);
					}
				}
			}
			if (type instanceof ITypeContainer) {
				type = ((ITypeContainer) type).getType();
			} else if (type instanceof IFunctionType) {
				type = ((IFunctionType) type).getReturnType();
			} else {
				type = null;
			}
		}

		// Pop all of the types from the stack, and build the string representation while doing so.
		List<IType> postfix = null;
		BitSet parenthesis = null;
		boolean needParenthesis = false;
		boolean needSpace = false;
		for (int j = numTypes; --j >= 0;) {
			IType tj = types[j];
			if (j > 0 && types[j - 1] instanceof IQualifierType) {
				if (needSpace)
					result.append(SPACE);
				appendTypeString(types[j - 1], normalize, result);
				result.append(SPACE);
				appendTypeString(tj, normalize, result);
				needSpace = true;
				--j;
			} else {
				// Handle post-fix.
				if (tj instanceof IFunctionType || tj instanceof IArrayType) {
					if (j == 0) {
						if (needSpace)
							result.append(SPACE);
						appendTypeString(tj, normalize, result);
						needSpace = true;
					} else {
						if (postfix == null) {
							postfix = new ArrayList<>();
						}
						postfix.add(tj);
						needParenthesis = true;
					}
				} else {
					if (needSpace)
						result.append(SPACE);
					if (needParenthesis && postfix != null) {
						result.append('(');
						if (parenthesis == null) {
							parenthesis = new BitSet();
						}
						parenthesis.set(postfix.size() - 1);
					}
					appendTypeString(tj, normalize, result);
					needParenthesis = false;
					needSpace = true;
				}
			}
		}

		if (postfix != null) {
			for (int j = postfix.size() - 1; j >= 0; j--) {
				if (parenthesis != null && parenthesis.get(j)) {
					result.append(')');
				}
				IType tj = postfix.get(j);
				appendTypeString(tj, normalize, result);
			}
		}

		if (cache != null) {
			// Store result in the cache.
			String toCache = result.substring(startOffset);
			// If the string to be cached is too long, we want to append it "by reference" instead.
			if (toCache.length() > TYPE_STRING_LENGTH_THRESHOLD) {
				// Remove the contents of the string from the buffer.
				result.setLength(startOffset);

				// Intern the string. This is required for calling appendStringReference(),
				// to ensure that we get a unique String object for unique contents.
				toCache = toCache.intern();

				// Re-append the string by reference.
				appendStringReference(toCache, result);
			}
			cache.put(originalType, toCache);
		}
	}

	/**
	 * For testing purposes, only.
	 * Returns the normalized string representation of the type defined by the given declarator.
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 */
	public static String getType(IASTDeclarator declarator) {
		// Get the most nested declarator.
		while (declarator.getNestedDeclarator() != null) {
			declarator = declarator.getNestedDeclarator();
		}

		IBinding binding = declarator.getName().resolveBinding();
		IType type = null;

		if (binding instanceof IEnumerator) {
			type = ((IEnumerator) binding).getType();
		} else if (binding instanceof IFunction) {
			type = ((IFunction) binding).getType();
		} else if (binding instanceof ITypedef) {
			type = ((ITypedef) binding).getType();
		} else if (binding instanceof IVariable) {
			type = ((IVariable) binding).getType();
		}

		if (type != null) {
			return getType(type);
		}

		return EMPTY_STRING;
	}

	/**
	 * For testing purposes, only.
	 * Return's the String representation of a node's type (if available).
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 */
	public static String getNodeType(IASTNode node) {
		if (node instanceof IASTDeclarator)
			return getType((IASTDeclarator) node);
		if (node instanceof IASTName && ((IASTName) node).resolveBinding() instanceof IVariable)
			return getType(((IVariable) ((IASTName) node).resolveBinding()).getType());
		if (node instanceof IASTName && ((IASTName) node).resolveBinding() instanceof IFunction)
			return getType(((IFunction) ((IASTName) node).resolveBinding()).getType());
		if (node instanceof IASTName && ((IASTName) node).resolveBinding() instanceof IType)
			return getType((IType) ((IASTName) node).resolveBinding());
		if (node instanceof IASTTypeId)
			return getType((IASTTypeId) node);

		return EMPTY_STRING;
	}

	/**
	 * Returns the type representation of the IASTTypeId as a String.
	 *
	 * @param typeId
	 * @return the type representation of the IASTTypeId as a String
	 */
	public static String getType(IASTTypeId typeId) {
		if (typeId instanceof CASTTypeId)
			return createCType(typeId.getAbstractDeclarator());
		else if (typeId instanceof CPPASTTypeId)
			return createCPPType(typeId.getAbstractDeclarator());

		return EMPTY_STRING;
	}

	private static String createCType(IASTDeclarator declarator) {
		IType type = CVisitor.createType(declarator);
		return getType(type);
	}

	private static String createCPPType(IASTDeclarator declarator) {
		IType type = CPPVisitor.createType(declarator);
		return getType(type);
	}

	/**
	 * @deprecated don't use it does something strange
	 */
	@Deprecated
	public static boolean isConst(IType type) {
		if (type instanceof IQualifierType) {
			return ((IQualifierType) type).isConst();
		} else if (type instanceof ITypeContainer) {
			return isConst(((ITypeContainer) type).getType());
		} else if (type instanceof IArrayType) {
			return isConst(((IArrayType) type).getType());
		} else if (type instanceof ICPPReferenceType) {
			return isConst(((ICPPReferenceType) type).getType());
		} else if (type instanceof IFunctionType) {
			return isConst(((IFunctionType) type).getReturnType());
		} else if (type instanceof IPointerType) {
			return isConst(((IPointerType) type).getType());
		} else if (type instanceof ITypedef) {
			return isConst(((ITypedef) type).getType());
		} else {
			return false;
		}
	}

	/**
	 * Returns the qualified name for the given binding including template arguments.
	 * If there are template arguments the arguments are neither normalized nor qualified.
	 * @since 5.3
	 */
	public static String getQualifiedName(ICPPBinding binding) {
		StringBuilder buf = new StringBuilder();
		appendCppName(binding, false, true, buf);
		return buf.toString();
	}

	private static void appendCppName(IBinding binding, boolean normalize, boolean qualify, StringBuilder result) {
		ICPPTemplateParameter tpar = getTemplateParameter(binding);
		if (tpar != null) {
			appendTemplateParameter(tpar, normalize, result);
		} else {
			if (qualify) {
				int pos = result.length();
				IBinding owner = binding.getOwner();
				if (owner instanceof ICPPNamespace || owner instanceof IType) {
					appendCppName(owner, normalize, qualify, result);
					if (owner instanceof ICPPNamespace && owner.getNameCharArray().length == 0) {
						if (binding instanceof IIndexBinding) {
							try {
								IIndexFile file = ((IIndexBinding) binding).getLocalToFile();
								if (file != null) {
									result.append('{');
									result.append(file.getLocation().getURI().toString());
									result.append('}');
								}
							} catch (CoreException e) {
								CCorePlugin.log(e);
							}
						} else {
							// The ASTInternal.getDeclaredInSourceFileOnly method may call this method
							// recursively for the same binding. Since a nested call is done to check if
							// the binding has a declaration outside of the source file, inside the nested
							// call we assume that the binding is not local to the source file.
							// See http://bugs.eclipse.org/509774
							if (fSourceFileOnlyCheckInProgress.get().add(binding)) {
								try {
									IASTNode node = ASTInternal.getDeclaredInSourceFileOnly(binding);
									if (node != null) {
										IPath filePath = new Path(node.getTranslationUnit().getFilePath());
										URI uri = UNCPathConverter.getInstance().toURI(filePath);
										result.append('{');
										result.append(uri.toString());
										result.append('}');
									}
								} finally {
									fSourceFileOnlyCheckInProgress.get().remove(binding);
								}
							}
						}
					}
				} else if (binding instanceof IType && owner instanceof ICPPFunction) {
					try {
						IScope scope = binding.getScope();
						if (scope != null) {
							IASTNode node = ASTInternal.getPhysicalNodeOfScope(scope);
							if (node != null)
								appendNodeLocation(node, true, result);
						}
					} catch (DOMException e) {
						// Ignore.
					}
				}
				if (result.length() > pos)
					result.append("::"); //$NON-NLS-1$
			}
			appendNameCheckAnonymous(binding, result);
		}

		if (binding instanceof ICPPTemplateInstance) {
			appendArgumentList(((ICPPTemplateInstance) binding).getTemplateArguments(), normalize, result);
		} else if (binding instanceof ICPPUnknownMemberClassInstance) {
			appendArgumentList(((ICPPUnknownMemberClassInstance) binding).getArguments(), normalize, result);
		}
	}

	private static ICPPTemplateParameter getTemplateParameter(IBinding binding) {
		if (binding instanceof ICPPTemplateParameter)
			return (ICPPTemplateParameter) binding;

		if (binding instanceof ICPPDeferredClassInstance)
			return getTemplateParameter(((ICPPDeferredClassInstance) binding).getTemplateDefinition());

		return null;
	}

	private static void appendNameCheckAnonymous(IBinding binding, StringBuilder result) {
		char[] name = binding.getNameCharArray();
		if (name != null && name.length > 0) {
			result.append(name);
		} else if (!(binding instanceof ICPPNamespace)) {
			appendNameForAnonymous(binding, result);
		}
	}

	public static char[] createNameForAnonymous(IBinding binding) {
		if (binding instanceof ICPPSpecialization) {
			// TODO: Do we need distinct names for distinct specializations?
			return createNameForAnonymous(((ICPPSpecialization) binding).getSpecializedBinding());
		}
		StringBuilder result = new StringBuilder();
		appendNameForAnonymous(binding, result);
		if (result.length() == 0)
			return null;

		return extractChars(result);
	}

	private static char[] extractChars(StringBuilder buf) {
		final int length = buf.length();
		char[] result = new char[length];
		buf.getChars(0, length, result, 0);
		return result;
	}

	private static void appendNameForAnonymous(IBinding binding, StringBuilder buf) {
		IASTNode node = null;
		if (binding instanceof ICInternalBinding) {
			node = ((ICInternalBinding) binding).getPhysicalNode();
		} else if (binding instanceof ICPPInternalBinding) {
			node = ((ICPPInternalBinding) binding).getDefinition();
		}
		if (node != null) {
			appendNodeLocation(node, !(binding instanceof ICPPNamespace), buf);
		}
	}

	private static void appendNodeLocation(IASTNode node, boolean includeOffset, StringBuilder buf) {
		IASTFileLocation loc = node.getFileLocation();
		if (loc == null) {
			node = node.getParent();
			if (node != null) {
				loc = node.getFileLocation();
			}
		}
		if (loc != null) {
			char[] file = getWorkspaceRelativePath(loc.getFileName(), node).toCharArray();
			buf.append('{');
			buf.append(file);
			if (includeOffset) {
				buf.append(':');
				buf.append(loc.getNodeOffset());
			}
			buf.append('}');
		}
	}

	/**
	 * Try to get a workspace-relative path for a filename.
	 * If that fails, just return the input path.
	 */
	private static String getWorkspaceRelativePath(String filename, IASTNode context) {
		if (context == null) {
			return filename;
		}
		IASTTranslationUnit ast = context.getTranslationUnit();
		if (ast == null) {
			return filename;
		}
		ITranslationUnit tu = ast.getOriginatingTranslationUnit();
		if (tu == null) {
			return filename;
		}
		ICProject cproject = tu.getCProject();
		if (cproject == null) {
			return filename;
		}
		IPath path = new Path(filename);
		IFile file = ResourceLookup.selectFileForLocation(path, cproject.getProject());
		if (file == null) {
			return filename;
		}
		IPath workspaceRelative = file.getFullPath();
		return workspaceRelative.toString();
	}

	/**
	 * @noreference This method is not intended to be referenced by clients.
	 * @deprecated This method is no longer used and is scheduled for removal in 10.0.
	 */
	@Deprecated
	public static void startTranslationUnit() {
	}

	/**
	 * @noreference This method is not intended to be referenced by clients.
	 * @deprecated This method is no longer used and is scheduled for removal in 10.0.
	 */
	@Deprecated
	public static void finishTranslationUnit() {
	}
}
