/*******************************************************************************
 * 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.ArrayBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.BaseTypeBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.BlockScope;
import net.sourceforge.phpdt.internal.compiler.lookup.MethodBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.ReferenceBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.Scope;
import net.sourceforge.phpdt.internal.compiler.lookup.TypeBinding;

public class CastExpression extends Expression {

	public Expression expression;

	public Expression type;

	public boolean needRuntimeCheckcast;

	// expression.implicitConversion holds the cast for baseType casting
	public CastExpression(Expression e, Expression t) {
		expression = e;
		type = t;

		// due to the fact an expression may start with ( and that a cast also
		// start with (
		// the field is an expression....it can be a TypeReference OR a
		// NameReference Or
		// an expression <--this last one is invalid.......

		// :-( .............

		// if (type instanceof TypeReference )
		// flag = IsTypeReference ;
		// else
		// if (type instanceof NameReference)
		// flag = IsNameReference ;
		// else
		// flag = IsExpression ;

	}

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

		return expression.analyseCode(currentScope, flowContext, flowInfo)
				.unconditionalInits();
	}

	public final void areTypesCastCompatible(BlockScope scope,
			TypeBinding castType, TypeBinding expressionType) {

		// see specifications 5.5
		// handle errors and process constant when needed

		// if either one of the type is null ==>
		// some error has been already reported some where ==>
		// we then do not report an obvious-cascade-error.

		needRuntimeCheckcast = false;
		if (castType == null || expressionType == null)
			return;

		// identity conversion cannot be performed upfront, due to side-effects
		// like constant propagation

		if (castType.isBaseType()) {
			if (expressionType.isBaseType()) {
				if (expressionType == castType) {
					expression.implicitWidening(castType, expressionType);
					constant = expression.constant; // use the same constant
					return;
				}
				if (expressionType.isCompatibleWith(castType)
						|| BaseTypeBinding.isNarrowing(castType.id,
								expressionType.id)) {
					expression.implicitConversion = (castType.id << 4)
							+ expressionType.id;
					if (expression.constant != Constant.NotAConstant)
						constant = expression.constant
								.castTo(expression.implicitConversion);
					return;
				}
			}
			scope.problemReporter().typeCastError(this, castType,
					expressionType);
			return;
		}

		// -----------cast to something which is NOT a base
		// type--------------------------
		if (expressionType == NullBinding) {
			// if (castType.isArrayType()){ // 26903 - need checkcast when
			// casting null to array type
			// needRuntimeCheckcast = true;
			// }
			return; // null is compatible with every thing
		}
		if (expressionType.isBaseType()) {
			scope.problemReporter().typeCastError(this, castType,
					expressionType);
			return;
		}

		if (expressionType.isArrayType()) {
			if (castType == expressionType)
				return; // identity conversion

			if (castType.isArrayType()) {
				// ------- (castType.isArray) expressionType.isArray -----------
				TypeBinding exprElementType = ((ArrayBinding) expressionType)
						.elementsType(scope);
				if (exprElementType.isBaseType()) {
					// <---stop the recursion-------
					if (((ArrayBinding) castType).elementsType(scope) == exprElementType)
						needRuntimeCheckcast = true;
					else
						scope.problemReporter().typeCastError(this, castType,
								expressionType);
					return;
				}
				// recursively on the elements...
				areTypesCastCompatible(scope, ((ArrayBinding) castType)
						.elementsType(scope), exprElementType);
				return;
			} else if (castType.isClass()) {
				// ------(castType.isClass) expressionType.isArray
				// ---------------
				if (scope.isJavaLangObject(castType))
					return;
			} else { // ------- (castType.isInterface) expressionType.isArray
						// -----------
				if (scope.isJavaLangCloneable(castType)
						|| scope.isJavaIoSerializable(castType)) {
					needRuntimeCheckcast = true;
					return;
				}
			}
			scope.problemReporter().typeCastError(this, castType,
					expressionType);
			return;
		}

		if (expressionType.isClass()) {
			if (castType.isArrayType()) {
				// ---- (castType.isArray) expressionType.isClass -------
				if (scope.isJavaLangObject(expressionType)) { // potential
																// runtime error
					needRuntimeCheckcast = true;
					return;
				}
			} else if (castType.isClass()) { // ----- (castType.isClass)
												// expressionType.isClass ------
				if (expressionType.isCompatibleWith(castType)) { // no
																	// runtime
																	// error
					if (castType.id == T_String)
						constant = expression.constant; // (String) cst is still
														// a constant
					return;
				}
				if (castType.isCompatibleWith(expressionType)) {
					// potential runtime error
					needRuntimeCheckcast = true;
					return;
				}
			} else { // ----- (castType.isInterface) expressionType.isClass
						// -------
				if (((ReferenceBinding) expressionType).isFinal()) {
					// no subclass for expressionType, thus compile-time check
					// is valid
					if (expressionType.isCompatibleWith(castType))
						return;
				} else { // a subclass may implement the interface ==> no
							// check at compile time
					needRuntimeCheckcast = true;
					return;
				}
			}
			scope.problemReporter().typeCastError(this, castType,
					expressionType);
			return;
		}

		// if (expressionType.isInterface()) { cannot be anything else
		if (castType.isArrayType()) {
			// ----- (castType.isArray) expressionType.isInterface ------
			if (scope.isJavaLangCloneable(expressionType)
					|| scope.isJavaIoSerializable(expressionType)) // potential
																	// runtime
																	// error
				needRuntimeCheckcast = true;
			else
				scope.problemReporter().typeCastError(this, castType,
						expressionType);
			return;
		} else if (castType.isClass()) { // ----- (castType.isClass)
											// expressionType.isInterface
											// --------
			if (scope.isJavaLangObject(castType)) // no runtime error
				return;
			if (((ReferenceBinding) castType).isFinal()) {
				// no subclass for castType, thus compile-time check is valid
				if (!castType.isCompatibleWith(expressionType)) {
					// potential runtime error
					scope.problemReporter().typeCastError(this, castType,
							expressionType);
					return;
				}
			}
		} else { // ----- (castType.isInterface) expressionType.isInterface
					// -------
			if (castType == expressionType)
				return; // identity conversion
			if (Scope.compareTypes(castType, expressionType) == NotRelated) {
				MethodBinding[] castTypeMethods = ((ReferenceBinding) castType)
						.methods();
				MethodBinding[] expressionTypeMethods = ((ReferenceBinding) expressionType)
						.methods();
				int exprMethodsLength = expressionTypeMethods.length;
				for (int i = 0, castMethodsLength = castTypeMethods.length; i < castMethodsLength; i++)
					for (int j = 0; j < exprMethodsLength; j++) {
						if ((castTypeMethods[i].returnType != expressionTypeMethods[j].returnType)
								&& (castTypeMethods[i].selector == expressionTypeMethods[j].selector)
								&& castTypeMethods[i]
										.areParametersEqual(expressionTypeMethods[j])) {
							scope.problemReporter().typeCastError(this,
									castType, expressionType);
						}
					}
			}
		}
		needRuntimeCheckcast = true;
		return;
	}

	/**
	 * Cast expression code generation
	 * 
	 * @param currentScope
	 *            net.sourceforge.phpdt.internal.compiler.lookup.BlockScope
	 * @param codeStream
	 *            net.sourceforge.phpdt.internal.compiler.codegen.CodeStream
	 * @param valueRequired
	 *            boolean
	 */
	// public void generateCode(
	// BlockScope currentScope,
	// CodeStream codeStream,
	// boolean valueRequired) {
	//
	// int pc = codeStream.position;
	// if (constant != NotAConstant) {
	// if (valueRequired
	// || needRuntimeCheckcast) { // Added for: 1F1W9IG: IVJCOM:WINNT - Compiler
	// omits casting check
	// codeStream.generateConstant(constant, implicitConversion);
	// if (needRuntimeCheckcast) {
	// codeStream.checkcast(this.resolvedType);
	// if (!valueRequired)
	// codeStream.pop();
	// }
	// }
	// codeStream.recordPositionsFrom(pc, this.sourceStart);
	// return;
	// }
	// expression.generateCode(
	// currentScope,
	// codeStream,
	// valueRequired || needRuntimeCheckcast);
	// if (needRuntimeCheckcast) {
	// codeStream.checkcast(this.resolvedType);
	// if (!valueRequired)
	// codeStream.pop();
	// } else {
	// if (valueRequired)
	// codeStream.generateImplicitConversion(implicitConversion);
	// }
	// codeStream.recordPositionsFrom(pc, this.sourceStart);
	// }
	public Expression innermostCastedExpression() {
		Expression current = this.expression;
		while (current instanceof CastExpression) {
			current = ((CastExpression) current).expression;
		}
		return current;
	}

	public StringBuffer printExpression(int indent, StringBuffer output) {

		output.append('(');
		type.print(0, output).append(") "); //$NON-NLS-1$
		return expression.printExpression(0, output);
	}

	public TypeBinding resolveType(BlockScope scope) {
		// compute a new constant if the cast is effective

		// due to the fact an expression may start with ( and that a cast can
		// also start with (
		// the field is an expression....it can be a TypeReference OR a
		// NameReference Or
		// any kind of Expression <-- this last one is invalid.......

		constant = Constant.NotAConstant;
		implicitConversion = T_undefined;
		if ((type instanceof TypeReference) || (type instanceof NameReference)) {
			this.resolvedType = type.resolveType(scope);
			TypeBinding castedExpressionType = expression.resolveType(scope);
			if (this.resolvedType != null && castedExpressionType != null) {
				areTypesCastCompatible(scope, this.resolvedType,
						castedExpressionType);
			}
			return this.resolvedType;
		} else { // expression as a cast !!!!!!!!
			TypeBinding castedExpressionType = expression.resolveType(scope);
			if (castedExpressionType == null)
				return null;
			scope.problemReporter().invalidTypeReference(type);
			return null;
		}
	}

	public String toStringExpression() {

		return "(" + type.toString(0) + ") " + //$NON-NLS-2$ //$NON-NLS-1$
				expression.toStringExpression();
	}

	public void traverse(ASTVisitor visitor, BlockScope blockScope) {

		if (visitor.visit(this, blockScope)) {
			type.traverse(visitor, blockScope);
			expression.traverse(visitor, blockScope);
		}
		visitor.endVisit(this, blockScope);
	}
}
