/*******************************************************************************
 * Copyright (c) 2000, 2006 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
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;

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 abstract class Statement extends ASTNode {
	
	public abstract FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo);
	
	/**
	 * INTERNAL USE ONLY.
	 * This is used to redirect inter-statements jumps.
	 */
	public void branchChainTo(BranchLabel label) {
		// do nothing by default
	}
	
	// Report an error if necessary
	public boolean complainIfUnreachable(FlowInfo flowInfo, BlockScope scope, boolean didAlreadyComplain) {
	
		if ((flowInfo.reachMode() & FlowInfo.UNREACHABLE) != 0) {
			this.bits &= ~ASTNode.IsReachable;
			boolean reported = flowInfo == FlowInfo.DEAD_END;
			if (!didAlreadyComplain && reported) {
				scope.problemReporter().unreachableCode(this);
			}
			return reported; // keep going for fake reachable
		}
		return false;
	}

	/**
	 * Generate invocation arguments, considering varargs methods
	 */
	public void generateArguments(MethodBinding binding, Expression[] arguments, BlockScope currentScope, CodeStream codeStream) {
		
		if (binding.isVarargs()) {
			// 5 possibilities exist for a call to the vararg method foo(int i, int ... value) : 
			//      foo(1), foo(1, null), foo(1, 2), foo(1, 2, 3, 4) & foo(1, new int[] {1, 2})
			TypeBinding[] params = binding.parameters;
			int paramLength = params.length;
			int varArgIndex = paramLength - 1;
			for (int i = 0; i < varArgIndex; i++) {
				arguments[i].generateCode(currentScope, codeStream, true);
			}

			ArrayBinding varArgsType = (ArrayBinding) params[varArgIndex]; // parameterType has to be an array type
			ArrayBinding codeGenVarArgsType = (ArrayBinding) binding.parameters[varArgIndex].erasure();
			int elementsTypeID = varArgsType.elementsType().id;
			int argLength = arguments == null ? 0 : arguments.length;

			if (argLength > paramLength) {
				// right number but not directly compatible or too many arguments - wrap extra into array
				// called with (argLength - lastIndex) elements : foo(1, 2) or foo(1, 2, 3, 4)
				// need to gen elements into an array, then gen each remaining element into created array
				codeStream.generateInlinedValue(argLength - varArgIndex);
				codeStream.newArray(codeGenVarArgsType); // create a mono-dimensional array
				for (int i = varArgIndex; i < argLength; i++) {
					codeStream.dup();
					codeStream.generateInlinedValue(i - varArgIndex);
					arguments[i].generateCode(currentScope, codeStream, true);
					codeStream.arrayAtPut(elementsTypeID, false);
				}
			} else if (argLength == paramLength) {
				// right number of arguments - could be inexact - pass argument as is
				TypeBinding lastType = arguments[varArgIndex].resolvedType;
				if (lastType == TypeBinding.NULL
					|| (varArgsType.dimensions() == lastType.dimensions()
						&& lastType.isCompatibleWith(varArgsType))) {
					// foo(1, new int[]{2, 3}) or foo(1, null) --> last arg is passed as-is
					arguments[varArgIndex].generateCode(currentScope, codeStream, true);
				} else {
					// right number but not directly compatible or too many arguments - wrap extra into array
					// need to gen elements into an array, then gen each remaining element into created array
					codeStream.generateInlinedValue(1);
					codeStream.newArray(codeGenVarArgsType); // create a mono-dimensional array
					codeStream.dup();
					codeStream.generateInlinedValue(0);
					arguments[varArgIndex].generateCode(currentScope, codeStream, true);
					codeStream.arrayAtPut(elementsTypeID, false);
				}
			} else { // not enough arguments - pass extra empty array
				// scenario: foo(1) --> foo(1, new int[0])
				// generate code for an empty array of parameterType
				codeStream.generateInlinedValue(0);
				codeStream.newArray(codeGenVarArgsType); // create a mono-dimensional array
			}
		} else if (arguments != null) { // standard generation for method arguments
			for (int i = 0, max = arguments.length; i < max; i++)
				arguments[i].generateCode(currentScope, codeStream, true);
		}
	}

	public abstract void generateCode(BlockScope currentScope, CodeStream codeStream);
	
	public boolean isEmptyBlock() {
		return false;
	}
	
	public boolean isValidJavaStatement() {
		//the use of this method should be avoid in most cases
		//and is here mostly for documentation purpose.....
		//while the parser is responsable for creating
		//welled formed expression statement, which results
		//in the fact that java-non-semantic-expression-used-as-statement
		//should not be parsable...thus not being built.
		//It sounds like the java grammar as help the compiler job in removing
		//-by construction- some statement that would have no effect....
		//(for example all expression that may do side-effects are valid statement
		// -this is an appromative idea.....-)

		return true;
	}
	
	public StringBuffer print(int indent, StringBuffer output) {
		return printStatement(indent, output);
	}
	public abstract StringBuffer printStatement(int indent, StringBuffer output);

	public abstract void resolve(BlockScope scope);
	
	/**
	 * Returns case constant associated to this statement (NotAConstant if none)
	 */
	public Constant resolveCase(BlockScope scope, TypeBinding testType, SwitchStatement switchStatement) {
		// statement within a switch that are not case are treated as normal statement.... 

		resolve(scope);
		return Constant.NotAConstant;
	}

}
