package org.jsondoc.core.util;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.Collection;
import java.util.Map;

import org.jsondoc.core.annotation.ApiObject;

public class JSONDocTypeBuilder {
	
	private static final String WILDCARD = "wildcard";
	private static final String UNDEFINED = "undefined";
	private static final String ARRAY = "array";

	public static JSONDocType build(JSONDocType jsondocType, Class<?> clazz, Type type) {
		if(clazz.isAssignableFrom(JSONDocDefaultType.class)) {
			jsondocType.addItemToType(UNDEFINED);
			return jsondocType;
		}

		if (Map.class.isAssignableFrom(clazz)) {
			jsondocType.addItemToType(getCustomClassName(clazz));

			if (type instanceof ParameterizedType) {
				Type mapKeyType = ((ParameterizedType) type).getActualTypeArguments()[0];
				Type mapValueType = ((ParameterizedType) type).getActualTypeArguments()[1];

				jsondocType.setMapKey(new JSONDocType());
				jsondocType.setMapValue(new JSONDocType());

				if (mapKeyType instanceof Class) {
					jsondocType.setMapKey(new JSONDocType(((Class<?>) mapKeyType).getSimpleName().toLowerCase()));
				} else if (mapKeyType instanceof WildcardType) {
					jsondocType.setMapKey(new JSONDocType(WILDCARD));
				} else {
					jsondocType.setMapKey(build(jsondocType.getMapKey(), (Class<?>) ((ParameterizedType) mapKeyType).getRawType(), mapKeyType));
				}

				if (mapValueType instanceof Class) {
					jsondocType.setMapValue(new JSONDocType(((Class<?>) mapValueType).getSimpleName().toLowerCase()));
				} else if (mapValueType instanceof WildcardType) {
					jsondocType.setMapValue(new JSONDocType(WILDCARD));
				} else {
					jsondocType.setMapValue(build(jsondocType.getMapValue(), (Class<?>) ((ParameterizedType) mapValueType).getRawType(), mapValueType));
				}

			}

		} else if (Collection.class.isAssignableFrom(clazz)) {
			if (type instanceof ParameterizedType) {
				Type parametrizedType = ((ParameterizedType) type).getActualTypeArguments()[0];
				jsondocType.addItemToType(getCustomClassName(clazz));
				
				if (parametrizedType instanceof Class) {
					jsondocType.addItemToType(getCustomClassName((Class<?>) parametrizedType));
				} else if (parametrizedType instanceof WildcardType) {
					jsondocType.addItemToType(WILDCARD);
				} else {
					return build(jsondocType, (Class<?>) ((ParameterizedType) parametrizedType).getRawType(), parametrizedType);
				}
			} else if (type instanceof GenericArrayType) {
				return build(jsondocType, clazz, ((GenericArrayType) type).getGenericComponentType());
			} else {
				jsondocType.addItemToType(getCustomClassName(clazz));
			}

		} else if (clazz.isArray()) {
			jsondocType.addItemToType(ARRAY);
			Class<?> componentType = clazz.getComponentType();
			return build(jsondocType, componentType, type);

		} else {
			jsondocType.addItemToType(getCustomClassName(clazz));
			if (type instanceof ParameterizedType) {
				Type parametrizedType = ((ParameterizedType) type).getActualTypeArguments()[0];
				
				if (parametrizedType instanceof Class) {
					jsondocType.addItemToType(getCustomClassName((Class<?>) parametrizedType));
				} else if (parametrizedType instanceof WildcardType) {
					jsondocType.addItemToType(WILDCARD);
				} else {
					return build(jsondocType, (Class<?>) ((ParameterizedType) parametrizedType).getRawType(), parametrizedType);
				}
			}
		}

		return jsondocType;
	}
	
	private static String getCustomClassName(Class<?> clazz) {
		if(clazz.isAnnotationPresent(ApiObject.class)) {
			ApiObject annotation = clazz.getAnnotation(ApiObject.class);
			if(annotation.name().isEmpty()) {
				return clazz.getSimpleName().toLowerCase();
			} else {
				return annotation.name();
			}
		} else {
			return clazz.getSimpleName().toLowerCase();
		}
	}

}
@@ -139,24 +139,63 @@
 flect.Type;%0A
+import java.lang.reflect.TypeVariable;%0A
 import java.
@@ -217,24 +217,24 @@
 ldcardType;%0A
-
 import java.
@@ -1360,32 +1360,163 @@
 ype(WILDCARD));%0A
+%09%09%09%09%7D  else if(mapKeyType instanceof TypeVariable%3C?%3E)%7B%0A%09%09%09%09%09jsondocType.addItemToType(((TypeVariable%3C?%3E) mapValueType).getName());%0A
 %09%09%09%09%7D else %7B%0A%09%09%09
@@ -1902,24 +1902,156 @@
 WILDCARD));%0A
+%09%09%09%09%7D else if(mapValueType instanceof TypeVariable%3C?%3E)%7B%0A%09%09%09%09%09jsondocType.addItemToType(((TypeVariable%3C?%3E) mapValueType).getName());%0A
 %09%09%09%09%7D else %7B
@@ -2669,32 +2669,172 @@
 Type(WILDCARD);%0A
+%09%09%09%09%7D else if(parametrizedType instanceof TypeVariable%3C?%3E)%7B%0A%09%09%09%09%09jsondocType.addItemToType(((TypeVariable%3C?%3E) parametrizedType).getName());%0A
 %09%09%09%09%7D else %7B%0A%09%09%09
@@ -3720,32 +3720,32 @@
 WildcardType) %7B%0A
-
 %09%09%09%09%09jsondocType
@@ -3762,32 +3762,172 @@
 Type(WILDCARD);%0A
+%09%09%09%09%7D else if(parametrizedType instanceof TypeVariable%3C?%3E)%7B%0A%09%09%09%09%09jsondocType.addItemToType(((TypeVariable%3C?%3E) parametrizedType).getName());%0A
 %09%09%09%09%7D else %7B%0A%09%09%09

Fix ClassCastException (java.lang.reflect.TypeVariable cannot be cast to java.lang.reflect.ParameterizedType) for object-fields specialized with generics