/*
 * @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.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.ToIntFunction;
import net.ranides.assira.collection.arrays.ArrayUtils;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class Bitmask<T> {
    
    private static final String NO_ELEMENT = "Stored value can't be mapped to any constant: ";
    
    final T[] universe;
    final ToIntFunction<T> function;
    final int usedbits;
    final Class<T> type;

    @SuppressWarnings({"PMD.ArrayIsStoredDirectly", "unchecked"})
    public Bitmask(T[] universe, ToIntFunction<T> function) {
        this.universe = ArrayUtils.copy(universe);
        this.function = function;
        this.type = (Class<T>)universe.getClass().getComponentType();
        int sum = 0;
        for(T item : universe) {
            sum |= function.applyAsInt(item);
        }
        this.usedbits = sum;
    }
    
    @SafeVarargs
    public final Set<T> constant(T... values) {
        return new CSet<>(this, mask(values));
    }
    
    public final Set<T> constant(int value) {
        if(value != (value & usedbits)) {
            throw new IllegalArgumentException(NO_ELEMENT + Integer.toHexString(value));
        }
        return new CSet<>(this, value);
    }
    
    @SafeVarargs
    public final Set<T> collect(T... values) {
        return new VSet<>(this, mask(values));
    }
    
    public Set<T> collect(int value) {
        if(value != (value & usedbits)) {
            throw new IllegalArgumentException(NO_ELEMENT + Integer.toHexString(value));
        }
        return new VSet<>(this, value);
    }
    
    private int mask(T[] values) {
        int mask = 0;
        for(T item : values) {
            mask |= function.applyAsInt(item);
        }
        return mask;
    }
    
    private int o2int(Object object) {
        return t2int(type.cast(object));
    }
    
    private int t2int(T object) {
        return function.applyAsInt(object);
    }

    @SuppressWarnings("unchecked")
    public Set<T> clone(Set<T> values) {
        if(values instanceof CSet<?>) {
            CSet<T> that = (CSet<T>)values;
            return new VSet<>(that.factory, that.values);
        }
        VSet<T> ret = new VSet<>(this, 0);
        ret.addAll(values);
        return ret;
    }
    
    private static class CSet<T> extends AbstractSet<T> {
        
        protected final Bitmask<T> factory;
        
        protected int values;

        public CSet(Bitmask<T> factory, int values) {
            this.factory = factory;
            this.values = values;
        }

        @Override
        public Iterator<T> iterator() {
            return factory.new PIterator(values);
        }

        @Override
        public int size() {
            return Integer.bitCount(values);
        }

        @Override
        public boolean contains(Object object) {
            if(!factory.type.isInstance(object)) {
                return false;
            }
            int v = factory.o2int(object);
            return v == (values & v);
        }

        @Override
        public boolean containsAll(Collection<?> object) {
            if(object instanceof CSet) {
                CSet<?> that = (CSet<?>)object;
                return (this.factory == that.factory) && (that.values == (this.values &that.values));
            }
            return super.containsAll(object);
        }
        
    }
        
    private static final class VSet<T> extends CSet<T> {
        
        public VSet(Bitmask<T> factory, int values) {
            super(factory, values);
        }

        @Override
        public boolean remove(Object value) {
            if(!factory.type.isInstance(value)) {
                return false;
            }
            
            int iv = factory.o2int(value);
            if(0 == (values & iv)) {
                return false;
            }
            values &= ~iv;
            return true;
        }

        @Override
        public boolean add(T value) {
            return iadd(factory.t2int(value));
        }

        @Override
        public boolean addAll(Collection<? extends T> object) {
            if(object instanceof CSet) {
                CSet<?> that = (CSet<?>)object;
                if(this.factory == that.factory) {
                    return iadd(that.values);
                }
            }
            return super.addAll(object);
        }
        
        private boolean iadd(int iv) {
            if(iv == (values & iv)) {
                return false;
            }
            values |= iv;
            return true;
        }
        
    }
    
    private final class PIterator implements Iterator<T> {
        
        private int value;
        private int index;

        public PIterator(int value) {
            this.value = value;
            this.index = 0;
        }

        @Override
        public boolean hasNext() {
            return 0 != value;
        }

        @Override
        public T next() {
            try {
                while(value!=0) {
                    T item = universe[index++];
                    int iv = t2int(item);
                    if( 0 != (value & iv) ) {
                        value &= ~iv;
                        return item;
                    }
                }
            } catch(IndexOutOfBoundsException cause) {
                throw new NoSuchElementException(NO_ELEMENT + Integer.toHexString(value));
            }
            throw new NoSuchElementException();
        }
        
    }
    
}
