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

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Function;
import net.ranides.assira.collection.ACollection;
import net.ranides.assira.collection.iterators.IteratorUtils;
import net.ranides.assira.functional.ProjectionFunction;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class SetUtils {
    
    private SetUtils() {
        /* utility class */
    }
    
    public static <R,T> Set<R> map(Set<T> set, Function<? super T, ? extends R> function) {
        return new SetAdapter<>(set, function);
    }
    
    public static <R,T> Set<R> map(Set<T> set, ProjectionFunction<T,R> function) {
        return new SetProjection<>(set, function);
    }
    
    private static class SetProjection<R,T> extends ACollection<R> implements Set<R>, Serializable {
        
        private static final long serialVersionUID = 1L;
        
        protected final Set<T> data;
        
        protected final ProjectionFunction<T,R> function;

        public SetProjection(Set<T> data, ProjectionFunction<T,R> function) {
            this.data = data;
            this.function = function;
        }

        @Override
        public Iterator<R> iterator() {
            return IteratorUtils.map(data.iterator(), function);
        }

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

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

        @Override
        public boolean add(R value) {
            return data.add( function.invert(value) );
        }

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

        @Override
        public int hashCode() {
            return this.data.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if(obj instanceof Set) {
                @SuppressWarnings("unchecked")
                Set<R> that = (Set<R>)obj;
                return this.size() == that.size() && this.containsAll(that);
            }
            return false;
        }
        
    }
    
    private static class SetAdapter<R,T> extends ACollection<R> implements Set<R>, Serializable {
        
        private static final long serialVersionUID = 1L;
        
        protected final Set<T> data;
        
        protected final Function<? super T, ? extends R> function;

        public SetAdapter(Set<T> data, Function<? super T, ? extends R> function) {
            this.data = data;
            this.function = function;
        }

        @Override
        public Iterator<R> iterator() {
            return IteratorUtils.map(data.iterator(), function);
        }

        @Override
        public int size() {
            return data.size();
        }
        
        @Override
        public int hashCode() {
            return this.data.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if(obj instanceof Set) {
                @SuppressWarnings("unchecked")
                Set<R> that = (Set<R>)obj;
                return this.size() == that.size() && this.containsAll(that);
            }
            return false;
        }

    }
    
    static class EmptyMS<K> extends ASet<K> implements MultiSet<K> {

        @Override
        public Iterator<K> iterator() {
            return Collections.emptyIterator();
        }

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

        @Override
        public Collection<K> getAll(Object key) {
            return Collections.emptySet();
        }
        
    }
    
}
