package com.jscape.util.reflection;

import java.util.Map;

import static com.jscape.util.CollectionTools.map;
import static com.jscape.util.Pair.pair;
import static com.jscape.util.Parameters.assertNotNull;

/**
 * @author Alex
 */
@SuppressWarnings("unchecked")
public class ReflectionTools {

    private static final Map<String, PrimitiveClassEntry> PRIMITIVE_TYPES = map(
            pair("boolean", new PrimitiveClassEntry(boolean.class, Boolean.class)),
            pair("byte", new PrimitiveClassEntry(byte.class, Byte.class)),
            pair("char", new PrimitiveClassEntry(char.class, Character.class)),
            pair("short", new PrimitiveClassEntry(short.class, Short.class)),
            pair("int", new PrimitiveClassEntry(int.class, Integer.class)),
            pair("long", new PrimitiveClassEntry(long.class, Long.class)),
            pair("float", new PrimitiveClassEntry(float.class, Float.class)),
            pair("double", new PrimitiveClassEntry(double.class, Double.class)),
            pair("void", new PrimitiveClassEntry(void.class, Void.class))
    );

    public static boolean isPrimitive(String name) {
        return PRIMITIVE_TYPES.containsKey(name);
    }

    public static Class typeToClass(Class type) {
        return type.isPrimitive() ? typeNameToWrapperClass(type.getName()) : type;
    }

    public static Class typeNameToWrapperClass(String typeName) {
        PrimitiveClassEntry entry = PRIMITIVE_TYPES.get(typeName);
        return entry != null ? entry.wrapperClass : null;
    }

    public static Class typeNameToPrimitiveClass(String typeName) {
        PrimitiveClassEntry entry = PRIMITIVE_TYPES.get(typeName);
        return entry != null ? entry.primitiveClass : null;
    }

    public static boolean match(Class[] declaredTypes, Class[] actualTypes, boolean explicit) {
        if (declaredTypes.length != actualTypes.length) {
            return false;
        }

        for (int i = 0; i < declaredTypes.length; ++i) {
            Class declaredType = declaredTypes[i].isPrimitive() ? typeToClass(declaredTypes[i]) : declaredTypes[i];
            Class actualType = actualTypes[i];

            if (explicit && declaredType != actualType) {
                return false;
            }

            if (actualType != null && !declaredType.isAssignableFrom(actualType)) {
                return false;
            }
        }

        return true;
    }

    public static boolean isArray(Object target) {
        return target instanceof Class ? ((Class) target).isArray() : target.getClass().isArray();
    }

    private ReflectionTools() {
        // empty
    }

    public static class PrimitiveClassEntry {

        public final Class primitiveClass;
        public final Class wrapperClass;

        public PrimitiveClassEntry(Class primitiveClass, Class wrapperClass) {
            assertNotNull(primitiveClass);
            this.primitiveClass = primitiveClass;

            assertNotNull(wrapperClass);
            this.wrapperClass = wrapperClass;
        }
    }
}
