/*******************************************************************************
 * 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.jdt.internal.core.util;

/**
 * Converter from element info to parsed compilation unit.
 *
 */

import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.ast.*;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.env.*;
import org.eclipse.jdt.internal.compiler.env.ISourceField;
import org.eclipse.jdt.internal.compiler.env.ISourceImport;
import org.eclipse.jdt.internal.compiler.env.ISourceMethod;
import org.eclipse.jdt.internal.compiler.env.ISourceType;

import org.eclipse.jdt.internal.compiler.lookup.CompilerModifiers;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.jdt.internal.core.*;
import org.eclipse.jdt.internal.core.JavaElement;
import org.eclipse.jdt.internal.core.SourceFieldElementInfo;
import org.eclipse.jdt.internal.core.SourceMethodElementInfo;

public class ElementInfoConverter implements CompilerModifiers {

	/*
	 * Convert a set of source type infos into a parsed compilation unit declaration
	 * The argument types are then all grouped in the same unit. The argument types must 
	 * at least contain one type.
	 * Can optionally add local and anonymous types
	 */
	public static CompilationUnitDeclaration buildCompilationUnit(
		SourceTypeElementInfo[] sourceTypes,
		boolean needLocalTypes,
		ProblemReporter problemReporter,
		CompilationResult compilationResult) {

		return 
			new ElementInfoConverter(needLocalTypes, problemReporter).convert(
				sourceTypes, 
				compilationResult);
	}
	
	private boolean needLocalTypes; // local and anoymous types
	private ProblemReporter problemReporter;
	private CompilationUnitDeclaration unit;
	
	private ElementInfoConverter(boolean needLocalTypes, ProblemReporter problemReporter) {
		this.needLocalTypes = needLocalTypes;
		this.problemReporter = problemReporter;
	}
	
	/*
	 * Convert an initializerinfo into a parsed initializer declaration
	 */
	private Initializer convert(InitializerElementInfo initializerInfo, CompilationResult compilationResult) {

		Block block = new Block(0);
		Initializer initializer = new Initializer(block, IConstants.AccDefault);

		int start = initializerInfo.getDeclarationSourceStart();
		int end = initializerInfo.getDeclarationSourceEnd();

		initializer.name = initializerInfo.getName();
		initializer.sourceStart = initializer.declarationSourceStart = start;
		initializer.sourceEnd = initializer.declarationSourceEnd = end;
		initializer.modifiers = initializerInfo.getModifiers();

		/* convert local and anonymous types */
		IJavaElement[] children = initializerInfo.getChildren();
		int typesLength = children.length;
		if (typesLength > 0) {
			Statement[] statements = new Statement[typesLength];
			for (int i = 0; i < typesLength; i++) {
				JavaElement type = (JavaElement)children[i];
				try {
					TypeDeclaration localType = convert((SourceTypeElementInfo)type.getElementInfo(), compilationResult);
					if ((localType.bits & ASTNode.IsAnonymousTypeMASK) != 0) {
						QualifiedAllocationExpression expression = new QualifiedAllocationExpression(localType);
						expression.type = localType.superclass;
						localType.superclass = null;
						localType.superInterfaces = null;
						localType.allocation = expression;
						statements[i] = expression;
					} else {
						statements[i] = localType;
					}
				} catch (JavaModelException e) {
					// ignore
				}
			}
			block.statements = statements;
		}
		
		return initializer;
	}

	/*
	 * Convert a source field info into a parsed field declaration
	 */
	private FieldDeclaration convert(SourceFieldElementInfo sourceField, CompilationResult compilationResult) {

		FieldDeclaration field = new FieldDeclaration();

		int start = sourceField.getNameSourceStart();
		int end = sourceField.getNameSourceEnd();

		field.name = sourceField.getName();
		field.sourceStart = start;
		field.sourceEnd = end;
		field.type = createTypeReference(sourceField.getTypeName(), start, end);
		field.declarationSourceStart = sourceField.getDeclarationSourceStart();
		field.declarationSourceEnd = sourceField.getDeclarationSourceEnd();
		field.modifiers = sourceField.getModifiers();

		/* convert local and anonymous types */
		if (this.needLocalTypes) {
			IJavaElement[] children = sourceField.getChildren();
			int typesLength = children.length;
			if (typesLength > 0) {
				ArrayInitializer initializer = new ArrayInitializer();
				field.initialization = initializer;
				Expression[] expressions = new Expression[typesLength];
				initializer.expressions = expressions;
				for (int i = 0; i < typesLength; i++) {
					IJavaElement localType = children[i];
					try {
						TypeDeclaration anonymousLocalTypeDeclaration = convert((SourceTypeElementInfo)((JavaElement)localType).getElementInfo(),compilationResult);
						QualifiedAllocationExpression expression = new QualifiedAllocationExpression(anonymousLocalTypeDeclaration);
						expression.type = anonymousLocalTypeDeclaration.superclass;
						anonymousLocalTypeDeclaration.superclass = null;
						anonymousLocalTypeDeclaration.superInterfaces = null;
						anonymousLocalTypeDeclaration.allocation = expression;
						expressions[i] = expression;
					} catch (JavaModelException e) {
						// ignore
					}
				}
			}
		}
		
		return field;
	}

	/*
	 * Convert a source method info into a parsed method/constructor declaration 
	 */
	private AbstractMethodDeclaration convert(SourceMethodElementInfo sourceMethod, CompilationResult compilationResult) {

		AbstractMethodDeclaration method;

		/* only source positions available */
		int start = sourceMethod.getNameSourceStart();
		int end = sourceMethod.getNameSourceEnd();

		if (sourceMethod.isConstructor()) {
			ConstructorDeclaration decl = new ConstructorDeclaration(compilationResult);
			decl.isDefaultConstructor = false;
			method = decl;
		} else {
			MethodDeclaration decl = new MethodDeclaration(compilationResult);
			/* convert return type */
			decl.returnType =
				createTypeReference(sourceMethod.getReturnTypeName(), start, end);
			method = decl;
		}
		method.selector = sourceMethod.getSelector();
		method.modifiers = sourceMethod.getModifiers();
		method.sourceStart = start;
		method.sourceEnd = end;
		method.declarationSourceStart = sourceMethod.getDeclarationSourceStart();
		method.declarationSourceEnd = sourceMethod.getDeclarationSourceEnd();

		/* convert arguments */
		char[][] argumentTypeNames = sourceMethod.getArgumentTypeNames();
		char[][] argumentNames = sourceMethod.getArgumentNames();
		int argumentCount = argumentTypeNames == null ? 0 : argumentTypeNames.length;
		long position = (long) start << 32 + end;
		method.arguments = new Argument[argumentCount];
		for (int i = 0; i < argumentCount; i++) {
			method.arguments[i] =
				new Argument(
					argumentNames[i],
					position,
					createTypeReference(argumentTypeNames[i], start, end),
					AccDefault);
			// do not care whether was final or not
		}

		/* convert thrown exceptions */
		char[][] exceptionTypeNames = sourceMethod.getExceptionTypeNames();
		int exceptionCount = exceptionTypeNames == null ? 0 : exceptionTypeNames.length;
		method.thrownExceptions = new TypeReference[exceptionCount];
		for (int i = 0; i < exceptionCount; i++) {
			method.thrownExceptions[i] =
				createTypeReference(exceptionTypeNames[i], start, end);
		}
		
		/* convert local and anonymous types */
		if (this.needLocalTypes) {
			IJavaElement[] children = sourceMethod.getChildren();
			int typesLength = children.length;
			if (typesLength != 0) {
				Statement[] statements = new Statement[typesLength];
				for (int i = 0; i < typesLength; i++) {
					JavaElement type = (JavaElement)children[i];
					try {
						TypeDeclaration localType = convert((SourceTypeElementInfo)type.getElementInfo(), compilationResult);
						if ((localType.bits & ASTNode.IsAnonymousTypeMASK) != 0) {
							QualifiedAllocationExpression expression = new QualifiedAllocationExpression(localType);
							expression.type = localType.superclass;
							localType.superclass = null;
							localType.superInterfaces = null;
							localType.allocation = expression;
							statements[i] = expression;
						} else {
							statements[i] = localType;
						}
					} catch (JavaModelException e) {
						// ignore
					}
				}
				method.statements = statements;
			}
		}
		
		return method;
	}

	/*
	 * Convert a source type info into a parsed type declaration
	 */
	private TypeDeclaration convert(SourceTypeElementInfo sourceType, CompilationResult compilationResult) {
		
		/* create type declaration - can be member type, local type or anonymous type */
		TypeDeclaration type = new TypeDeclaration(compilationResult);
		if (sourceType.getEnclosingType() == null) {
			IType typeHandle = sourceType.getHandle();
			try {
				if (typeHandle.isAnonymous()) {
					type.name = TypeDeclaration.ANONYMOUS_EMPTY_NAME;
					type.bits |= ASTNode.AnonymousAndLocalMask;
				} else {
					if (typeHandle.isLocal()) {
						type.bits |= ASTNode.IsLocalTypeMASK;
					}
				}
			} catch (JavaModelException e) {
				// could not figure, assume toplevel
			}
		}  else {
			type.bits |= ASTNode.IsMemberTypeMASK;
		}
		if ((type.bits & ASTNode.IsAnonymousTypeMASK) == 0) {
			type.name = sourceType.getName();
		}
		int start, end; // only positions available
		type.sourceStart = start = sourceType.getNameSourceStart();
		type.sourceEnd = end = sourceType.getNameSourceEnd();
		type.modifiers = sourceType.getModifiers();
		type.declarationSourceStart = sourceType.getDeclarationSourceStart();
		type.declarationSourceEnd = sourceType.getDeclarationSourceEnd();
		type.bodyEnd = type.declarationSourceEnd;

		/* set superclass and superinterfaces */
		if (sourceType.getSuperclassName() != null)
			type.superclass =
				createTypeReference(sourceType.getSuperclassName(), start, end);
		char[][] interfaceNames = sourceType.getInterfaceNames();
		int interfaceCount = interfaceNames == null ? 0 : interfaceNames.length;
		type.superInterfaces = new TypeReference[interfaceCount];
		for (int i = 0; i < interfaceCount; i++) {
			type.superInterfaces[i] = createTypeReference(interfaceNames[i], start, end);
		}
		
		/* convert member types */
		ISourceType[] sourceMemberTypes = sourceType.getMemberTypes();
		int sourceMemberTypeCount =
			sourceMemberTypes == null ? 0 : sourceMemberTypes.length;
		type.memberTypes = new TypeDeclaration[sourceMemberTypeCount];
		for (int i = 0; i < sourceMemberTypeCount; i++) {
			type.memberTypes[i] = convert((SourceTypeElementInfo)sourceMemberTypes[i], compilationResult);
		}
		
		/* convert fields and initializers */
		ISourceField[] sourceFields = sourceType.getFields();
		int sourceFieldCount = sourceFields == null ? 0 : sourceFields.length;
		InitializerElementInfo[] initializers = null;
		int initializerCount = 0;
		if (this.needLocalTypes) {
			initializers = sourceType.getInitializers();
			initializerCount = initializers.length;
			type.fields = new FieldDeclaration[initializerCount + sourceFieldCount];
			for (int i = 0; i < initializerCount; i++) {
				type.fields[i] = convert(initializers[i], compilationResult);
			}
		} else {
			type.fields = new FieldDeclaration[sourceFieldCount];
		}
		int length = initializerCount + sourceFieldCount;
		int index = 0;
		for (int i = initializerCount; i < length; i++) {
			type.fields[i] = convert((SourceFieldElementInfo)sourceFields[index++], compilationResult);
		}

		/* convert methods - need to add default constructor if necessary */
		ISourceMethod[] sourceMethods = sourceType.getMethods();
		int sourceMethodCount = sourceMethods == null ? 0 : sourceMethods.length;

		/* source type has a constructor ?           */
		/* by default, we assume that one is needed. */
		int neededCount = 0;
		if (!type.isInterface()) {
			neededCount = 1;
			for (int i = 0; i < sourceMethodCount; i++) {
				if (sourceMethods[i].isConstructor()) {
					neededCount = 0;
					// Does not need the extra constructor since one constructor already exists.
					break;
				}
			}
		}
		type.methods = new AbstractMethodDeclaration[sourceMethodCount + neededCount];
		if (neededCount != 0) { // add default constructor in first position
			type.methods[0] = type.createsInternalConstructor(false, false);
		}
		boolean isInterface = type.isInterface();
		for (int i = 0; i < sourceMethodCount; i++) {
			AbstractMethodDeclaration method =convert((SourceMethodElementInfo)sourceMethods[i], compilationResult);
			if (isInterface || method.isAbstract()) { // fix-up flag 
				method.modifiers |= AccSemicolonBody;
			}
			type.methods[neededCount + i] = method;
		}

		return type;
	}

	/*
	 * Convert a set of source element types into a parsed compilation unit declaration
	 * The argument types are then all grouped in the same unit. The argument types must 
	 * at least contain one type.
	 */
	private CompilationUnitDeclaration convert(SourceTypeElementInfo[] sourceTypes, CompilationResult compilationResult) {
		
		SourceTypeElementInfo sourceType = sourceTypes[0];
		if (sourceType.getName() == null)
			return null; // do a basic test that the sourceType is valid

		this.unit = new CompilationUnitDeclaration(this.problemReporter, compilationResult, 0);
		// not filled at this point

		/* only positions available */
		int start = sourceType.getNameSourceStart();
		int end = sourceType.getNameSourceEnd();

		/* convert package and imports */
		if (sourceType.getPackageName() != null
			&& sourceType.getPackageName().length > 0)
			// if its null then it is defined in the default package
			this.unit.currentPackage =
				createImportReference(sourceType.getPackageName(), start, end, false, AccDefault);
		ISourceImport[]  sourceImports = sourceType.getImports();
		int importCount = sourceImports == null ? 0 : sourceImports.length;
		this.unit.imports = new ImportReference[importCount];
		for (int i = 0; i < importCount; i++) {
			ISourceImport sourceImport = sourceImports[i];
			this.unit.imports[i] = createImportReference(
				sourceImport.getName(), 
				sourceImport.getDeclarationSourceStart(),
				sourceImport.getDeclarationSourceEnd(),
				sourceImport.onDemand(),
				sourceImport.getModifiers());
		}
		/* convert type(s) */
		int typeCount = sourceTypes.length;
		this.unit.types = new TypeDeclaration[typeCount];
		for (int i = 0; i < typeCount; i++) {
			this.unit.types[i] = convert(sourceTypes[i], compilationResult);
		}
		return this.unit;
	}

	/*
	 * Build an import reference from an import name, e.g. java.lang.*
	 */
	private ImportReference createImportReference(
		char[] importName,
		int start,
		int end, 
		boolean onDemand,
		int modifiers) {
	
		char[][] qImportName = CharOperation.splitOn('.', importName);
		long[] positions = new long[qImportName.length];
		long position = (long) start << 32 + end;
		for (int i = 0; i < qImportName.length; i++) {
			positions[i] = position; // dummy positions
		}
		return new ImportReference(
			qImportName,
			positions,
			onDemand,
			modifiers);
	}

	/*
	 * Build a type reference from a readable name, e.g. java.lang.Object[][]
	 */
	private TypeReference createTypeReference(
		char[] typeSignature,
		int start,
		int end) {

		/* count identifiers and dimensions */
		int max = typeSignature.length;
		int dimStart = max;
		int dim = 0;
		int identCount = 1;
		for (int i = 0; i < max; i++) {
			switch (typeSignature[i]) {
				case '[' :
					if (dim == 0)
						dimStart = i;
					dim++;
					break;
				case '.' :
					identCount++;
					break;
			}
		}
		/* rebuild identifiers and dimensions */
		if (identCount == 1) { // simple type reference
			if (dim == 0) {
				return new SingleTypeReference(typeSignature, (((long) start )<< 32) + end);
			} else {
				char[] identifier = new char[dimStart];
				System.arraycopy(typeSignature, 0, identifier, 0, dimStart);
				return new ArrayTypeReference(identifier, dim, (((long) start) << 32) + end);
			}
		} else { // qualified type reference
			long[] positions = new long[identCount];
			long pos = (((long) start) << 32) + end;
			for (int i = 0; i < identCount; i++) {
				positions[i] = pos;
			}
			char[][] identifiers =
				CharOperation.splitOn('.', typeSignature, 0, dimStart);
			if (dim == 0) {
				return new QualifiedTypeReference(identifiers, positions);
			} else {
				return new ArrayQualifiedTypeReference(identifiers, dim, positions);
			}
		}
	}
}
