/*******************************************************************************
 * Copyright (c) 2004, 2014 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:
 *     Andrew Niefer (IBM Corporation) - initial API and implementation
 *     Markus Schorn (Wind River Systems)
 *     Bryan Wilkinson (QNX)
 *     Sergey Prigogin (Google)
 *     Andrew Ferguson (Symbian)
 *******************************************************************************/
package org.eclipse.cdt.internal.core.dom.parser.cpp;

import org.eclipse.cdt.core.dom.ILinkage;
import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IField;
import org.eclipse.cdt.core.dom.ast.IScope;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.ITypedef;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTElaboratedTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNameSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBase;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBlockScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPConstructor;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPField;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPUsingDeclaration;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexBinding;
import org.eclipse.cdt.core.parser.util.ArrayUtil;
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.ProblemBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
import org.eclipse.core.runtime.PlatformObject;

/**
 * Binding for a class type.
 */
public class CPPClassType extends PlatformObject implements ICPPInternalClassTypeMixinHost {

	public static class CPPClassTypeProblem extends ProblemBinding implements ICPPClassType {
		public CPPClassTypeProblem(IASTName name, int id) {
			super(name, id);
		}

		public CPPClassTypeProblem(ICPPASTNameSpecifier nameSpec, int id) {
			super(nameSpec, id, nameSpec instanceof IASTName ? null : nameSpec.toCharArray());
		}

		public CPPClassTypeProblem(IASTNode node, int id, char[] arg) {
			super(node, id, arg);
		}

		@Override
		public ICPPBase[] getBases() {
			return ICPPBase.EMPTY_BASE_ARRAY;
		}

		@Override
		public IField[] getFields() {
			return IField.EMPTY_FIELD_ARRAY;
		}

		@Override
		public ICPPField[] getDeclaredFields() {
			return ICPPField.EMPTY_CPPFIELD_ARRAY;
		}

		@Override
		public ICPPMethod[] getMethods() {
			return ICPPMethod.EMPTY_CPPMETHOD_ARRAY;
		}

		@Override
		public ICPPMethod[] getAllDeclaredMethods() {
			return ICPPMethod.EMPTY_CPPMETHOD_ARRAY;
		}

		@Override
		public ICPPMethod[] getDeclaredMethods() {
			return ICPPMethod.EMPTY_CPPMETHOD_ARRAY;
		}

		@Override
		public ICPPConstructor[] getConstructors() {
			return ICPPConstructor.EMPTY_CONSTRUCTOR_ARRAY;
		}

		@Override
		public int getKey() {
			return k_class;
		}

		@Override
		public IField findField(String name) {
			return null;
		}

		@Override
		public IScope getCompositeScope() {
			return this;
		}

		@Override
		public IBinding[] getFriends() {
			return IBinding.EMPTY_BINDING_ARRAY;
		}

		@Override
		public ICPPClassType[] getNestedClasses() {
			return ICPPClassType.EMPTY_CLASS_ARRAY;
		}

		@Override
		public ICPPUsingDeclaration[] getUsingDeclarations() {
			return ICPPUsingDeclaration.EMPTY_USING_DECL_ARRAY;
		}

		@Override
		public boolean isFinal() {
			return false;
		}

		@Override
		public int getVisibility(IBinding member) {
			throw new IllegalArgumentException(member.getName() + " is not a member of " + getName()); //$NON-NLS-1$
		}
	}

	private IASTName definition;
	private IASTName[] declarations;
	private boolean checked;
	private ICPPClassType typeInIndex;
	private ICPPBase[] bases;

	public CPPClassType(IASTName name, IBinding indexBinding) {
		name = stripQualifier(name);
		IASTNode parent = name.getParent();
		while (parent instanceof IASTName) {
			parent = parent.getParent();
		}

		if (parent instanceof IASTCompositeTypeSpecifier) {
			definition = name;
		} else {
			declarations = new IASTName[] { name };
		}
		name.setBinding(this);
		if (indexBinding instanceof ICPPClassType && indexBinding instanceof IIndexBinding) {
			typeInIndex = (ICPPClassType) indexBinding;
		}
	}

	@Override
	public IASTNode[] getDeclarations() {
		return declarations;
	}

	@Override
	public IASTNode getDefinition() {
		return definition;
	}

	@Override
	public void checkForDefinition() {
		// Ambiguity resolution ensures that definitions are resolved.
		if (!checked) {
			if (definition == null && typeInIndex == null) {
				IIndex index = getPhysicalNode().getTranslationUnit().getIndex();
				if (index != null) {
					typeInIndex = (ICPPClassType) index.adaptBinding(this);
				}
			}
			checked = true;
		}
	}

	@Override
	public ICPPASTCompositeTypeSpecifier getCompositeTypeSpecifier() {
		if (definition != null) {
			IASTNode node = definition;
			while (node instanceof IASTName)
				node = node.getParent();
			if (node instanceof ICPPASTCompositeTypeSpecifier)
				return (ICPPASTCompositeTypeSpecifier) node;
		}
		return null;
	}

	private ICPPASTElaboratedTypeSpecifier getElaboratedTypeSpecifier() {
		if (declarations != null) {
			IASTNode node = declarations[0];
			while (node instanceof IASTName)
				node = node.getParent();
			if (node instanceof ICPPASTElaboratedTypeSpecifier)
				return (ICPPASTElaboratedTypeSpecifier) node;
		}
		return null;
	}

	@Override
	public final String getName() {
		return new String(getNameCharArray());
	}

	@Override
	public char[] getNameCharArray() {
		return (definition != null) ? definition.getSimpleID() : declarations[0].getSimpleID();
	}

	@Override
	public IScope getScope() {
		IASTName name = definition != null ? definition : declarations[0];
		name = stripQualifier(name);

		IScope scope = CPPVisitor.getContainingScope(name);
		if (definition == null && name.getPropertyInParent() != ICPPASTQualifiedName.SEGMENT_NAME) {
			IASTNode node = declarations[0].getParent().getParent();
			if (node instanceof IASTSimpleDeclaration && ((IASTSimpleDeclaration) node).getDeclarators().length == 0
					&& !getElaboratedTypeSpecifier().isFriend()) {
				// 3.3.1.5 class-key identifier ;
			} else {
				while (scope instanceof ICPPClassScope || scope instanceof ICPPFunctionScope) {
					try {
						scope = scope.getParent();
					} catch (DOMException e1) {
					}
				}
			}
		}
		return scope;
	}

	@Override
	public ICPPClassScope getCompositeScope() {
		checkForDefinition();
		if (definition != null) {
			return getCompositeTypeSpecifier().getScope();
		}
		// fwd-declarations must be backed up from the index
		if (typeInIndex != null) {
			IScope scope = typeInIndex.getCompositeScope();
			if (scope instanceof ICPPClassScope)
				return (ICPPClassScope) scope;
		}
		return null;
	}

	public IASTNode getPhysicalNode() {
		return definition != null ? (IASTNode) definition : declarations[0];
	}

	@Override
	public int getKey() {
		if (definition != null)
			return getCompositeTypeSpecifier().getKey();

		return getElaboratedTypeSpecifier().getKind();
	}

	@Override
	public void addDefinition(IASTNode node) {
		if (node instanceof ICPPASTCompositeTypeSpecifier) {
			definition = ((ICPPASTCompositeTypeSpecifier) node).getName();
		} else {
			assert false;
		}
	}

	@Override
	public void addDeclaration(IASTNode node) {
		if (node instanceof ICPPASTElaboratedTypeSpecifier) {
			IASTName name = ((ICPPASTElaboratedTypeSpecifier) node).getName();

			if (declarations == null) {
				declarations = new IASTName[] { name };
				return;
			}

			// Keep the lowest offset declaration in [0]
			if (declarations.length > 0 && ((ASTNode) node).getOffset() < ((ASTNode) declarations[0]).getOffset()) {
				declarations = ArrayUtil.prepend(IASTName.class, declarations, name);
			} else {
				declarations = ArrayUtil.append(IASTName.class, declarations, name);
			}
		} else {
			assert false;
		}
	}

	@Override
	public String[] getQualifiedName() {
		return CPPVisitor.getQualifiedName(this);
	}

	@Override
	public char[][] getQualifiedNameCharArray() {
		return CPPVisitor.getQualifiedNameCharArray(this);
	}

	@Override
	public boolean isGloballyQualified() throws DOMException {
		IScope scope = getScope();
		while (scope != null) {
			if (scope instanceof ICPPBlockScope)
				return false;
			scope = scope.getParent();
		}
		return true;
	}

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

	@Override
	public boolean isSameType(IType type) {
		if (type == this)
			return true;
		if (type instanceof ITypedef || type instanceof IIndexBinding)
			return type.isSameType(this);
		return false;
	}

	@Override
	public ICPPBase[] getBases() {
		if (bases == null) {
			ICPPBase[] result = ClassTypeHelper.getBases(this);
			// Do not cache the computed bases if the class is incomplete.
			// When the class becomes complete, the answer may be different.
			if (result != ICPPBase.NO_BASES_BECAUSE_TYPE_IS_INCOMPLETE) {
				bases = result;
			}
			return result;
		}
		return bases;
	}

	@Override
	public IField[] getFields() {
		return ClassTypeHelper.getFields(this);
	}

	@Override
	public ICPPField[] getDeclaredFields() {
		return ClassTypeHelper.getDeclaredFields(this);
	}

	@Override
	public ICPPMethod[] getMethods() {
		return ClassTypeHelper.getMethods(this);
	}

	@Override
	public ICPPMethod[] getAllDeclaredMethods() {
		return ClassTypeHelper.getAllDeclaredMethods(this);
	}

	@Override
	public ICPPMethod[] getDeclaredMethods() {
		return ClassTypeHelper.getDeclaredMethods(this);
	}

	@Override
	public ICPPConstructor[] getConstructors() {
		return ClassTypeHelper.getConstructors(this);
	}

	@Override
	public IBinding[] getFriends() {
		return ClassTypeHelper.getFriends(this);
	}

	@Override
	public ICPPClassType[] getNestedClasses() {
		return ClassTypeHelper.getNestedClasses(this);
	}

	@Override
	public ICPPUsingDeclaration[] getUsingDeclarations() {
		return ClassTypeHelper.getUsingDeclarations(this);
	}

	@Override
	public IField findField(String name) {
		return ClassTypeHelper.findField(this, name);
	}

	@Override
	public Object clone() {
		try {
			return super.clone();
		} catch (CloneNotSupportedException e) {
		}
		return null;
	}

	/**
	 * For debugging purposes, only.
	 */
	@Override
	public String toString() {
		return getName();
	}

	@Override
	public IBinding getOwner() {
		if (definition != null) {
			return CPPVisitor.findNameOwner(definition, true);
		}
		return CPPVisitor.findDeclarationOwner(declarations[0], true);
	}

	@Override
	public boolean isAnonymous() {
		if (getNameCharArray().length > 0)
			return false;

		ICPPASTCompositeTypeSpecifier spec = getCompositeTypeSpecifier();
		if (spec != null) {
			IASTNode node = spec.getParent();
			if (node instanceof IASTSimpleDeclaration) {
				if (((IASTSimpleDeclaration) node).getDeclarators().length == 0) {
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public boolean isFinal() {
		ICPPASTCompositeTypeSpecifier typeSpecifier = getCompositeTypeSpecifier();
		if (typeSpecifier != null) {
			return typeSpecifier.isFinal();
		}
		return false;
	}

	private IASTName stripQualifier(IASTName name) {
		if (name instanceof ICPPASTQualifiedName) {
			name = ((ICPPASTQualifiedName) name).getLastName();
		}
		return name;
	}

	@Override
	public int getVisibility(IBinding member) {
		return ClassTypeHelper.getVisibility(this, member);
	}
}
