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

package net.ranides.assira.collection;

import java.util.*;
import net.ranides.assira.generic.BiFunction;
import net.ranides.assira.generic.Function;
import net.ranides.assira.generic.ValueUtils;

/**
 *
 * @author ranides
 */
public final class SetUtils {

    private SetUtils() { }

    /**
     * Tworzy nowy zbiór zawierający podane wartości.
     * @param <K>
     * @param values
     * @return
     */
    public static <K> Set<K> asHashSet(K... values) {
        return new HashSet<>(Arrays.asList(values));
    }

    /**
     * Tworzy nowy zbiór zawierający podane wartości oraz zachowujący ich kolejność.
     * @param <K>
     * @param values
     * @return
     */
    public static <K> Set<K> asLinkedHashSet(K... values) {
        return new LinkedHashSet<>(Arrays.asList(values));
    }

    /**
     * Tworzy nowy zbiór zawierający podane wartości oraz zachowujący "natural ordering".
     * @param <K>
     * @param values
     * @return
     */
    public static <K> SortedSet<K> asTreeSet(K... values) {
        return new TreeSet<>(Arrays.asList(values));
    }

    /**
     * Tworzy nowy zbiór zawierający podane wartości {@code enum}.
     * @param <T>
     * @param values
     * @return
     */
    public static <T extends Enum<T>> EnumSet<T> asEnumSet(T... values) {
        return EnumSet.copyOf(Arrays.asList(values));
    }


    /**
     * Konwertuje podaną wartość na 1-elementowy zbiór. Uwaga! Zwróconego zbioru
     * nie da się modyfikować.
     * @param <K>
     * @param value
     * @return
     */
    public static <K> Set<K> wrap(K value) {
        return new SingleCollection<>(value);
    }

    /**
     * Wykonuje na elementach zbioru operację OR i zwraca wyliczony wynik.
     * @param values
     * @return
     */
    public static long asLong(Set<Long> values) {
        long result = 0;
        for(long value : values) { result |= value; }
        return result;
    }

    /**
     * Wykonuje na elementach zbioru operację OR i zwraca wyliczony wynik.
     * @param values
     * @return
     */
    public static int asInt(Set<Integer> values) {
        int result = 0;
        for(int value : values) { result |= value; }
        return result;
    }

    /**
     * Tworzy na podstawie podanych wartości nowy adapter, który używa do dwu-stronnej
     * transformacji wartości podanej funkcji. Zmiany wprowadzone w adapterze mają
     * wpływ na kolekcję źródłową, i vice versa.
     * <p>
     * Adapter obsługuje wszystkie operacje odczytu, wstawiania oraz usuwania.
     * </p>
     * @param <T>
     * @param <S>
     * @param values
     * @param function
     * @return
     */
    public static <T,S> Set<T> adapt(final Set<S> values, final BiFunction<T,S> function) {
        return new AbstractSet<T>() {
            @Override
            public int size() {
                return values.size();
            }
            @Override
            public boolean contains(Object value) {
                return values.contains( function.inverse((T)value) );
            }
            @Override
            public Iterator<T> iterator() {
                return CollectionUtils.adapt(values.iterator(), function);
            }
            @Override
            public boolean add(T value) {
                return values.add(function.inverse(value));
            }
            @Override
            public boolean remove(Object value) {
                return values.add(function.inverse((T)value));
            }
            @Override
            public void clear() {
                values.clear();
            }
        };
    }

    /**
     * Tworzy na podstawie podanych wartości nowy adapter, który używa do transformacji
     * wartości podanej funkcji. Zmiany wprowadzone w kolekcji źródłowej są widoczne
     * w adapterze.
     * <p>
     * Adapter jest kolekcją tylko do odczytu - nie obsługuje operacji wstawiania
     * i usuwania (ponieważ do ich zaplementowania konieczne jest odwzorowanie
     * dwukierunkowe).
     * </p>
     * @param <T>
     * @param <S>
     * @param values
     * @param function
     * @return
     */
    public static <T,S> Set<T> adapt(final Set<S> values, final Function<T,S> function) {
        return new AbstractSet<T>() {
            @Override
            public int size() {
                return values.size();
            }
            @Override
            public boolean contains(Object value) {
                for(T item : this) {
                    if(ValueUtils.equals(item, value)) { return true; }
                }
                return false;
            }
            @Override
            public Iterator<T> iterator() {
                return CollectionUtils.adapt(values.iterator(), function);
            }
        };
    }

}
