package org.implab.gradle.common.utils;

import java.text.MessageFormat;
import java.util.Iterator;
import java.util.Spliterators;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.gradle.api.provider.Provider;

public final class Values {

    private Values() {
    }

    /**
     * Converts the supplied value to a string.
     */
    public static String toString(Object value) {
        if (value == null) {
            return null;
        } else if (value instanceof String string) {
            return string;
        } else if (value instanceof Provider<?> provider) {
            return toString(provider.getOrNull());
        } else if (value instanceof Supplier<?> supplier) {
            return toString(supplier.get());
        } else {
            return value.toString();
        }
    }

    public static <T> Stream<T> stream(Iterator<T> remaining) {
        return StreamSupport.stream(
                Spliterators.spliteratorUnknownSize(remaining, 0),
                false);
    }

    public static <T> Optional<T> take(Iterator<T> iterator) {
        return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty();
    }

    public static <T> Iterable<T> iterable(T[] values) {
        return () -> new ArrayIterator<>(values);
    }

    public static <T> Optional<T> optional(Provider<T> provider) {
        return provider.isPresent() ? Optional.of(provider.get()) : Optional.empty();
    }

    public static <T> T required(Provider<T> provider, String providerName) {
        if (!provider.isPresent())
            throw new IllegalStateException(
                    MessageFormat.format("The value for the '{0}' provider must be specified", providerName));
        return provider.get();
    }

    public static <U,V> Set<V> mapSet(Set<U> values, Function<U,V> mapper) {
        return values.stream().map(mapper).collect(Collectors.toUnmodifiableSet());
    }

    private static class ArrayIterator<T> implements Iterator<T> {
        private final T[] data;

        private int pos = 0;

        ArrayIterator(T[] data) {
            this.data = data;
        }

        @Override
        public boolean hasNext() {
            return pos < data.length;
        }

        @Override
        public T next() {
            return data[pos++];
        }
    }

}
