/*******************************************************************************
 * 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.util.HashtableOfPackage;
import net.sourceforge.phpdt.internal.compiler.util.HashtableOfType;

public class PackageBinding extends Binding implements TypeConstants {
	public char[][] compoundName;

	PackageBinding parent;

	LookupEnvironment environment;

	HashtableOfType knownTypes;

	HashtableOfPackage knownPackages;

	protected PackageBinding() {
	}

	public PackageBinding(char[][] compoundName, PackageBinding parent,
			LookupEnvironment environment) {
		this.compoundName = compoundName;
		this.parent = parent;
		this.environment = environment;
		this.knownTypes = null; // initialized if used... class counts can be
								// very large 300-600
		this.knownPackages = new HashtableOfPackage(3); // sub-package counts
														// are typically 0-3
	}

	public PackageBinding(char[] topLevelPackageName,
			LookupEnvironment environment) {
		this(new char[][] { topLevelPackageName }, null, environment);
	}

	/*
	 * Create the default package.
	 */

	public PackageBinding(LookupEnvironment environment) {
		this(CharOperation.NO_CHAR_CHAR, null, environment);
	}

	private void addNotFoundPackage(char[] simpleName) {
		knownPackages.put(simpleName, LookupEnvironment.TheNotFoundPackage);
	}

	private void addNotFoundType(char[] simpleName) {
		if (knownTypes == null)
			knownTypes = new HashtableOfType(25);
		knownTypes.put(simpleName, LookupEnvironment.TheNotFoundType);
	}

	void addPackage(PackageBinding element) {
		knownPackages.put(
				element.compoundName[element.compoundName.length - 1], element);
	}

	void addType(ReferenceBinding element) {
		if (knownTypes == null)
			knownTypes = new HashtableOfType(25);
		knownTypes.put(element.compoundName[element.compoundName.length - 1],
				element);
	}

	/*
	 * API Answer the receiver's binding type from Binding.BindingID.
	 */

	public final int bindingType() {
		return PACKAGE;
	}

	private PackageBinding findPackage(char[] name) {
		if (!environment.isPackage(this.compoundName, name))
			return null;

		char[][] compoundName = CharOperation.arrayConcat(this.compoundName,
				name);
		PackageBinding newPackageBinding = new PackageBinding(compoundName,
				this, environment);
		addPackage(newPackageBinding);
		return newPackageBinding;
	}

	/*
	 * Answer the subpackage named name; ask the oracle for the package if its
	 * not in the cache. Answer null if it could not be resolved.
	 * 
	 * NOTE: This should only be used when we know there is NOT a type with the
	 * same name.
	 */

	PackageBinding getPackage(char[] name) {
		PackageBinding binding = getPackage0(name);
		if (binding != null) {
			if (binding == LookupEnvironment.TheNotFoundPackage)
				return null;
			else
				return binding;
		}
		if ((binding = findPackage(name)) != null)
			return binding;

		// not found so remember a problem package binding in the cache for
		// future lookups
		addNotFoundPackage(name);
		return null;
	}

	/*
	 * Answer the subpackage named name if it exists in the cache. Answer
	 * theNotFoundPackage if it could not be resolved the first time it was
	 * looked up, otherwise answer null.
	 * 
	 * NOTE: Senders must convert theNotFoundPackage into a real problem package
	 * if its to returned.
	 */

	PackageBinding getPackage0(char[] name) {
		return knownPackages.get(name);
	}

	/*
	 * Answer the type named name; ask the oracle for the type if its not in the
	 * cache. Answer a NotVisible problem type if the type is not visible from
	 * the invocationPackage. Answer null if it could not be resolved.
	 * 
	 * NOTE: This should only be used by source types/scopes which know there is
	 * NOT a package with the same name.
	 */

	ReferenceBinding getType(char[] name) {
		ReferenceBinding binding = getType0(name);
		if (binding == null) {
			if ((binding = environment.askForType(this, name)) == null) {
				// not found so remember a problem type binding in the cache for
				// future lookups
				addNotFoundType(name);
				return null;
			}
		}

		if (binding == LookupEnvironment.TheNotFoundType)
			return null;
		if (binding instanceof UnresolvedReferenceBinding)
			binding = ((UnresolvedReferenceBinding) binding)
					.resolve(environment);
		if (binding.isNestedType())
			return new ProblemReferenceBinding(name, InternalNameProvided);
		return binding;
	}

	/*
	 * Answer the type named name if it exists in the cache. Answer
	 * theNotFoundType if it could not be resolved the first time it was looked
	 * up, otherwise answer null.
	 * 
	 * NOTE: Senders must convert theNotFoundType into a real problem reference
	 * type if its to returned.
	 */

	ReferenceBinding getType0(char[] name) {
		if (knownTypes == null)
			return null;
		return knownTypes.get(name);
	}

	/*
	 * Answer the package or type named name; ask the oracle if it is not in the
	 * cache. Answer null if it could not be resolved.
	 * 
	 * When collisions exist between a type name & a package name, answer the
	 * package. Treat the type as if it does not exist... a problem was already
	 * reported when the type was defined.
	 * 
	 * NOTE: no visibility checks are performed. THIS SHOULD ONLY BE USED BY
	 * SOURCE TYPES/SCOPES.
	 */

	public Binding getTypeOrPackage(char[] name) {
		PackageBinding packageBinding = getPackage0(name);
		if (packageBinding != null
				&& packageBinding != LookupEnvironment.TheNotFoundPackage)
			return packageBinding;

		ReferenceBinding typeBinding = getType0(name);
		if (typeBinding != null
				&& typeBinding != LookupEnvironment.TheNotFoundType) {
			if (typeBinding instanceof UnresolvedReferenceBinding)
				typeBinding = ((UnresolvedReferenceBinding) typeBinding)
						.resolve(environment);
			if (typeBinding.isNestedType())
				return new ProblemReferenceBinding(name, InternalNameProvided);
			return typeBinding;
		}

		// always look for the name as a sub-package if its not a known type
		if (packageBinding == null
				&& (packageBinding = findPackage(name)) != null)
			return packageBinding;
		if (typeBinding == null) {
			// if no package was found, find the type named name relative to the
			// receiver
			if ((typeBinding = environment.askForType(this, name)) != null) {
				if (typeBinding.isNestedType())
					return new ProblemReferenceBinding(name,
							InternalNameProvided);
				return typeBinding;
			}

			// Since name could not be found, add problem bindings
			// to the collections so it will be reported as an error next time.
			addNotFoundPackage(name);
			addNotFoundType(name);
		} else {
			if (packageBinding == LookupEnvironment.TheNotFoundPackage)
				packageBinding = null;
			if (typeBinding == LookupEnvironment.TheNotFoundType)
				typeBinding = null;
		}

		if (packageBinding != null)
			return packageBinding;
		else
			return typeBinding;
	}

	public char[] readableName() /* java.lang */{
		return CharOperation.concatWith(compoundName, '.');
	}

	public String toString() {
		if (compoundName == CharOperation.NO_CHAR_CHAR)
			return "The Default Package"; //$NON-NLS-1$
		else
			return "package " + ((compoundName != null) ? CharOperation.toString(compoundName) : "UNNAMED"); //$NON-NLS-1$ //$NON-NLS-2$
	}
}
