package thyyxxk.webserver.utils;

import cn.hutool.core.util.ReflectUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @description: 层级菜单工具
 * @author: DingJie
 * @create: 2021/8/1016:09
 */
@Slf4j
public class TreeUtil {
    public interface TowFunction<T, T1> {
        void apply(T t, T1 t1);
    }

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Option {
        private String id;
        private String parentId;
        private String children;
        private String sort;
    }

    public static <T> List<T> objectTree(List<T> tempList, Option option) {
        return objectTree(tempList, option, null);
    }

    public static <T> List<T> objectTree(List<T> tempList, Option option, TowFunction<T, T> addCallback) {
        String id = StringUtil.getMethodName(option.getId());
        String parentId = StringUtil.getMethodName(option.getParentId());
        String getChildren = StringUtil.getMethodName(option.getChildren());
        String setChildren = StringUtil.setMethodName(option.getChildren());

        List<T> resultList = new LinkedList<>();
        Map<Object, T> treeMap = new HashMap<>(tempList.size());

        for (T item : tempList) {
            Object key = ReflectUtil.invoke(item, id);
            Object parent = ReflectUtil.invoke(item, parentId);
            treeMap.put(key, item);
            if (parent == null) {
                if (addCallback != null) {
                    addCallback.apply(null, item);
                }
                resultList.add(item);
            }
        }

        for (T item : tempList) {
            Object key = ReflectUtil.invoke(item, parentId);
            T tree = treeMap.get(key);
            if (tree != null) {
                List<T> child = ReflectUtil.invoke(tree, getChildren);
                if (null == child) {
                    ReflectUtil.invoke(tree, setChildren, new ArrayList<>());
                    child = ReflectUtil.invoke(tree, getChildren);
                }
                if (addCallback != null) {
                    addCallback.apply(tree, item);
                }
                child.add(item);
            }
        }

        if (option.getSort() != null) {
            return objectSort(resultList, option.getSort(), option.getChildren());
        }

        return resultList;
    }

    public static <T> List<T> objectTree(List<T> tempList, TowFunction<T, T> addCallback) {
        return objectTree(tempList, "id", "parentId", addCallback);
    }

    public static <T> List<T> objectSort(List<T> tempList, String sortName) {
        return objectSort(tempList, sortName, "children");
    }

    public static <T> List<T> objectSort(List<T> tempList, String sortName, String childrenName) {
        String getSortName = StringUtil.getMethodName(sortName);
        String getChildrenName = StringUtil.getMethodName(childrenName);
        String setChildrenName = StringUtil.setMethodName(childrenName);
        Stream<T> sorted = tempList
                .stream()
                .sorted((Comparator.comparing(a -> {
                            List<T> child = ReflectUtil.invoke(a, getChildrenName);
                            if (child != null && !child.isEmpty()) {
                                ReflectUtil.invoke(a, setChildrenName, objectSort(child, sortName));
                            }
                            Integer sort = ReflectUtil.invoke(a, getSortName);
                            return sort == null ? 0 : sort;
                        }
                )));

        return sorted.collect(Collectors.toList());
    }

    public static <T> List<T> objectTree(List<T> tempList) {
        return objectTree(tempList, "id", "parentId", null);
    }

    public static <T> List<T> objectTreeSort(List<T> tempList) {
        Option defaultOption = getDefaultOption();
        return objectTree(tempList, defaultOption, null);
    }

    public static <T> List<T> objectTree(List<T> tempList, String id, String parentId, TowFunction<T, T> addCallback) {
        Option tOption = Option.builder()
                .id(id)
                .parentId(parentId)
                .children("children")
                .build();
        return objectTree(tempList, tOption, addCallback);
    }

    public static Option getDefaultOption() {
        return Option.builder()
                .id("id")
                .parentId("parentId")
                .children("children")
                .sort("sort")
                .build();
    }

}
