/*******************************************************************************
 * 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;

public class NestedTypeBinding extends SourceTypeBinding {

	public SourceTypeBinding enclosingType;

	public SyntheticArgumentBinding[] enclosingInstances;

	public SyntheticArgumentBinding[] outerLocalVariables;

	public int enclosingInstancesSlotSize; // amount of slots used by synthetic
											// enclosing instances

	public int outerLocalVariablesSlotSize; // amount of slots used by synthetic
											// outer local variables

	public NestedTypeBinding(char[][] typeName, ClassScope scope,
			SourceTypeBinding enclosingType) {
		super(typeName, enclosingType.fPackage, scope);
		this.tagBits |= IsNestedType;
		this.enclosingType = enclosingType;
	}

	/*
	 * Add a new synthetic argument for <actualOuterLocalVariable>. Answer the
	 * new argument or the existing argument if one already existed.
	 */
	public SyntheticArgumentBinding addSyntheticArgument(
			LocalVariableBinding actualOuterLocalVariable) {
		SyntheticArgumentBinding synthLocal = null;

		if (outerLocalVariables == null) {
			synthLocal = new SyntheticArgumentBinding(actualOuterLocalVariable);
			outerLocalVariables = new SyntheticArgumentBinding[] { synthLocal };
		} else {
			int size = outerLocalVariables.length;
			int newArgIndex = size;
			for (int i = size; --i >= 0;) { // must search backwards
				if (outerLocalVariables[i].actualOuterLocalVariable == actualOuterLocalVariable)
					return outerLocalVariables[i]; // already exists
				if (outerLocalVariables[i].id > actualOuterLocalVariable.id)
					newArgIndex = i;
			}
			SyntheticArgumentBinding[] synthLocals = new SyntheticArgumentBinding[size + 1];
			System.arraycopy(outerLocalVariables, 0, synthLocals, 0,
					newArgIndex);
			synthLocals[newArgIndex] = synthLocal = new SyntheticArgumentBinding(
					actualOuterLocalVariable);
			System.arraycopy(outerLocalVariables, newArgIndex, synthLocals,
					newArgIndex + 1, size - newArgIndex);
			outerLocalVariables = synthLocals;
		}
		// System.out.println("Adding synth arg for local var: " + new
		// String(actualOuterLocalVariable.name) + " to: " + new
		// String(this.readableName()));
		if (scope.referenceCompilationUnit().isPropagatingInnerClassEmulation)
			this.updateInnerEmulationDependents();
		return synthLocal;
	}

	/*
	 * Add a new synthetic argument for <enclosingType>. Answer the new argument
	 * or the existing argument if one already existed.
	 */
	public SyntheticArgumentBinding addSyntheticArgument(
			ReferenceBinding enclosingType) {
		SyntheticArgumentBinding synthLocal = null;
		if (enclosingInstances == null) {
			synthLocal = new SyntheticArgumentBinding(enclosingType);
			enclosingInstances = new SyntheticArgumentBinding[] { synthLocal };
		} else {
			int size = enclosingInstances.length;
			int newArgIndex = size;
			for (int i = size; --i >= 0;) {
				if (enclosingInstances[i].type == enclosingType)
					return enclosingInstances[i]; // already exists
				if (this.enclosingType() == enclosingType)
					newArgIndex = 0;
			}
			SyntheticArgumentBinding[] newInstances = new SyntheticArgumentBinding[size + 1];
			System.arraycopy(enclosingInstances, 0, newInstances,
					newArgIndex == 0 ? 1 : 0, size);
			newInstances[newArgIndex] = synthLocal = new SyntheticArgumentBinding(
					enclosingType);
			enclosingInstances = newInstances;
		}
		// System.out.println("Adding synth arg for enclosing type: " + new
		// String(enclosingType.readableName()) + " to: " + new
		// String(this.readableName()));
		if (scope.referenceCompilationUnit().isPropagatingInnerClassEmulation)
			this.updateInnerEmulationDependents();
		return synthLocal;
	}

	/*
	 * Add a new synthetic argument and field for <actualOuterLocalVariable>.
	 * Answer the new argument or the existing argument if one already existed.
	 */
	public SyntheticArgumentBinding addSyntheticArgumentAndField(
			LocalVariableBinding actualOuterLocalVariable) {
		SyntheticArgumentBinding synthLocal = addSyntheticArgument(actualOuterLocalVariable);
		if (synthLocal == null)
			return null;

		if (synthLocal.matchingField == null)
			synthLocal.matchingField = addSyntheticField(actualOuterLocalVariable);
		return synthLocal;
	}

	/*
	 * Add a new synthetic argument and field for <enclosingType>. Answer the
	 * new argument or the existing argument if one already existed.
	 */
	public SyntheticArgumentBinding addSyntheticArgumentAndField(
			ReferenceBinding enclosingType) {
		SyntheticArgumentBinding synthLocal = addSyntheticArgument(enclosingType);
		if (synthLocal == null)
			return null;

		if (synthLocal.matchingField == null)
			synthLocal.matchingField = addSyntheticField(enclosingType);
		return synthLocal;
	}

	/**
	 * Compute the resolved positions for all the synthetic arguments
	 */
	final public void computeSyntheticArgumentSlotSizes() {

		int slotSize = 0;
		// insert enclosing instances first, followed by the outerLocals
		SyntheticArgumentBinding[] enclosingInstances = this
				.syntheticEnclosingInstances();
		int enclosingInstancesCount = enclosingInstances == null ? 0
				: enclosingInstances.length;
		for (int i = 0; i < enclosingInstancesCount; i++) {
			SyntheticArgumentBinding argument = enclosingInstances[i];
			// position the enclosing instance synthetic arg
			argument.resolvedPosition = slotSize + 1; // shift by 1 to leave
														// room for aload0==this
			if (slotSize + 1 > 0xFF) { // no more than 255 words of arguments
				this.scope.problemReporter().noMoreAvailableSpaceForArgument(
						argument, this.scope.referenceType());
			}
			if ((argument.type == LongBinding)
					|| (argument.type == DoubleBinding)) {
				slotSize += 2;
			} else {
				slotSize++;
			}
		}
		this.enclosingInstancesSlotSize = slotSize;

		slotSize = 0; // reset, outer local are not positionned yet, since
						// will be appended to user arguments
		SyntheticArgumentBinding[] outerLocals = this
				.syntheticOuterLocalVariables();
		int outerLocalsCount = outerLocals == null ? 0 : outerLocals.length;
		for (int i = 0; i < outerLocalsCount; i++) {
			SyntheticArgumentBinding argument = outerLocals[i];
			// do NOT position the outerlocal synthetic arg yet, since will be
			// appended to user arguments
			if ((argument.type == LongBinding)
					|| (argument.type == DoubleBinding)) {
				slotSize += 2;
			} else {
				slotSize++;
			}
		}
		this.outerLocalVariablesSlotSize = slotSize;
	}

	/*
	 * Answer the receiver's enclosing type... null if the receiver is a top
	 * level type.
	 */
	public ReferenceBinding enclosingType() {

		return enclosingType;
	}

	/*
	 * Answer the synthetic argument for <actualOuterLocalVariable> or null if
	 * one does not exist.
	 */
	public SyntheticArgumentBinding getSyntheticArgument(
			LocalVariableBinding actualOuterLocalVariable) {

		if (outerLocalVariables == null)
			return null; // is null if no outer local variables are known

		for (int i = outerLocalVariables.length; --i >= 0;)
			if (outerLocalVariables[i].actualOuterLocalVariable == actualOuterLocalVariable)
				return outerLocalVariables[i];
		return null;
	}

	public SyntheticArgumentBinding[] syntheticEnclosingInstances() {
		return enclosingInstances; // is null if no enclosing instances are
									// required
	}

	public ReferenceBinding[] syntheticEnclosingInstanceTypes() {
		if (enclosingInstances == null)
			return null;

		int length = enclosingInstances.length;
		ReferenceBinding types[] = new ReferenceBinding[length];
		for (int i = 0; i < length; i++)
			types[i] = (ReferenceBinding) enclosingInstances[i].type;
		return types;
	}

	public SyntheticArgumentBinding[] syntheticOuterLocalVariables() {

		return outerLocalVariables; // is null if no outer locals are required
	}

	/*
	 * Trigger the dependency mechanism forcing the innerclass emulation to be
	 * propagated to all dependent source types.
	 */
	public void updateInnerEmulationDependents() {
		// nothing to do in general, only local types are doing anything
	}

	/*
	 * Answer the synthetic argument for <targetEnclosingType> or null if one
	 * does not exist.
	 */
	public SyntheticArgumentBinding getSyntheticArgument(
			ReferenceBinding targetEnclosingType, boolean onlyExactMatch) {

		if (enclosingInstances == null)
			return null; // is null if no enclosing instances are known

		// exact match
		for (int i = enclosingInstances.length; --i >= 0;)
			if (enclosingInstances[i].type == targetEnclosingType)
				if (enclosingInstances[i].actualOuterLocalVariable == null)
					return enclosingInstances[i];

		// type compatibility : to handle cases such as
		// class T { class M{}}
		// class S extends T { class N extends M {}} --> need to use S as a
		// default enclosing instance for the super constructor call in N().
		if (!onlyExactMatch) {
			for (int i = enclosingInstances.length; --i >= 0;)
				if (enclosingInstances[i].actualOuterLocalVariable == null)
					if (targetEnclosingType
							.isSuperclassOf((ReferenceBinding) enclosingInstances[i].type))
						return enclosingInstances[i];
		}
		return null;
	}
}
