package com.njzscloud.common.core.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import com.njzscloud.common.core.ex.Exceptions;
import com.njzscloud.common.core.jackson.Jackson;

import java.util.*;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 对象分组工具
 */
public class GroupUtil {

    // region K-O

    // region Collection

    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param <SV> 值类型 / 源集合元素类型
     * @param <K>  键类型
     * @return Map&lt;K, SV&gt;
     */
    public static <SV, K> Map<K, SV> k_o(Collection<SV> src, Function<SV, K> kf) {
        return k_o(src, kf, it -> it, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 vf</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param vf   值提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <V>  值类型
     * @return Map&lt;K, V&gt;
     */
    public static <SV, K, V> Map<K, V> k_o(Collection<SV> src, Function<SV, K> kf, Function<SV, V> vf) {
        return k_o(src, kf, vf, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 源集合元素</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param mf   Map 提供函数
     * @param <SV> 值类型 / 源集合元素类型
     * @param <K>  键类型
     * @param <M>  Map 类型
     * @return Map&lt;K, SV&gt;
     */
    public static <SV, K, M extends Map<K, SV>> M k_o(Collection<SV> src, Function<SV, K> kf, Supplier<M> mf) {
        return k_o(src, kf, it -> it, mf);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 vf</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param vf   值提供函数
     * @param mf   Map 提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <V>  值类型
     * @param <M>  Map 类型
     * @return Map&lt;K, V&gt;
     */
    public static <SV, K, V, M extends Map<K, V>> M k_o(Collection<SV> src, Function<SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
        if (CollUtil.isEmpty(src)) return MapUtil.empty(null);

        return k_o(src.stream(), kf, vf, mf);
    }

    // endregion

    // region Collection 索引

    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
     * <p>kf 中可得到 源集合元素 索引</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @return Map&lt;K, VT&gt;
     */
    public static <SV, K> Map<K, SV> k_o(Collection<SV> src, BiFunction<Integer, SV, K> kf) {
        return k_o(src, kf, it -> it, HashMap::new);
    }

    /**
     * <p>分组</p>
     * Map 为 HashMap, 键为 kf, 值为 vf</p>
     * <p>kf 中可得到 源集合元素 索引</p>
     *
     * @param src  源集合
     * @param kf   K 提供函数
     * @param vf   V 提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <V>  值类型
     * @return Map&lt;K, V&gt;
     */
    public static <SV, K, V> Map<K, V> k_o(Collection<SV> src, BiFunction<Integer, SV, K> kf, Function<SV, V> vf) {
        return k_o(src, kf, vf, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 源集合元素</p>
     * <p>kf 中可得到 源集合元素 索引</p>
     *
     * @param src  源集合
     * @param kf   K 提供函数
     * @param mf   Map 提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <M>  Map 类型
     * @return Map&lt;K, SV&gt;
     */
    public static <SV, K, M extends Map<K, SV>> M k_o(Collection<SV> src, BiFunction<Integer, SV, K> kf, Supplier<M> mf) {
        return k_o(src, kf, it -> it, mf);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 vf</p>
     * <p>kf 中可得到 源集合元素 索引</p>
     *
     * @param src  源集合
     * @param kf   K 提供函数
     * @param vf   V 提供函数
     * @param mf   Map 提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <V>  值类型
     * @param <M>  Map 类型
     * @return Map&lt;K, V&gt;
     */
    public static <SV, K, V, M extends Map<K, V>> M k_o(Collection<SV> src, BiFunction<Integer, SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
        if (CollUtil.isEmpty(src)) return MapUtil.empty(null);

        M map = mf.get();

        int i = 0;
        for (SV sv : src) {
            K k = kf.apply(i, sv);
            if (map.containsKey(k)) throw Exceptions.error("重复键：{}", sv);
            V v = vf.apply(sv);
            map.put(k, v);
            i++;
        }
        return map;
    }

    /**
     * <p>分组</p>
     * Map 为 HashMap, 键为 kf, 值为 vf</p>
     * <p>vf 中可得到 源集合元素 索引</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param vf   值提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <V>  值类型
     * @return Map&lt;K, V&gt;
     */
    public static <SV, K, V> Map<K, V> k_o(Collection<SV> src, Function<SV, K> kf, BiFunction<Integer, SV, V> vf) {
        return k_o(src, kf, vf, HashMap::new);
    }

    /**
     * <p>分组</p>
     * Map 为 mf, 键为 kf, 值为 vf</p>
     * <p>vf 中可得到 源集合元素 索引</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param vf   值提供函数
     * @param mf   Map 提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <V>  值类型
     * @param <M>  Map 类型
     * @return Map&lt;K, V&gt;
     */
    public static <SV, K, V, M extends Map<K, V>> M k_o(Collection<SV> src, Function<SV, K> kf, BiFunction<Integer, SV, V> vf, Supplier<M> mf) {
        if (CollUtil.isEmpty(src)) return MapUtil.empty(null);

        M map = mf.get();

        int i = 0;
        for (SV sv : src) {
            K k = kf.apply(sv);
            if (map.containsKey(k)) throw Exceptions.error("重复键：{}", sv);
            V v = vf.apply(i, sv);
            map.put(k, v);
            i++;
        }
        return map;
    }

    /**
     * <p>分组</p>
     * Map 为 HashMap, 键为 kf, 值为 vf</p>
     * <p>kf vf 中可得到 源集合元素 索引</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param vf   值提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <V>  值类型
     * @return Map&lt;K, V&gt;
     */
    public static <SV, K, V> Map<K, V> k_o(Collection<SV> src, BiFunction<Integer, SV, K> kf, BiFunction<Integer, SV, V> vf) {
        return k_o(src, kf, vf, HashMap::new);
    }

    /**
     * <p>分组</p>
     * Map 为 mf, 键为 kf, 值为 vf</p>
     * <p>kf vf 中可得到 源集合元素 索引</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param vf   值提供函数
     * @param mf   Map 提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <V>  值类型
     * @param <M>  Map 类型
     * @return Map&lt;K, V&gt;
     */
    public static <SV, K, V, M extends Map<K, V>> M k_o(Collection<SV> src, BiFunction<Integer, SV, K> kf, BiFunction<Integer, SV, V> vf, Supplier<M> mf) {
        if (CollUtil.isEmpty(src)) return MapUtil.empty(null);

        M map = mf.get();

        int i = 0;
        for (SV sv : src) {
            K k = kf.apply(i, sv);
            if (map.containsKey(k)) throw Exceptions.error("重复键：{}", sv);
            V v = vf.apply(i, sv);
            map.put(k, v);
            i++;
        }
        return map;
    }
    // endregion

    // region Stream

    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
     *
     * @param stream 源集合
     * @param kf     键提供函数
     * @param <SV>   源集合元素类型
     * @param <K>    键类型
     * @return Map&lt;K, SV&gt;
     */
    public static <SV, K> Map<K, SV> k_o(Stream<SV> stream, Function<SV, K> kf) {
        return k_o(stream, kf, it -> it, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 vf</p>
     *
     * @param stream 源集合
     * @param kf     键提供函数
     * @param vf     值提供函数
     * @param <SV>   源集合元素类型
     * @param <K>    键类型
     * @param <V>    值类型
     * @return Map&lt;K, V&gt;
     */
    public static <SV, K, V> Map<K, V> k_o(Stream<SV> stream, Function<SV, K> kf, Function<SV, V> vf) {
        return k_o(stream, kf, vf, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 vf</p>
     *
     * @param stream 源集合
     * @param kf     键提供函数
     * @param mf     Map 提供函数
     * @param <SV>   源集合元素类型
     * @param <K>    键类型
     * @param <M>    Map 类型
     * @return Map&lt;K, SV&gt;
     */
    public static <SV, K, M extends Map<K, SV>> M k_o(Stream<SV> stream, Function<SV, K> kf, Supplier<M> mf) {
        return k_o(stream, kf, it -> it, mf);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 vf</p>
     *
     * @param stream 源集合
     * @param kf     键提供函数
     * @param vf     值提供函数
     * @param mf     Map 提供函数
     * @param <SV>   源集合元素类型
     * @param <K>    键类型
     * @param <V>    值类型
     * @param <M>    Map 类型
     * @return Map&lt;K, V&gt;
     */
    public static <SV, K, V, M extends Map<K, V>> M k_o(Stream<SV> stream, Function<SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
        if (stream == null) return MapUtil.empty(null);
        BinaryOperator<V> op = (oldVal, val) -> {
            throw Exceptions.error("值【{}】与【{}】对应的键重复", Jackson.toJsonStr(oldVal), Jackson.toJsonStr(val));
        };
        return stream.collect(Collectors.toMap(kf, vf, op, mf));
    }
    // endregion

    // endregion

    // region K-A

    // region Collection

    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param <K>  键类型
     * @param <SV> 源集合元素类型
     * @return Map&lt;K, List&lt;V&gt;&gt;
     */
    public static <SV, K> Map<K, List<SV>> k_a(Collection<SV> src, Function<SV, K> kf) {
        return k_a(src, kf, it -> it, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 vf</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param vf   值提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <V>  值类型
     * @return Map&lt;K, List&lt;V&gt;&gt;
     */
    public static <SV, K, V> Map<K, List<V>> k_a(Collection<SV> src, Function<SV, K> kf, Function<SV, V> vf) {
        return k_a(src, kf, vf, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 源集合元素</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param mf   Map 提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <M>  Map 类型
     * @return Map&lt;K, List&lt;SV&gt;&gt;
     */
    public static <SV, K, M extends Map<K, List<SV>>> M k_a(Collection<SV> src, Function<SV, K> kf, Supplier<M> mf) {
        return k_a(src, kf, it -> it, mf);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 vf</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param vf   值提供函数
     * @param mf   Map 提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <V>  值类型
     * @param <M>  Map 类型
     * @return Map&lt;K, List&lt;V&gt;&gt;
     */
    public static <SV, K, V, M extends Map<K, List<V>>> M k_a(Collection<SV> src, Function<SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
        if (CollUtil.isEmpty(src)) return MapUtil.empty(null);
        return src.stream().collect(Collectors.groupingBy(kf, mf, Collectors.mapping(vf, Collectors.toList())));
    }
    // endregion

    // region Stream


    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
     *
     * @param stream 源集合
     * @param kf     键提供函数
     * @param <K>    键类型
     * @param <SV>   源集合元素类型
     * @return Map&lt;K, List&lt;V&gt;&gt;
     */
    public static <SV, K> Map<K, List<SV>> k_a(Stream<SV> stream, Function<SV, K> kf) {
        return k_a(stream, kf, it -> it, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 vf</p>
     *
     * @param stream 源集合
     * @param kf     键提供函数
     * @param vf     值提供函数
     * @param <SV>   源集合元素类型
     * @param <K>    键类型
     * @param <V>    值类型
     * @return Map&lt;K, List&lt;V&gt;&gt;
     */
    public static <SV, K, V> Map<K, List<V>> k_a(Stream<SV> stream, Function<SV, K> kf, Function<SV, V> vf) {
        return k_a(stream, kf, vf, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 源集合元素</p>
     *
     * @param stream 源集合
     * @param kf     键提供函数
     * @param mf     Map 提供函数
     * @param <SV>   源集合元素类型
     * @param <K>    键类型
     * @param <M>    Map 类型
     * @return Map&lt;K, List&lt;SV&gt;&gt;
     */
    public static <SV, K, M extends Map<K, List<SV>>> M k_a(Stream<SV> stream, Function<SV, K> kf, Supplier<M> mf) {
        return k_a(stream, kf, it -> it, mf);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 vf</p>
     *
     * @param stream 源集合
     * @param kf     键提供函数
     * @param vf     值提供函数
     * @param mf     Map 提供函数
     * @param <SV>   源集合元素类型
     * @param <K>    键类型
     * @param <V>    值类型
     * @param <M>    Map 类型
     * @return Map&lt;K, List&lt;V&gt;&gt;
     */
    public static <SV, K, V, M extends Map<K, List<V>>> M k_a(Stream<SV> stream, Function<SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
        if (stream == null) return MapUtil.empty(null);
        return stream.collect(Collectors.groupingBy(kf, mf, Collectors.mapping(vf, Collectors.toList())));
    }
    // endregion

    // endregion

    // region K-S
    // region Collection

    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @return Map&lt;K, Set&lt;SV&gt;&gt;
     */
    public static <SV, K> Map<K, Set<SV>> k_s(Collection<SV> src, Function<SV, K> kf) {
        return k_s(src, kf, it -> it, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 vf</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param vf   值提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <V>  值类型
     * @return Map&lt;K, Set&lt;V&gt;&gt;
     */
    public static <SV, K, V> Map<K, Set<V>> k_s(Collection<SV> src, Function<SV, K> kf, Function<SV, V> vf) {
        return k_s(src, kf, vf, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 vf</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param mf   Map 提供函数
     * @param <K>  键类型
     * @param <SV> 源集合元素类型
     * @param <M>  Map 类型
     * @return Map&lt;K, Set&lt;SV&gt;&gt;
     */
    public static <SV, K, M extends Map<K, Set<SV>>> M k_s(Collection<SV> src, Function<SV, K> kf, Supplier<M> mf) {
        return k_s(src, kf, it -> it, mf);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 vf</p>
     *
     * @param src  源集合
     * @param kf   键提供函数
     * @param vf   值提供函数
     * @param mf   Map 提供函数
     * @param <SV> 源集合元素类型
     * @param <K>  键类型
     * @param <V>  值类型
     * @param <M>  Map 类型
     * @return Map&lt;K, Set&lt;V&gt;&gt;
     */
    public static <SV, K, V, M extends Map<K, Set<V>>> M k_s(Collection<SV> src, Function<SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
        if (CollUtil.isEmpty(src)) return MapUtil.empty(null);
        return src.stream().collect(Collectors.groupingBy(kf, mf, Collectors.mapping(vf, Collectors.toSet())));
    }
    // endregion

    // region Stream

    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
     *
     * @param stream 源集合
     * @param kf     键提供函数
     * @param <SV>   源集合元素类型
     * @param <K>    键类型
     * @return Map&lt;K, Set&lt;SV&gt;&gt;
     */
    public static <SV, K> Map<K, Set<SV>> k_s(Stream<SV> stream, Function<SV, K> kf) {
        return k_s(stream, kf, it -> it, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 HashMap, 键为 kf, 值为 vf</p>
     *
     * @param stream 源集合
     * @param kf     键提供函数
     * @param vf     值提供函数
     * @param <SV>   源集合元素类型
     * @param <K>    键类型
     * @param <V>    值类型
     * @return Map&lt;K, Set&lt;V&gt;&gt;
     */
    public static <SV, K, V> Map<K, Set<V>> k_s(Stream<SV> stream, Function<SV, K> kf, Function<SV, V> vf) {
        return k_s(stream, kf, vf, HashMap::new);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 vf</p>
     *
     * @param stream 源集合
     * @param kf     键提供函数
     * @param mf     Map 提供函数
     * @param <K>    键类型
     * @param <SV>   源集合元素类型
     * @param <M>    Map 类型
     * @return Map&lt;K, Set&lt;SV&gt;&gt;
     */
    public static <SV, K, M extends Map<K, Set<SV>>> M k_s(Stream<SV> stream, Function<SV, K> kf, Supplier<M> mf) {
        return k_s(stream, kf, it -> it, mf);
    }

    /**
     * <p>分组</p>
     * <p>Map 为 mf, 键为 kf, 值为 vf</p>
     *
     * @param stream 源集合
     * @param kf     键提供函数
     * @param vf     值提供函数
     * @param mf     Map 提供函数
     * @param <SV>   源集合元素类型
     * @param <K>    键类型
     * @param <V>    值类型
     * @param <M>    Map 类型
     * @return Map&lt;K, Set&lt;V&gt;&gt;
     */
    public static <SV, K, V, M extends Map<K, Set<V>>> M k_s(Stream<SV> stream, Function<SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
        if (stream == null) return MapUtil.empty(null);
        return stream.collect(Collectors.groupingBy(kf, mf, Collectors.mapping(vf, Collectors.toSet())));
    }
    // endregion

    // endregion

}
