/*******************************************************************************
 * Copyright (c) 2000, 2004 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 org.eclipse.wst.jsdt.internal.compiler.ast;

import java.util.ArrayList;

import org.eclipse.wst.jsdt.core.compiler.CharOperation;
import org.eclipse.wst.jsdt.internal.compiler.ASTVisitor;
import org.eclipse.wst.jsdt.internal.compiler.impl.*;
import org.eclipse.wst.jsdt.internal.compiler.codegen.*;
import org.eclipse.wst.jsdt.internal.compiler.flow.*;
import org.eclipse.wst.jsdt.internal.compiler.lookup.*;
import org.eclipse.wst.jsdt.internal.compiler.problem.*;
import org.eclipse.wst.jsdt.internal.compiler.util.Util;

public abstract class Expression extends Statement {

	public static final boolean isConstantValueRepresentable(
		Constant constant,
		int constantTypeID,
		int targetTypeID) {

		//true if there is no loss of precision while casting.
		// constantTypeID == constant.typeID
		if (targetTypeID == constantTypeID)
			return true;
		switch (targetTypeID) {
			case T_char :
				switch (constantTypeID) {
					case T_char :
						return true;
					case T_double :
						return constant.doubleValue() == constant.charValue();
					case T_float :
						return constant.floatValue() == constant.charValue();
					case T_int :
						return constant.intValue() == constant.charValue();
					case T_short :
						return constant.shortValue() == constant.charValue();
					case T_byte :
						return constant.byteValue() == constant.charValue();
					case T_long :
						return constant.longValue() == constant.charValue();
					default :
						return false;//boolean
				} 

			case T_float :
				switch (constantTypeID) {
					case T_char :
						return constant.charValue() == constant.floatValue();
					case T_double :
						return constant.doubleValue() == constant.floatValue();
					case T_float :
						return true;
					case T_int :
						return constant.intValue() == constant.floatValue();
					case T_short :
						return constant.shortValue() == constant.floatValue();
					case T_byte :
						return constant.byteValue() == constant.floatValue();
					case T_long :
						return constant.longValue() == constant.floatValue();
					default :
						return false;//boolean
				} 
				
			case T_double :
				switch (constantTypeID) {
					case T_char :
						return constant.charValue() == constant.doubleValue();
					case T_double :
						return true;
					case T_float :
						return constant.floatValue() == constant.doubleValue();
					case T_int :
						return constant.intValue() == constant.doubleValue();
					case T_short :
						return constant.shortValue() == constant.doubleValue();
					case T_byte :
						return constant.byteValue() == constant.doubleValue();
					case T_long :
						return constant.longValue() == constant.doubleValue();
					default :
						return false; //boolean
				} 
				
			case T_byte :
				switch (constantTypeID) {
					case T_char :
						return constant.charValue() == constant.byteValue();
					case T_double :
						return constant.doubleValue() == constant.byteValue();
					case T_float :
						return constant.floatValue() == constant.byteValue();
					case T_int :
						return constant.intValue() == constant.byteValue();
					case T_short :
						return constant.shortValue() == constant.byteValue();
					case T_byte :
						return true;
					case T_long :
						return constant.longValue() == constant.byteValue();
					default :
						return false; //boolean
				} 
				
			case T_short :
				switch (constantTypeID) {
					case T_char :
						return constant.charValue() == constant.shortValue();
					case T_double :
						return constant.doubleValue() == constant.shortValue();
					case T_float :
						return constant.floatValue() == constant.shortValue();
					case T_int :
						return constant.intValue() == constant.shortValue();
					case T_short :
						return true;
					case T_byte :
						return constant.byteValue() == constant.shortValue();
					case T_long :
						return constant.longValue() == constant.shortValue();
					default :
						return false; //boolean
				} 
				
			case T_int :
				switch (constantTypeID) {
					case T_char :
						return constant.charValue() == constant.intValue();
					case T_double :
						return constant.doubleValue() == constant.intValue();
					case T_float :
						return constant.floatValue() == constant.intValue();
					case T_int :
						return true;
					case T_short :
						return constant.shortValue() == constant.intValue();
					case T_byte :
						return constant.byteValue() == constant.intValue();
					case T_long :
						return constant.longValue() == constant.intValue();
					default :
						return false; //boolean
				} 
				
			case T_long :
				switch (constantTypeID) {
					case T_char :
						return constant.charValue() == constant.longValue();
					case T_double :
						return constant.doubleValue() == constant.longValue();
					case T_float :
						return constant.floatValue() == constant.longValue();
					case T_int :
						return constant.intValue() == constant.longValue();
					case T_short :
						return constant.shortValue() == constant.longValue();
					case T_byte :
						return constant.byteValue() == constant.longValue();
					case T_long :
						return true;
					default :
						return false; //boolean
				} 
				
			default :
				return false; //boolean
		} 
	}
	
	public Constant constant;
	
	//Some expression may not be used - from a java semantic point
	//of view only - as statements. Other may. In order to avoid the creation
	//of wrappers around expression in order to tune them as expression
	//Expression is a subclass of Statement. See the message isValidJavaStatement()

	public int implicitConversion;
	public TypeBinding resolvedType;

	public Expression() {
		super();
	}

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

		return flowInfo;
	}

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

		return analyseCode(currentScope, flowContext, flowInfo);
	}

	/**
	 * Returns false if cast is not legal. 
	 */
	public final boolean checkCastTypesCompatibility(
		BlockScope scope,
		TypeBinding castType,
		TypeBinding expressionType,
		Expression expression) {
	
		// 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.
	
		if (castType == null || expressionType == null) return true;
	
		// identity conversion cannot be performed upfront, due to side-effects
		// like constant propagation
				
		if (castType.isBaseType()) {
			if (expressionType.isBaseType()) {
				if (expressionType == castType) {
					if (expression != null) {
						this.constant = expression.constant; //use the same constant
					}
					tagAsUnnecessaryCast(scope, castType);
					return true;
				}
				boolean necessary = false;
				if (expressionType.isCompatibleWith(castType)
						|| (necessary = BaseTypeBinding.isNarrowing(castType.id, expressionType.id))) {
					if (expression != null) {
						expression.implicitConversion = (castType.id << 4) + expressionType.id;
						if (expression.constant != Constant.NotAConstant) {
							constant = expression.constant.castTo(expression.implicitConversion);
						}
					}
					if (!necessary) tagAsUnnecessaryCast(scope, castType);
					return true;
					
				}
			}
			reportIllegalCast(scope, castType, expressionType);
			return false;
		}
	
		//-----------cast to something which is NOT a base type--------------------------	
		if (expressionType == NullBinding) {
			tagAsUnnecessaryCast(scope, castType);
			return true; //null is compatible with every thing
		}
		if (expressionType.isBaseType()) {
			reportIllegalCast(scope, castType, expressionType);
			return false;
		}
	
		if (expressionType.isArrayType()) {
			if (castType == expressionType) {
				tagAsUnnecessaryCast(scope, castType);
				return true; // identity conversion
			}
	
			if (castType.isArrayType()) {
				//------- (castType.isArray) expressionType.isArray -----------
				TypeBinding exprElementType = ((ArrayBinding) expressionType).elementsType();
				if (exprElementType.isBaseType()) {
					// <---stop the recursion------- 
					if (((ArrayBinding) castType).elementsType() == exprElementType) {
						tagAsNeedCheckCast();
						return true;
					} else {
						reportIllegalCast(scope, castType, expressionType);
						return false;
					}
				}
				// recursively on the elements...
				return checkCastTypesCompatibility(
					scope,
					((ArrayBinding) castType).elementsType(),
					exprElementType,
					expression);
			} else if (
				castType.isClass()) {
				//------(castType.isClass) expressionType.isArray ---------------	
				if (castType.id == T_Object) {
					tagAsUnnecessaryCast(scope, castType);
					return true;
				}
			} else { //------- (castType.isInterface) expressionType.isArray -----------
				if (castType.id == T_JavaLangCloneable || castType.id == T_JavaIoSerializable) {
					tagAsNeedCheckCast();
					return true;
				}
			}
			reportIllegalCast(scope, castType, expressionType);
			return false;
		}
	
		if (expressionType.isClass()) {
			if (castType.isArrayType()) {
				// ---- (castType.isArray) expressionType.isClass -------
				if (expressionType.id == T_Object) { // potential runtime error
					tagAsNeedCheckCast();
					return true;
				}
			} else if (castType.isClass()) { // ----- (castType.isClass) expressionType.isClass ------
				
				ReferenceBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType.erasure());
				if (match != null) {
					if (expression != null && castType.id == T_String) this.constant = expression.constant; // (String) cst is still a constant
					return checkUnsafeCast(scope, castType, expressionType, match, false);
				}
				match = ((ReferenceBinding)castType).findSuperTypeErasingTo((ReferenceBinding)expressionType.erasure());
				if (match != null) {
					tagAsNeedCheckCast();
					return checkUnsafeCast(scope, castType, expressionType, match, true);
				}
			} else { // ----- (castType.isInterface) expressionType.isClass -------  

				ReferenceBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType.erasure());
				if (match != null) {
					return checkUnsafeCast(scope, castType, expressionType, match, false);
				}
				// a subclass may implement the interface ==> no check at compile time
				if (!((ReferenceBinding) expressionType).isFinal()) {
					tagAsNeedCheckCast();
					match = ((ReferenceBinding)castType).findSuperTypeErasingTo((ReferenceBinding)expressionType.erasure());
					if (match != null) {
						return checkUnsafeCast(scope, castType, expressionType, match, true);
					}
					return true;
				}
				// no subclass for expressionType, thus compile-time check is valid
			}
			reportIllegalCast(scope, castType, expressionType);
			return false;
		}
	
		//	if (expressionType.isInterface()) { cannot be anything else
		if (castType.isArrayType()) {
			// ----- (castType.isArray) expressionType.isInterface ------
			if (expressionType.id == T_JavaLangCloneable
					|| expressionType.id == T_JavaIoSerializable) {// potential runtime error
				tagAsNeedCheckCast();
				return true;
			} else {
				reportIllegalCast(scope, castType, expressionType);
				return false;
			}
		} else if (castType.isClass()) { // ----- (castType.isClass) expressionType.isInterface --------

			if (castType.id == T_Object) { // no runtime error
				tagAsUnnecessaryCast(scope, castType);
				return true;
			}
			if (((ReferenceBinding) castType).isFinal()) {
				// no subclass for castType, thus compile-time check is valid
				ReferenceBinding match = ((ReferenceBinding)castType).findSuperTypeErasingTo((ReferenceBinding)expressionType.erasure());
				if (match == null) {
					// potential runtime error
					reportIllegalCast(scope, castType, expressionType);
					return false;
				}				
			}
		} else { // ----- (castType.isInterface) expressionType.isInterface -------

			ReferenceBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType.erasure());
			if (match != null) {
				return checkUnsafeCast(scope, castType, expressionType, match, false);
			}
			
			match = ((ReferenceBinding)castType).findSuperTypeErasingTo((ReferenceBinding)expressionType.erasure());
			if (match != null) {
				tagAsNeedCheckCast();
				return checkUnsafeCast(scope, castType, expressionType, match, true);
			}  else {
				MethodBinding[] castTypeMethods = getAllInheritedMethods((ReferenceBinding) castType);
				MethodBinding[] expressionTypeMethods =
					getAllInheritedMethods((ReferenceBinding) expressionType);
				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)
								&& (CharOperation.equals(castTypeMethods[i].selector, expressionTypeMethods[j].selector))
								&& castTypeMethods[i].areParametersEqual(expressionTypeMethods[j])) {
							reportIllegalCast(scope, castType, expressionType);
							return false;

						}
					}
				}
			}
		}
		tagAsNeedCheckCast();
		return true;
	}	
	
	private MethodBinding[] getAllInheritedMethods(ReferenceBinding binding) {
		ArrayList collector = new ArrayList();
		getAllInheritedMethods0(binding, collector);
		return (MethodBinding[]) collector.toArray(new MethodBinding[collector.size()]);
	}
	
	private void getAllInheritedMethods0(ReferenceBinding binding, ArrayList collector) {
		if (!binding.isInterface()) return;
		MethodBinding[] methodBindings = binding.methods();
		for (int i = 0, max = methodBindings.length; i < max; i++) {
			collector.add(methodBindings[i]);
		}
		ReferenceBinding[] superInterfaces = binding.superInterfaces();
		for (int i = 0, max = superInterfaces.length; i < max; i++) {
			getAllInheritedMethods0(superInterfaces[i], collector);
		}
	}

	public boolean checkUnsafeCast(Scope scope, TypeBinding castType, TypeBinding expressionType, TypeBinding match, boolean isNarrowing) {
		if (match == castType) {
			if (!isNarrowing) tagAsUnnecessaryCast(scope, castType);
			return true;
		}
		if (castType.isBoundParameterizedType() || castType.isGenericType()) {
			if (match.isProvablyDistinctFrom(isNarrowing ? expressionType : castType)) {
				reportIllegalCast(scope, castType, expressionType);
				return false; 
			}
		}
		if (!isNarrowing) tagAsUnnecessaryCast(scope, castType);
		return true;
	}
	
	/**
	 * Base types need that the widening is explicitly done by the compiler using some bytecode like i2f.
	 * Also check unsafe type operations.
	 */ 
	public void computeConversion(Scope scope, TypeBinding runtimeTimeType, TypeBinding compileTimeType) {

		if (runtimeTimeType == null || compileTimeType == null)
			return;
		if (this.implicitConversion != 0) return; // already set independantly
		
		switch (runtimeTimeType.id) {
			case T_byte :
			case T_short :
			case T_char :
				this.implicitConversion = (T_int << 4) + compileTimeType.id;
				break;
			case T_String :
			case T_float :
			case T_boolean :
			case T_double :
			case T_int : //implicitConversion may result in i2i which will result in NO code gen
			case T_long :
				this.implicitConversion = (runtimeTimeType.id << 4) + compileTimeType.id;
				break;
			default : // regular object ref
//				if (compileTimeType.isRawType() && runtimeTimeType.isBoundParameterizedType()) {
//				    scope.problemReporter().unsafeRawExpression(this, compileTimeType, runtimeTimeType);
//				}		
		}
	}	

	/**
	 * Expression statements are plain expressions, however they generate like
	 * normal expressions with no value required.
	 *
	 * @param currentScope org.eclipse.wst.jsdt.internal.compiler.lookup.BlockScope
	 * @param codeStream org.eclipse.wst.jsdt.internal.compiler.codegen.CodeStream 
	 */
	public void generateCode(BlockScope currentScope, CodeStream codeStream) {

		if ((bits & IsReachableMASK) == 0) {
			return;
		}
		generateCode(currentScope, codeStream, false);
	}

	/**
	 * Every expression is responsible for generating its implicit conversion when necessary.
	 *
	 * @param currentScope org.eclipse.wst.jsdt.internal.compiler.lookup.BlockScope
	 * @param codeStream org.eclipse.wst.jsdt.internal.compiler.codegen.CodeStream
	 * @param valueRequired boolean
	 */
	public void generateCode(
		BlockScope currentScope,
		CodeStream codeStream,
		boolean valueRequired) {

		if (constant != NotAConstant) {
			// generate a constant expression
			int pc = codeStream.position;
			codeStream.generateConstant(constant, implicitConversion);
			codeStream.recordPositionsFrom(pc, this.sourceStart);
		} else {
			// actual non-constant code generation
			throw new ShouldNotImplement(Util.bind("ast.missingCode")); //$NON-NLS-1$
		}
	}

	/**
	 * Default generation of a boolean value
	 * @param currentScope
	 * @param codeStream
	 * @param trueLabel
	 * @param falseLabel
	 * @param valueRequired
	 */
	public void generateOptimizedBoolean(
		BlockScope currentScope,
		CodeStream codeStream,
		Label trueLabel,
		Label falseLabel,
		boolean valueRequired) {

		// a label valued to nil means: by default we fall through the case... 
		// both nil means we leave the value on the stack

		if ((constant != Constant.NotAConstant) && (constant.typeID() == T_boolean)) {
			int pc = codeStream.position;
			if (constant.booleanValue() == true) {
				// constant == true
				if (valueRequired) {
					if (falseLabel == null) {
						// implicit falling through the FALSE case
						if (trueLabel != null) {
							codeStream.goto_(trueLabel);
						}
					}
				}
			} else {
				if (valueRequired) {
					if (falseLabel != null) {
						// implicit falling through the TRUE case
						if (trueLabel == null) {
							codeStream.goto_(falseLabel);
						}
					}
				}
			}
			codeStream.recordPositionsFrom(pc, this.sourceStart);
			return;
		}
		generateCode(currentScope, codeStream, valueRequired);
		// branching
		int position = codeStream.position;
		if (valueRequired) {
			if (falseLabel == null) {
				if (trueLabel != null) {
					// Implicit falling through the FALSE case
					codeStream.ifne(trueLabel);
				}
			} else {
				if (trueLabel == null) {
					// Implicit falling through the TRUE case
					codeStream.ifeq(falseLabel);
				} else {
					// No implicit fall through TRUE/FALSE --> should never occur
				}
			}
		}
		// reposition the endPC
		codeStream.updateLastRecordedEndPC(position);
	}

	/* Optimized (java) code generation for string concatenations that involve StringBuffer
	 * creation: going through this path means that there is no need for a new StringBuffer
	 * creation, further operands should rather be only appended to the current one.
	 * By default: no optimization.
	 */
	public void generateOptimizedStringConcatenation(
		BlockScope blockScope,
		CodeStream codeStream,
		int typeID) {

		if (typeID == T_String && this.constant != NotAConstant && this.constant.stringValue().length() == 0) {
			return; // optimize str + ""
		}
		generateCode(blockScope, codeStream, true);
		codeStream.invokeStringConcatenationAppendForType(typeID);
	}

	/* Optimized (java) code generation for string concatenations that involve StringBuffer
	 * creation: going through this path means that there is no need for a new StringBuffer
	 * creation, further operands should rather be only appended to the current one.
	 */
	public void generateOptimizedStringConcatenationCreation(
		BlockScope blockScope,
		CodeStream codeStream,
		int typeID) {

		// Optimization only for integers and strings
		if (typeID == T_Object) {
			// in the case the runtime value of valueOf(Object) returns null, we have to use append(Object) instead of directly valueOf(Object)
			// append(Object) returns append(valueOf(Object)), which means that the null case is handled by append(String).
			codeStream.newStringContatenation();
			codeStream.dup();
			codeStream.invokeStringConcatenationDefaultConstructor();
			generateCode(blockScope, codeStream, true);
			codeStream.invokeStringConcatenationAppendForType(T_Object);
			return;
		}
		codeStream.newStringContatenation();
		codeStream.dup();
		if (typeID == T_String || typeID == T_null) {
			if (constant != NotAConstant) {
				String stringValue = constant.stringValue();
				if (stringValue.length() == 0) {  // optimize ""+<str> 
					codeStream.invokeStringConcatenationDefaultConstructor();
					return;
				}
				codeStream.ldc(stringValue);
			} else {
				generateCode(blockScope, codeStream, true);
				codeStream.invokeStringValueOf(T_Object);
			}
		} else {
			generateCode(blockScope, codeStream, true);
			codeStream.invokeStringValueOf(typeID);
		}
		codeStream.invokeStringConcatenationStringConstructor();
	}

	public boolean isCompactableOperation() {

		return false;
	}

	//Return true if the conversion is done AUTOMATICALLY by the vm
	//while the javaVM is an int based-machine, thus for example pushing
	//a byte onto the stack , will automatically create an int on the stack
	//(this request some work d be done by the VM on signed numbers)
	public boolean isConstantValueOfTypeAssignableToType(TypeBinding constantType, TypeBinding targetType) {

		if (constant == Constant.NotAConstant)
			return false;
		if (constantType == targetType)
			return true;
		if (constantType.isBaseType() && targetType.isBaseType()) {
			//No free assignment conversion from anything but to integral ones.
			if ((constantType == IntBinding
				|| BaseTypeBinding.isWidening(T_int, constantType.id))
				&& (BaseTypeBinding.isNarrowing(targetType.id, T_int))) {
				//use current explicit conversion in order to get some new value to compare with current one
				return isConstantValueRepresentable(constant, constantType.id, targetType.id);
			}
		}
		return false;
	}

	public boolean isTypeReference() {
		return false;
	}
	
	/**
	 * Constant usable for bytecode pattern optimizations, but cannot be inlined
	 * since it is not strictly equivalent to the definition of constant expressions.
	 * In particular, some side-effects may be required to occur (only the end value
	 * is known).
	 * @return Constant known to be of boolean type
	 */ 
	public Constant optimizedBooleanConstant() {
		return this.constant;
	}

	public StringBuffer print(int indent, StringBuffer output) {
		printIndent(indent, output);
		return printExpression(indent, output);
	}

	public abstract StringBuffer printExpression(int indent, StringBuffer output);
	
	public StringBuffer printStatement(int indent, StringBuffer output) {
		return print(indent, output).append(";"); //$NON-NLS-1$
	}

	public void reportIllegalCast(Scope scope, TypeBinding castType, TypeBinding expressionType) {
		// do nothing by default
	}
	
	public void resolve(BlockScope scope) {
		// drops the returning expression's type whatever the type is.

		this.resolveType(scope);
		return;
	}

	public TypeBinding resolveType(BlockScope scope) {
		// by default... subclasses should implement a better TC if required.

		return null;
	}

	public TypeBinding resolveType(ClassScope classScope) {
		// by default... subclasses should implement a better TB if required.
		return null;
	}

	public TypeBinding resolveTypeExpecting(
		BlockScope scope,
		TypeBinding expectedType) {

		this.setExpectedType(expectedType); // needed in case of generic method invocation
		TypeBinding expressionType = this.resolveType(scope);
		if (expressionType == null) return null;
		if (expressionType == expectedType) return expressionType;
		
		if (!expressionType.isCompatibleWith(expectedType)) {
			scope.problemReporter().typeMismatchError(expressionType, expectedType, this);
			return null;
		}
		return expressionType;
	}

	/**
	 * Record the type expectation before this expression is typechecked.
	 * e.g. String s = foo();, foo() will be tagged as being expected of type String
	 * Used to trigger proper inference of generic method invocations.
	 */
	public void setExpectedType(TypeBinding expectedType) {
	    // do nothing by default
	}

	public void tagAsUnnecessaryCast(Scope scope, TypeBinding castType) {
	    // do nothing by default
	}
	
	public void tagAsNeedCheckCast() {
	    // do nothing by default		
	}
	
	public Expression toTypeReference() {
		//by default undefined

		//this method is meanly used by the parser in order to transform
		//an expression that is used as a type reference in a cast ....
		//--appreciate the fact that castExpression and ExpressionWithParenthesis
		//--starts with the same pattern.....

		return this;
	}
	
	public void traverse(ASTVisitor visitor, BlockScope scope) {
		// do nothing by default
	}
	public void traverse(ASTVisitor visitor, ClassScope scope) {
		// do nothing by default
	}
	public void traverse(ASTVisitor visitor, CompilationUnitScope scope) {
		// do nothing by default
	}
}
