package jp.sourceforge.glad.reflect;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectionUtils {

    // ---- java.lang.Class

    public static Field getField(Class<?> clazz, String name) {
        try {
            return clazz.getField(name);
        } catch (NoSuchFieldException e) {
            throw new NoSuchFieldRuntimeException(e);
        }
    }

    public static Field getDeclaredField(Class<?> clazz, String name) {
        try {
            return clazz.getDeclaredField(name);
        } catch (NoSuchFieldException e) {
            throw new NoSuchFieldRuntimeException(e);
        }
    }

    public static Method getMethod(
            Class<?> clazz, String name, Class<?>... paramTypes) {
        try {
            return clazz.getMethod(name, paramTypes);
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodRuntimeException(e);
        }
    }

    public static Method getDeclaredMethod(
            Class<?> clazz, String name, Class<?>... paramTypes) {
        try {
            return clazz.getDeclaredMethod(name, paramTypes);
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodRuntimeException(e);
        }
    }

    public static String toString(Class<?> clazz) {
        if (clazz.isArray()) {
            return toString(clazz.getComponentType()) + "[]";
        } else {
            return clazz.getName();
        }
    }

    public static String toString(Class<?> clazz, boolean varArgs) {
        if (varArgs) {
            return toString(clazz.getComponentType()) + "...";
        } else {
            return toString(clazz);
        }
    }

    public static String toSimpleString(Class<?> clazz) {
        return clazz.getSimpleName();
    }

    public static String toSimpleString(Class<?> clazz, boolean varArgs) {
        if (varArgs) {
            return toSimpleString(clazz.getComponentType()) + "...";
        } else {
            return toSimpleString(clazz);
        }
    }

    // ---- java.lang.reflect.AccessibleObject

    public static void makeAccessible(AccessibleObject object) {
        if (!object.isAccessible()) {
            object.setAccessible(true);
        }
    }

    // ---- java.lang.reflect.Field

    @SuppressWarnings("unchecked")
    public static <T> T get(Field field, Object target) {
        try {
            return (T) field.get(target);
        } catch (IllegalAccessException e) {
            throw new IllegalAccessRuntimeException(e);
        }
    }

    public static void set(Field field, Object target, Object value) {
        try {
            field.set(target, value);
        } catch (IllegalAccessException e) {
            throw new IllegalAccessRuntimeException(e);
        }
    }

    public static String toString(Field field) {
        return fieldToString(field.getDeclaringClass(), field.getName());
    }

    public static String toSimpleString(Field field) {
        return fieldToSimpleString(field.getDeclaringClass(), field.getName());
    }

    public static String fieldToString(Class<?> clazz, String name) {
        return toString(clazz) + '#' + name;
    }

    public static String fieldToSimpleString(Class<?> clazz, String name) {
        return toSimpleString(clazz) + '#' + name;
    }

    // ---- java.lang.reflect.Method

    @SuppressWarnings("unchecked")
    public static <T> T invoke(Method method, Object target, Object... args) {
        args = getArguments(method, args);
        try {
            return (T) method.invoke(target, args);
        } catch (IllegalAccessException e) {
            throw new IllegalAccessRuntimeException(e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof Error) {
                throw (Error) t;
            } else if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InvocationTargetRuntimeException(e);
            }
        }
    }

    public static Object[] getArguments(Method method, Object... args) {
        if (!method.isVarArgs()) {
            return args;
        }
        Class<?>[] paramTypes = method.getParameterTypes();
        if (args.length <= paramTypes.length) {
            return args;
        }
        int lastIndex = paramTypes.length - 1;
        Class<?> lastParamType = paramTypes[lastIndex].getComponentType();
        int length = args.length - lastIndex;
        Object lastArg = Array.newInstance(lastParamType, length);
        System.arraycopy(args, lastIndex, lastArg, 0, length);
        Object[] newArgs = new Object[paramTypes.length];
        System.arraycopy(args, 0, newArgs, 0, lastIndex);
        newArgs[lastIndex] = lastArg;
        return newArgs;
    }

    public static String toString(Method method) {
        return methodToString(
                method.getDeclaringClass(),
                method.getName(),
                method.isVarArgs(),
                method.getParameterTypes());
    }

    public static String toSimpleString(Method method) {
        return methodToSimpleString(
                method.getDeclaringClass(),
                method.getName(),
                method.isVarArgs(),
                method.getParameterTypes());
    }

    public static String methodToString(
            Class<?> clazz, String name, Class<?>... paramTypes) {
        return methodToString(clazz, name, false, paramTypes);
    }

    public static String methodToString(
            Class<?> clazz, String name,
            boolean varArgs, Class<?>... paramTypes) {
        return toString(clazz) + '#'
                + methodToString(name, varArgs, paramTypes);
    }

    public static String methodToString(String name, Class<?>... paramTypes) {
        return methodToString(name, false, paramTypes);
    }

    public static String methodToString(
            String name, boolean varArgs, Class<?>[] paramTypes) {
        StringBuilder sb = new StringBuilder();
        sb.append(name);
        sb.append('(');
        if (paramTypes.length > 0) {
            int lastIndex = paramTypes.length - 1;
            for (int i = 0; i < lastIndex; ++i) {
                sb.append(toString(paramTypes[i]));
                sb.append(", ");
            }
            sb.append(toString(paramTypes[lastIndex], varArgs));
        }
        sb.append(')');
        return sb.toString();
    }

    public static String methodToSimpleString(
            Class<?> clazz, String name, Class<?>... paramTypes) {
        return methodToSimpleString(clazz, name, false, paramTypes);
    }

    public static String methodToSimpleString(
            Class<?> clazz, String name,
            boolean varArgs, Class<?>... paramTypes) {
        return toSimpleString(clazz) + '#'
                + methodToSimpleString(name, varArgs, paramTypes);
    }

    public static String methodToSimpleString(
            String name, Class<?>... paramTypes) {
        return methodToString(name, false, paramTypes);
    }

    public static String methodToSimpleString(
            String name, boolean varArgs, Class<?>[] paramTypes) {
        StringBuilder sb = new StringBuilder();
        sb.append(name);
        sb.append('(');
        if (paramTypes.length > 0) {
            int lastIndex = paramTypes.length - 1;
            for (int i = 0; i < lastIndex; ++i) {
                sb.append(toSimpleString(paramTypes[i]));
                sb.append(", ");
            }
            sb.append(toSimpleString(paramTypes[lastIndex], varArgs));
        }
        sb.append(')');
        return sb.toString();
    }

}
