package com.heyu.api.data.utils;

import com.alibaba.fastjson.JSON;
import com.heyu.api.common.LBiFunction0;
import com.heyu.api.common.test.AriseUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ClassUtils;
import org.junit.Test;

import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;

@Slf4j
public class SanUtils {


    public static <T, R, V> Map<R, V> list2Map(List<V> values, LBiFunction0<T, R> function) {
        try {
            if (CollectionUtils.isEmpty(values)) {
                return new HashMap<>();

            }
            Map<R, V> map = new HashMap<>();
            Method method = getMethod(function);
            for (V value : values) {
                map.put((R) method.invoke(value), value);
            }
            return map;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new HashMap<>();
    }

    public static <T, R, V> List<R> list2listFilterNull(List<V> values, LBiFunction0<T, R> function) {

        List<R> list = list2list(values, function);

        List<R> result = new ArrayList<>();

        if (CollectionUtils.isNotEmpty(list)) {
            for (R r : list) {
                if (r != null) {
                    result.add(r);
                }
            }
        }

        return result;
    }


    public static <T, R, V> List<R> setNull(List<V> values, LBiFunction0<T, R>... functions) {
        List<R> list = new ArrayList<>();
        try {
            if (CollectionUtils.isEmpty(values)) {
                return list;

            }

            for (LBiFunction0<T, R> function : functions) {
                Method method = getSetMethod(function);
                for (V value : values) {
                    Object[] objects = new Object[]{null};
                    method.invoke(value, objects);
                }
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }


    public static <T, R, V> List<R> list2list(List<V> values, LBiFunction0<T, R> function) {
        List<R> list = new ArrayList<>();
        try {
            if (CollectionUtils.isEmpty(values)) {
                return list;

            }
            Method method = getMethod(function);
            for (V value : values) {
                list.add((R) method.invoke(value));
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    public static <T, R, V> Map<R, List<V>> list2GroupBy(List<V> values, LBiFunction0<T, R> function) {
        if(CollectionUtils.isEmpty(values)){
            return new HashMap<>();
        }
        Map<R, List<V>> listmap = new HashMap<>();
        Method method = getMethod(function);
        try {
            for (int i = 0; i < values.size(); i++) {
                V v = values.get(i);
                R r = (R) method.invoke(v);
                List<V> list = listmap.get(r);
                if (list == null) {
                    list = new ArrayList<>();
                }
                list.add(v);
                listmap.put(r, list);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return listmap;
    }



    public static <T, R, V> R findAnyNotNull(List<V> values, LBiFunction0<T, R> function) {
        try {
            if (CollectionUtils.isEmpty(values)) {
                return null;

            }
            Method method = getMethod(function);
            for (V value : values) {
                R r = (R) method.invoke(value);
                if (r != null) {
                    return r;
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static <T, R, V> R sum(List<V> values, LBiFunction0<T, R> function) {
        BigDecimal sum = BigDecimal.ZERO;
        Class<?> clazz = null;
        try {
            if (CollectionUtils.isEmpty(values)) {
                return null;
            }
            Method method = getMethod(function);
            clazz = method.getReturnType();
            for (V value : values) {
                R r = (R) method.invoke(value);
                if (r != null) {
                    sum = NumberUtils.objToBigDecimalDefault(r, BigDecimal.ZERO).add(sum);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return (R) parseBasicTypeWrapper(clazz, sum);
    }


    private static <T, R> Method getSetMethod(LBiFunction0<T, R> function) {
        try {
            Method method = function.getClass().getDeclaredMethods()[1];
            method.setAccessible(true);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
            String methodName = serializedLambda.getImplMethodName();
            String className = serializedLambda.getImplClass().replace("/", ".");

            methodName = "set" + methodName.substring(3);

            Class<?> clazz = Class.forName(className);

            Method methods[] = clazz.getDeclaredMethods();
            for (Method m : methods) {
                if (m.getName().equals(methodName)) {
                    return m;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    private static <T, R> Method getMethod(LBiFunction0<T, R> function) {
        try {
            Method method = function.getClass().getDeclaredMethods()[1];
            method.setAccessible(true);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
            String methodName = serializedLambda.getImplMethodName();
            String className = serializedLambda.getImplClass().replace("/", ".");
            Class<?> clazz = Class.forName(className);

            Method methods[] = clazz.getDeclaredMethods();
            for (Method m : methods) {
                if (m.getName().equals(methodName)) {
                    return m;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    private static <T, R> List<Method> getMethods(LBiFunction0<T, R> function) {
        try {
            Method method = function.getClass().getDeclaredMethods()[1];
            method.setAccessible(true);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
            String methodName = serializedLambda.getImplMethodName();
            String className = serializedLambda.getImplClass().replace("/", ".");
            Class<?> clazz = Class.forName(className);

            List<Method> list = new ArrayList<>();
            Method methods[] = clazz.getDeclaredMethods();
            for (Method m : methods) {
                if (m.getName().equals(methodName)) {
                    list.add(m);
                }
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static Method getRealMethod(List<Method> methods, Class paramClazz) {
        for (Method method : methods) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes != null
                    && parameterTypes.length == 1
                    && ClassUtils.isAssignable(paramClazz, parameterTypes[0])) {
                return method;
            }
        }
        return null;
    }

    public static <T, R, V> List<R> convert(List<V> list, LBiFunction0<T, R> function) {
        List<Method> methods = getMethods(function);
        List result = new ArrayList<>();
        for (V v : list) {
            try {
                Class clazz = v.getClass();
                Method method = getRealMethod(methods, clazz);
                int modifiers = method.getModifiers();
                Object object = null;
                if (Modifier.isStatic(modifiers)) {
                    object = method.invoke(null, v);
                } else {
                    object = method.invoke(v);
                }
                result.add(object);
            } catch (Exception e) {
                log.error("convert exception", e);
            }
        }
        return result;
    }


    /**
     * 基本类型包装类解析
     */
    public static Object parseBasicTypeWrapper(Class<?> parameterType, Object value) {
        if (Number.class.isAssignableFrom(parameterType)) {
            if (parameterType == Integer.class || parameterType == int.class) {
                return NumberUtils.objToIntDefault(value, null);
            } else if (parameterType == Short.class || parameterType == short.class) {
                return NumberUtils.objToShortDefault(value, null);
            } else if (parameterType == Long.class || parameterType == long.class) {
                return NumberUtils.objToLongDefault(value, null);
            } else if (parameterType == Float.class || parameterType == float.class) {
                return NumberUtils.objToFloatDefault(value, null);
            } else if (parameterType == Double.class || parameterType == double.class) {
                return NumberUtils.objToDoubleDefault(value, null);
            } else if (parameterType == Byte.class || parameterType == byte.class) {
                return NumberUtils.objToByteDefault(value, null);
            } else if (parameterType == BigDecimal.class) {
                return NumberUtils.objToBigDecimalDefault(value, null);
            }
        } else if (parameterType == Boolean.class || parameterType == boolean.class) {
            return NumberUtils.objToBooleanDefault(value, null);
        } else if (parameterType == Character.class || parameterType == char.class) {
            return NumberUtils.objToCharacterDefault(value, null);
        } else if (parameterType == String.class) {
            return value != null ? value.toString() : null;
        } else if (parameterType == Date.class && value instanceof Date) {
            return value;
        } else if (parameterType == Date.class && value instanceof String) {
            Object target = DateUtils.parseStr2Date((String) value, YYYY_MM_DD_HH_MM_SS);
            if (target == null) {
                target = DateUtils.parseStr2Date((String) value, YYYY_MM_DD);
                if (target == null) {
                    target = DateUtils.parseStr2Date((String) value, YYYY);
                    if (target == null) {
                        target = DateUtils.parseStr2Date((String) value, YYYY_MM);
                        if (target == null) {
                            target = DateUtils.parseStr2Date((String) value, YYYY_MM_DD_HH);
                            if (target == null) {
                                target = DateUtils.parseStr2Date((String) value, YYYY_MM_DD_HH_MM);
                            }
                        }
                    }
                }
            }
            return target;
        }
        return null;
    }

    public static String YYYY = "yyyy";
    public static String YYYY_MM = "yyyy-MM";
    public static String YYYY_MM_DD = "yyyy-MM-dd";
    public static String YYYY_MM_DD_HH = "yyyy-MM-dd HH";
    public static String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm";
    public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";

    private static final List<Class<?>> primitiveTypes = new ArrayList<>(32);

    static {
        primitiveTypes.add(Boolean.class);
        primitiveTypes.add(Byte.class);
        primitiveTypes.add(Character.class);
        primitiveTypes.add(Double.class);
        primitiveTypes.add(Float.class);
        primitiveTypes.add(Integer.class);
        primitiveTypes.add(Long.class);
        primitiveTypes.add(Short.class);
        primitiveTypes.add(BigDecimal.class);

        primitiveTypes.add(boolean.class);
        primitiveTypes.add(byte.class);
        primitiveTypes.add(char.class);
        primitiveTypes.add(double.class);
        primitiveTypes.add(float.class);
        primitiveTypes.add(int.class);
        primitiveTypes.add(long.class);
        primitiveTypes.add(short.class);
        primitiveTypes.add(String.class);
        primitiveTypes.add(Date.class);
    }

    public static boolean isBasicDataTypes(Class clazz) {
        return primitiveTypes.contains(clazz) ? true : false;
    }


    public static <T> T get(Future<T> t) {

        return get(t, 3000L);

    }


    public static <T> T get(Future<T> t, long timeout) {
        if (t != null) {
            try {
                return t.get(timeout, TimeUnit.MILLISECONDS);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        return null;
    }


    private static ExecutorService threadPool;


    static {
        threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }


    public static <T, R, V> List<R> parallel2List(List<V> params, LBiFunction0<T, R> function) {

        List<R> list = new ArrayList<>();


        Map<String, Future<R>> map = new HashMap<>();

        for (int i = 0; i < params.size(); i++) {
            V v = params.get(i);
            Future<R> countFuture = threadPool.submit(getAsynCallable(function, v));


            map.put(i + "", countFuture);
        }


        Map<String, R> agentResult = new HashMap<>();

        map.forEach((key, future) -> Optional.ofNullable(get(future)).ifPresent(osResp -> {
            agentResult.put(key, osResp);
        }));
        for (Map.Entry<String, R> entry : agentResult.entrySet()) {
            list.add(entry.getValue());
        }
        return list;

    }


    public static <T, R, V, K> Map<K, R> parallel2Map(List<V> params, LBiFunction0<T, R> function, LBiFunction0<T, K> functionKey) {
        Map<K, Future<R>> map = new HashMap<>();

        for (int i = 0; i < params.size(); i++) {
            V v = params.get(i);

            Future<R> countFuture = threadPool.submit(getAsynCallable(function, v));

            K key = null;

            Object object = getKeyDeValue(v);

            Method method = getMethod(functionKey);

            method.setAccessible(true);

            try {
                int modifiers = method.getModifiers();
                if (Modifier.isStatic(modifiers)) {
                    key = (K) method.invoke(null, object);
                } else {
                    key = (K) method.invoke(object);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            map.put(key, countFuture);
        }

        Map<K, R> agentResult = new HashMap<>();

        map.forEach((key, future) -> Optional.ofNullable(get(future)).ifPresent(osResp -> {
            agentResult.put(key, osResp);
        }));

        return agentResult;

    }

    private static Object getKeyDeValue(Object v) {
        if (v instanceof Map) {
            for (Map.Entry<?, ?> entry : ((Map<?, ?>) v).entrySet()) {
                return getKeyDeValue(entry.getValue());
            }
        } else if (v instanceof List) {
            return getKeyDeValue(((List<?>) v).get(0));
        } else if (!isBasicDataTypes(v.getClass())) {
            return v;
        }

        return null;
    }


    private static <T, P, R, E> Callable getAsynCallable(LBiFunction0<T, R> function0, P value) {
        return new Callable<R>() {
            @Override
            public R call() throws Exception {

                Object object = null;
                try {
                    Method method = getMethod(function0);

                    int modifiers = method.getModifiers();

                    if (Modifier.isStatic(modifiers)) {

                        object = method.invoke(null, value);

                    } else {
                        object = method.invoke(value);
                    }

                    return (R) object;
                } catch (Exception e) {
                   log.error("线程池异常",e);
                } finally {

                }
                return null;
            }
        };
    }



    private static  Callable getAsynRun(Runnable runnable) {
        return new Callable() {
            @Override
            public R call() throws Exception {

                Object object = null;
                try {
                    runnable.run();

                } catch (Exception e) {
                    log.error("线程池异常",e);
                }
                return null;
            }
        };
    }



    public static AsynRunDTO build(Runnable ... target){
        Map<String, Future<R>> map = new HashMap<>();
        for (int i = 0; i < target.length; i++) {
            Runnable v = target[i];
            Future<R> countFuture = threadPool.submit(getAsynRun(v));
            map.put(i + "", countFuture);
        }

        AsynRunDTO  asynRunDTO = new AsynRunDTO();
        asynRunDTO.setMap(map);
        return asynRunDTO;
    }


    @Test
    public void test(){
        build(
                ()->{
                    System.out.println("1");
                },
                ()->{
                    System.out.println("2");
                }
                ).run();
    }




    public static void main(String[] args) {


        Map<Long, String> map = new HashMap<>();
        map.put(null,"owiew");
        System.out.println(JSON.toJSONString(map));

        System.out.println(map.get(null));

    }


    public static AriseUser getName(AriseUser ariseUser) {

        return ariseUser;

    }


}
