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

import net.sourceforge.phpdt.internal.compiler.ASTVisitor;
import net.sourceforge.phpdt.internal.compiler.flow.FlowContext;
import net.sourceforge.phpdt.internal.compiler.flow.FlowInfo;
import net.sourceforge.phpdt.internal.compiler.lookup.BlockScope;
import net.sourceforge.phpdt.internal.compiler.lookup.InvocationSite;
import net.sourceforge.phpdt.internal.compiler.lookup.LocalTypeBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.MethodBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.MethodScope;
import net.sourceforge.phpdt.internal.compiler.lookup.ReferenceBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.SourceTypeBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.TypeBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.VariableBinding;

public class ExplicitConstructorCall extends Statement implements
		InvocationSite {

	public Expression[] arguments;

	public Expression qualification;

	public MethodBinding binding;

	public int accessMode;

	public final static int ImplicitSuper = 1;

	public final static int Super = 2;

	public final static int This = 3;

	public VariableBinding[][] implicitArguments;

	boolean discardEnclosingInstance;

	MethodBinding syntheticAccessor;

	public ExplicitConstructorCall(int accessMode) {
		this.accessMode = accessMode;
	}

	public FlowInfo analyseCode(BlockScope currentScope,
			FlowContext flowContext, FlowInfo flowInfo) {

		// must verify that exceptions potentially thrown by this expression are
		// caught in the method.

		try {
			((MethodScope) currentScope).isConstructorCall = true;

			// process enclosing instance
			if (qualification != null) {
				flowInfo = qualification.analyseCode(currentScope, flowContext,
						flowInfo).unconditionalInits();
			}
			// process arguments
			if (arguments != null) {
				for (int i = 0, max = arguments.length; i < max; i++) {
					flowInfo = arguments[i].analyseCode(currentScope,
							flowContext, flowInfo).unconditionalInits();
				}
			}

			ReferenceBinding[] thrownExceptions;
			if ((thrownExceptions = binding.thrownExceptions) != NoExceptions) {
				// check exceptions
				flowContext.checkExceptionHandlers(thrownExceptions,
						(accessMode == ImplicitSuper) ? (ASTNode) currentScope
								.methodScope().referenceContext
								: (ASTNode) this, flowInfo, currentScope);
			}
			manageEnclosingInstanceAccessIfNecessary(currentScope);
			manageSyntheticAccessIfNecessary(currentScope);
			return flowInfo;
		} finally {
			((MethodScope) currentScope).isConstructorCall = false;
		}
	}

	/**
	 * Constructor call code generation
	 * 
	 * @param currentScope
	 *            net.sourceforge.phpdt.internal.compiler.lookup.BlockScope
	 * @param codeStream
	 *            net.sourceforge.phpdt.internal.compiler.codegen.CodeStream
	 */
	// public void generateCode(BlockScope currentScope, CodeStream codeStream)
	// {
	//
	// if ((bits & IsReachableMASK) == 0) {
	// return;
	// }
	// try {
	// ((MethodScope) currentScope).isConstructorCall = true;
	//
	// int pc = codeStream.position;
	// codeStream.aload_0();
	//
	// // handling innerclass constructor invocation
	// ReferenceBinding targetType = binding.declaringClass;
	// // handling innerclass instance allocation - enclosing instance arguments
	// if (targetType.isNestedType()) {
	// codeStream.generateSyntheticEnclosingInstanceValues(
	// currentScope,
	// targetType,
	// discardEnclosingInstance ? null : qualification,
	// this);
	// }
	// // regular code gen
	// if (arguments != null) {
	// for (int i = 0, max = arguments.length; i < max; i++) {
	// arguments[i].generateCode(currentScope, codeStream, true);
	// }
	// }
	// // handling innerclass instance allocation - outer local arguments
	// if (targetType.isNestedType()) {
	// codeStream.generateSyntheticOuterArgumentValues(
	// currentScope,
	// targetType,
	// this);
	// }
	// if (syntheticAccessor != null) {
	// // synthetic accessor got some extra arguments appended to its signature,
	// which need values
	// for (int i = 0,
	// max = syntheticAccessor.parameters.length - binding.parameters.length;
	// i < max;
	// i++) {
	// codeStream.aconst_null();
	// }
	// codeStream.invokespecial(syntheticAccessor);
	// } else {
	// codeStream.invokespecial(binding);
	// }
	// codeStream.recordPositionsFrom(pc, this.sourceStart);
	// } finally {
	// ((MethodScope) currentScope).isConstructorCall = false;
	// }
	// }
	public boolean isImplicitSuper() {
		// return true if I'm of these compiler added statement super();

		return (accessMode == ImplicitSuper);
	}

	public boolean isSuperAccess() {

		return accessMode != This;
	}

	public boolean isTypeAccess() {

		return true;
	}

	/*
	 * Inner emulation consists in either recording a dependency link only, or
	 * performing one level of propagation.
	 * 
	 * Dependency mechanism is used whenever dealing with source target types,
	 * since by the time we reach them, we might not yet know their exact need.
	 */
	void manageEnclosingInstanceAccessIfNecessary(BlockScope currentScope) {
		ReferenceBinding superType;

		// perform some emulation work in case there is some and we are inside a
		// local type only
		if ((superType = binding.declaringClass).isNestedType()
				&& currentScope.enclosingSourceType().isLocalType()) {

			if (superType.isLocalType()) {
				((LocalTypeBinding) superType).addInnerEmulationDependent(
						currentScope, qualification != null);
			} else {
				// locally propagate, since we already now the desired shape for
				// sure
				currentScope.propagateInnerEmulation(superType,
						qualification != null);
			}
		}
	}

	public void manageSyntheticAccessIfNecessary(BlockScope currentScope) {

		// perform some emulation work in case there is some and we are inside a
		// local type only
		if (binding.isPrivate() && (accessMode != This)) {

			// if (currentScope
			// .environment()
			// .options
			// .isPrivateConstructorAccessChangingVisibility) {
			// binding.tagForClearingPrivateModifier();
			// // constructor will not be dumped as private, no emulation
			// required thus
			// } else {
			syntheticAccessor = ((SourceTypeBinding) binding.declaringClass)
					.addSyntheticMethod(binding, isSuperAccess());
			currentScope.problemReporter().needToEmulateMethodAccess(binding,
					this);
			// }
		}
	}

	public void resolve(BlockScope scope) {
		// the return type should be void for a constructor.
		// the test is made into getConstructor

		// mark the fact that we are in a constructor call.....
		// unmark at all returns
		try {
			((MethodScope) scope).isConstructorCall = true;
			ReferenceBinding receiverType = scope.enclosingSourceType();
			if (accessMode != This)
				receiverType = receiverType.superclass();

			if (receiverType == null) {
				return;
			}

			// qualification should be from the type of the enclosingType
			if (qualification != null) {
				if (accessMode != Super) {
					scope.problemReporter()
							.unnecessaryEnclosingInstanceSpecification(
									qualification, receiverType);
				}
				ReferenceBinding enclosingType = receiverType.enclosingType();
				if (enclosingType == null) {
					scope.problemReporter()
							.unnecessaryEnclosingInstanceSpecification(
									qualification, receiverType);
					discardEnclosingInstance = true;
				} else {
					TypeBinding qTb = qualification.resolveTypeExpecting(scope,
							enclosingType);
					qualification.implicitWidening(qTb, qTb);
				}
			}

			// arguments buffering for the method lookup
			TypeBinding[] argTypes = NoParameters;
			if (arguments != null) {
				boolean argHasError = false; // typeChecks all arguments
				int length = arguments.length;
				argTypes = new TypeBinding[length];
				for (int i = 0; i < length; i++)
					if ((argTypes[i] = arguments[i].resolveType(scope)) == null)
						argHasError = true;
				if (argHasError)
					return;
			}
			if ((binding = scope.getConstructor(receiverType, argTypes, this))
					.isValidBinding()) {
				if (isMethodUseDeprecated(binding, scope))
					scope.problemReporter().deprecatedMethod(binding, this);

				// see for user-implicit widening conversion
				if (arguments != null) {
					int length = arguments.length;
					TypeBinding[] paramTypes = binding.parameters;
					for (int i = 0; i < length; i++)
						arguments[i].implicitWidening(paramTypes[i],
								argTypes[i]);
				}
				if (binding.isPrivate()) {
					binding.modifiers |= AccPrivateUsed;
				}
			} else {
				if (binding.declaringClass == null)
					binding.declaringClass = receiverType;
				scope.problemReporter().invalidConstructor(this, binding);
			}
		} finally {
			((MethodScope) scope).isConstructorCall = false;
		}
	}

	public void setActualReceiverType(ReferenceBinding receiverType) {
		// ignored
	}

	public void setDepth(int depth) {
		// ignore for here
	}

	public void setFieldIndex(int depth) {
		// ignore for here
	}

	public StringBuffer printStatement(int indent, StringBuffer output) {

		printIndent(indent, output);
		if (qualification != null)
			qualification.printExpression(0, output).append('.');
		if (accessMode == This) {
			output.append("this("); //$NON-NLS-1$
		} else {
			output.append("super("); //$NON-NLS-1$
		}
		if (arguments != null) {
			for (int i = 0; i < arguments.length; i++) {
				if (i > 0)
					output.append(", "); //$NON-NLS-1$
				arguments[i].printExpression(0, output);
			}
		}
		return output.append(");"); //$NON-NLS-1$
	}

	public String toString(int tab) {

		String s = tabString(tab);
		if (qualification != null)
			s = s + qualification.toStringExpression() + "."; //$NON-NLS-1$
		if (accessMode == This) {
			s = s + "this("; //$NON-NLS-1$
		} else {
			s = s + "super("; //$NON-NLS-1$
		}
		if (arguments != null)
			for (int i = 0; i < arguments.length; i++) {
				s = s + arguments[i].toStringExpression();
				if (i != arguments.length - 1)
					s = s + ", "; //$NON-NLS-1$
			}
		s = s + ")"; //$NON-NLS-1$
		return s;
	}

	public void traverse(ASTVisitor visitor, BlockScope scope) {

		if (visitor.visit(this, scope)) {
			if (qualification != null) {
				qualification.traverse(visitor, scope);
			}
			if (arguments != null) {
				int argumentLength = arguments.length;
				for (int i = 0; i < argumentLength; i++)
					arguments[i].traverse(visitor, scope);
			}
		}
		visitor.endVisit(this, scope);
	}
}
