/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;

import java.util.Map;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;

/*
 * Not all fields defined by this type (& its subclasses) are initialized when it is created.
 * Some are initialized only when needed.
 *
 * Accessors have been provided for some public fields so all TypeBindings have the same API...
 * but access public fields directly whenever possible.
 * Non-public fields have accessors which should be used everywhere you expect the field to be initialized.
 *
 * null is NOT a valid value for a non-public field... it just means the field is not initialized.
 */
abstract public class TypeBinding extends Binding implements BaseTypes, TagBits, TypeConstants, TypeIds {
	public int id = NoId;
	public long tagBits = 0; // See values in the interface TagBits below
/**
 * Match a well-known type id to its binding
 */
public static final TypeBinding wellKnownType(Scope scope, int id) {
		switch (id) { 
			case T_boolean :
				return BooleanBinding;
			case T_byte :
				return ByteBinding;
			case T_char :
				return CharBinding;
			case T_short :
				return ShortBinding;
			case T_double :
				return DoubleBinding;
			case T_float :
				return FloatBinding;
			case T_int :
				return IntBinding;
			case T_long :
				return LongBinding;
			case T_JavaLangObject :
				return scope.getJavaLangObject();
			case T_JavaLangString :
				return scope.getJavaLangString();
			default : 
				return null;
		}
	}
/* API
 * Answer the receiver's binding type from Binding.BindingID.
 */

public int kind() {
	return Binding.TYPE;
}
/* Answer true if the receiver can be instantiated
 */
public boolean canBeInstantiated() {
	return !isBaseType();
}

/**
 * Perform capture conversion on a given type (only effective on parameterized type with wildcards)
 */
public TypeBinding capture(Scope scope, int position) {
	return this;
}

/**
 * Collect the substitutes into a map for certain type variables inside the receiver type
 * e.g.   Collection<T>.findSubstitute(T, Collection<List<X>>):   T --> List<X>
 */
public void collectSubstitutes(Scope scope, TypeBinding otherType, Map substitutes, int constraint) {
    // no substitute by default
}
/**
 *  Answer the receiver's constant pool name.
 *  NOTE: This method should only be used during/after code gen.
 *  e.g. 'java/lang/Object' 
 */
public abstract char[] constantPoolName();

public String debugName() {
	return new String(readableName());
}
/*
 * Answer the receiver's dimensions - 0 for non-array types
 */
public int dimensions(){
	return 0;
}
/* Answer the receiver's enclosing type... null if the receiver is a top level type.
*/

public ReferenceBinding enclosingType() {
	return null;
}
public TypeBinding erasure() {
    return this;
}
/**
 * Returns the type to use for generic cast, or null if none required
 */
public TypeBinding genericCast(TypeBinding otherType) {
    if (this == otherType) return null;
	TypeBinding otherErasure = otherType.erasure();
	if (otherErasure == this.erasure()) return null;
	return otherErasure;
}

/**
 * Answer the receiver classfile signature.
 * Arrays & base types do not distinguish between signature() & constantPoolName().
 * NOTE: This method should only be used during/after code gen.
 */
public char[] genericTypeSignature() {
    return signature();
}
public abstract PackageBinding getPackage();

public final boolean isAnonymousType() {
	return (tagBits & IsAnonymousType) != 0;
}
public boolean isAnnotationType() {
	return false;
}
/* Answer true if the receiver is an array
*/
public final boolean isArrayType() {
	return (tagBits & IsArrayType) != 0;
}
/* Answer true if the receiver is a base type
*/
public final boolean isBaseType() {
	return (tagBits & IsBaseType) != 0;
}

	
/**
 *  Returns true if parameterized type AND not of the form List<?>
 */
public boolean isBoundParameterizedType() {
	return (this.tagBits & TagBits.IsBoundParameterizedType) != 0;
}

/**
 * Returns true if the type is the capture of some wildcard
 */
public boolean isCapture() {
    return false;
}

public boolean isClass() {
	return false;
}
/* Answer true if the receiver type can be assigned to the argument type (right)
*/
public abstract boolean isCompatibleWith(TypeBinding right);

public boolean isEnum() {
	return false;
}
/**
 * Returns true if a type is identical to another one,
 * or for generic types, true if compared to its raw type.
 */
public boolean isEquivalentTo(TypeBinding otherType) {
    if (this == otherType) return true;
    if (otherType == null) return false;
    if (otherType.isWildcard()) // wildcard
		return ((WildcardBinding) otherType).boundCheck(this);
	return false;
}

public boolean isGenericType() {
    return false;
}

/* Answer true if the receiver's hierarchy has problems (always false for arrays & base types)
*/
public final boolean isHierarchyInconsistent() {
	return (tagBits & HierarchyHasProblems) != 0;
}
public boolean isInterface() {
	return false;
}
/**
 * Returns true if a type is intersecting with another one,
 */
public boolean isIntersectingWith(TypeBinding otherType) {
    return this == otherType;
}
/**
 * Returns true if the current type denotes an intersection type: Number & Comparable<?>
 */
public boolean isIntersectionType() {
	return false;
}
public final boolean isLocalType() {
	return (tagBits & IsLocalType) != 0;
}

public final boolean isMemberType() {
	return (tagBits & IsMemberType) != 0;
}

public final boolean isNestedType() {
	return (tagBits & IsNestedType) != 0;
}
public final boolean isNumericType() {
	switch (id) {
		case T_int :
		case T_float :
		case T_double :
		case T_short :
		case T_byte :
		case T_long :
		case T_char :
			return true;
		default :
			return false;
	}
}

/**
 * Returns true if the type is parameterized, e.g. List<String>
 */
public boolean isParameterizedType() {
    return false;
}

/**
 * Returns true if the two types are statically known to be different at compile-time,
 * e.g. a type variable is not provably known to be distinct from another type
 */
public boolean isProvablyDistinctFrom(TypeBinding otherType, int depth) {
	if (this == otherType) return false;
	if (depth > 1) return true;
	switch (otherType.kind()) {
		case Binding.TYPE_PARAMETER :
		case Binding.WILDCARD_TYPE :
			return false;
	}
	switch(kind()) {
		
		case Binding.TYPE_PARAMETER :
		case Binding.WILDCARD_TYPE :
			return false;
			
		case Binding.PARAMETERIZED_TYPE :
			ParameterizedTypeBinding parameterizedType = (ParameterizedTypeBinding) this;
			if (parameterizedType.type.isProvablyDistinctFrom(otherType.erasure(), depth)) return true;
			switch (otherType.kind()) {
				case Binding.GENERIC_TYPE :
				case Binding.RAW_TYPE :
					return false;
				case Binding.PARAMETERIZED_TYPE :
					TypeBinding[] arguments = parameterizedType.arguments;
					if (arguments == null) return false;
					ParameterizedTypeBinding otherParameterizedType = (ParameterizedTypeBinding) otherType;
					TypeBinding[] otherArguments = otherParameterizedType.arguments;
					if (otherArguments == null) return false;
					for (int i = 0, length = arguments.length; i < length; i++) {
						if (arguments[i].isProvablyDistinctFrom(otherArguments[i], depth+1)) return true;
					}
					return false;
					
			}
			break;

		case Binding.RAW_TYPE :
			return this.erasure().isProvablyDistinctFrom(otherType.erasure(), 0);
			
		case Binding.GENERIC_TYPE :
			return this != otherType.erasure();
	}
	return this != otherType;
}

public boolean isRawType() {
    return false;
}

/**
 * JLS(3) 4.7. 
 * Note: Foo<?>.Bar is also reifiable
 */
public boolean isReifiable() {
	
	TypeBinding leafType = leafComponentType();
	if (!(leafType instanceof ReferenceBinding)) 
		return true;
	ReferenceBinding current = (ReferenceBinding) leafType;
	do {
		switch(current.kind()) {
			
			case Binding.TYPE_PARAMETER :
			case Binding.WILDCARD_TYPE :
			case Binding.GENERIC_TYPE :
				return false;
				
			case Binding.PARAMETERIZED_TYPE :
				if (current.isBoundParameterizedType()) 
					return false;
				break;
				
			case Binding.RAW_TYPE :
				return true;
		}
		if (current.isStatic()) 
			return true;
	} while ((current = current.enclosingType()) != null);
	return true;
}

// JLS3: 4.5.1.1
public boolean isTypeArgumentContainedBy(TypeBinding otherType) {
	if (this == otherType)
		return true;
	switch(otherType.kind()) {
		// allow wildcard containment
		case Binding.WILDCARD_TYPE :
			TypeBinding lowerBound = this;
			TypeBinding upperBound = this;
			switch (this.kind()) {
				case Binding.WILDCARD_TYPE :
					WildcardBinding wildcard = (WildcardBinding) this;
					switch(wildcard.boundKind) {
						case Wildcard.EXTENDS :
							upperBound = wildcard.bound;
							lowerBound = null;
							break;
						case Wildcard. SUPER :
							upperBound = wildcard;
							lowerBound = wildcard.bound;
							break;
						case Wildcard.UNBOUND :
							upperBound = wildcard;
							lowerBound = null;
					}
					break;
				case Binding.TYPE_PARAMETER :
					if (this.isCapture()) {
						CaptureBinding capture = (CaptureBinding) this;
						if (capture.lowerBound != null) lowerBound = capture.lowerBound;
					}
			}
			WildcardBinding otherWildcard = (WildcardBinding) otherType;
			if (otherWildcard.otherBounds != null) return false; // not a true wildcard (intersection type)
			switch(otherWildcard.boundKind) {
				case Wildcard.EXTENDS:
					if (otherWildcard.bound == this) return true; // ? extends T  <=  ? extends ? extends T
					return upperBound != null && upperBound.isCompatibleWith(otherWildcard.bound);
	
				case Wildcard.SUPER :
					if (otherWildcard.bound == this) return true; // ? super T  <=  ? super ? super T
					return lowerBound != null && otherWildcard.bound.isCompatibleWith(lowerBound);
	
				case Wildcard.UNBOUND :
				default:
					return true;
			}
		// allow List<?> to match List<? extends Object> (and reciprocally)
		case Binding.PARAMETERIZED_TYPE :
			if (!this.isParameterizedType()) return false;
			ParameterizedTypeBinding paramType = (ParameterizedTypeBinding) this;
            ParameterizedTypeBinding otherParamType = (ParameterizedTypeBinding) otherType;
            if (paramType.type != otherParamType.type) 
                return false;
            if (!paramType.isStatic()) { // static member types do not compare their enclosing
            	ReferenceBinding enclosing = enclosingType();
            	if (enclosing != null) {
            		ReferenceBinding otherEnclosing = otherParamType.enclosingType();
            		if (otherEnclosing == null) return false;
            		if ((otherEnclosing.tagBits & HasDirectWildcard) == 0) {
						if (enclosing != otherEnclosing) return false;
            		} else {
            			if (!enclosing.isEquivalentTo(otherParamType.enclosingType())) return false;
            		}
            	}
            }
            int length = paramType.arguments == null ? 0 : paramType.arguments.length;
            TypeBinding[] otherArguments = otherParamType.arguments;
            int otherLength = otherArguments == null ? 0 : otherArguments.length;
            if (otherLength != length) 
                return false;
            nextArgument: for (int i = 0; i < length; i++) {
            	TypeBinding argument = paramType.arguments[i];
            	TypeBinding otherArgument = otherArguments[i];
            	if (argument == otherArgument) 
            		continue nextArgument;
            	int kind = argument.kind();
            	if (otherArgument.kind() != kind)
            		return false;
           		switch(kind) {
        			case Binding.PARAMETERIZED_TYPE :
        				if (argument.isTypeArgumentContainedBy(otherArgument)) // recurse
	        				continue nextArgument;
        				break;
        			case Binding.WILDCARD_TYPE :
        				WildcardBinding wildcard = (WildcardBinding) argument;
        				otherWildcard = (WildcardBinding) otherArgument;
        				switch (wildcard.boundKind) {
        					case Wildcard.EXTENDS :
        						// match "? extends <upperBound>" with "?"
        						if (otherWildcard.boundKind == Wildcard.UNBOUND && wildcard.bound == wildcard.typeVariable().upperBound())
        							continue nextArgument; 
        						break;
        					case Wildcard.SUPER :
        						break;
        					case Wildcard.UNBOUND :
        						// match "?" with "? extends <upperBound>"
        						if (otherWildcard.boundKind == Wildcard.EXTENDS && otherWildcard.bound == otherWildcard.typeVariable().upperBound())
        							continue nextArgument; 
        						break;
        				}
        				break;
           		}
           		return false;
            }
            return true;
	}
	return false;
}

/**
 * Returns false if two given types could not intersect as argument types:
 * List<Throwable> & List<Runnable> --> false
 * List<? extends Throwable> & List<? extends Runnable> --> true
 * List<? extends String> & List<? extends Runnable> --> false
 */
public boolean isTypeArgumentIntersecting(TypeBinding otherArgument) {
	if (this == otherArgument)
		return true;
	switch (kind()) {
		
		// TYPE_PARAM & ANY TYPE
		case Binding.TYPE_PARAMETER :   
			return true;
			
		case Binding.WILDCARD_TYPE :
			switch (otherArgument.kind()) {
				
				// WILDCARD & TYPE_PARAM
				case Binding.TYPE_PARAMETER :
					return true;
					
				// WILDCARD & WILDCARD
				case Binding.WILDCARD_TYPE :
					TypeBinding lowerBound1 = null;
					TypeBinding upperBound1 = null;
					WildcardBinding wildcard = (WildcardBinding) this;
					switch(wildcard.boundKind) {
						case Wildcard.EXTENDS :
							upperBound1 = wildcard.bound;
							break;
						case Wildcard. SUPER :
							lowerBound1 = wildcard.bound;
							break;
						case Wildcard.UNBOUND :
					}
				
					TypeBinding lowerBound2 = null;
					TypeBinding upperBound2 = null;
					WildcardBinding otherWildcard = (WildcardBinding) otherArgument;
					switch(otherWildcard.boundKind) {
						case Wildcard.EXTENDS :
							upperBound2 = otherWildcard.bound;
							break;
						case Wildcard. SUPER :
							lowerBound2 = otherWildcard.bound;
							break;
						case Wildcard.UNBOUND :
					}
					if (lowerBound1 != null) {
						if (lowerBound2 != null) {
							return true; // Object could always be a candidate
							
						} else if (upperBound2 != null) {
							return lowerBound1.isCompatibleWith(upperBound2);
						} else {
							return true;
						}
					} else if (upperBound1 != null) {
						if (upperBound1.isTypeVariable()) return true;
						if (lowerBound2 != null) {
							return lowerBound2.isCompatibleWith(upperBound1);
				
						} else if (upperBound2 != null) {
							if (upperBound1.isInterface()) {
								if (upperBound2.isInterface())
									return true;
								if (upperBound2.isArrayType() || ((upperBound2 instanceof ReferenceBinding) && ((ReferenceBinding)upperBound2).isFinal())) {
									return upperBound2.isCompatibleWith(upperBound1);
								}
								return true;
							} else {
								if (upperBound2.isInterface()) {
									if (upperBound1.isArrayType() || ((upperBound1 instanceof ReferenceBinding) && ((ReferenceBinding)upperBound1).isFinal())) {
										return upperBound1.isCompatibleWith(upperBound2);
									}
								} else {
									return upperBound1.isCompatibleWith(upperBound2);									
								}
							}
							return true;
						} else {
							return true;
						}
					} else {
						return true;
					}
					
				// WILDCARD & OTHER TYPE
				default :
					 wildcard = (WildcardBinding) this;
					switch(wildcard.boundKind) {
						case Wildcard.EXTENDS :
							return otherArgument.isCompatibleWith(wildcard.bound);
						case Wildcard. SUPER :
							return wildcard.bound.isCompatibleWith(otherArgument);
						case Wildcard.UNBOUND :
						default:
							return true;
					}
			}
			
		default:
			switch (otherArgument.kind()) {

				// OTHER TYPE & TYPE_PARAM
				case Binding.TYPE_PARAMETER :
					return true;

				// OTHER TYPE & WILDCARD
				case Binding.WILDCARD_TYPE :
					WildcardBinding otherWildcard = (WildcardBinding) otherArgument;
					switch(otherWildcard.boundKind) {
						case Wildcard.EXTENDS :
							return this.isCompatibleWith(otherWildcard.bound);
						case Wildcard. SUPER :
							return otherWildcard.bound.isCompatibleWith(this);
						case Wildcard.UNBOUND :
						default:
							return true;
					}					

				// OTHER TYPE & OTHER TYPE
				default :
					return false;
			}
	}
}

/**
 * Returns true if the type was declared as a type variable
 */
public boolean isTypeVariable() {
    return false;
}

/**
 * Returns true if wildcard type of the form '?' (no bound)
 */
public boolean isUnboundWildcard() {
	return false;
}

/**
 * Returns true if the type is a subclass of java.lang.Error or java.lang.RuntimeException
 */
public boolean isUncheckedException(boolean includeSupertype) {
	return false;
}

/**
 * Returns true if the type is a wildcard
 */
public boolean isWildcard() {
    return false;
}
	
/**
 * Meant to be invoked on compatible types, to figure if unchecked conversion is necessary
 */
public boolean needsUncheckedConversion(TypeBinding targetType) {

	if (this == targetType) return false;
	targetType = targetType.leafComponentType();
	if (!(targetType instanceof ReferenceBinding)) 
		return false;

	TypeBinding currentType = this.leafComponentType();
	if (!(currentType instanceof ReferenceBinding))
		return false;
	
	ReferenceBinding compatible = ((ReferenceBinding)currentType).findSuperTypeWithSameErasure(targetType);
	if (compatible == null) 
		return false;

	while (compatible.isRawType()) {
		if (targetType.isGenericType()) return true;
		if (targetType.isBoundParameterizedType()) return true;

		if (compatible.isStatic()) break;
		if ((compatible = compatible.enclosingType()) == null) break;
		if ((targetType = targetType.enclosingType()) == null) break;
	}
	return false;
}

public TypeBinding leafComponentType(){
	return this;
}

/**
 * Answer the qualified name of the receiver's package separated by periods
 * or an empty string if its the default package.
 *
 * For example, {java.util.Hashtable}.
 */

public char[] qualifiedPackageName() {
	PackageBinding packageBinding = getPackage();
	return packageBinding == null  || packageBinding.compoundName == CharOperation.NO_CHAR_CHAR
		? CharOperation.NO_CHAR
		: packageBinding.readableName();
}
/**
* Answer the source name for the type.
* In the case of member types, as the qualified name from its top level type.
* For example, for a member type N defined inside M & A: "A.M.N".
*/

public abstract char[] qualifiedSourceName();

/**
 * Answer the receiver classfile signature.
 * Arrays & base types do not distinguish between signature() & constantPoolName().
 * NOTE: This method should only be used during/after code gen.
 */
public char[] signature() {
	return constantPoolName();
}

public abstract char[] sourceName();

public void swapUnresolved(UnresolvedReferenceBinding unresolvedType, ReferenceBinding resolvedType, LookupEnvironment environment) {
	// subclasses must override if they wrap another type binding
}
public TypeVariableBinding[] typeVariables() {
	return NoTypeVariables;
}
}
