/* *******************************************************************
 * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
 * 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: 
 *     PARC     initial implementation 
 * ******************************************************************/

package org.aspectj.ajdt.internal.compiler.ast;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import org.aspectj.org.eclipse.jdt.core.compiler.CharOperation;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.Argument;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.Expression;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.NameReference;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.Statement;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.TypeParameter;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.aspectj.org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.aspectj.org.eclipse.jdt.internal.compiler.codegen.CodeStream;
import org.aspectj.org.eclipse.jdt.internal.compiler.impl.Constant;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;
import org.aspectj.weaver.AjAttribute;
import org.aspectj.weaver.patterns.WildTypePattern;

public class AstUtil {

	private AstUtil() {
	}

	public static void addMethodBinding(SourceTypeBinding sourceType, MethodBinding method) {
		int len = sourceType.methods.length;
		MethodBinding[] temp = new MethodBinding[len + 1];
		System.arraycopy(sourceType.methods, 0, temp, 0, len);
		temp[len] = method;
		sourceType.methods = temp;
	}

	public static void addMethodDeclaration(TypeDeclaration typeDec, AbstractMethodDeclaration dec) {
		AbstractMethodDeclaration[] methods = typeDec.methods;
		int len = methods.length;
		AbstractMethodDeclaration[] newMethods = new AbstractMethodDeclaration[len + 1];
		System.arraycopy(methods, 0, newMethods, 0, len);
		newMethods[len] = dec;
		typeDec.methods = newMethods;
	}

	public static Argument makeFinalArgument(char[] name, TypeBinding typeBinding) {
		long pos = 0; // XXX encode start and end location
		LocalVariableBinding binding = new LocalVariableBinding(name, typeBinding, Modifier.FINAL, true);
		Argument ret = new Argument(name, pos, makeTypeReference(typeBinding), Modifier.FINAL);
		ret.binding = binding;
		return ret;
	}

	public static TypeReference makeTypeReference(TypeBinding binding) {
		// ??? does this work for primitives
		QualifiedTypeReference ref = new QualifiedTypeReference(new char[][] { binding.sourceName() }, new long[] { 0 }); // ???
		ref.resolvedType = binding;
		ref.constant = Constant.NotAConstant;
		return ref;
	}

	public static NameReference makeNameReference(TypeBinding binding) {

		char[][] name = new char[][] { binding.sourceName() };
		long[] dummyPositions = new long[name.length];
		QualifiedNameReference ref = new QualifiedNameReference(name, dummyPositions, 0, 0);
		ref.binding = binding;
		ref.constant = Constant.NotAConstant;
		return ref;
	}

	public static ReturnStatement makeReturnStatement(Expression expr) {
		return new ReturnStatement(expr, 0, 0);
	}

	public static MethodDeclaration makeMethodDeclaration(MethodBinding binding) {
		MethodDeclaration ret = new MethodDeclaration(null);
		ret.binding = binding;
		int nargs = binding.parameters.length;
		ret.arguments = new Argument[nargs];
		for (int i = 0; i < nargs; i++) {
			ret.arguments[i] = makeFinalArgument(("arg" + i).toCharArray(), binding.parameters[i]);
		}
		return ret;
	}

	public static void setStatements(MethodDeclaration ret, List statements) {
		ret.statements = (Statement[]) statements.toArray(new Statement[statements.size()]);
	}

	public static SingleNameReference makeLocalVariableReference(LocalVariableBinding binding) {
		SingleNameReference ret = new SingleNameReference(binding.name, 0);
		ret.binding = binding;
//		ret.codegenBinding = binding;
		ret.constant = Constant.NotAConstant;
		ret.bits &= ~ASTNode.RestrictiveFlagMASK; // clear bits
		ret.bits |= Binding.VARIABLE;
		return ret;
	}

	public static SingleNameReference makeResolvedLocalVariableReference(LocalVariableBinding binding) {
		SingleNameReference ret = new SingleNameReference(binding.name, 0);
		ret.binding = binding;
//		ret.codegenBinding = binding;
		ret.constant = Constant.NotAConstant;
		ret.bits &= ~ASTNode.RestrictiveFlagMASK; // clear bits
		ret.bits |= Binding.LOCAL;
		return ret;
	}

	public static int makePublic(int modifiers) {
		return makePackageVisible(modifiers) | ClassFileConstants.AccPublic;
	}

	public static int makePackageVisible(int modifiers) {
		modifiers &= ~(ClassFileConstants.AccPublic | ClassFileConstants.AccPrivate | ClassFileConstants.AccProtected);
		return modifiers;
	}

	public static CompilationUnitScope getCompilationUnitScope(Scope scope) {
		if (scope instanceof CompilationUnitScope) {
			return (CompilationUnitScope) scope;
		}
		return getCompilationUnitScope(scope.parent);
	}

	public static void generateParameterLoads(TypeBinding[] parameters, CodeStream codeStream) {
		int paramIndex = 0;
		int varIndex = 0;
		while (paramIndex < parameters.length) {
			TypeBinding param = parameters[paramIndex++];
			codeStream.load(param, varIndex);
			varIndex += slotsNeeded(param);
		}
	}

	public static void generateParameterLoads(TypeBinding[] parameters, CodeStream codeStream, int offset) {
		int paramIndex = 0;
		int varIndex = offset;
		while (paramIndex < parameters.length) {
			TypeBinding param = parameters[paramIndex++];
			codeStream.load(param, varIndex);
			varIndex += slotsNeeded(param);
		}
	}

	public static void generateReturn(TypeBinding returnType, CodeStream codeStream) {
		if (returnType.id == TypeIds.T_void) {
			codeStream.return_();
		} else if (returnType.isBaseType()) {
			switch (returnType.id) {
			case TypeIds.T_boolean:
			case TypeIds.T_int:
			case TypeIds.T_byte:
			case TypeIds.T_short:
			case TypeIds.T_char:
				codeStream.ireturn();
				break;
			case TypeIds.T_float:
				codeStream.freturn();
				break;
			case TypeIds.T_long:
				codeStream.lreturn();
				break;
			case TypeIds.T_double:
				codeStream.dreturn();
				break;
			default:
				throw new RuntimeException("huh");
			}
		} else {
			codeStream.areturn();
		}
	}

	// XXX this could be inconsistent for wierd case, i.e. a class named "java_lang_String"
	public static char[] makeMangledName(ReferenceBinding type) {
		return CharOperation.concatWith(type.compoundName, '_');
	}

	public static final char[] PREFIX = "ajc".toCharArray();

	// XXX not efficient
	public static char[] makeAjcMangledName(char[] kind, ReferenceBinding type, char[] name) {
		return CharOperation.concat(CharOperation.concat(PREFIX, new char[] { '$' }, kind), '$', makeMangledName(type), '$', name);
	}

	public static char[] makeAjcMangledName(char[] kind, char[] p, char[] name) {
		return CharOperation.concat(CharOperation.concat(PREFIX, new char[] { '$' }, kind), '$', p, '$', name);
	}

	public static List getAjSyntheticAttribute() {
		ArrayList ret = new ArrayList(1);
		ret.add(new EclipseAttributeAdapter(new AjAttribute.AjSynthetic()));
		return ret;
	}

	public static long makeLongPos(int start, int end) {
		return (long) end | ((long) start << 32);
	}

	public static char[][] getCompoundName(String string) {
		return WildTypePattern.splitNames(string, true);
	}

	public static TypeBinding[] insert(TypeBinding first, TypeBinding[] rest) {
		if (rest == null) {
			return new TypeBinding[] { first };
		}

		int len = rest.length;
		TypeBinding[] ret = new TypeBinding[len + 1];
		ret[0] = first;
		System.arraycopy(rest, 0, ret, 1, len);
		return ret;
	}

	public static Argument[] insert(Argument first, Argument[] rest) {
		if (rest == null) {
			return new Argument[] { first };
		}

		int len = rest.length;
		Argument[] ret = new Argument[len + 1];
		ret[0] = first;
		System.arraycopy(rest, 0, ret, 1, len);
		return ret;
	}
	
	public static TypeParameter[] insert(TypeParameter first, TypeParameter[] rest) {
		if (rest == null) {
			return new TypeParameter[]{first};
		}
		int len = rest.length;
		TypeParameter[] ret = new TypeParameter[len + 1];
		ret[0] = first;
		System.arraycopy(rest, 0, ret, 1, len);
		return ret;
	}
	
	public static TypeVariableBinding[] insert(TypeVariableBinding first, TypeVariableBinding[] rest) {
		if (rest == null) {
			return new TypeVariableBinding[]{first};
		}
		int len = rest.length;
		TypeVariableBinding[] ret = new TypeVariableBinding[len + 1];
		ret[0] = first;
		System.arraycopy(rest, 0, ret, 1, len);
		return ret;
	}
	
	public static TypeVariableBinding[] insert(TypeVariableBinding[] first, TypeVariableBinding[] rest) {
		if (rest == null) {
			TypeVariableBinding[] ret = new TypeVariableBinding[first.length];
			System.arraycopy(first, 0, ret, 0, first.length);
			return ret;
		}
		int len = rest.length;
		TypeVariableBinding[] ret = new TypeVariableBinding[first.length+len];
		System.arraycopy(first,0,ret,0,first.length);
		System.arraycopy(rest,0,ret,first.length,len);
		return ret;
	}

	public static Expression[] insert(Expression first, Expression[] rest) {
		if (rest == null) {
			return new Expression[] { first };
		}

		int len = rest.length;
		Expression[] ret = new Expression[len + 1];
		ret[0] = first;
		System.arraycopy(rest, 0, ret, 1, len);
		return ret;
	}

	public static Argument[] copyArguments(Argument[] inArgs) {
		// Lets do a proper copy
		if (inArgs == null)
			return new Argument[] {};
		Argument[] outArgs = new Argument[inArgs.length];
		for (int i = 0; i < inArgs.length; i++) {
			Argument argument = inArgs[i];
			outArgs[i] = new Argument(argument.name, 0, argument.type, argument.modifiers);
		}
		return outArgs;

		// if (inArgs == null) return new Argument[] {};
		// int len = inArgs.length;
		// Argument[] outArgs = new Argument[len];
		// //??? we're not sure whether or not copying these is okay
		// System.arraycopy(inArgs, 0, outArgs, 0, len);
		// return outArgs;

	}

	public static Statement[] remove(int i, Statement[] statements) {
		int len = statements.length;
		Statement[] ret = new Statement[len - 1];
		System.arraycopy(statements, 0, ret, 0, i);
		System.arraycopy(statements, i + 1, ret, i, len - i - 1);
		return ret;
	}

	public static int slotsNeeded(TypeBinding type) {
		if (type == TypeBinding.DOUBLE || type == TypeBinding.LONG)
			return 2;
		else
			return 1;
	}

	public static void replaceMethodBinding(MessageSend send, MethodBinding newBinding) {
		send.binding =/* send.codegenBinding =*/ newBinding;
		send.setActualReceiverType(newBinding.declaringClass);

	}
}
