/*******************************************************************************
 * 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.core.compiler.CharOperation;
import net.sourceforge.phpdt.internal.compiler.ASTVisitor;
import net.sourceforge.phpdt.internal.compiler.CompilationResult;
import net.sourceforge.phpdt.internal.compiler.flow.FlowInfo;
import net.sourceforge.phpdt.internal.compiler.flow.InitializationFlowContext;
import net.sourceforge.phpdt.internal.compiler.impl.ReferenceContext;
import net.sourceforge.phpdt.internal.compiler.lookup.ClassScope;
import net.sourceforge.phpdt.internal.compiler.lookup.MethodBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.MethodScope;
import net.sourceforge.phpdt.internal.compiler.lookup.ReferenceBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.TypeBinding;
import net.sourceforge.phpdt.internal.compiler.parser.UnitParser;
import net.sourceforge.phpdt.internal.compiler.problem.AbortCompilation;
import net.sourceforge.phpdt.internal.compiler.problem.AbortCompilationUnit;
import net.sourceforge.phpdt.internal.compiler.problem.AbortMethod;
import net.sourceforge.phpdt.internal.compiler.problem.AbortType;
import net.sourceforge.phpdt.internal.compiler.problem.ProblemSeverities;

public abstract class AbstractMethodDeclaration extends ASTNode implements
		ProblemSeverities, ReferenceContext {

	public MethodScope scope;

	// it is not relevent for constructor but it helps to have the name of the
	// constructor here
	// which is always the name of the class.....parsing do extra work to fill
	// it up while it do not have to....
	public char[] selector;

	public int declarationSourceStart;

	public int declarationSourceEnd;

	public int modifiers;

	public int modifiersSourceStart;

	public Argument[] arguments;

	public TypeReference[] thrownExceptions;

	public Statement[] statements;

	public int explicitDeclarations;

	public MethodBinding binding;

	public boolean ignoreFurtherInvestigation = false;

	public boolean needFreeReturn = false;

	public int bodyStart;

	public int bodyEnd = -1;

	public CompilationResult compilationResult;

	AbstractMethodDeclaration(CompilationResult compilationResult) {
		this.compilationResult = compilationResult;
	}

	/*
	 * We cause the compilation task to abort to a given extent.
	 */
	public void abort(int abortLevel) {

		if (scope == null) {
			throw new AbortCompilation(); // cannot do better
		}

		CompilationResult compilationResult = scope.referenceCompilationUnit().compilationResult;

		switch (abortLevel) {
		case AbortCompilation:
			throw new AbortCompilation(compilationResult);
		case AbortCompilationUnit:
			throw new AbortCompilationUnit(compilationResult);
		case AbortType:
			throw new AbortType(compilationResult);
		default:
			throw new AbortMethod(compilationResult);
		}
	}

	public abstract void analyseCode(ClassScope scope,
			InitializationFlowContext initializationContext, FlowInfo info);

	/**
	 * Bind and add argument's binding into the scope of the method
	 */
	public void bindArguments() {

		if (arguments != null) {
			// by default arguments in abstract/native methods are considered to
			// be used (no complaint is expected)
			boolean used = binding == null || binding.isAbstract();// ||
																	// binding.isNative();

			int length = arguments.length;
			for (int i = 0; i < length; i++) {
				TypeBinding argType = binding == null ? null
						: binding.parameters[i];
				arguments[i].bind(scope, argType, used);
			}
		}
	}

	/**
	 * Record the thrown exception type bindings in the corresponding type
	 * references.
	 */
	public void bindThrownExceptions() {

		if (this.thrownExceptions != null && this.binding != null
				&& this.binding.thrownExceptions != null) {
			int thrownExceptionLength = this.thrownExceptions.length;
			int length = this.binding.thrownExceptions.length;
			if (length == thrownExceptionLength) {
				for (int i = 0; i < length; i++) {
					this.thrownExceptions[i].resolvedType = this.binding.thrownExceptions[i];
				}
			} else {
				int bindingIndex = 0;
				for (int i = 0; i < thrownExceptionLength
						&& bindingIndex < length; i++) {
					TypeReference thrownException = this.thrownExceptions[i];
					ReferenceBinding thrownExceptionBinding = this.binding.thrownExceptions[bindingIndex];
					char[][] bindingCompoundName = thrownExceptionBinding.compoundName;
					if (thrownException instanceof SingleTypeReference) {
						// single type reference
						int lengthName = bindingCompoundName.length;
						char[] thrownExceptionTypeName = thrownException
								.getTypeName()[0];
						if (CharOperation.equals(thrownExceptionTypeName,
								bindingCompoundName[lengthName - 1])) {
							thrownException.resolvedType = thrownExceptionBinding;
							bindingIndex++;
						}
					} else {
						// qualified type reference
						if (CharOperation.equals(thrownException.getTypeName(),
								bindingCompoundName)) {
							thrownException.resolvedType = thrownExceptionBinding;
							bindingIndex++;
						}
					}
				}
			}
		}
	}

	public CompilationResult compilationResult() {

		return this.compilationResult;
	}

	/**
	 * Bytecode generation for a method
	 */
	// public void generateCode(ClassScope classScope, ClassFile classFile) {
	//		
	// int problemResetPC = 0;
	// classFile.codeStream.wideMode = false; // reset wideMode to false
	// if (ignoreFurtherInvestigation) {
	// // method is known to have errors, dump a problem method
	// if (this.binding == null)
	// return; // handle methods with invalid signature or duplicates
	// int problemsLength;
	// IProblem[] problems =
	// scope.referenceCompilationUnit().compilationResult.getProblems();
	// IProblem[] problemsCopy = new IProblem[problemsLength = problems.length];
	// System.arraycopy(problems, 0, problemsCopy, 0, problemsLength);
	// classFile.addProblemMethod(this, binding, problemsCopy);
	// return;
	// }
	// // regular code generation
	// try {
	// problemResetPC = classFile.contentsOffset;
	// this.generateCode(classFile);
	// } catch (AbortMethod e) {
	// // a fatal error was detected during code generation, need to restart
	// code gen if possible
	// if (e.compilationResult == CodeStream.RESTART_IN_WIDE_MODE) {
	// // a branch target required a goto_w, restart code gen in wide mode.
	// try {
	// this.traverse(new ResetStateForCodeGenerationVisitor(), classScope);
	// classFile.contentsOffset = problemResetPC;
	// classFile.methodCount--;
	// classFile.codeStream.wideMode = true; // request wide mode
	// this.generateCode(classFile); // restart method generation
	// } catch (AbortMethod e2) {
	// int problemsLength;
	// IProblem[] problems =
	// scope.referenceCompilationUnit().compilationResult.getAllProblems();
	// IProblem[] problemsCopy = new IProblem[problemsLength = problems.length];
	// System.arraycopy(problems, 0, problemsCopy, 0, problemsLength);
	// classFile.addProblemMethod(this, binding, problemsCopy, problemResetPC);
	// }
	// } else {
	// // produce a problem method accounting for this fatal error
	// int problemsLength;
	// IProblem[] problems =
	// scope.referenceCompilationUnit().compilationResult.getAllProblems();
	// IProblem[] problemsCopy = new IProblem[problemsLength = problems.length];
	// System.arraycopy(problems, 0, problemsCopy, 0, problemsLength);
	// classFile.addProblemMethod(this, binding, problemsCopy, problemResetPC);
	// }
	// }
	// }
	//
	// private void generateCode(ClassFile classFile) {
	//
	// classFile.generateMethodInfoHeader(binding);
	// int methodAttributeOffset = classFile.contentsOffset;
	// int attributeNumber = classFile.generateMethodInfoAttribute(binding);
	// if ((!binding.isNative()) && (!binding.isAbstract())) {
	// int codeAttributeOffset = classFile.contentsOffset;
	// classFile.generateCodeAttributeHeader();
	// CodeStream codeStream = classFile.codeStream;
	// codeStream.reset(this, classFile);
	// // initialize local positions
	// this.scope.computeLocalVariablePositions(binding.isStatic() ? 0 : 1,
	// codeStream);
	//
	// // arguments initialization for local variable debug attributes
	// if (arguments != null) {
	// for (int i = 0, max = arguments.length; i < max; i++) {
	// LocalVariableBinding argBinding;
	// codeStream.addVisibleLocalVariable(argBinding = arguments[i].binding);
	// argBinding.recordInitializationStartPC(0);
	// }
	// }
	// if (statements != null) {
	// for (int i = 0, max = statements.length; i < max; i++)
	// statements[i].generateCode(scope, codeStream);
	// }
	// if (this.needFreeReturn) {
	// codeStream.return_();
	// }
	// // local variable attributes
	// codeStream.exitUserScope(scope);
	// codeStream.recordPositionsFrom(0, this.declarationSourceEnd);
	// classFile.completeCodeAttribute(codeAttributeOffset);
	// attributeNumber++;
	// } else {
	// checkArgumentsSize();
	// }
	// classFile.completeMethodInfo(methodAttributeOffset, attributeNumber);
	//
	// // if a problem got reported during code gen, then trigger problem method
	// creation
	// if (ignoreFurtherInvestigation) {
	// throw new
	// AbortMethod(scope.referenceCompilationUnit().compilationResult);
	// }
	// }
	// private void checkArgumentsSize() {
	// TypeBinding[] parameters = binding.parameters;
	// int size = 1; // an abstact method or a native method cannot be static
	// for (int i = 0, max = parameters.length; i < max; i++) {
	// TypeBinding parameter = parameters[i];
	// if (parameter == LongBinding || parameter == DoubleBinding) {
	// size += 2;
	// } else {
	// size++;
	// }
	// if (size > 0xFF) {
	// scope.problemReporter().noMoreAvailableSpaceForArgument(scope.locals[i],
	// scope.locals[i].declaration);
	// }
	// }
	// }
	public boolean hasErrors() {
		return this.ignoreFurtherInvestigation;
	}

	public boolean isAbstract() {

		if (binding != null)
			return binding.isAbstract();
		return (modifiers & AccAbstract) != 0;
	}

	public boolean isClinit() {

		return false;
	}

	public boolean isConstructor() {

		return false;
	}

	public boolean isDefaultConstructor() {

		return false;
	}

	public boolean isInitializationMethod() {

		return false;
	}

	// public boolean isNative() {
	//
	// if (binding != null)
	// return binding.isNative();
	// return (modifiers & AccNative) != 0;
	// }

	public boolean isStatic() {

		if (binding != null)
			return binding.isStatic();
		return (modifiers & AccStatic) != 0;
	}

	/**
	 * Fill up the method body with statement
	 */
	public abstract void parseStatements(UnitParser parser,
			CompilationUnitDeclaration unit);

	public StringBuffer print(int tab, StringBuffer output) {

		printIndent(tab, output);
		printModifiers(this.modifiers, output);
		printReturnType(0, output).append(this.selector).append('(');
		if (this.arguments != null) {
			for (int i = 0; i < this.arguments.length; i++) {
				if (i > 0)
					output.append(", "); //$NON-NLS-1$
				this.arguments[i].print(0, output);
			}
		}
		output.append(')');
		if (this.thrownExceptions != null) {
			output.append(" throws "); //$NON-NLS-1$
			for (int i = 0; i < this.thrownExceptions.length; i++) {
				if (i > 0)
					output.append(", "); //$NON-NLS-1$
				this.thrownExceptions[i].print(0, output);
			}
		}
		printBody(tab + 1, output);
		return output;
	}

	public StringBuffer printBody(int indent, StringBuffer output) {

		if (isAbstract() || (this.modifiers & AccSemicolonBody) != 0)
			return output.append(';');

		output.append(" {"); //$NON-NLS-1$
		if (this.statements != null) {
			for (int i = 0; i < this.statements.length; i++) {
				output.append('\n');
				this.statements[i].printStatement(indent, output);
			}
		}
		output.append('\n'); //$NON-NLS-1$
		printIndent(indent == 0 ? 0 : indent - 1, output).append('}');
		return output;
	}

	public StringBuffer printReturnType(int indent, StringBuffer output) {

		return output;
	}

	public void resolve(ClassScope upperScope) {

		if (binding == null) {
			ignoreFurtherInvestigation = true;
		}

		try {
			bindArguments();
			bindThrownExceptions();
			resolveStatements();
		} catch (AbortMethod e) { // ========= abort on fatal error
									// =============
			this.ignoreFurtherInvestigation = true;
		}
	}

	public void resolveStatements() {

		if (statements != null) {
			int i = 0, length = statements.length;
			while (i < length)
				statements[i++].resolve(scope);
		}
	}

	public String returnTypeToString(int tab) {

		return ""; //$NON-NLS-1$
	}

	public void tagAsHavingErrors() {

		ignoreFurtherInvestigation = true;
	}

	public String toString(int tab) {

		String s = tabString(tab);
		if (modifiers != AccDefault) {
			s += modifiersString(modifiers);
		}

		s += returnTypeToString(0);
		s += new String(selector) + "("; //$NON-NLS-1$
		if (arguments != null) {
			for (int i = 0; i < arguments.length; i++) {
				s += arguments[i].toString(0);
				if (i != (arguments.length - 1))
					s = s + ", "; //$NON-NLS-1$
			}
			;
		}
		;
		s += ")"; //$NON-NLS-1$
		if (thrownExceptions != null) {
			s += " throws "; //$NON-NLS-1$
			for (int i = 0; i < thrownExceptions.length; i++) {
				s += thrownExceptions[i].toString(0);
				if (i != (thrownExceptions.length - 1))
					s = s + ", "; //$NON-NLS-1$
			}
			;
		}
		;

		s += toStringStatements(tab + 1);
		return s;
	}

	public String toStringStatements(int tab) {

		if (isAbstract() || (this.modifiers & AccSemicolonBody) != 0)
			return ";"; //$NON-NLS-1$

		String s = " {"; //$NON-NLS-1$
		if (statements != null) {
			for (int i = 0; i < statements.length; i++) {
				s = s + "\n" + statements[i].toString(tab); //$NON-NLS-1$
				if (!(statements[i] instanceof Block)) {
					s += ";"; //$NON-NLS-1$
				}
			}
		}
		s += "\n" + tabString(tab == 0 ? 0 : tab - 1) + "}"; //$NON-NLS-2$ //$NON-NLS-1$
		return s;
	}

	public void traverse(ASTVisitor visitor, ClassScope classScope) {
	}
}
