/*******************************************************************************
 * 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 net.sourceforge.phpdt.internal.core.util;

import java.util.ArrayList;

import net.sourceforge.phpdt.core.IField;
import net.sourceforge.phpdt.core.IJavaElement;
import net.sourceforge.phpdt.core.IMethod;
import net.sourceforge.phpdt.core.IType;
import net.sourceforge.phpdt.core.compiler.CharOperation;
import net.sourceforge.phpdt.internal.compiler.ASTVisitor;
import net.sourceforge.phpdt.internal.compiler.ast.AbstractMethodDeclaration;
import net.sourceforge.phpdt.internal.compiler.ast.Argument;
import net.sourceforge.phpdt.internal.compiler.ast.CompilationUnitDeclaration;
import net.sourceforge.phpdt.internal.compiler.ast.FieldDeclaration;
import net.sourceforge.phpdt.internal.compiler.ast.TypeDeclaration;
import net.sourceforge.phpdt.internal.compiler.ast.TypeReference;
import net.sourceforge.phpdt.internal.compiler.lookup.BlockScope;
import net.sourceforge.phpdt.internal.compiler.lookup.ClassScope;

/**
 * Finds an ASTNode given an IJavaElement in a CompilationUnitDeclaration
 */
public class ASTNodeFinder {
	private CompilationUnitDeclaration unit;

	public ASTNodeFinder(CompilationUnitDeclaration unit) {
		this.unit = unit;
	}

	/*
	 * Finds the FieldDeclaration in the given ast corresponding to the given
	 * field handle. Returns null if not found.
	 */
	public FieldDeclaration findField(IField fieldHandle) {
		TypeDeclaration typeDecl = findType((IType) fieldHandle.getParent());
		if (typeDecl == null)
			return null;
		FieldDeclaration[] fields = typeDecl.fields;
		if (fields != null) {
			char[] fieldName = fieldHandle.getElementName().toCharArray();
			for (int i = 0, length = fields.length; i < length; i++) {
				FieldDeclaration field = fields[i];
				if (CharOperation.equals(fieldName, field.name)) {
					return field;
				}
			}
		}
		return null;
	}

	/*
	 * Finds the Initializer in the given ast corresponding to the given
	 * initializer handle. Returns null if not found.
	 */
	// public Initializer findInitializer(IInitializer initializerHandle) {
	// TypeDeclaration typeDecl =
	// findType((IType)initializerHandle.getParent());
	// if (typeDecl == null) return null;
	// FieldDeclaration[] fields = typeDecl.fields;
	// if (fields != null) {
	// int occurenceCount = ((JavaElement)initializerHandle).occurrenceCount;
	// for (int i = 0, length = fields.length; i < length; i++) {
	// FieldDeclaration field = fields[i];
	// if (field instanceof Initializer && --occurenceCount == 0) {
	// return (Initializer)field;
	// }
	// }
	// }
	// return null;
	// }
	/*
	 * Finds the AbstractMethodDeclaration in the given ast corresponding to the
	 * given method handle. Returns null if not found.
	 */
	public AbstractMethodDeclaration findMethod(IMethod methodHandle) {
		TypeDeclaration typeDecl = findType((IType) methodHandle.getParent());
		if (typeDecl == null)
			return null;
		AbstractMethodDeclaration[] methods = typeDecl.methods;
		if (methods != null) {
			char[] selector = methodHandle.getElementName().toCharArray();
			String[] parameterTypeSignatures = methodHandle.getParameterTypes();
			int parameterCount = parameterTypeSignatures.length;
			nextMethod: for (int i = 0, length = methods.length; i < length; i++) {
				AbstractMethodDeclaration method = methods[i];
				if (CharOperation.equals(selector, method.selector)) {
					Argument[] args = method.arguments;
					int argsLength = args == null ? 0 : args.length;
					if (argsLength == parameterCount) {
						for (int j = 0; j < parameterCount; j++) {
							TypeReference type = args[j].type;
							String signature = Util.typeSignature(type);
							if (!signature.equals(parameterTypeSignatures[j])) {
								continue nextMethod;
							}
						}
						return method;
					}
				}
			}
		}
		return null;
	}

	/*
	 * Finds the TypeDeclaration in the given ast corresponding to the given
	 * type handle. Returns null if not found.
	 */
	public TypeDeclaration findType(IType typeHandle) {
		IJavaElement parent = typeHandle.getParent();
		final char[] typeName = typeHandle.getElementName().toCharArray();
		// final int occurenceCount = ((SourceType)typeHandle).occurrenceCount;
		final boolean findAnonymous = typeName.length == 0;
		class Visitor extends ASTVisitor {
			TypeDeclaration result;

			int count = 0;

			public boolean visit(TypeDeclaration typeDeclaration,
					BlockScope scope) {
				if (result != null)
					return false;
				// if ((typeDeclaration.bits & ASTNode.IsAnonymousTypeMASK) !=
				// 0) {
				// if (findAnonymous && ++count == occurenceCount) {
				// result = typeDeclaration;
				// }
				// } else {
				if (!findAnonymous
						&& CharOperation.equals(typeName, typeDeclaration.name)) {
					result = typeDeclaration;
				}
				// }
				return false; // visit only one level
			}
		}
		switch (parent.getElementType()) {
		case IJavaElement.COMPILATION_UNIT:
			ArrayList types = this.unit.types;
			if (types != null) {
				for (int i = 0, length = types.size(); i < length; i++) {
					TypeDeclaration type = (TypeDeclaration) types.get(i);// [i];
					if (CharOperation.equals(typeName, type.name)) {
						return type;
					}
				}
			}
			break;
		case IJavaElement.TYPE:
			TypeDeclaration parentDecl = findType((IType) parent);
			if (parentDecl == null)
				return null;
			// types = parentDecl.memberTypes;
			// if (types != null) {
			// for (int i = 0, length = types.length; i < length; i++) {
			// TypeDeclaration type = types[i];
			// if (CharOperation.equals(typeName, type.name)) {
			// return type;
			// }
			// }
			// }
			break;
		case IJavaElement.FIELD:
			FieldDeclaration fieldDecl = findField((IField) parent);
			if (fieldDecl == null)
				return null;
			Visitor visitor = new Visitor();
			fieldDecl.traverse(visitor, null);
			return visitor.result;
			// case IJavaElement.INITIALIZER:
			// Initializer initializer = findInitializer((IInitializer)parent);
			// if (initializer == null) return null;
			// visitor = new Visitor();
			// initializer.traverse(visitor, null);
			// return visitor.result;
		case IJavaElement.METHOD:
			AbstractMethodDeclaration methodDecl = findMethod((IMethod) parent);
			if (methodDecl == null)
				return null;
			visitor = new Visitor();
			methodDecl.traverse(visitor, (ClassScope) null);
			return visitor.result;
		}
		return null;
	}
}
