/*-
 * ~~~~~~licensing~~~~~~
 * core-support-design
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.platform.core.support.design.classgen;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.ArrayInitializerExpr;
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
import com.github.javaparser.ast.expr.CharLiteralExpr;
import com.github.javaparser.ast.expr.ClassExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MemberValuePair;
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.type.ArrayType;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.PrimitiveType;
import com.github.javaparser.ast.type.Type;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.MethodParametersAttribute;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.SignatureAttribute;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.AnnotationMemberValue;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.BooleanMemberValue;
import javassist.bytecode.annotation.ByteMemberValue;
import javassist.bytecode.annotation.CharMemberValue;
import javassist.bytecode.annotation.ClassMemberValue;
import javassist.bytecode.annotation.DoubleMemberValue;
import javassist.bytecode.annotation.FloatMemberValue;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.LongMemberValue;
import javassist.bytecode.annotation.MemberValue;
import javassist.bytecode.annotation.ShortMemberValue;
import javassist.bytecode.annotation.StringMemberValue;

/**
 * 
 * Util class for converting javaparser's CompilationUnits (@see com.github.javaparser.ast.CompilationUnit)
 * to javassist structures
 * 
 * @author sstarovoytenkov
 *
 */
@Deprecated(since = "1.10", forRemoval = true)
public class Cu2JaConverter {
	
	private static final Logger log = LoggerFactory.getLogger(Cu2JaConverter.class);

	public final static ClassPool classPool = ClassPool.getDefault();
	
	public static final Map<PrimitiveType.Primitive, CtClass> primitivesMap = new HashMap<>();
	
	static {
		primitivesMap.put(PrimitiveType.Primitive.BOOLEAN, CtClass.booleanType);
		primitivesMap.put(PrimitiveType.Primitive.BYTE, CtClass.byteType);
		primitivesMap.put(PrimitiveType.Primitive.CHAR, CtClass.charType);
		primitivesMap.put(PrimitiveType.Primitive.DOUBLE, CtClass.doubleType);
		primitivesMap.put(PrimitiveType.Primitive.FLOAT, CtClass.floatType);
		primitivesMap.put(PrimitiveType.Primitive.INT, CtClass.intType);
		primitivesMap.put(PrimitiveType.Primitive.LONG, CtClass.longType);
		primitivesMap.put(PrimitiveType.Primitive.SHORT, CtClass.shortType);
	}
	
	protected List<String> imports = new ArrayList<>();
	
	protected List<String> defaultImports = Arrays.asList(new String[] {"java.util", "java.lang"});
	
	protected Map<String, String> importMap = new HashMap<>();
	
	
	
	public void clearImports() {
		importMap.clear();
		imports.clear();
	}
	
	public void addImport(String importString) {
		
		log.trace("--> Adding import: " + importString);
		
		String targetString = importString;
		boolean isPackage = importString.endsWith("*"); 
		if (isPackage)
			targetString = targetString.substring(0, targetString.lastIndexOf('.'));
		
		if (imports.contains(targetString))
			return;
		
		imports.add(targetString);
		if (!isPackage) {
			String className = targetString.substring(targetString.lastIndexOf('.') + 1);
			importMap.put(className, targetString);
		}
		
	}
	
	public CtClass convert(CompilationUnit compilationUnit) {
		CtClass result = null;
		
		Optional<TypeDeclaration<?>> opt = compilationUnit.getPrimaryType();
		if (opt.isEmpty())
			return null;
		
		NodeList<ImportDeclaration> importDecls = compilationUnit.getImports();
		for (ImportDeclaration imp: importDecls)
			addImport(imp.getNameAsString());
		
		String packageName = compilationUnit.getPackageDeclaration().get().getName().asString();
		
		TypeDeclaration<?> primaryType = opt.get();
		if (!primaryType.isClassOrInterfaceDeclaration())
			return null;
		
		ClassOrInterfaceDeclaration primaryClass = primaryType.asClassOrInterfaceDeclaration();
		
		String typeName = primaryClass.getNameAsString();
		
		log.trace("Converting: " + packageName + ":" + typeName);
		
		try {
			result = classPool.get(packageName + "." + typeName);
			result.detach();
			result = null;
		} catch (NotFoundException e) {
			// NOOP
			// e.printStackTrace();
		}
		if (result == null) {
			if (primaryClass.isInterface())
				result = classPool.makeInterface(packageName + "." + typeName);
			else
				result = classPool.makeClass(packageName + "." + typeName);
			
			log.trace("\tCreated " + (result.isInterface()?"interface":"class") 
					+ ": " + result.getName());
		}/* else {
			if (result.isFrozen())
				result.defrost();
		}*/
		
		addAnnotations(primaryClass, result);
		
		addFields(primaryClass, result);
		
		addMethods(primaryClass, result);
		
		return result;
	} 
	
	protected void addFields(ClassOrInterfaceDeclaration source, CtClass target) {
		
		List<FieldDeclaration> fieldDecls = source.getFields();
		if ((fieldDecls == null) || (fieldDecls.isEmpty()))
			return;
		
		for (FieldDeclaration decl: fieldDecls) {
			try {
				List<CtField> fields = createFields(decl, target);
				for (CtField field: fields)
					if (field != null) {
						target.addField(field);
					}
			} catch (Exception e) {
				log.error("Error creting field", e);;
			}
		}
		
	}
	
	protected List<CtField> createFields(FieldDeclaration decl, CtClass target) {
		List<CtField> fields = new ArrayList<>();
		
		NodeList<AnnotationExpr> annotaionExprs = decl.getAnnotations();
		decl.setAnnotations(new NodeList<>());
		
		List<Annotation> annotations = convertAnnotations(annotaionExprs, target.getClassFile().getConstPool());
		Annotation[] annotationsArray = annotations.stream().toArray(Annotation[]::new); 
		
		NodeList<VariableDeclarator> vars = decl.getVariables();
		
		for (VariableDeclarator vd: vars) {

			FieldDeclaration clone = decl.clone();
			clone.setVariables(NodeList.nodeList(vd));
			
			Type fieldType = vd.getType();
			resolveType(fieldType);
		
			AnnotationsAttribute attr = new AnnotationsAttribute(target.getClassFile().getConstPool()
					, AnnotationsAttribute.visibleTag);
			attr.setAnnotations(annotationsArray);
			
			log.trace("-- FOUND FIELD :: [" 
					+ vd.getNameAsString()
					+ "]:" + fieldType.asString()
					+ "\n" + clone.toString());
			
			try {
				CtField field = CtField.make(clone.toString(), target);
				field.getFieldInfo().addAttribute(attr);
				fields.add(field);
			} catch (CannotCompileException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		
		return fields;
	}
	
	protected void addMethods(ClassOrInterfaceDeclaration source, CtClass target) {
		List<MethodDeclaration> methodDecls = source.getMethods();
		if ((methodDecls == null) || methodDecls.isEmpty())
			return;
		
		classPool.importPackage("javax.ws.rs");
		
		for (MethodDeclaration decl: methodDecls) {
			try {
				CtMethod method = createMethod(decl, target);
				if (method != null) {
					target.addMethod(method);
					method.getMethodInfo().rebuildStackMap(classPool);
					/*MethodParametersAttribute mp = (MethodParametersAttribute)method.getMethodInfo().getAttribute(MethodParametersAttribute.tag);
					if ((mp!=null) && (mp.size()>0)) {
						log.trace("!!!+++ size: " + mp.size());
						log.trace("!!!+++ parameter0: " + mp.parameterName(0));
					}*/					
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	protected CtMethod createMethod(MethodDeclaration decl, CtClass target) throws CannotCompileException, NotFoundException, BadBytecode {

		CtMethod method = null;
		
		log.trace("\t--> Adding method: " + decl.getNameAsString());
		
		// return type
		
		Type returnType = decl.getType();
		resolveType(returnType);
		
		// body

		Optional<BlockStmt> bodyOpt = decl.getBody();
		String body = "";
		if (bodyOpt.isPresent())
			body = bodyOpt.get().toString();
		log.trace("\t\t--> Result: " + decl.getDeclarationAsString() + body);	

		// annotations storage

		NodeList<AnnotationExpr> methodAnnotations =  decl.getAnnotations();
		Map<String, NodeList<AnnotationExpr>> paramAnnotations = new HashMap<>();

		
		// parameters

		
		List<CtClass> paramClasses = new LinkedList<>();
		
		NodeList<Parameter> parameters = decl.getParameters();

		List<String> paramNames = new LinkedList<>();
		List<Integer> paramFlags = new LinkedList<>();
		
		for (Parameter parameter: parameters) {
			
			resolveType(parameter.getType());
			NodeList<AnnotationExpr> annotations = parameter.getAnnotations();
			resolveAnnotations(annotations);
			
			NodeList<AnnotationExpr> annos = parameter.getAnnotations();
			if ((annos != null) && !annos.isEmpty()) {
				paramAnnotations.put(parameter.getNameAsString(), annos);
				parameter.setAnnotations(new NodeList<>());
			}
			
			paramClasses.add(convertType(parameter.getType(), target));
			
			paramNames.add(parameter.getNameAsString());
			paramFlags.add(0);
		}
		
		CtClass[] parameterClasses = paramClasses.toArray(new CtClass[] {});

		// create method
		
		if (target.isInterface()) {
			// log.trace("--!! TODO: add abstract method");
			method = CtNewMethod.abstractMethod(convertType(returnType, target)
					, decl.getNameAsString()
					, parameterClasses
					, null
					, target);
		} else {
			convertType(returnType, target);
			log.trace("^^^ CREATING METHOD ::: \n" + decl.getDeclarationAsString()+body + "\n\n");
			method = CtNewMethod.make(decl.getDeclarationAsString()+body, target);
			/*method = CtNewMethod.make(
					convertModifiers(decl.getModifiers())
					, convertType(returnType, target)
					, decl.getNameAsString()
					, parameterClasses
					, null
					, body
					, target);*/
		}

		if (method != null) {
		
			// create signature
			
			List<SignatureAttribute.Type> sigParamTypes = new LinkedList<>();
			
			for (Parameter p: parameters) {
				sigParamTypes.add(convertToSingatureType(p.getType(), target));
			}
			
			SignatureAttribute.Type sigReturnType = convertToSingatureType(returnType, target);
			
			SignatureAttribute.MethodSignature ms = new SignatureAttribute.MethodSignature(
					null
					, sigParamTypes.toArray(new SignatureAttribute.Type[] {})
					, sigReturnType
					, null);
			
			method.setGenericSignature(ms.encode());
		
			// add param names
			if (paramNames.size() > 0) {
				MethodParametersAttribute mpa = new MethodParametersAttribute(
						method.getMethodInfo().getConstPool()
						, paramNames.toArray(new String[] {})
						, paramFlags.stream().mapToInt(i->i).toArray());
				
				method.setAttribute(MethodParametersAttribute.tag, mpa.get());
				// method.getMethodInfo().addAttribute(mpa);
				/*method.getMethodInfo().rebuildStackMap(classPool);
				mp = (MethodParametersAttribute)method.getMethodInfo().getAttribute(MethodParametersAttribute.tag);
				if (mp!=null) {
					log.trace("++++++ size: " + mp.size());
					log.trace("++++++ parameter0: " + mp.parameterName(0));
				}*/
			}
			
			// add annotations
			// -- to method
			resolveAnnotations(methodAnnotations);
			List<Annotation> methodAnnotationsFinal = convertAnnotations(methodAnnotations, method.getMethodInfo().getConstPool());
			if (!methodAnnotationsFinal.isEmpty()) {
				AnnotationsAttribute attributeAnno = new AnnotationsAttribute(method.getMethodInfo().getConstPool(), AnnotationsAttribute.visibleTag);
				for (Annotation anno: methodAnnotationsFinal)
					attributeAnno.addAnnotation(anno);
				method.getMethodInfo().addAttribute(attributeAnno);
			}
			
			// add parameter annotations
			ParameterAnnotationsAttribute paa = (ParameterAnnotationsAttribute)method.getMethodInfo().getAttribute(ParameterAnnotationsAttribute.visibleTag);

			Annotation[][] methodParamAnnotations = new Annotation[paramNames.size()][];
			String[] paramNamesArray = paramNames.toArray(new String[] {});
			
			boolean annotationsExist = false;
			
			for (int i=0; i<paramNamesArray.length; i++) {
				String paramName = paramNamesArray[i];
				NodeList<AnnotationExpr> currentParamAnnotations = paramAnnotations.getOrDefault(paramName, new NodeList<>());
				if (currentParamAnnotations.isEmpty())
					continue;
				List<Annotation> annos = 
						convertAnnotations(
								currentParamAnnotations
								, method.getMethodInfo().getConstPool()
						)
						.stream().filter(f->f!=null)
						.collect(Collectors.toList());
				Annotation[] annotToSet = annos.toArray(new Annotation[] {});
				if (annotToSet == null) {
					methodParamAnnotations[i] = new Annotation[] {}; 
				} else {
					methodParamAnnotations[i] = annotToSet;
					annotationsExist = true;
				}
			}
			if (annotationsExist && (methodParamAnnotations!=null)) {
				if (paa == null) {
					paa = new ParameterAnnotationsAttribute(method.getMethodInfo().getConstPool()
							, ParameterAnnotationsAttribute.visibleTag); 
					method.getMethodInfo().addAttribute(paa);
				}
				paa.setAnnotations(methodParamAnnotations);
			}		
			method.getMethodInfo().rebuildStackMap(classPool);
		}
		
		return method;
	}
	
	protected int convertModifiers(NodeList<Modifier> modifiers) {
		int result = 0;
		
		for (Modifier m: modifiers) {
			switch (m.getKeyword()) {
			case ABSTRACT:
				result = result | javassist.Modifier.ABSTRACT;
				break;
			case DEFAULT:
				break;
			case FINAL:
				result = result | javassist.Modifier.FINAL;
				break;
			case NATIVE:
				result = result | javassist.Modifier.NATIVE;
				break;
			case NON_SEALED:
				break;
			case PRIVATE:
				result = result | javassist.Modifier.PRIVATE;
				break;
			case PROTECTED:
				result = result | javassist.Modifier.PROTECTED;
				break;
			case PUBLIC:
				result = result | javassist.Modifier.PUBLIC;
				break;
			case SEALED:
				break;
			case STATIC:
				result = result | javassist.Modifier.STATIC;
				break;
			case STRICTFP:
				result = result | javassist.Modifier.STRICT;
				break;
			case SYNCHRONIZED:
				result = result | javassist.Modifier.SYNCHRONIZED;
				break;
			case TRANSIENT:
				result = result | javassist.Modifier.TRANSIENT;
				break;
			case TRANSITIVE:
				break;
			case VOLATILE:
				result = result | javassist.Modifier.VOLATILE;
				break;
			}
		}
		
		return result;
	}
	
	protected SignatureAttribute.Type convertToSingatureType(Type source, CtClass owner){
		if (source.isVoidType())
			return new SignatureAttribute.BaseType("void");
		if (source.isPrimitiveType())
			return new SignatureAttribute.BaseType(source.asPrimitiveType().getType().name().toLowerCase());
		if (source.isArrayType())
			return new SignatureAttribute.ArrayType(1, convertToSingatureType(source.asArrayType().getComponentType(), owner));
		if (source.isClassOrInterfaceType()) {
			ClassOrInterfaceType cit = source.asClassOrInterfaceType();
			List<SignatureAttribute.TypeArgument> sigTypeArguments = new LinkedList<>();
			if (cit.getTypeArguments().isPresent()) {
				NodeList<Type> typeArguments = cit.getTypeArguments().get();
				for (Type t: typeArguments) {
					SignatureAttribute.Type ta = convertToSingatureType(t, owner);
					if (ta instanceof SignatureAttribute.ObjectType) {
						sigTypeArguments.add(new SignatureAttribute.TypeArgument((SignatureAttribute.ObjectType)ta));
					}
				}
			}
			if (sigTypeArguments.isEmpty())
				return new SignatureAttribute.ClassType(cit.getNameAsString());
			else
				return new SignatureAttribute.ClassType(cit.getNameAsString()
						, sigTypeArguments.toArray(new SignatureAttribute.TypeArgument[] {}));
		}
		return null;
	}
	
	protected CtClass convertType(Type source, CtClass owner){
		if (source.isVoidType())
			return CtClass.voidType;
		if (source.isPrimitiveType()) {
			PrimitiveType pt = source.asPrimitiveType();
			return primitivesMap.get(pt.getType());
		}
		if (source.isArrayType()) {
			ArrayType at = source.asArrayType();
			Type componentType = at.getComponentType();
			resolveType(componentType);
			CtClass componentClass = convertType(componentType, owner);
			CtClass result = getOrCreate(componentClass.getName() + "[]", classPool);
			return result;
		}
		if (source.isClassOrInterfaceType()) {
			ClassOrInterfaceType cit = source.asClassOrInterfaceType();
			resolveType(cit);
			return getOrCreate(cit.getNameAsString(), classPool);
		}
		return CtClass.voidType;
	}
	
	protected CtClass getOrCreate(String className, ClassPool classPool) {
		CtClass result;
		try {
			result = classPool.get(className);
		} catch (NotFoundException e) {
			result = classPool.makeClass(className);
		}
		return result;	
	}
	
	protected void resolveAnnotations(NodeList<AnnotationExpr> annotations) {
		if ((annotations == null) || annotations.isEmpty())
			return;
		for (AnnotationExpr annotation: annotations) {
			resolveAnnotation(annotation);
		}
	}

	protected void resolveAnnotation(AnnotationExpr annotation) {
		annotation.setName(getFQN(annotation.getNameAsString()));
	}
	
	protected Type resolveType(Type origin) {
		Type result = origin;
		
		if (origin.isClassOrInterfaceType()) {
			ClassOrInterfaceType classType = origin.asClassOrInterfaceType();
			String className = classType.getNameAsString();
			if (!className.contains("."))
				className = getFQN(className);
			classType.setName(className);
			
			Optional<NodeList<Type>> typeArgumentsOpt = classType.getTypeArguments();
			if (typeArgumentsOpt.isPresent()) {
				NodeList<Type> typeArguments = typeArgumentsOpt.get();
				for (Type typeArgument: typeArguments)
					resolveType(typeArgument);
			}
			
			return classType;
		}
		
		return result;
	}
	
	protected String getFQN(String className) {
		if (importMap.containsKey(className))
			return importMap.get(className);
		
		for (String pkg: imports) {
			try {
				// Thread.currentThread().getContextClassLoader().loadClass(pkg + "." + className);
				this.getClass().getClassLoader().loadClass(pkg + "." + className);
			} catch (ClassNotFoundException e) {
				continue;
			}
			return pkg + "." + className;
		}

		for (String pkg: defaultImports) {
			try {
				// Thread.currentThread().getContextClassLoader().loadClass(pkg + "." + className);
				this.getClass().getClassLoader().loadClass(pkg + "." + className);
			} catch (ClassNotFoundException e) {
				continue;
			}
			return pkg + "." + className;
		}
		
		return className;
	}
	
	protected void addAnnotations(ClassOrInterfaceDeclaration source, CtClass target) {
		NodeList<AnnotationExpr> annotationExpressions = source.getAnnotations();
		if ((annotationExpressions == null) || annotationExpressions.isEmpty())
			return;
		
		ClassFile classFile = target.getClassFile();
		ConstPool constPool = classFile.getConstPool();
		
		AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool
				, AnnotationsAttribute.visibleTag);

		List<Annotation> annotations = convertAnnotations(annotationExpressions, constPool);
		for (Annotation anno: annotations)
			if (anno != null)
				annotationsAttribute.addAnnotation(anno);
/*		
		for (AnnotationExpr expr: annotationExpressions) {
			String name = expr.getNameAsString();
			log.trace("\tFound annotation: " + name);
			Annotation annotation = new Annotation(name, constPool);
			
			if (expr.isSingleMemberAnnotationExpr()) {
				SingleMemberAnnotationExpr smae = expr.asSingleMemberAnnotationExpr();
				Expression memberValue = smae.getMemberValue();
				annotation.addMemberValue("value", convertMemberValue(memberValue, constPool));
			} else
			if (expr.isNormalAnnotationExpr()) {
				NormalAnnotationExpr nae = expr.asNormalAnnotationExpr();
				NodeList<MemberValuePair> pairs = nae.getPairs();
				for (MemberValuePair pair: pairs) {
					annotation.addMemberValue(pair.getNameAsString()
							, convertMemberValue(pair.getValue(), constPool));
				}
			}
			
			annotationsAttribute.addAnnotation(annotation);
		}
*/		
		classFile.addAttribute(annotationsAttribute);
	}
	
	protected List<Annotation> convertAnnotations(NodeList<AnnotationExpr> annotationExpressions, ConstPool constPool){
		if ((annotationExpressions == null) || annotationExpressions.isEmpty())
			return Collections.emptyList();
		List<Annotation> result = new ArrayList<>();
		
		for (AnnotationExpr expr: annotationExpressions)
			result.add(convertAnnotation(expr, constPool));
		
		return result;
	}
	
	protected Annotation convertAnnotation(AnnotationExpr expr, ConstPool constPool) {
		String name = expr.getNameAsString();
		// log.trace("\tFound annotation: " + name);
		Annotation annotation = new Annotation(name, constPool);
		
		if (expr.isSingleMemberAnnotationExpr()) {
			SingleMemberAnnotationExpr smae = expr.asSingleMemberAnnotationExpr();
			Expression memberValue = smae.getMemberValue();
			annotation.addMemberValue("value", convertMemberValue(memberValue, "value", name, constPool));
		} else
		if (expr.isNormalAnnotationExpr()) {
			NormalAnnotationExpr nae = expr.asNormalAnnotationExpr();
			NodeList<MemberValuePair> pairs = nae.getPairs();
			for (MemberValuePair pair: pairs) {
				annotation.addMemberValue(pair.getNameAsString()
						, convertMemberValue(pair.getValue(),pair.getNameAsString(), name, constPool));
			}
		}
		return annotation;
	}
	
	protected MemberValue convertMemberValue(Expression value, String memberName, String annotationName, ConstPool constPool) {
		/* 
		 *	@TO_IMPLEMENT
	    	 
	    	??? EnumMemberValue ???
	    	 
	    	
	    	@DONE	
	    	
	    	ArrayMemberValue 
	    	BooleanMemberValue
	    	CharMemberValue 
	    	StringMemberValue
	    	AnnotationMemberValue 
	    	IntegerMemberValue | ByteMemberValue | ShortMemberValue  
	    	LongMemberValue 
	    	DoubleMemberValue | FloatMemberValue 
	    	ClassMemberValue 
		*/
		
		Class<?> targetType = getTargetMemberType(annotationName, memberName);
		
		if (value.isStringLiteralExpr()) {
			StringMemberValue smv = new StringMemberValue(constPool);
			smv.setValue(value.asStringLiteralExpr().getValue());
			return smv;
		}
		
		if (value.isArrayInitializerExpr()) {
			ArrayInitializerExpr aie = value.asArrayInitializerExpr();
			ArrayMemberValue amv = new ArrayMemberValue(constPool);
			List<MemberValue> values = new ArrayList<>();
			for (Expression member: aie.getValues()) {
				MemberValue newMember = convertMemberValue(member, memberName, annotationName, constPool);
				if (newMember != null)
					values.add(newMember);
			}
			amv.setValue(values.toArray(new MemberValue[] {}));
			return amv;
		}
		
		if (value.isAnnotationExpr()) {
			AnnotationExpr ann = value.asAnnotationExpr();
			AnnotationMemberValue amv = new AnnotationMemberValue(constPool);
			Annotation anno = convertAnnotation(ann, constPool);
			amv.setValue(anno);
			return amv;
		}
		
		if (value.isIntegerLiteralExpr() 
				|| value.isLongLiteralExpr()
				|| value.isDoubleLiteralExpr()) {
			
			if (targetType.isArray())
				targetType = targetType.getComponentType();
			
			Number numValue = 0;  
			
			if (value.isIntegerLiteralExpr()) {
				numValue = value.asIntegerLiteralExpr().asNumber();
			}
	
			if (value.isLongLiteralExpr()) {
				numValue = value.asLongLiteralExpr().asNumber();
			}
	
			if (value.isDoubleLiteralExpr()) {
				double d = value.asDoubleLiteralExpr().asDouble();
				numValue = Double.valueOf(d);
			}
			
			if ("byte".equals(targetType.getName())) {
				byte v = numValue.byteValue();
				ByteMemberValue mv = new ByteMemberValue(constPool);
				mv.setValue(v);
				return mv;
			} else 
			if ("short".equals(targetType.getName())) {
				short v = numValue.shortValue();
				ShortMemberValue mv = new ShortMemberValue(constPool);
				mv.setValue(v);
				return mv;
			} else 
			if ("int".equals(targetType.getName())) {
				int v = numValue.intValue();
				IntegerMemberValue mv = new IntegerMemberValue(constPool);
				mv.setValue(v);
				return mv;
			} else 
			if ("long".equals(targetType.getName())) {
				long v = numValue.longValue();
				LongMemberValue mv = new LongMemberValue(constPool);
				mv.setValue(v);
				return mv;
			} else 
			if ("float".equals(targetType.getName())) {
				float v = numValue.floatValue();
				FloatMemberValue mv = new FloatMemberValue(constPool);
				mv.setValue(v);
				return mv;
			} else 
			if ("double".equals(targetType.getName())) {
				double v = numValue.doubleValue();
				DoubleMemberValue mv = new DoubleMemberValue(constPool);
				mv.setValue(v);
				return mv;
			} 
			
		}
		if (value.isBooleanLiteralExpr()) {
			BooleanLiteralExpr ble = value.asBooleanLiteralExpr();
			BooleanMemberValue bmv = new BooleanMemberValue(constPool);
			bmv.setValue(ble.getValue());
			return bmv;
		}
		
		if (value.isCharLiteralExpr()) {
			CharLiteralExpr сle = value.asCharLiteralExpr();
			CharMemberValue сmv = new CharMemberValue(constPool);
			сmv.setValue(сle.asChar());
			return сmv;
		}

		if (value.isClassExpr()) {
			ClassExpr ce = value.asClassExpr();
			ClassMemberValue cmv = new ClassMemberValue(constPool);
			Type t = resolveType(ce.getType());
			CtClass converted = convertType(t, null);
			cmv.setValue(converted.getName());
			return cmv;
		}
		
		StringMemberValue smv = new StringMemberValue(constPool);
		smv.setValue("@ENTAXY_NOT_SUPPORTED: " + value.getClass().getSimpleName());
		return smv;
		
	}
	
	protected Class<?> getTargetMemberType(String annotationClass, String memberName) {
		try {
			Class<?> clazz = this.getClass().getClassLoader().loadClass(annotationClass);
			if (clazz.isAnnotation()) {
				log.trace("\t+++ FOUND ANNOTATION CLASS: " + clazz.getName());
				// String methodName = "get" + memberName.substring(0,1).toUpperCase() + memberName.substring(1);
				Method m;
				try {
					m = clazz.getMethod(memberName);
					if (m == null) {
						log.trace("\t\t+++ Method not found: " + memberName);
					} else {
						Class<?> returnType = m.getReturnType();
						log.trace("\t\t+++ Method [" + memberName + "] returns: " + returnType.getName());
						return returnType;
					}
				} catch (NoSuchMethodException | SecurityException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
	
/*	protected String getTypeFullName(Type t) {
		
	}*/
}
