package net.ranides.assira.collection.sets;

import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * This class extends standard Set collection, providing additional functionality.
 * - offers conversion of set into int
 * - offers fast conversion into mutable or immutable MaskSet
 * <p>
 * This class can be created directly. You must use MaskFactory to obtain instances of this class.
 *
 * @param <T> type of
 */
public abstract class MaskSet<T> extends AbstractSet<T> {

    protected final MaskFactory<T> factory;

    protected MaskSet(MaskFactory<T> factory) {
        this.factory = factory;
    }

    /**
     * Converts this mask into int value.
     *
     * @return int
     */
    public abstract int intValue();

    /**
     * Converts this mask into mutable set.
     *
     * @return new mask
     */
    public abstract MaskSet<T> toMutable();

    /**
     * Converts this mask into immutable set.
     *
     * @return new mask
     */
    public abstract MaskSet<T> toImmutable();

    static abstract class Abstract<T> extends MaskSet<T> {

        protected Abstract(MaskFactory<T> factory) {
            super(factory);
        }

        @Override
        public final MaskSet<T> toMutable() {
            return new Mutable<>(factory, intValue());
        }

        @Override
        public final MaskSet<T> toImmutable() {
            return new Immutable<>(factory, intValue());
        }

        @Override
        public final Iterator<T> iterator() {
            return new ConstIterator<>(factory, intValue());
        }

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

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

        @Override
        public final boolean containsAll(Collection<?> object) {
            if(isEmpty()) {
                return object.isEmpty();
            }
            if (object instanceof MaskSet) {
                MaskSet<?> that = (MaskSet<?>) object;
                if (this.factory == that.factory) {
                    return that.intValue() == (this.intValue() & that.intValue());
                }
            }
            return super.containsAll(object);
        }

    }

    static class Empty<T> extends MaskSet<T> {

        protected Empty(MaskFactory<T> factory) {
            super(factory);
        }

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

        @Override
        public final MaskSet<T> toMutable() {
            return new Mutable<>(factory, 0);
        }

        @Override
        public final MaskSet<T> toImmutable() {
            return new Immutable<>(factory, 0);
        }

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

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

        @Override
        public final boolean contains(Object object) {
            return false;
        }

        @Override
        public final boolean containsAll(Collection<?> object) {
            return object.isEmpty();
        }

    }

    static class Immutable<T> extends Abstract<T> {

        private final int values;

        public Immutable(MaskFactory<T> factory, int values) {
            super(factory);
            this.values = values;
        }

        @Override
        public int intValue() {
            return values;
        }

    }

    static class Mutable<T> extends Abstract<T> {

        private int values;

        public Mutable(MaskFactory<T> factory, int values) {
            super(factory);
            this.values = values;
        }

        @Override
        public int intValue() {
            return values;
        }

        @Override
        public boolean remove(Object value) {
            if(!factory.type.isInstance(value)) {
                return false;
            }

            int iv = factory.objectToInt(value);
            if(0 == (values & iv)) {
                return false;
            }
            values &= ~iv;
            return true;
        }

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

        @Override
        public boolean addAll(Collection<? extends T> object) {
            if(object instanceof MaskSet) {
                MaskSet<?> that = (MaskSet<?>)object;
                if(this.factory == that.factory) {
                    return iadd(that.intValue());
                }
            }
            return super.addAll(object);
        }

        private boolean iadd(int iv) {
            if(iv == (values & iv)) {
                return false;
            }
            values |= iv;
            return true;
        }

    }

    private static final class ConstIterator<T> implements Iterator<T> {

        private final MaskFactory<T> factory;
        private int value;
        private int index;

        public ConstIterator(MaskFactory<T> factory, int value) {
            this.factory = factory;
            this.value = value;
            this.index = 0;
        }

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

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

    }
}
