package org.minimallycorrect.javatransformer.api;

import lombok.*;
import org.minimallycorrect.javatransformer.internal.util.JVMUtil;

import java.util.*;

@Data
@RequiredArgsConstructor(staticName = "of")
public class Annotation {
	@NonNull
	public final Type type;
	@NonNull
	@Getter(AccessLevel.NONE)
	@Setter(AccessLevel.NONE)
	public final Map<String, Object> values;

	public static Annotation of(Type t, Object value) {
		val map = new HashMap<String, Object>();
		map.put("value", value);
		return of(t, map);
	}

	public static Annotation of(Type t) {
		return of(t, Collections.emptyMap());
	}

	@SuppressWarnings("unchecked")
	public <T> T get(String key, Class<T> clazz) {
		val value = values.get(key);
		if (value == null)
			return null;
		if (clazz.isAssignableFrom(value.getClass()))
			return (T) value;
		if (clazz.isEnum()) {
			if (!(value instanceof String[]))
				throw new IllegalArgumentException("value for " + key + " is not a String[] so can't be mapped to enum. Actual type " + value.getClass().getName() + " value " + value);
			val array = ((String[]) value);
			val type = new Type(array[0]);
			if (!type.getClassName().endsWith(clazz.getName()))
				throw new IllegalArgumentException("value for " + key + " is of enum type " + type + " which does not match expected type " + clazz.getName() + " actual value " + Arrays.toString(array));
			return (T) JVMUtil.searchEnum((Class<? extends Enum<?>>) clazz, array[1]);
		}
		throw new UnsupportedOperationException("Can't convert enum value of type " + value.getClass().getName() + " value " + value + " to " + clazz.getName());
	}

	public void set(String key, Object value) {
		if (value != null) {
			val clazz = value.getClass();
			if (clazz.isEnum())
				value = new String[] { Type.of(clazz.getName()).descriptor, ((Enum) value).name()};
		}
		values.put(key, value);
	}

	@SneakyThrows
	public <T extends java.lang.annotation.Annotation> T toAnnotation(Class<T> clazz) {
		if (!clazz.getName().equals(type.getClassName()))
			throw new IllegalArgumentException("Type " + type + " can't be mapped to annotation class " + clazz);
		val values = new HashMap<String, Object>();
		for (val key : this.values.keySet()) {
			val returnType = clazz.getMethod(key).getReturnType();
			values.put(key, returnType.isPrimitive() ? this.values.get(key) : get(key, returnType));
		}
		// TODO: change this to use our own proxy instead of reusing a sun.misc class
		//noinspection unchecked
		return (T) sun.reflect.annotation.AnnotationParser.annotationForMap(clazz, values);
	}
}

@@ -1950,24 +1950,146 @@
 T%3E clazz) %7B%0A
+%09%09if (!clazz.isAnnotation())%0A%09%09%09throw new IllegalArgumentException(%22Class %22 + clazz.getName() + %22 is not an annotation%22);%0A
 %09%09if (!clazz
@@ -2128,16 +2128,16 @@
 ame()))%0A
-
 %09%09%09throw
@@ -2327,26 +2327,22 @@
 %0A%09%09%09val 
-returnType
+method
  = clazz
@@ -2356,16 +2356,44 @@
 hod(key)
+;%0A%09%09%09val returnType = method
 .getRetu
@@ -2405,31 +2405,30 @@
 e();%0A%09%09%09
-values.put(key,
+Object value =
  returnT
@@ -2490,16 +2490,102 @@
 urnType)
+;%0A%09%09%09if (value == null)%0A%09%09%09%09value = method.getDefaultValue();%0A%09%09%09values.put(key, value
 );%0A%09%09%7D%0A%09

Use default annotation value if value is missing in Annotation.toAnnotation