/*******************************************************************************
 * 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.impl.Constant;
import net.sourceforge.phpdt.internal.compiler.lookup.BlockScope;
import net.sourceforge.phpdt.internal.compiler.lookup.FieldBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.ReferenceBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.SourceTypeBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.TypeBinding;

public class AssertStatement extends Statement {

	public Expression assertExpression, exceptionArgument;

	// for local variable attribute
	int preAssertInitStateIndex = -1;

	private FieldBinding assertionSyntheticFieldBinding;

	public AssertStatement(Expression exceptionArgument,
			Expression assertExpression, int startPosition) {

		this.assertExpression = assertExpression;
		this.exceptionArgument = exceptionArgument;
		sourceStart = startPosition;
		sourceEnd = exceptionArgument.sourceEnd;
	}

	public AssertStatement(Expression assertExpression, int startPosition) {

		this.assertExpression = assertExpression;
		sourceStart = startPosition;
		sourceEnd = assertExpression.sourceEnd;
	}

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

		preAssertInitStateIndex = currentScope.methodScope()
				.recordInitializationStates(flowInfo);

		Constant cst = this.assertExpression.optimizedBooleanConstant();
		boolean isOptimizedTrueAssertion = cst != NotAConstant
				&& cst.booleanValue() == true;
		boolean isOptimizedFalseAssertion = cst != NotAConstant
				&& cst.booleanValue() == false;

		FlowInfo assertInfo = flowInfo.copy();
		if (isOptimizedTrueAssertion) {
			assertInfo.setReachMode(FlowInfo.UNREACHABLE);
		}
		assertInfo = assertExpression.analyseCode(currentScope, flowContext,
				assertInfo).unconditionalInits();

		if (exceptionArgument != null) {
			// only gets evaluated when escaping - results are not taken into
			// account
			FlowInfo exceptionInfo = exceptionArgument.analyseCode(
					currentScope, flowContext, assertInfo.copy());

			if (!isOptimizedTrueAssertion) {
				flowContext.checkExceptionHandlers(currentScope
						.getJavaLangAssertionError(), this, exceptionInfo,
						currentScope);
			}
		}

		// add the assert support in the clinit
		manageSyntheticAccessIfNecessary(currentScope);
		if (isOptimizedFalseAssertion) {
			return flowInfo; // if assertions are enabled, the following code
								// will be unreachable
		} else {
			return flowInfo.mergedWith(assertInfo.unconditionalInits());
		}
	}

	// public void generateCode(BlockScope currentScope, CodeStream codeStream)
	// {
	//
	// if ((bits & IsReachableMASK) == 0) {
	// return;
	// }
	// int pc = codeStream.position;
	//	
	// if (this.assertionSyntheticFieldBinding != null) {
	// Label assertionActivationLabel = new Label(codeStream);
	// codeStream.getstatic(this.assertionSyntheticFieldBinding);
	// codeStream.ifne(assertionActivationLabel);
	// Label falseLabel = new Label(codeStream);
	// assertExpression.generateOptimizedBoolean(currentScope, codeStream,
	// (falseLabel = new Label(codeStream)), null , true);
	// codeStream.newJavaLangAssertionError();
	// codeStream.dup();
	// if (exceptionArgument != null) {
	// exceptionArgument.generateCode(currentScope, codeStream, true);
	// codeStream.invokeJavaLangAssertionErrorConstructor(exceptionArgument.implicitConversion
	// & 0xF);
	// } else {
	// codeStream.invokeJavaLangAssertionErrorDefaultConstructor();
	// }
	// codeStream.athrow();
	// falseLabel.place();
	// assertionActivationLabel.place();
	// }
	//		
	// // May loose some local variable initializations : affecting the local
	// variable attributes
	// if (preAssertInitStateIndex != -1) {
	// codeStream.removeNotDefinitelyAssignedVariables(currentScope,
	// preAssertInitStateIndex);
	// }
	// codeStream.recordPositionsFrom(pc, this.sourceStart);
	// }
	public StringBuffer printStatement(int tab, StringBuffer output) {

		printIndent(tab, output);
		output.append("assert "); //$NON-NLS-1$
		this.assertExpression.printExpression(0, output);
		if (this.exceptionArgument != null) {
			output.append(": "); //$NON-NLS-1$
			this.exceptionArgument.printExpression(0, output);
		}
		return output.append(';');
	}

	public void resolve(BlockScope scope) {

		assertExpression.resolveTypeExpecting(scope, BooleanBinding);
		if (exceptionArgument != null) {
			TypeBinding exceptionArgumentType = exceptionArgument
					.resolveType(scope);
			if (exceptionArgumentType != null) {
				if (exceptionArgumentType.id == T_void) {
					scope.problemReporter().illegalVoidExpression(
							exceptionArgument);
				}
				exceptionArgument.implicitConversion = (exceptionArgumentType.id << 4)
						+ exceptionArgumentType.id;
			}
		}
	}

	public void traverse(ASTVisitor visitor, BlockScope scope) {

		if (visitor.visit(this, scope)) {
			assertExpression.traverse(visitor, scope);
			if (exceptionArgument != null) {
				exceptionArgument.traverse(visitor, scope);
			}
		}
		visitor.endVisit(this, scope);
	}

	public void manageSyntheticAccessIfNecessary(BlockScope currentScope) {

		// need assertion flag: $assertionsDisabled on outer most source clas
		// (in case of static member of interface, will use the outermost static
		// member - bug 22334)
		SourceTypeBinding outerMostClass = currentScope.enclosingSourceType();
		while (outerMostClass.isLocalType()) {
			ReferenceBinding enclosing = outerMostClass.enclosingType();
			if (enclosing == null || enclosing.isInterface())
				break;
			outerMostClass = (SourceTypeBinding) enclosing;
		}

		this.assertionSyntheticFieldBinding = outerMostClass.addSyntheticField(
				this, currentScope);

		// find <clinit> and enable assertion support
		TypeDeclaration typeDeclaration = outerMostClass.scope.referenceType();
		AbstractMethodDeclaration[] methods = typeDeclaration.methods;
		for (int i = 0, max = methods.length; i < max; i++) {
			AbstractMethodDeclaration method = methods[i];
			if (method.isClinit()) {
				((Clinit) method)
						.addSupportForAssertion(assertionSyntheticFieldBinding);
				break;
			}
		}
	}

	public String toString(int tab) {

		StringBuffer buffer = new StringBuffer(tabString(tab));
		buffer.append("assert "); //$NON-NLS-1$
		buffer.append(this.assertExpression);
		if (this.exceptionArgument != null) {
			buffer.append(":"); //$NON-NLS-1$
			buffer.append(this.exceptionArgument);
			buffer.append(";"); //$NON-NLS-1$
		}
		return buffer.toString();
	}

}
