package io.delphiplatform.api.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public abstract class CollectionUtils {

    public static boolean isEmpty(CharSequence value) {
        return StringUtils.isEmpty(value);
    }

    public static boolean isNotEmpty(CharSequence value) {
        return StringUtils.isNotEmpty(value);
    }

    public static boolean isEmpty(Collection<?> collection) {
        return collection == null || collection.isEmpty();
    }

    public static boolean isEmpty(Map<?, ?> map) {
        return map == null || map.isEmpty();
    }

    public static boolean isNotEmpty(Map<?, ?> map) {
        return !isEmpty(map);
    }

    public static boolean areEmpty(Collection<Collection<?>> collections) {
        if (CollectionUtils.isEmpty(collections)) {
            return true;
        }
        return collections.stream().allMatch(CollectionUtils::isEmpty);
    }

    public static boolean areEmpty(Collection<?>... collections) {
        if (collections == null) {
            return true;
        }
        return Stream.of(collections).allMatch(CollectionUtils::isEmpty);
    }

    public static boolean areEmpty(CharSequence... values) {
        if (values == null) {
            return true;
        }
        return Stream.of(values).allMatch(CollectionUtils::isEmpty);
    }

    public static <T> BinaryOperator<T> firstOfTwoMerger() {
        return (a, b) -> a;
    }

    public static boolean isNotEmpty(Collection<?> collection) {
        return !isEmpty(collection);
    }

    public static <T> boolean contains(Collection<T> collection, T element) {
        return !isEmpty(collection) && collection.contains(element);
    }

    public static <T> boolean notContains(Collection<T> collection, T element) {
        return !contains(collection, element);
    }

    public static boolean contains(String charSequence, String element) {
        return isNotEmpty(charSequence) && charSequence.contains(element);
    }

    public static boolean containsAny(Collection<?> collection, Collection<?> candidates) {
        if (isEmpty(collection)) {
            return false;
        }

        return candidates.stream().anyMatch(collection::contains);
    }

    public static boolean containsAll(Collection<?> collection, Collection<?> candidates) {
        if (isEmpty(collection)) {
            return false;
        }

        return collection.containsAll(candidates);
    }

    public static <E> Set<E> toSet(Collection<E> collection) {
        if (isEmpty(collection)) {
            return Collections.emptySet();
        }
        return new HashSet<>(collection);
    }

    /**
     * @return The list without duplicate elements
     * @see Stream#distinct()
     */
    public static <E> List<E> distinct(List<E> list) {
        if (isEmpty(list)) {
            return list;
        }
        return list.stream().distinct().collect(Collectors.toList());
    }

    public static Stream<LocalDate> getDateRange(LocalDate start, LocalDate end) {
        return Stream.iterate(start, date -> date.plusDays(1))
            .limit(ChronoUnit.DAYS.between(start, end) + 1);
    }

    public static IntSummaryStatistics getIntSummaryStatistics(Stream<Integer> stream) {
        return stream.mapToInt(Integer::intValue).summaryStatistics();
    }

    public static <A, B> List<Pair<A, B>> zipLists(List<A> listA, List<B> listB) {
        if (listA.size() != listB.size()) {
            throw new IllegalArgumentException("Lists sizes must be equal");
        }

        List<Pair<A, B>> pairList = new ArrayList<>(listA.size());

        for (int i = 0; i < listA.size(); i++) {
            pairList.add(Pair.of(listA.get(i), listB.get(i)));
        }

        return pairList;
    }

    public static <T> T firstNotNull(T... args) {
        return Arrays.stream(args)
            .filter(Objects::nonNull)
            .findFirst()
            .orElseGet(null);
    }

    public static <T> List<T> merge2Lists(List<T> list1, List<T> list2) {
        List<T> mergedList = new ArrayList<>();
        mergedList.addAll(list1);
        mergedList.addAll(list2);

        return mergedList;
    }
}
