package com.nuliji.tools;

import com.nuliji.tools.annotation.Dto;
import com.nuliji.tools.exception.SystemException;
import org.modelmapper.AbstractConverter;
import org.modelmapper.Converter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.modelmapper.ModelMapper;
import org.modelmapper.config.Configuration;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.*;

import static org.modelmapper.convention.MatchingStrategies.STRICT;


/**
 * 使用ModelMapper转换Dto和Entity
 */

public class Transform {
    private static final ModelMapper modelMapper = new ModelMapper();

    private static final Logger logger = LoggerFactory.getLogger(Transform.class);

    static {
//        modelMapper.getConfiguration().setFieldMatchingEnabled(true);
        modelMapper.getConfiguration().setMatchingStrategy(STRICT);
        modelMapper.getConfiguration().setFieldAccessLevel(Configuration.AccessLevel.PRIVATE);
        Converter<Collection<Long>, String> longToString = new AbstractConverter<Collection<Long>, String>() {
            protected String convert(Collection<Long> source) {
                return Tools.joinIds(source);
            }
        };
        modelMapper.addConverter(longToString);
        Converter<String, Collection<Long>> stringToLong = new AbstractConverter<String, Collection<Long>>() {
            protected Collection<Long> convert(String source) {
                return Tools.splitIds(source);
            }
        };
        modelMapper.addConverter(stringToLong);
        Converter<Date, Long> dateToLong = new AbstractConverter<Date, Long>() {
            protected Long convert(Date source) {
                return source.getTime() / 1000;
            }
        };
        modelMapper.addConverter(dateToLong);
        Converter<Long, Date> longToDate = new AbstractConverter<Long, Date>() {
            protected Date convert(Long source) {
                return new Date(source * 1000);
            }
        };
        modelMapper.addConverter(longToDate);
    }

    public static <S, T> T dtoToEntity(S source) {
        Class sourceClass = source.getClass();
        if(!sourceClass.isAnnotationPresent(Dto.class)){
            throw new SystemException("Class " + sourceClass.getName() + " need dto annotation");
        }
        Dto dto = (Dto) sourceClass.getAnnotation(Dto.class);
        return (T) transfer(source, dto.entity());
    }

    public static <S, T> T convert(S source, Class<?> clazz) {
        return (T) transfer(source, clazz);
    }

    public static <S, T> List<T> convert(Collection<S> source, Class<T> clazz) {
        List<T> al = new ArrayList<>();
        for(S s:source){
            al.add(Transform.convert(s, clazz));
        }
        return al;
    }

    public static <T> void replace(List<T> targetList, Collection<T> objectList, Class<?> objectClass, String indexProp) throws RuntimeException {
        if(objectList.size() == 0 || targetList.size() == 0) return ;
        Map<Object, T> objectMap = new HashMap<>(objectList.size());
        Field objectField = getField(objectClass, indexProp);

        try {
            for(T o: objectList){
                objectMap.put(objectField.get(o), o);
            }
            targetList.replaceAll(e-> {
                try {
                    return objectMap.get(objectField.get(e));
                } catch (IllegalAccessException e1) {
                    e1.printStackTrace();
                    return e;
                }
            });
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("ObjectClass:"+objectClass.getName()+" access "+indexProp+" exception");
        }
    }

    public static <T, S> void combine(List<T> targetList, Collection<S> objectList, Class<?> targetClass, String keyProp, String targetProp, Class<?> objectClass, String indexProp) throws RuntimeException {
        if(objectList.size() == 0 || targetList.size() == 0) return ;
        Map<Object, S> objectMap = new HashMap<>(objectList.size());
        Field objectField = getField(objectClass, indexProp);

        try {
            for(S o: objectList){
                objectMap.put(objectField.get(o), o);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("ObjectClass:"+objectClass.getName()+" access "+indexProp+" exception");
        }
        combine(targetList, objectMap, targetClass, keyProp, targetProp);
    }

    public static <T, S, R> void combine(List<T> targetList, Collection<S> objectList, Class<?> targetClass, String keyProp, String targetProp, Class<?> objectClass, String indexProp, Class<?> transform) throws RuntimeException {
        if(objectList.size() == 0 || targetList.size() == 0) return ;
        Map<Object, R> objectMap = new HashMap<>(objectList.size());
        Field objectField = getField(objectClass, indexProp);

        try {
            for(S o: objectList){
                objectMap.put(objectField.get(o), convert(o, transform));
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("ObjectClass:"+objectClass.getName()+" access "+indexProp+" exception");
        }
        combine(targetList, objectMap, targetClass, keyProp, targetProp);
    }

    public static <T, S> void combine(List<T> targetList, Map<Object, S> objectMap, Class<?> targetClass, String keyProp, String targetProp) throws RuntimeException {
        if(objectMap.size() == 0 || targetList.size() == 0) return ;
        Field targetField = getField(targetClass, targetProp);
        Field targetKeyField = getField(targetClass, keyProp);

        try {
            for(T t: targetList){
                S o = objectMap.get(targetKeyField.get(t));
                targetField.set(t, o);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("TargetClass:"+targetClass.getName()+" access "+keyProp+" exception");
        }
    }
    public static <T, S> void combine(List<T> targetList, Map<Object, Collection<S>> objectMap, Class<?> targetClass, String keyProp, String targetProp, Class<?> transform) throws RuntimeException {
        if(objectMap.size() == 0 || targetList.size() == 0) return ;
        Field targetField = getField(targetClass, targetProp);
        Field targetKeyField = getField(targetClass, keyProp);

        try {
            for(T t: targetList){
                Collection<S> o = objectMap.get(targetKeyField.get(t));
                if (transform == null){
                    targetField.set(t, o);
                }else{
                    targetField.set(t, convert(o, transform));
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("TargetClass:"+targetClass.getName()+" access "+keyProp+" exception");
        }
    }

    private static Object transfer(Object source, Class<?> clazz) {
        if (source == null) {
            return null;
        }
        return modelMapper.map(source, clazz);
    }
    private static Object transfer(Object source, Type type){
        if (source == null) {
            return null;
        }
        logger.info("{}", type);
        return modelMapper.map(source, type);
    }

    public static Field getField(Class<?> clazz, String prop){
        Field field;
        try {
            field = clazz.getDeclaredField(prop);
            field.setAccessible(true);
        } catch (NoSuchFieldException e) {
            // 关系继承类
            try {
                field = clazz.getSuperclass().getDeclaredField(prop);
                field.setAccessible(true);
            } catch (NoSuchFieldException e1) {
                throw new RuntimeException("TargetClass:"+clazz.getName()+" no "+prop);
            }
        }
        return field;
    }


    public static <T> Collection getIds(Collection<T> list, Class<?> clazz, String idProp) throws RuntimeException {
        Set ids = new HashSet<>();
        if(list == null || list.size() == 0) return ids;
        Field field = Transform.getField(clazz, idProp);
        try {
            for(T item: list){
                ids.add(field.get(item));
            }
            return ids;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("Class:"+clazz.getName()+" access "+idProp+" exception");
        }
    }

    public static <T> Map getMap(Collection<T> list, Class<?> clazz, String keyProp) throws RuntimeException{
        Map map = new HashMap<>();
        if(list == null || list.size() == 0) return map;
        Field keyField = Transform.getField(clazz, keyProp);
        try{
            for(T item: list){
                map.put(keyField.get(item), item);
            }
            return map;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("Class:"+clazz.getName()+" access "+keyProp+" exception");
        }
    }

    public static <T> Map getMap(Collection<T> list, Class<?> clazz, String keyProp, String valueProp) throws RuntimeException{
        Map map = new HashMap<>();
        if(list == null || list.size() == 0) return map;
        Field keyField = Transform.getField(clazz, keyProp);
        Field valueField = Transform.getField(clazz, valueProp);
        try{
            for(T item: list){
                map.put(keyField.get(item), valueField.get(item));
            }
            return map;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("Class:"+clazz.getName()+" access "+keyProp+","+valueProp+" exception");
        }
    }

    public static <T,S> Map<Object, List<T>> getMapList(List<S> list, List<T> targetList, Class<?> clazz, String keyProp, String targetProp, Class<?> targetClazz, String indexProp){
        return new HashMap<Object, List<T>>();
    }
}
