package com.jn.langx.util.reflect;

import com.jn.langx.annotation.Name;
import com.jn.langx.annotation.Singleton;
import com.jn.langx.util.Preconditions;
import com.jn.langx.util.collection.Arrs;
import com.jn.langx.util.collection.Collects;
import com.jn.langx.util.function.Consumer;
import com.jn.langx.util.reflect.parameter.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

@Singleton
public class ParameterServiceRegistry {
    private static final ParameterServiceRegistry INSTANCE = new ParameterServiceRegistry();
    private static final MethodParameterSupplier methodParameterSupplier;
    private static final ConstructorParameterSupplier constructorParameterSupplier;
    private static final Map<String, MethodParameterSupplier> methodParameterSupplierRegistry = new HashMap<String, MethodParameterSupplier>();
    private static final Map<String, ConstructorParameterSupplier> constructorParameterSupplierRegistry = new HashMap<String, ConstructorParameterSupplier>();

    private static final String JAVA_6_SUPPLIER_NAME = "langx_java6";
    private static final String JAVA_8_SUPPLIER_NAME = "langx_java8";

    private ParameterServiceRegistry() {

    }

    static {
        boolean JDK_PARAMETER_FOUND = false;
        try {
            Class.forName("java.lang.reflect.Parameter");
            JDK_PARAMETER_FOUND = true;
        } catch (ClassNotFoundException ex) {
        }

        loadMethodParameterSuppliers();
        String defaultName = JDK_PARAMETER_FOUND ? JAVA_8_SUPPLIER_NAME : JAVA_6_SUPPLIER_NAME;
        MethodParameterSupplier defaultMethodParameterSupplier = methodParameterSupplierRegistry.get(defaultName);
        if (defaultMethodParameterSupplier == null && JDK_PARAMETER_FOUND) {
            defaultMethodParameterSupplier = methodParameterSupplierRegistry.get(JAVA_6_SUPPLIER_NAME);
        }
        methodParameterSupplier = defaultMethodParameterSupplier;

        loadConstructorParameterSuppliers();
        ConstructorParameterSupplier defaultConstructorParameterSupplier = constructorParameterSupplierRegistry.get(defaultName);
        if (defaultConstructorParameterSupplier == null && JDK_PARAMETER_FOUND) {
            defaultConstructorParameterSupplier = constructorParameterSupplierRegistry.get(JAVA_6_SUPPLIER_NAME);
        }
        constructorParameterSupplier = defaultConstructorParameterSupplier;

    }

    private static void loadMethodParameterSuppliers() {
        ServiceLoader<MethodParameterSupplier> loader = ServiceLoader.load(MethodParameterSupplier.class);
        Collects.forEach(loader, new Consumer<MethodParameterSupplier>() {
            @Override
            public void accept(MethodParameterSupplier supplier) {
                if (supplier.getClass().getPackage().getName().startsWith("com.jn.langx.")) {
                    Class<? extends MethodParameterSupplier> clazz = supplier.getClass();
                    String name = Reflects.getFQNClassName(clazz);
                    methodParameterSupplierRegistry.put(name, supplier);
                    if (Reflects.hasAnnotation(clazz, Name.class)) {
                        Name nameAnno = Reflects.getAnnotation(clazz, Name.class);
                        name = nameAnno.value();
                        methodParameterSupplierRegistry.put(name, supplier);
                    }

                }
            }
        });
    }


    private static void loadConstructorParameterSuppliers() {
        ServiceLoader<ConstructorParameterSupplier> loader = ServiceLoader.load(ConstructorParameterSupplier.class);
        Collects.forEach(loader, new Consumer<ConstructorParameterSupplier>() {
            @Override
            public void accept(final ConstructorParameterSupplier supplier) {
                if (supplier.getClass().getPackage().getName().startsWith("com.jn.langx.")) {
                    Class<? extends ConstructorParameterSupplier> clazz = supplier.getClass();
                    String name = Reflects.getFQNClassName(clazz);
                    constructorParameterSupplierRegistry.put(name, supplier);
                    if (Reflects.hasAnnotation(clazz, Name.class)) {
                        Name nameAnno = Reflects.getAnnotation(clazz, Name.class);
                        name = nameAnno.value();
                        constructorParameterSupplierRegistry.put(name, supplier);
                    }

                }
            }
        });
    }

    public static ParameterServiceRegistry getInstance() {
        return INSTANCE;
    }


    public List<MethodParameter> getMethodParameters(final Method method) {
        return getMethodParameters(null, method);
    }

    public List<MethodParameter> getMethodParameters(final String supplierName, final Method method) {
        Preconditions.checkNotNull(method);
        int count = method.getParameterTypes().length;
        final List<MethodParameter> parameters = Collects.newArrayList();
        if (count > 0) {
            Collects.forEach(Arrs.range(count), new Consumer<Integer>() {
                @Override
                public void accept(Integer index) {
                    parameters.add(getMethodParameter(supplierName, method, index));
                }
            });
        }
        return parameters;
    }

    public MethodParameter getMethodParameter(Method method, int index) {
        return getMethodParameter(null, method, index);
    }

    public MethodParameter getMethodParameter(String supplierName, Method method, int index) {
        return getMethodParameter(supplierName, "arg" + index, 0, method, index);
    }

    public MethodParameter getMethodParameter(String name, int modifiers, Method method, int index) {
        return getMethodParameter(null, name, modifiers, method, index);
    }

    public MethodParameter getMethodParameter(String supplierName, String name, int modifiers, Method method, int index) {
        return findMethodParameterSupplier(supplierName, true).get(new ParameterMeta(name, modifiers, method, index));
    }

    public List<ConstructorParameter> getConstructorParameters(final Constructor constructor) {
        return getConstructorParameters(null, constructor);
    }

    public List<ConstructorParameter> getConstructorParameters(final String supplierName, final Constructor constructor) {
        Preconditions.checkNotNull(constructor);
        int count = constructor.getParameterTypes().length;
        final List<ConstructorParameter> parameters = Collects.newArrayList();
        if (count > 0) {
            Collects.forEach(Arrs.range(count), new Consumer<Integer>() {
                @Override
                public void accept(Integer index) {
                    parameters.add(getConstructorParameter(supplierName, constructor, index));
                }
            });
        }
        return parameters;
    }

    public ConstructorParameter getConstructorParameter(Constructor constructor, int index) {
        return getConstructorParameter(null, constructor, index);
    }

    public ConstructorParameter getConstructorParameter(String supplierName, Constructor constructor, int index) {
        return getConstructorParameter(supplierName, "arg" + index, 0, constructor, index);
    }

    public ConstructorParameter getConstructorParameter(String name, int modifiers, Constructor constructor, int index) {
        return getConstructorParameter(null, name, modifiers, constructor, index);
    }

    public ConstructorParameter getConstructorParameter(String supplierName, String name, int modifiers, Constructor constructor, int index) {
        return findConstructorParameterSupplier(supplierName, true).get(new ParameterMeta(name, modifiers, constructor, index));
    }


    public MethodParameterSupplier getDefaultMethodParameterSupplier() {
        return methodParameterSupplier;
    }

    public ConstructorParameterSupplier getDefaultConstructorParameterSupplier() {
        return constructorParameterSupplier;
    }

    public MethodParameterSupplier findMethodParameterSupplier(String name) {
        return findMethodParameterSupplier(name, true);
    }

    public MethodParameterSupplier findMethodParameterSupplier(String name, boolean useDefaultIfNotFound) {
        MethodParameterSupplier supplier = methodParameterSupplierRegistry.get(name);
        if (supplier == null && useDefaultIfNotFound) {
            return getDefaultMethodParameterSupplier();
        }
        return supplier;
    }

    public ConstructorParameterSupplier findConstructorParameterSupplier(String name) {
        return findConstructorParameterSupplier(name, true);
    }

    public ConstructorParameterSupplier findConstructorParameterSupplier(String name, boolean useDefaultIfNotFound) {
        ConstructorParameterSupplier supplier = constructorParameterSupplierRegistry.get(name);
        if (supplier == null && useDefaultIfNotFound) {
            return getDefaultConstructorParameterSupplier();
        }
        return supplier;
    }

    public void register(String name, MethodParameterSupplier supplier) {
        if (methodParameterSupplierRegistry.get(name) == null) {
            methodParameterSupplierRegistry.put(name, supplier);
        }
    }

    public void register(String name, ConstructorParameterSupplier supplier) {
        if (constructorParameterSupplierRegistry.get(name) == null) {
            constructorParameterSupplierRegistry.put(name, supplier);
        }
    }

}
