/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Genady Beriozkin - added support for reporting assignment with no effect
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
//import checkers.inference.ownership.quals.*;

import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.codegen.*;
import org.eclipse.jdt.internal.compiler.flow.*;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.*;

public class Assignment extends Expression {

	public Expression lhs;
	public Expression expression;
		
	public Assignment(Expression lhs, Expression expression, int sourceEnd) {
		//lhs is always a reference by construction ,
		//but is build as an expression ==> the checkcast cannot fail

		this.lhs = lhs;
		lhs.bits |= IsStrictlyAssigned; // tag lhs as assigned
		
		this.expression = expression;

		this.sourceStart = lhs.sourceStart;
		this.sourceEnd = sourceEnd;
	}

	public FlowInfo analyseCode(
		BlockScope currentScope,
		FlowContext flowContext,
		FlowInfo flowInfo) {
		// record setting a variable: various scenarii are possible, setting an array reference, 
		// a field reference, a blank final field reference, a field of an enclosing instance or 
		// just a local variable.

		LocalVariableBinding local = this.lhs.localVariableBinding();
		int nullStatus = this.expression.nullStatus(flowInfo);
		if (local != null && nullStatus == FlowInfo.NULL) {
				flowContext.recordUsingNullReference(currentScope, local, this.lhs, FlowInfo.NON_NULL, flowInfo);
		}
		flowInfo = ((Reference) lhs)
			.analyseAssignment(currentScope, flowContext, flowInfo, (/*@OwnPar*/ /*@NoRep*/ Assignment)this, false)
			.unconditionalInits();
		if (local != null) {
			switch(nullStatus) {
				case FlowInfo.NULL :
					flowInfo.markAsDefinitelyNull(local);
					break;
				case FlowInfo.NON_NULL :
					flowInfo.markAsDefinitelyNonNull(local);
					break;
			}
		}		
		return flowInfo;
	}

	void checkAssignmentEffect(BlockScope scope) {
		
		Binding left = getDirectBinding(this.lhs);
		if (left != null && left == getDirectBinding(this.expression)) {
			scope.problemReporter().assignmentHasNoEffect((/*@OwnPar*/ /*@NoRep*/ Assignment)this, left.shortReadableName());
			this.bits |= AssignmentHasNoEffect; // record assignment has no effect
		}
	}

	void checkAssignment(BlockScope scope, TypeBinding lhsType, TypeBinding rhsType) {
		
		FieldBinding leftField = getLastField(this.lhs);
		if (leftField != null &&  rhsType != NullBinding && lhsType.isWildcard() && ((WildcardBinding)lhsType).boundKind != Wildcard.SUPER) {
		    scope.problemReporter().wildcardAssignment(lhsType, rhsType, this.expression);
		} else if (leftField != null && leftField.declaringClass != null /*length pseudo field*/&& leftField.declaringClass.isRawType()) {
		    scope.problemReporter().unsafeRawFieldAssignment(leftField, rhsType, this.lhs);
		} else if (rhsType.needsUncheckedConversion(lhsType)) {
		    scope.problemReporter().unsafeTypeConversion(this.expression, rhsType, lhsType);
		}		
	}
	
	public void generateCode(
		BlockScope currentScope,
		CodeStream codeStream,
		boolean valueRequired) {

		// various scenarii are possible, setting an array reference, 
		// a field reference, a blank final field reference, a field of an enclosing instance or 
		// just a local variable.

		int pc = codeStream.position;
		if ((this.bits & AssignmentHasNoEffect) != 0) {
			if (valueRequired) {
//				if (this.expression instanceof PostfixExpression) {
//					// discard entire assignment and rhs for cases like: "b = b++"
//					this.lhs.generateCode(currentScope, codeStream, true);
//				} else {
				this.expression.generateCode(currentScope, codeStream, true);
//				}
			}
		} else {
			 ((Reference) lhs).generateAssignment(currentScope, codeStream, (/*@OwnPar*/ /*@NoRep*/ Assignment)this, valueRequired);
			// variable may have been optimized out
			// the lhs is responsible to perform the implicitConversion generation for the assignment since optimized for unused local assignment.
		}
		codeStream.recordPositionsFrom(pc, this.sourceStart);
	}

	Binding getDirectBinding(Expression someExpression) {
		if (someExpression instanceof SingleNameReference) {
			return ((SingleNameReference)someExpression).binding;
		} else if (someExpression instanceof FieldReference) {
			FieldReference fieldRef = (FieldReference)someExpression;
			if (fieldRef.receiver.isThis() && !(fieldRef.receiver instanceof QualifiedThisReference)) {
				return fieldRef.binding;
			}			
//		} else if (someExpression instanceof PostfixExpression) { // recurse for postfix: i++ --> i
//			// note: "b = b++" is equivalent to doing nothing, not to "b++"
//			return getDirectBinding(((PostfixExpression) someExpression).lhs);
		}
		return null;
	}
	FieldBinding getLastField(Expression someExpression) {
	    if (someExpression instanceof SingleNameReference) {
	        if ((someExpression.bits & RestrictiveFlagMASK) == Binding.FIELD) {
	            return (FieldBinding) ((SingleNameReference)someExpression).binding;
	        }
	    } else if (someExpression instanceof FieldReference) {
	        return ((FieldReference)someExpression).binding;
	    } else if (someExpression instanceof QualifiedNameReference) {
	        QualifiedNameReference qName = (QualifiedNameReference) someExpression;
	        if (qName.otherBindings == null && ((someExpression.bits & RestrictiveFlagMASK) == Binding.FIELD)) {
	            return (FieldBinding)qName.binding;
	        } else {
	            return qName.otherBindings[qName.otherBindings.length - 1];
	        }
	    }
	    return null;
	}	

	public int nullStatus(FlowInfo flowInfo) {
		return this.expression.nullStatus(flowInfo);
	}
	
	public StringBuffer print(int indent, StringBuffer output) {

		//no () when used as a statement 
		printIndent(indent, output);
		return printExpressionNoParenthesis(indent, output);
	}
	public StringBuffer printExpression(int indent, StringBuffer output) {

		//subclass redefine printExpressionNoParenthesis()
		output.append('(');
		return printExpressionNoParenthesis(0, output).append(')');
	} 

	public StringBuffer printExpressionNoParenthesis(int indent, StringBuffer output) {

		lhs.printExpression(indent, output).append(" = "); //$NON-NLS-1$
		return expression.printExpression(0, output);
	}
	
	public StringBuffer printStatement(int indent, StringBuffer output) {

		//no () when used as a statement 
		return print(indent, output).append(';');
	}

	public TypeBinding resolveType(BlockScope scope) {

		// due to syntax lhs may be only a NameReference, a FieldReference or an ArrayReference
		this.constant = Constant.NotAConstant;
		if (!(this.lhs instanceof Reference) || this.lhs.isThis()) {
			scope.problemReporter().expressionShouldBeAVariable(this.lhs);
			return null;
		}
		TypeBinding lhsType = lhs.resolveType(scope);
		this.expression.setExpectedType(lhsType); // needed in case of generic method invocation
		if (lhsType != null) 
			this.resolvedType = lhsType.capture(scope, this.sourceEnd);
		TypeBinding rhsType = this.expression.resolveType(scope);
		if (lhsType == null || rhsType == null) {
			return null;
		}
		checkAssignmentEffect(scope);

		// Compile-time conversion of base-types : implicit narrowing integer into byte/short/character
		// may require to widen the rhs expression at runtime
		if (lhsType != rhsType) // must call before computeConversion() and typeMismatchError()
			scope.compilationUnitScope().recordTypeConversion(lhsType, rhsType);
		if ((this.expression.isConstantValueOfTypeAssignableToType(rhsType, lhsType)
				|| (lhsType.isBaseType() && BaseTypeBinding.isWidening(lhsType.id, rhsType.id)))
				|| rhsType.isCompatibleWith(lhsType)) {
			this.expression.computeConversion(scope, lhsType, rhsType);
			checkAssignment(scope, lhsType, rhsType);
			if (this.expression instanceof CastExpression 
					&& (this.expression.bits & ASTNode.UnnecessaryCast) == 0) {
				CastExpression.checkNeedForAssignedCast(scope, lhsType, (CastExpression) this.expression);
			}			
			return this.resolvedType;
		} else if (scope.isBoxingCompatibleWith(rhsType, lhsType) 
							|| (rhsType.isBaseType()  // narrowing then boxing ?
									&& scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5 // autoboxing
									&& !lhsType.isBaseType()
									&& this.expression.isConstantValueOfTypeAssignableToType(rhsType, scope.environment().computeBoxingType(lhsType)))) {
			this.expression.computeConversion(scope, lhsType, rhsType);
			if (this.expression instanceof CastExpression 
					&& (this.expression.bits & ASTNode.UnnecessaryCast) == 0) {
				CastExpression.checkNeedForAssignedCast(scope, lhsType, (CastExpression) this.expression);
			}			
			return this.resolvedType;
		} 
		scope.problemReporter().typeMismatchError(rhsType, lhsType, this.expression);
		return lhsType;
	}
	/* (non-Javadoc)
	 * @see org.eclipse.jdt.internal.compiler.ast.Expression#resolveTypeExpecting(org.eclipse.jdt.internal.compiler.lookup.BlockScope, org.eclipse.jdt.internal.compiler.lookup.TypeBinding)
	 */
	public TypeBinding resolveTypeExpecting(
			BlockScope scope,
			TypeBinding expectedType) {

		TypeBinding type = super.resolveTypeExpecting(scope, expectedType);
		if (type == null) return null;
		TypeBinding lhsType = this.resolvedType; 
		TypeBinding rhsType = this.expression.resolvedType;
		// signal possible accidental boolean assignment (instead of using '==' operator)
		if (expectedType == BooleanBinding 
				&& lhsType == BooleanBinding 
				&& (this.lhs.bits & IsStrictlyAssigned) != 0) {
			scope.problemReporter().possibleAccidentalBooleanAssignment((/*@OwnPar*/ /*@NoRep*/ Assignment)this);
		}
		checkAssignment(scope, lhsType, rhsType);
		return type;
	}

	public void traverse(ASTVisitor visitor, BlockScope scope) {
		
		if (visitor.visit((/*@OwnPar*/ /*@NoRep*/ Assignment)this, scope)) {
			lhs.traverse(visitor, scope);
			expression.traverse(visitor, scope);
		}
		visitor.endVisit((/*@OwnPar*/ /*@NoRep*/ Assignment)this, scope);
	}
}
