/*
 *  @author Ranides Atterwim <ranides@gmail.com>
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.collection;

import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import net.ranides.assira.generic.Function;
import net.ranides.assira.generic.Predicate;

/**
 * Generyczne operacje na kolekcjach. Jeśli to możliwe, korzystać z innych klas
 * dedykowanych dla konkretnego typu kolekcji, ponieważ poniższe metody opierają
 * się głównie na iteratorach i często mają bardzo kiepską złożoność rzędu O(N)
 * @author ranides
 */
@SuppressWarnings({"PMD.ShortVar"})
public final class CollectionUtils {

    private CollectionUtils() { }


    /**
     * Pobiera index-ty element z kolekcji, lub zwraca wartość domyślną, jeśli
     * kolekcja ma mniejszy rozmiar niż konieczne.
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values
     * @param index
     * @param ddefault
     * @return
     */
    public static <T> T get(Collection<T> values, int index, T ddefault) {
        if(null == values) { return ddefault; }
        if(index<0 || index >= values.size() ) { return ddefault; }
        Iterator<T> iterator = values.iterator();
        // pomijamy (index-1) elementów
        for(int i=0; i<index; i++) {
            iterator.next();
        }
        return iterator.next();
    }


    /**
     * Pobiera index-ty element z kolekcji, lub zwraca null, jeśli
     * kolekcja ma mniejszy rozmiar niż konieczne.
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values
     * @param index
     * @return
     */
    public static <T> T get(Collection<T> values, int index) {
        return get(values, index, null);
    }


    /**
     * Wyszukuje i zwraca pierwszy obiekt o klasie T, lub null, jeśli takiej wartości brak
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values kolekcja, w której obiekt klasy jest szukany
     * @param clazz szukana klasa
     * @return
     */
    public static <T> T first(Iterable<?> values, Class<T> clazz) {
        if(null == values) { return null; }
        Iterator<?> i = values.iterator();
        while(i.hasNext()) {
            Object value = i.next();
            if(clazz.isInstance(value)) { return clazz.cast(value); }
        }
        return null;
    }


    /**
     * Wyszukuje i zwraca pierwszy obiekt w kolekcji, lub null, jeśli kolekcja pusta.
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values kolekcja, z której obiekt jest zwracany
     * @return
     */
    public static <T> T first(Iterable<T> values) {
        return null == values ? null : first(values.iterator());
    }


    /**
     * Jeśli obiekt jest kolekcją lub iteratorem zwraca pierwszy element.
     * Jeśli innym obiektem, zwraca po prostu ten obiekt.
     * Jeśli kolekcja jest pusta - zwraca null.
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param values kolekcja, iterator lub zwykły obiekt
     * @return psuedo-pierwszy element
     */
    public static <T> T first(Iterator<T> iterator) {
        return iterator.hasNext() ? iterator.next() : null;
    }


    /**
     * Wyszukuje i zwraca ostatni obiekt o klasie T, lub null, jeśli takiej wartości brak.
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values kolekcja, w której obiekt klasy jest szukany
     * @param clazz szukana klasa
     * @return
     */
    public static <T> T last(Iterable<?> values, Class<T> clazz) {
        if(null == values) { return null; }
        Iterator<?> i = values.iterator();
        T result = null;
        while(i.hasNext()) {
            Object value = i.next();
            if(clazz.isInstance(value)) { result = clazz.cast(value); }
        }
        return result;
    }

    /**
     * Wyszukuje i zwraca pierwszy obiekt w kolekcji, lub null, jeśli kolekcja pusta
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values kolekcja, z której obiekt jest zwracany
     * @return
     */
    public static <T> T last(Iterable<T> values) {
        return null == values ? null : last(values.iterator());
    }

    /**
     * Jeśli obiekt jest kolekcją lub iteratorem zwraca pierwszy element.
     * Jeśli innym obiektem, zwraca po prostu ten obiekt.
     * Jeśli kolekcja jest pusta - zwraca null.
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param values kolekcja, iterator lub zwykły obiekt
     * @return psuedo-pierwszy element
     */
    public static <T> T last(Iterator<T> iterator) {
        T result = null;
        while(iterator.hasNext()) { result = iterator.next(); }
        return result;
    }

    /**
     * Zwraca rozmiar kolekcji.
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values
     * @return
     */
    public static <T> int size(Collection<T> values) {
        return null == values ? 0 : values.size();
    }
    
    /**
     * Sprawdza czy kolekcja jest pusta.
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values
     * @return
     */
    public static <T> boolean isEmpty(Collection<T> values) {
        return 0 == size(values);
    }

    /**
     * Sprawdza, czy kolekcje mają ten sam rozmiar.
     * Kolekcje, które są równe {@code null} traktowane są jako puste (rozmiar 0).
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T1>
     * @param <T2>
     * @param a
     * @param b
     * @return
     */
    public static <T1,T2> boolean equalSize(Collection<T1> a, Collection<T2> b) {
        return size(a) == size(b);
    }

    /**
     * Sprawdza, czy kolekcje zawierają te same elementy ignorując kolejność.
     * Uwaga: bardzo kosztowna metoda, ponieważ kopiuje a następnie sortuje obie
     * kolekcje.
     * @param a
     * @param b
     * @return
     */
    public static boolean equalContent(Collection<?> a, Collection<?> b) {
        if( a.size() != b.size() ){
            return false;
        }
        // najpierw spróbujmy tak sprawdzić - takie porównanie powinno być względnie tanie
        // (złożoność O(N)) w porówaniu do następnej metody
        if( a.equals(b) || b.equals(a) ) {
            return true;
        }
        // dużo kopiowania, na dodatek mamy sortowanie, czyli złożoność
        // na pamięć O(N) oraz operacje O(N*log(N)) + O(N)
        Object[] asort = a.toArray();
        Object[] bsort = b.toArray();
        Arrays.sort(asort);
        Arrays.sort(bsort);
        return Arrays.equals(asort, bsort);
    }

    /**
     * Przycina kolekcję jeśli kolekcja ma rozmiar większy od podanej granicy.
     * @param <T>
     * @param values
     * @param size maksymalny rozmiar kolekcji
     * @return
     * "view" kolekcji podanej jako argument. Modyfikacja obciętej kolekcji
     * ma wpływ na kolekcję źródłową.
     */
    public static <T> Collection<T> clip(Collection<T> values, int size) {
        if(values.size() <= size) { return values; }
        return new ClipCollection<>(values, size);
    }

    private static final class ClipIterator<T> implements Iterator<T> {

        private final Iterator<T> iterator;
        private final int size;
        private int index;

        public ClipIterator(Collection<T> values, int max) {
            this.iterator = values.iterator();
            this.size = max;
            this.index = 0;
        }
        @Override
        public boolean hasNext() {
            return index < size;
        }
        @Override
        public T next() {
            index++;
            return iterator.next();
        }
        @Override
        public void remove() {
            iterator.remove();
        }

    }

    private static final class ClipCollection<T> extends AbstractSet<T> {

        private final Collection<T> values;
        private final int size;

        public ClipCollection(Collection<T> values, int size) {
            this.values = values;
            this.size = size;
        }

        @Override
        public Iterator<T> iterator() {
            return new ClipIterator<>(values, size);
        }

        @Override
        public int size() {
            return size;
        }

    }

    /**
     * Konwertuje podany obiekt iterable na kolekcję o rozmiarze równym {@code size}.
     * Ponieważ na podstawie iteratora nie można wydedukować rozmiaru zbioru,
     * podana wartość {@code size} musi być odpowiednia, tzn nie może być
     * większa niż ilość elementów zwróconych przez iterator.
     *
     * @param <T>
     * @param size
     * @param iterable 
     * @return
     */
    public static <T> Collection<T> asCollection(final int size, final Iterable<T> iterable) {
        return new AbstractCollection<T>() {
            @Override
            public Iterator<T> iterator() { return iterable.iterator(); }
            @Override
            public int size() { return size; }
        };
    }

    /**
     * Zwraca iterator transformujący leniwie elementy za pomocą podanej funkcji odwzorującej.
     * @param <T>
     * @param <S>
     * @param iterator
     * @param function
     * @return
     */
    public static <T,S> Iterator<T> adapt(final Iterator<S> iterator, final Function<T,S> function) {
        return new Iterator<T>() {
            @Override
            public boolean hasNext() {
                return iterator.hasNext();
            }
            @Override
            public T next() {
                return function.apply(iterator.next());
            }
            @Override
            public void remove() {
                iterator.remove();
            }
        };
    }

    /**
     * Zwraca kolekcję transformującą leniwie elementy za pomocą podanej funkcji odwzorującej.
     * @param <T>
     * @param <S>
     * @param collection
     * @param function
     * @return
     */
    public static <T,S> Collection<T> adapt(final Collection<S> collection, final Function<T,S> function) {
        return new AbstractSet<T>() {
            @Override
            public Iterator<T> iterator() {
                return adapt(collection.iterator(), function);
            }
            @Override
            public int size() {
                return collection.size();
            }
        };
    }
    
    public static <T> Iterator<T> filter(final Iterator<T> iterator, final Predicate<T> predicate) {
        return new FilterIterator<T>(iterator) {
            @Override
            public boolean accept(T source) {
                return predicate.apply(source);
            }
        };
    }
    
    public static <T> Iterable<T> filter(final Iterable<T> iterable, final Predicate<T> predicate) {
        return new Iterable<T>() {
            @Override
            public Iterator<T> iterator() {
                return filter(iterable.iterator(), predicate);
            }
        };
    }

}
