/*
 * @author Ranides Atterwim {@literal <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.Set;
import java.util.function.IntUnaryOperator;
import lombok.experimental.UtilityClass;
import net.ranides.assira.collection.iterators.IntIterator;
import net.ranides.assira.collection.iterators.IntIteratorUtils;
import net.ranides.assira.functional.covariant.IntProjectionFunction;

/**
 * Static methods working with Sets
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
@UtilityClass
public final class IntSetUtils {

    /**
     * Creates view which transforms values stored inside source set.
     * Returned view does not support modifications.
     *
     * @param set source set
     * @param function mapping function
     * @return read-only view
     */
    public static IntSet map(IntSet set, IntUnaryOperator function) {
        return new SetAdapter(set, function);
    }

    /**
     * Creates view which transforms values stored inside source set.
     * Returned view supports modifications: it uses passed projection for conversion in both directions.
     *
     * @param set source set
     * @param function mapping projection
     * @return mutable view
     */
    public static IntSet map(IntSet set, IntProjectionFunction function) {
        return new SetProjection(set, function);
    }
    
    private static class SetProjection extends AIntSet implements Serializable {
        
        private static final long serialVersionUID = 1L;
        
        protected final IntSet data;
        
        protected final IntProjectionFunction function;

        public SetProjection(IntSet data, IntProjectionFunction function) {
            this.data = data;
            this.function = function;
        }

        @Override
        public IntIterator iterator() {
            return IntIteratorUtils.map(data.iterator(), function::applyAsInt);
        }

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

        @Override
        public boolean remove(Object value) {
            if(value instanceof Integer) {
                Object v = function.invert((Integer) value);
                return data.remove(v);
            } else {
                return false;
            }
        }

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

        @Override
        @SuppressWarnings("unchecked")
        public boolean contains(Object value) {
            if(value instanceof Integer) {
                Object v = function.invert((Integer) value);
                return data.contains(v);
            } else {
                return false;
            }
        }

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

        @Override
        public boolean equals(Object obj) {
            if(obj instanceof Set) {
                Set<?> that = (Set<?>)obj;
                return this.size() == that.size() && this.containsAll(that);
            }
            return false;
        }
        
    }
    
    private static class SetAdapter extends AIntSet implements Serializable {
        
        private static final long serialVersionUID = 1L;
        
        protected final IntSet data;
        
        protected final IntUnaryOperator function;

        public SetAdapter(IntSet data, IntUnaryOperator function) {
            this.data = data;
            this.function = function;
        }

        @Override
        public IntIterator iterator() {
            return IntIteratorUtils.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) {
                Set<?> that = (Set<?>)obj;
                return this.size() == that.size() && this.containsAll(that);
            }
            return false;
        }

    }

}
