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

import net.sourceforge.phpdt.core.compiler.CharOperation;
import net.sourceforge.phpdt.internal.compiler.ast.ConstructorDeclaration;
import net.sourceforge.phpdt.internal.compiler.env.IBinaryField;
import net.sourceforge.phpdt.internal.compiler.env.IBinaryMethod;
import net.sourceforge.phpdt.internal.compiler.env.IBinaryNestedType;
import net.sourceforge.phpdt.internal.compiler.env.IBinaryType;
import net.sourceforge.phpdt.internal.compiler.problem.AbortCompilation;

/*
 * Not all fields defined by this type 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.
 */

public final class BinaryTypeBinding extends ReferenceBinding {
	// all of these fields are ONLY guaranteed to be initialized if accessed
	// using their public accessor method
	private ReferenceBinding superclass;

	private ReferenceBinding enclosingType;

	private ReferenceBinding[] superInterfaces;

	private FieldBinding[] fields;

	private MethodBinding[] methods;

	private ReferenceBinding[] memberTypes;

	// For the link with the principle structure
	private LookupEnvironment environment;

	public BinaryTypeBinding(PackageBinding packageBinding,
			IBinaryType binaryType, LookupEnvironment environment) {
		this.compoundName = CharOperation.splitOn('/', binaryType.getName());
		// computeId();

		this.tagBits |= IsBinaryBinding;
		this.environment = environment;
		this.fPackage = packageBinding;
		this.fileName = binaryType.getFileName();

		// source name must be one name without "$".
		char[] possibleSourceName = this.compoundName[this.compoundName.length - 1];
		int start = CharOperation.lastIndexOf('$', possibleSourceName) + 1;
		if (start == 0) {
			this.sourceName = possibleSourceName;
		} else {
			this.sourceName = new char[possibleSourceName.length - start];
			System.arraycopy(possibleSourceName, start, this.sourceName, 0,
					this.sourceName.length);
		}

		this.modifiers = binaryType.getModifiers();
		if (binaryType.isInterface())
			this.modifiers |= AccInterface;
	}

	public FieldBinding[] availableFields() {
		FieldBinding[] availableFields = new FieldBinding[fields.length];
		int count = 0;

		for (int i = 0; i < fields.length; i++) {
			try {
				availableFields[count] = resolveTypeFor(fields[i]);
				count++;
			} catch (AbortCompilation a) {
			}
		}

		System.arraycopy(availableFields, 0,
				availableFields = new FieldBinding[count], 0, count);
		return availableFields;
	}

	public MethodBinding[] availableMethods() {
		if ((modifiers & AccUnresolved) == 0)
			return methods;

		MethodBinding[] availableMethods = new MethodBinding[methods.length];
		int count = 0;

		for (int i = 0; i < methods.length; i++) {
			try {
				availableMethods[count] = resolveTypesFor(methods[i]);
				count++;
			} catch (AbortCompilation a) {
			}
		}
		System.arraycopy(availableMethods, 0,
				availableMethods = new MethodBinding[count], 0, count);
		return availableMethods;
	}

	void cachePartsFrom(IBinaryType binaryType, boolean needFieldsAndMethods) {
		char[] superclassName = binaryType.getSuperclassName();
		if (superclassName != null)
			// attempt to find the superclass if it exists in the cache
			// (otherwise - resolve it when requested)
			this.superclass = environment.getTypeFromConstantPoolName(
					superclassName, 0, -1);

		char[] enclosingTypeName = binaryType.getEnclosingTypeName();
		if (enclosingTypeName != null) {
			// attempt to find the enclosing type if it exists in the cache
			// (otherwise - resolve it when requested)
			this.enclosingType = environment.getTypeFromConstantPoolName(
					enclosingTypeName, 0, -1);
			this.tagBits |= MemberTypeMask; // must be a member type not a
											// top-level or local type
			// if (this.enclosingType().isStrictfp())
			// this.modifiers |= AccStrictfp;
			if (this.enclosingType().isDeprecated())
				this.modifiers |= AccDeprecatedImplicitly;
		}

		this.memberTypes = NoMemberTypes;
		IBinaryNestedType[] memberTypeStructures = binaryType.getMemberTypes();
		if (memberTypeStructures != null) {
			int size = memberTypeStructures.length;
			if (size > 0) {
				this.memberTypes = new ReferenceBinding[size];
				for (int i = 0; i < size; i++)
					// attempt to find each member type if it exists in the
					// cache (otherwise - resolve it when requested)
					this.memberTypes[i] = environment
							.getTypeFromConstantPoolName(
									memberTypeStructures[i].getName(), 0, -1);
			}
		}

		this.superInterfaces = NoSuperInterfaces;
		char[][] interfaceNames = binaryType.getInterfaceNames();
		if (interfaceNames != null) {
			int size = interfaceNames.length;
			if (size > 0) {
				this.superInterfaces = new ReferenceBinding[size];
				for (int i = 0; i < size; i++)
					// attempt to find each superinterface if it exists in the
					// cache (otherwise - resolve it when requested)
					this.superInterfaces[i] = environment
							.getTypeFromConstantPoolName(interfaceNames[i], 0,
									-1);
			}
		}
		if (needFieldsAndMethods) {
			createFields(binaryType.getFields());
			createMethods(binaryType.getMethods());
		}
	}

	private void createFields(IBinaryField[] iFields) {
		this.fields = NoFields;
		if (iFields != null) {
			int size = iFields.length;
			if (size > 0) {
				this.fields = new FieldBinding[size];
				for (int i = 0; i < size; i++) {
					IBinaryField field = iFields[i];
					this.fields[i] = new FieldBinding(field.getName(),
							environment.getTypeFromSignature(field
									.getTypeName(), 0, -1), field
									.getModifiers(), this, field.getConstant());
				}
			}
		}
	}

	private MethodBinding createMethod(IBinaryMethod method) {
		int modifiers = method.getModifiers() | AccUnresolved;

		ReferenceBinding[] exceptions = NoExceptions;
		char[][] exceptionTypes = method.getExceptionTypeNames();
		if (exceptionTypes != null) {
			int size = exceptionTypes.length;
			if (size > 0) {
				exceptions = new ReferenceBinding[size];
				for (int i = 0; i < size; i++)
					exceptions[i] = environment.getTypeFromConstantPoolName(
							exceptionTypes[i], 0, -1);
			}
		}

		TypeBinding[] parameters = NoParameters;
		char[] signature = method.getMethodDescriptor(); // of the form
															// (I[Ljava/jang/String;)V
		int numOfParams = 0;
		char nextChar;
		int index = 0; // first character is always '(' so skip it
		while ((nextChar = signature[++index]) != ')') {
			if (nextChar != '[') {
				numOfParams++;
				if (nextChar == 'L')
					while ((nextChar = signature[++index]) != ';')
						;
			}
		}

		// Ignore synthetic argument for member types.
		int startIndex = (method.isConstructor() && isMemberType() && !isStatic()) ? 1
				: 0;
		int size = numOfParams - startIndex;
		if (size > 0) {
			parameters = new TypeBinding[size];
			index = 1;
			int end = 0; // first character is always '(' so skip it
			for (int i = 0; i < numOfParams; i++) {
				while ((nextChar = signature[++end]) == '[')
					;
				if (nextChar == 'L')
					while ((nextChar = signature[++end]) != ';')
						;

				if (i >= startIndex) // skip the synthetic arg if necessary
					parameters[i - startIndex] = environment
							.getTypeFromSignature(signature, index, end);
				index = end + 1;
			}
		}

		MethodBinding binding = null;
		if (method.isConstructor())
			binding = new MethodBinding(modifiers, parameters, exceptions, this);
		else
			binding = new MethodBinding(modifiers, method.getSelector(),
					environment.getTypeFromSignature(signature, index + 1, -1), // index
																				// is
																				// currently
																				// pointing
																				// at
																				// the
																				// ')'
					parameters, exceptions, this);
		return binding;
	}

	/**
	 * Create method bindings for binary type, filtering out <clinit> and
	 * synthetics
	 */
	private void createMethods(IBinaryMethod[] iMethods) {
		int total = 0, initialTotal = 0, iClinit = -1;
		int[] toSkip = null;
		if (iMethods != null) {
			total = initialTotal = iMethods.length;
			for (int i = total; --i >= 0;) {
				IBinaryMethod method = iMethods[i];
				// if ((method.getModifiers() & AccSynthetic) != 0) {
				// // discard synthetics methods
				// if (toSkip == null) toSkip = new int[iMethods.length];
				// toSkip[i] = -1;
				// total--;
				// } else
				if (iClinit == -1) {
					char[] methodName = method.getSelector();
					if (methodName.length == 8 && methodName[0] == '<') {
						// discard <clinit>
						iClinit = i;
						total--;
					}
				}
			}
		}
		if (total == 0) {
			this.methods = NoMethods;
			return;
		}

		this.methods = new MethodBinding[total];
		if (total == initialTotal) {
			for (int i = 0; i < initialTotal; i++)
				this.methods[i] = createMethod(iMethods[i]);
		} else {
			for (int i = 0, index = 0; i < initialTotal; i++)
				if (iClinit != i && (toSkip == null || toSkip[i] != -1))
					this.methods[index++] = createMethod(iMethods[i]);
		}
		modifiers |= AccUnresolved; // until methods() is sent
	}

	/*
	 * Answer the receiver's enclosing type... null if the receiver is a top
	 * level type.
	 * 
	 * NOTE: enclosingType of a binary type is resolved when needed
	 */

	public ReferenceBinding enclosingType() {
		if (enclosingType == null)
			return null;
		if (enclosingType instanceof UnresolvedReferenceBinding)
			enclosingType = ((UnresolvedReferenceBinding) enclosingType)
					.resolve(environment);
		return enclosingType;
	}

	// NOTE: the type of each field of a binary type is resolved when needed

	public FieldBinding[] fields() {
		for (int i = fields.length; --i >= 0;)
			resolveTypeFor(fields[i]);
		return fields;
	}

	// NOTE: the return type, arg & exception types of each method of a binary
	// type are resolved when needed

	public MethodBinding getExactConstructor(TypeBinding[] argumentTypes) {
		int argCount = argumentTypes.length;
		nextMethod: for (int m = methods.length; --m >= 0;) {
			MethodBinding method = methods[m];
			if (method.selector == ConstructorDeclaration.ConstantPoolName
					&& method.parameters.length == argCount) {
				resolveTypesFor(method);
				TypeBinding[] toMatch = method.parameters;
				for (int p = 0; p < argCount; p++)
					if (toMatch[p] != argumentTypes[p])
						continue nextMethod;
				return method;
			}
		}
		return null;
	}

	// NOTE: the return type, arg & exception types of each method of a binary
	// type are resolved when needed
	// searches up the hierarchy as long as no potential (but not exact) match
	// was found.

	public MethodBinding getExactMethod(char[] selector,
			TypeBinding[] argumentTypes) {
		int argCount = argumentTypes.length;
		int selectorLength = selector.length;
		boolean foundNothing = true;
		nextMethod: for (int m = methods.length; --m >= 0;) {
			MethodBinding method = methods[m];
			if (method.selector.length == selectorLength
					&& CharOperation.prefixEquals(method.selector, selector)) {
				foundNothing = false; // inner type lookups must know that a
										// method with this name exists
				if (method.parameters.length == argCount) {
					resolveTypesFor(method);
					TypeBinding[] toMatch = method.parameters;
					for (int p = 0; p < argCount; p++)
						if (toMatch[p] != argumentTypes[p])
							continue nextMethod;
					return method;
				}
			}
		}

		if (foundNothing) {
			if (isInterface()) {
				if (superInterfaces.length == 1)
					return superInterfaces[0].getExactMethod(selector,
							argumentTypes);
			} else if (superclass != null) {
				return superclass.getExactMethod(selector, argumentTypes);
			}
		}
		return null;
	}

	// NOTE: the type of a field of a binary type is resolved when needed

	public FieldBinding getField(char[] fieldName) {
		int fieldLength = fieldName.length;
		for (int f = fields.length; --f >= 0;) {
			char[] name = fields[f].name;
			if (name.length == fieldLength
					&& CharOperation.prefixEquals(name, fieldName))
				return resolveTypeFor(fields[f]);
		}
		return null;
	}

	// NOTE: the return type, arg & exception types of each method of a binary
	// type are resolved when needed

	public MethodBinding[] getMethods(char[] selector) {
		int count = 0;
		int lastIndex = -1;
		int selectorLength = selector.length;
		for (int m = 0, length = methods.length; m < length; m++) {
			MethodBinding method = methods[m];
			if (method.selector.length == selectorLength
					&& CharOperation.prefixEquals(method.selector, selector)) {
				resolveTypesFor(method);
				count++;
				lastIndex = m;
			}
		}
		if (count == 1)
			return new MethodBinding[] { methods[lastIndex] };
		if (count > 0) {
			MethodBinding[] result = new MethodBinding[count];
			count = 0;
			for (int m = 0; m <= lastIndex; m++) {
				MethodBinding method = methods[m];
				if (method.selector.length == selectorLength
						&& CharOperation
								.prefixEquals(method.selector, selector))
					result[count++] = method;
			}
			return result;
		}
		return NoMethods;
	}

	// NOTE: member types of binary types are resolved when needed

	public ReferenceBinding[] memberTypes() {
		for (int i = memberTypes.length; --i >= 0;)
			if (memberTypes[i] instanceof UnresolvedReferenceBinding)
				memberTypes[i] = ((UnresolvedReferenceBinding) memberTypes[i])
						.resolve(environment);
		return memberTypes;
	}

	// NOTE: the return type, arg & exception types of each method of a binary
	// type are resolved when needed

	public MethodBinding[] methods() {
		if ((modifiers & AccUnresolved) == 0)
			return methods;

		for (int i = methods.length; --i >= 0;)
			resolveTypesFor(methods[i]);
		modifiers ^= AccUnresolved;
		return methods;
	}

	private TypeBinding resolveType(TypeBinding type) {
		if (type instanceof UnresolvedReferenceBinding)
			return ((UnresolvedReferenceBinding) type).resolve(environment);
		if (type instanceof ArrayBinding) {
			ArrayBinding array = (ArrayBinding) type;
			if (array.leafComponentType instanceof UnresolvedReferenceBinding)
				array.leafComponentType = ((UnresolvedReferenceBinding) array.leafComponentType)
						.resolve(environment);
		}
		return type;
	}

	private FieldBinding resolveTypeFor(FieldBinding field) {
		field.type = resolveType(field.type);
		return field;
	}

	private MethodBinding resolveTypesFor(MethodBinding method) {
		if ((method.modifiers & AccUnresolved) == 0)
			return method;

		if (!method.isConstructor())
			method.returnType = resolveType(method.returnType);
		for (int i = method.parameters.length; --i >= 0;)
			method.parameters[i] = resolveType(method.parameters[i]);
		for (int i = method.thrownExceptions.length; --i >= 0;)
			if (method.thrownExceptions[i] instanceof UnresolvedReferenceBinding)
				method.thrownExceptions[i] = ((UnresolvedReferenceBinding) method.thrownExceptions[i])
						.resolve(environment);
		method.modifiers ^= AccUnresolved;
		return method;
	}

	/*
	 * Answer the receiver's superclass... null if the receiver is Object or an
	 * interface.
	 * 
	 * NOTE: superclass of a binary type is resolved when needed
	 */

	public ReferenceBinding superclass() {
		if (superclass == null)
			return null;
		if (superclass instanceof UnresolvedReferenceBinding)
			superclass = ((UnresolvedReferenceBinding) superclass)
					.resolve(environment);
		return superclass;
	}

	// NOTE: superInterfaces of binary types are resolved when needed

	public ReferenceBinding[] superInterfaces() {
		for (int i = superInterfaces.length; --i >= 0;)
			if (superInterfaces[i] instanceof UnresolvedReferenceBinding)
				superInterfaces[i] = ((UnresolvedReferenceBinding) superInterfaces[i])
						.resolve(environment);
		return superInterfaces;
	}

	public String toString() {
		String s = ""; //$NON-NLS-1$

		if (isDeprecated())
			s += "deprecated "; //$NON-NLS-1$
		if (isPublic())
			s += "public "; //$NON-NLS-1$
		if (isProtected())
			s += "protected "; //$NON-NLS-1$
		if (isPrivate())
			s += "private "; //$NON-NLS-1$
		if (isAbstract() && isClass())
			s += "abstract "; //$NON-NLS-1$
		if (isStatic() && isNestedType())
			s += "static "; //$NON-NLS-1$
		if (isFinal())
			s += "final "; //$NON-NLS-1$

		s += isInterface() ? "interface " : "class "; //$NON-NLS-1$ //$NON-NLS-2$
		s += (compoundName != null) ? CharOperation.toString(compoundName)
				: "UNNAMED TYPE"; //$NON-NLS-1$

		s += "\n\textends "; //$NON-NLS-1$
		s += (superclass != null) ? superclass.debugName() : "NULL TYPE"; //$NON-NLS-1$

		if (superInterfaces != null) {
			if (superInterfaces != NoSuperInterfaces) {
				s += "\n\timplements : "; //$NON-NLS-1$
				for (int i = 0, length = superInterfaces.length; i < length; i++) {
					if (i > 0)
						s += ", "; //$NON-NLS-1$
					s += (superInterfaces[i] != null) ? superInterfaces[i]
							.debugName() : "NULL TYPE"; //$NON-NLS-1$
				}
			}
		} else {
			s += "NULL SUPERINTERFACES"; //$NON-NLS-1$
		}

		if (enclosingType != null) {
			s += "\n\tenclosing type : "; //$NON-NLS-1$
			s += enclosingType.debugName();
		}

		if (fields != null) {
			if (fields != NoFields) {
				s += "\n/*   fields   */"; //$NON-NLS-1$
				for (int i = 0, length = fields.length; i < length; i++)
					s += (fields[i] != null) ? "\n" + fields[i].toString() : "\nNULL FIELD"; //$NON-NLS-1$ //$NON-NLS-2$
			}
		} else {
			s += "NULL FIELDS"; //$NON-NLS-1$
		}

		if (methods != null) {
			if (methods != NoMethods) {
				s += "\n/*   methods   */"; //$NON-NLS-1$
				for (int i = 0, length = methods.length; i < length; i++)
					s += (methods[i] != null) ? "\n" + methods[i].toString() : "\nNULL METHOD"; //$NON-NLS-1$ //$NON-NLS-2$
			}
		} else {
			s += "NULL METHODS"; //$NON-NLS-1$
		}

		if (memberTypes != null) {
			if (memberTypes != NoMemberTypes) {
				s += "\n/*   members   */"; //$NON-NLS-1$
				for (int i = 0, length = memberTypes.length; i < length; i++)
					s += (memberTypes[i] != null) ? "\n" + memberTypes[i].toString() : "\nNULL TYPE"; //$NON-NLS-1$ //$NON-NLS-2$
			}
		} else {
			s += "NULL MEMBER TYPES"; //$NON-NLS-1$
		}

		s += "\n\n\n"; //$NON-NLS-1$
		return s;
	}
}
