package com.emr.util;

import org.springframework.util.CollectionUtils;

import java.util.*;

public final class ListUtils {
    public static <T, K> Map<K, T> toMap(List<T> list, FieldSelector<T, K> selector) {
        if (CollectionUtils.isEmpty(list)) return Collections.emptyMap();
        Map<K, T> map = new HashMap<>(list.size());
        for (T t : list) {
            K key = selector.select(t);
            if (key != null) map.put(key, t);
        }
        return map;
    }

    public static <T, K> Map<K, List<T>> groupBy(List<T> list, FieldSelector<T, K> selector) {
        if (CollectionUtils.isEmpty(list)) return Collections.emptyMap();
        Map<K, List<T>> map = new HashMap<>();
        for (T t : list) {
            K key = selector.select(t);
            if (key == null) continue;
            if (!map.containsKey(key)) {
                map.put(key, new ArrayList<T>());
            }
            map.get(key).add(t);
        }
        return map;
    }

    public static <T> List<List<T>> partition(List<T> list, int size) {
        if (list == null) {
            throw new NullPointerException("List must not be null");
        } else if (size <= 0) {
            throw new IllegalArgumentException("Size must be greater than 0");
        } else {
            return new Partition(list, size);
        }
    }

    private static class Partition<T> extends AbstractList<List<T>> {
        private final List<T> list;
        private final int size;

        private Partition(List<T> list, int size) {
            this.list = list;
            this.size = size;
        }

        public List<T> get(int index) {
            int listSize = this.size();
            if (index < 0) {
                throw new IndexOutOfBoundsException("Index " + index + " must not be negative");
            } else if (index >= listSize) {
                throw new IndexOutOfBoundsException("Index " + index + " must be less than size " + listSize);
            } else {
                int start = index * this.size;
                int end = Math.min(start + this.size, this.list.size());
                return this.list.subList(start, end);
            }
        }

        public int size() {
            return (int)Math.ceil((double)this.list.size() / (double)this.size);
        }

        public boolean isEmpty() {
            return this.list.isEmpty();
        }
    }

    public static <T, K> List<K> select(List<T> list, FieldSelector<T, K> selector) {
        if (CollectionUtils.isEmpty(list)) return Collections.emptyList();
        List<K> filedList = new ArrayList<>(list.size());
        for (T t : list) {
            K key = selector.select(t);
            if (key != null) filedList.add(key);
        }
        return filedList;
    }

    public static <T, K> List<K> distinctSelect(List<T> list, FieldSelector<T, K> selector) {
        if (CollectionUtils.isEmpty(list)) return Collections.emptyList();
        Set<K> filedSet = new HashSet<>();
        for (T t : list) {
            K key = selector.select(t);
            if (key != null) filedSet.add(key);
        }
        return new ArrayList<>(filedSet);
    }

    @SafeVarargs
    public static <T> List<T> unionWithoutDuplicate(List<T>... values) {
        if (null == values || values.length <= 0) return Collections.emptyList();
        Set<T> unionFiledSet = new HashSet<>();
        for (List<T> value : values) {
            if (!CollectionUtils.isEmpty(value)) {
                unionFiledSet.addAll(value);
            }
        }
        return new ArrayList<>(unionFiledSet);
    }

    public static <T, K> List<T> skipDuplicateKey(List<T> list, FieldSelector<T, K> selector) {
        if (CollectionUtils.isEmpty(list)) return Collections.emptyList();
        List<T> filedList = new ArrayList<>(list.size());
        Map<K, T> map = toMap(list, selector);
        for (K key : map.keySet()) {
            filedList.add(map.get(key));
        }
        return filedList;
    }
}
