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

package net.ranides.assira.collection;

import net.ranides.assira.collection.iterators.IteratorUtils;
import net.ranides.assira.functional.ProjectionFunction;

import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class CollectionUtils {
	
	private CollectionUtils() {
		/* utility class */
	}
	
	public static <T> Optional<T> first(Collection<T> values) {
		return IteratorUtils.first(values.iterator());
	}
	
	public static <T> Optional<T> first(Collection<T> values, Predicate<? super T> predicate) {
		return IteratorUtils.first(values.iterator(), predicate);
	}
	
	public static <T> Optional<T> last(Collection<T> values) {
		return IteratorUtils.last(values.iterator());
	}
	
	public static <T> Optional<T> last(Collection<T> values, Predicate<? super T> predicate) {
		return IteratorUtils.last(values.iterator(), predicate);
	}
	
    @SuppressWarnings("unchecked")
	public static <T> Collection<T> filter(Collection<T> values, Predicate<? super T> predicate) {
		return new ACollection<T>() {
			@Override
			public Iterator<T> iterator() {
				return IteratorUtils.filter(values.iterator(), predicate);
			}

			@Override
			public int size() {
				return IteratorUtils.size(iterator());
			}
			
			@Override
			public boolean contains(Object value) {
				return predicate.test((T)value) && values.contains(value);
			}
		};
	}
	
	public static <S,T> Collection<T> map(Collection<S> values, Function<? super S,? extends T> function) {
        return new ACollection<T>() {
			@Override
			public Iterator<T> iterator() {
				return IteratorUtils.map(values.iterator(), function);
			}
			@Override
			public int size() {
				return values.size();
			}
		};
    }
    
    public static <S,T> Collection<T> map(Collection<S> values, ProjectionFunction<S,T> function) {
        return new ACollection<T>() {
			@Override
			public Iterator<T> iterator() {
				return IteratorUtils.map(values.iterator(), function);
			}
			@Override
			public int size() {
				return values.size();
			}

            @Override
			@SuppressWarnings("unchecked")
            public boolean remove(Object value) {
				Object v;
				try {
					v = function.invert((T)value);
				} catch(ClassCastException _e) {
					return false;
				}
                return values.remove(v);
            }

            @Override
            public boolean add(T value) {
                return values.add(function.invert(value));
            }

            @Override
			@SuppressWarnings("unchecked")
            public boolean contains(Object value) {
				Object v;
				try {
					v = function.invert((T)value);
				} catch(ClassCastException _e) {
					return false;
				}
                return values.contains(v);
            }
            
		};
    }
    
    public static <T> boolean equivalent(Collection<T> a, Collection<T> b) {
		if( a.size() != b.size() ){
            return false;
        }
		// Set powinien oferować dobrą implementację funkcji containsAll
		if(a instanceof Set<?>) {
			return a.containsAll(b);
		}
		if(b instanceof Set<?>) {
			return b.containsAll(a);
		}
		
        // 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);
	}

}
