package jadx.core.codegen;

import jadx.core.Consts;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class AnnotationGen {

	private final ClassNode cls;
	private final ClassGen classGen;

	public AnnotationGen(ClassNode cls, ClassGen classGen) {
		this.cls = cls;
		this.classGen = classGen;
	}

	public void addForClass(CodeWriter code) {
		add(cls, code);
	}

	public void addForMethod(CodeWriter code, MethodNode mth) {
		add(mth, code);
	}

	public void addForField(CodeWriter code, FieldNode field) {
		add(field, code);
	}

	public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
		AnnotationsList aList = paramsAnnotations.getParamList().get(n);
		if (aList == null || aList.isEmpty()) {
			return;
		}
		for (Annotation a : aList.getAll()) {
			formatAnnotation(code, a);
			code.add(' ');
		}
	}

	private void add(IAttributeNode node, CodeWriter code) {
		AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
		if (aList == null || aList.isEmpty()) {
			return;
		}
		for (Annotation a : aList.getAll()) {
			String aCls = a.getAnnotationClass();
			if (aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
				// skip
				if (Consts.DEBUG) {
					code.startLine("// " + a);
				}
			} else {
				code.startLine();
				formatAnnotation(code, a);
			}
		}
	}

	private void formatAnnotation(CodeWriter code, Annotation a) {
		code.add('@');
		classGen.useType(code, a.getType());
		Map<String, Object> vl = a.getValues();
		if (!vl.isEmpty()) {
			code.add('(');
			if (vl.size() == 1 && vl.containsKey("value")) {
				encodeValue(code, vl.get("value"));
			} else {
				for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
					Entry<String, Object> e = it.next();
					code.add(e.getKey());
					code.add(" = ");
					encodeValue(code, e.getValue());
					if (it.hasNext()) {
						code.add(", ");
					}
				}
			}
			code.add(')');
		}
	}

	@SuppressWarnings("unchecked")
	public void addThrows(MethodNode mth, CodeWriter code) {
		Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
		if (an != null) {
			Object exs = an.getDefaultValue();
			code.add(" throws ");
			for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
				ArgType ex = it.next();
				classGen.useType(code, ex);
				if (it.hasNext()) {
					code.add(", ");
				}
			}
		}
	}

	public Object getAnnotationDefaultValue(String name) {
		Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
		if (an != null) {
			Annotation defAnnotation = (Annotation) an.getDefaultValue();
			return defAnnotation.getValues().get(name);
		}
		return null;
	}

	// TODO: refactor this boilerplate code
	public void encodeValue(CodeWriter code, Object val) {
		if (val == null) {
			code.add("null");
			return;
		}
		if (val instanceof String) {
			code.add(StringUtils.unescapeString((String) val));
		} else if (val instanceof Integer) {
			code.add(TypeGen.formatInteger((Integer) val));
		} else if (val instanceof Character) {
			code.add(StringUtils.unescapeChar((Character) val));
		} else if (val instanceof Boolean) {
			code.add(Boolean.TRUE.equals(val) ? "true" : "false");
		} else if (val instanceof Float) {
			code.add(TypeGen.formatFloat((Float) val));
		} else if (val instanceof Double) {
			code.add(TypeGen.formatDouble((Double) val));
		} else if (val instanceof Long) {
			code.add(TypeGen.formatLong((Long) val));
		} else if (val instanceof Short) {
			code.add(TypeGen.formatShort((Short) val));
		} else if (val instanceof Byte) {
			code.add(TypeGen.formatByte((Byte) val));
		} else if (val instanceof ArgType) {
			classGen.useType(code, (ArgType) val);
			code.add(".class");
		} else if (val instanceof FieldInfo) {
			// must be a static field
			FieldInfo field = (FieldInfo) val;
			InsnGen.makeStaticFieldAccess(code, field, classGen);
		} else if (val instanceof Iterable) {
			code.add('{');
			Iterator<?> it = ((Iterable) val).iterator();
			while (it.hasNext()) {
				Object obj = it.next();
				encodeValue(code, obj);
				if (it.hasNext()) {
					code.add(", ");
				}
			}
			code.add('}');
		} else if (val instanceof Annotation) {
			formatAnnotation(code, (Annotation) val);
		} else {
			// TODO: also can be method values
			throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
		}
	}
}

@@ -1232,24 +1232,29 @@
  int n) %7B%0A%09%09
+List%3C
 AnnotationsL
@@ -1252,26 +1252,31 @@
 otationsList
- a
+%3E param
 List = param
@@ -1302,16 +1302,99 @@
 amList()
+;%0A%09%09if (n %3E= paramList.size()) %7B%0A%09%09%09return;%0A%09%09%7D%0A%09%09AnnotationsList aList = paramList
 .get(n);

fix annotations processing for method arguments