package com.dslplatform.json.runtime;

import com.dslplatform.json.ConfigurationException;
import com.dslplatform.json.Nullable;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

abstract class Reflection {

	static final class ReadField implements Settings.Function {
		private final Field field;

		ReadField(Field field) {
			this.field = field;
		}

		@Override
		public Object apply(@Nullable Object instance) {
			try {
				return field.get(instance);
			} catch (IllegalAccessException e) {
				throw new ConfigurationException("Unable to read field " + field.getName() + " of " + field.getDeclaringClass(), e);
			}
		}
	}

	static final class ReadMethod implements Settings.Function {
		private final Method method;

		ReadMethod(Method method) {
			this.method = method;
		}

		@Override
		public Object apply(@Nullable Object instance) {
			try {
				return method.invoke(instance);
			} catch (IllegalAccessException | InvocationTargetException e) {
				throw new ConfigurationException("Unable to call method " + method.getName() + " of " + method.getDeclaringClass(), e);
			}
		}
	}

	static final class SetField implements Settings.BiConsumer {
		private final Field field;

		SetField(Field field) {
			this.field = field;
		}

		@Override
		public void accept(Object instance, @Nullable Object value) {
			try {
				field.set(instance, value);
			} catch (IllegalAccessException e) {
				throw new ConfigurationException("Unable to set field " + field.getName() + " of " + field.getDeclaringClass(), e);
			}
		}
	}

	static final class SetMethod implements Settings.BiConsumer {
		private final Method method;

		SetMethod(Method method) {
			this.method = method;
		}

		@Override
		public void accept(Object instance, @Nullable Object value) {
			try {
				method.invoke(instance, value);
			} catch (IllegalAccessException | InvocationTargetException e) {
				throw new ConfigurationException("Unable to call method " + method.getName() + " of " + method.getDeclaringClass(), e);
			}
		}
	}

	private static boolean canUseTypeName = true;

	static String typeDescription(Type manifest) {
		if (manifest instanceof Class<?>) {
			return ((Class<?>)manifest).getName();
		} else if (canUseTypeName) {
			try {
				return tryTypeName(manifest);
			} catch (NoSuchMethodError ignore) {
				canUseTypeName = false;
			}
		}
		return manifest.toString();
	}

	private static String tryTypeName(Type manifest) {
		return manifest.getTypeName();
	}
}

@@ -279,16 +279,43 @@
 t.Type;%0D
+%0Aimport java.util.Objects;%0D
 %0A%0D%0Aabstr
@@ -593,32 +593,224 @@
 get(instance);%0D%0A
+%09%09%09%7D catch (IllegalArgumentException e)%7B%0D%0A%09%09%09%09throw new ConfigurationException(%22Unable to find field %22 + field.getName() + %22 in %22 + field.getDeclaringClass() + %22 for object %22 + instance, e);%0D%0A
 %09%09%09%7D catch (Ille
@@ -1238,32 +1238,235 @@
 oke(instance);%0D%0A
+%09%09%09%7D catch (IllegalArgumentException e)%7B%0D%0A%09%09%09%09throw new ConfigurationException(%22Unable to invoke method %22 + method.getName() + %22 in class %22 + method.getDeclaringClass() + %22 for object %22 + instance, e);%0D%0A
 %09%09%09%7D catch (Ille
@@ -1926,32 +1926,310 @@
 tance, value);%0D%0A
+%09%09%09%7D catch (IllegalArgumentException e)%7B%0D%0A%09%09%09%09throw new ConfigurationException(%22Unable to set field %22 + field.getName() + %22 in %22 + field.getDeclaringClass() + %22 of object %22 + instance + %22 to value of type %22 + (value == null ? %22Void%22 : value.getClass().getCanonicalName()), e);%0D%0A
 %09%09%09%7D catch (Ille
@@ -2636,24 +2636,24 @@
 %0D%0A%09%09%09try %7B%0D%0A
-
 %09%09%09%09method.i
@@ -2669,32 +2669,324 @@
 tance, value);%0D%0A
+%09%09%09%7D catch (IllegalArgumentException e)%7B%0D%0A%09%09%09%09throw new ConfigurationException(%22Unable to invoke method %22 + method.getName() + %22 in %22 + method.getDeclaringClass() + %22 on object %22 + instance + %22 with an argument of type %22 + (value == null ? %22Void%22 : value.getClass().getCanonicalName()), e);%0D%0A
 %09%09%09%7D catch (Ille

improve exception message on setter parameter mismatch when using reflection