package org.vertexium.util;

import java.io.Closeable;
import java.lang.reflect.Array;
import java.util.*;

public class IterableUtils {
    @SuppressWarnings("unchecked")
    public static <T> List<T> toList(Iterable<? extends T> iterable) {
        if (iterable instanceof List) {
            return (List<T>) iterable;
        }
        List<T> results = new ArrayList<>();
        for (T o : iterable) {
            results.add(o);
        }
        close(iterable);
        return results;
    }

    public static <T> List<T> toList(Iterator<T> iterator) {
        List<T> results = new ArrayList<>();
        while (iterator.hasNext()) {
            T o = iterator.next();
            results.add(o);
        }
        return results;
    }

    @SuppressWarnings("unchecked")
    public static <T> Set<T> toSet(Iterable<? extends T> iterable) {
        if (iterable instanceof Set) {
            return (Set<T>) iterable;
        }
        Set<T> results = new HashSet<>();
        for (T o : iterable) {
            results.add(o);
        }
        close(iterable);
        return results;
    }

    @SuppressWarnings("unchecked")
    public static <T> Set<T> toSet(T[] iterable) {
        Set<T> results = new HashSet<>();
        Collections.addAll(results, iterable);
        return results;
    }

    @SuppressWarnings("unchecked")
    public static <T> T[] toArray(Iterable<? extends T> iterable, Class<T> type) {
        List<? extends T> list = toList(iterable);
        T[] array = (T[]) Array.newInstance(type, list.size());
        return list.toArray(array);
    }

    public static <T> int count(Iterable<T> iterable) {
        int count = 0;
        for (T ignore : iterable) {
            count++;
        }
        close(iterable);
        return count;
    }

    public static <T> int count(Iterator<T> iterator) {
        int count = 0;
        while (iterator.hasNext()) {
            count++;
            iterator.next();
        }
        close(iterator);
        return count;
    }

    public static <T> Iterable<T> toIterable(final T[] arr) {
        return new Iterable<T>() {
            @Override
            public Iterator<T> iterator() {
                return new Iterator<T>() {
                    int index = 0;

                    @Override
                    public boolean hasNext() {
                        return index < arr.length;
                    }

                    @Override
                    public T next() {
                        return arr[index++];
                    }

                    @Override
                    public void remove() {
                        throw new RuntimeException("Not supported");
                    }
                };
            }
        };
    }

    public static <T> T single(final Iterable<? extends T> it) {
        Iterator<? extends T> i = it.iterator();
        if (!i.hasNext()) {
            throw new IllegalStateException("No items found.");
        }

        T result = i.next();

        if (i.hasNext()) {
            close(it);
            throw new IllegalStateException("More than 1 item found.");
        }

        close(it);
        return result;
    }

    private static <T> void close(Iterable<? extends T> it) {
        if (it instanceof Closeable) {
            CloseableUtils.closeQuietly((Closeable) it);
        }
    }

    private static <T> void close(Iterator<? extends T> it) {
        if (it instanceof Closeable) {
            CloseableUtils.closeQuietly((Closeable) it);
        }
    }

    public static <T> T singleOrDefault(final Iterable<? extends T> it, T defaultValue) {
        Iterator<? extends T> i = it.iterator();
        if (!i.hasNext()) {
            close(it);
            return defaultValue;
        }

        T result = i.next();

        if (i.hasNext()) {
            close(it);
            throw new IllegalStateException("More than 1 item found.");
        }

        close(it);
        return result;
    }

    public static String join(Iterable items, String sep) {
        boolean first = true;
        StringBuilder sb = new StringBuilder();
        for (Object o : items) {
            if (!first) {
                sb.append(sep);
            }
            sb.append(o);
            first = false;
        }
        return sb.toString();
    }
}

@@ -3818,32 +3818,68 @@
 (i.hasNext()) %7B%0A
+            T nextValue = i.next();%0A
             clos
@@ -3945,32 +3945,71 @@
 an 1 item found.
+ %5B%22 + result + %22, %22 + nextValue + %22...%5D
 %22);%0A        %7D%0A%0A 

add better error message for more than one element