/**
 * Copyright 2010 - 2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.exodus.entitystore.util;

import jetbrains.exodus.core.dataStructures.hash.LongHashSet;
import jetbrains.exodus.core.dataStructures.hash.LongSet;
import jetbrains.exodus.entitystore.EntityId;
import jetbrains.exodus.entitystore.PersistentEntityId;
import jetbrains.exodus.entitystore.iterate.EntityIdSet;
import jetbrains.exodus.entitystore.iterate.SortedEntityIdSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.BitSet;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class ImmutableSingleTypeEntityIdBitSet implements SortedEntityIdSet {
    private final int singleTypeId;
    private final int size;
    private final long min;
    private final long max;
    private final BitSet data;

    public ImmutableSingleTypeEntityIdBitSet(final int singleTypeId, final long[] source) {
        this(singleTypeId, source, source.length);
    }

    public ImmutableSingleTypeEntityIdBitSet(final int singleTypeId, final long[] source, int length) {
        if (length > source.length) {
            throw new IllegalArgumentException();
        }
        this.singleTypeId = singleTypeId;
        size = length;
        min = source[0];
        max = source[size - 1];
        final long bitsCount = max - min + 1;
        if (min < 0 || bitsCount >= Integer.MAX_VALUE) {
            throw new IllegalArgumentException();
        }
        data = new BitSet((int) bitsCount);
        for (int i = 0; i < size; i++) {
            data.set((int) (source[i] - min));
        }
    }

    @Override
    public EntityIdSet add(@Nullable EntityId id) {
        throw new UnsupportedOperationException();
    }

    @Override
    public EntityIdSet add(int typeId, long localId) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean contains(@Nullable EntityId id) {
        return id != null && contains(id.getTypeId(), id.getLocalId());
    }

    @Override
    public boolean contains(int typeId, long localId) {
        return typeId == singleTypeId
                && localId >= min
                && localId <= max
                && data.get((int) (localId - min));
    }

    @Override
    public boolean remove(@Nullable EntityId id) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean remove(int typeId, long localId) {
        throw new UnsupportedOperationException();
    }

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

    @Override
    public Iterator<EntityId> iterator() {
        return new IdIterator();
    }

    @Override
    public int indexOf(@NotNull EntityId entityId) {
        long id = entityId.getLocalId();
        if (!contains(entityId.getTypeId(), id)) {
            return -1;
        }
        int nextBitIndex = data.nextSetBit(0);
        int result = 0;
        if (nextBitIndex != -1) {
            id -= min;
            while (nextBitIndex < id) {
                result++;
                nextBitIndex = data.nextSetBit(nextBitIndex + 1);
                if (nextBitIndex == -1) {
                    break;
                }
            }
        }
        return result;
    }

    @Override
    public EntityId getFirst() {
        return new PersistentEntityId(singleTypeId, min);
    }

    @Override
    public EntityId getLast() {
        return new PersistentEntityId(singleTypeId, max);
    }

    @Override
    public Iterator<EntityId> reverseIterator() {
        return new ReverseIdIterator();
    }

    @NotNull
    @Override
    public LongSet getTypeSetSnapshot(int typeId) {
        if (typeId == singleTypeId) {
            LongHashSet result = new LongHashSet(size);
            int next = data.nextSetBit(0);
            while (next != -1) {
                result.add(next + min);
                // if (next == Integer.MAX_VALUE) { break; }
                next = data.nextSetBit(next + 1);
            }
            return result;
        }
        return LongSet.EMPTY;
    }

    class IdIterator implements Iterator<EntityId> {
        int nextBitIndex = data.nextSetBit(0);

        @Override
        public boolean hasNext() {
            return nextBitIndex != -1;
        }

        @Override
        public EntityId next() {
            if (nextBitIndex != -1) {
                final int bitIndex = nextBitIndex;
                nextBitIndex = data.nextSetBit(nextBitIndex + 1);
                return new PersistentEntityId(singleTypeId, bitIndex + min);
            } else {
                throw new NoSuchElementException();
            }
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    class ReverseIdIterator implements Iterator<EntityId> {
        int nextBitIndex = data.previousSetBit((int) (max - min));

        @Override
        public boolean hasNext() {
            return nextBitIndex != -1;
        }

        @Override
        public EntityId next() {
            if (nextBitIndex != -1) {
                final int bitIndex = nextBitIndex;
                nextBitIndex = data.previousSetBit(nextBitIndex - 1);
                return new PersistentEntityId(singleTypeId, bitIndex + min);
            } else {
                throw new NoSuchElementException();
            }
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}

@@ -699,24 +699,87 @@
 ongHashSet;%0A
+import jetbrains.exodus.core.dataStructures.hash.LongIterator;%0A
 import jetbr
@@ -2234,32 +2234,585 @@
        %7D%0A    %7D%0A%0A
+    public ImmutableSingleTypeEntityIdBitSet(final int singleTypeId, long min, long max, final LongIterator source, int length) %7B%0A        this.singleTypeId = singleTypeId;%0A        this.min = min;%0A        this.max = max;%0A        size = length;%0A        final long bitsCount = max - min + 1;%0A        if (min %3C 0 %7C%7C bitsCount %3E= Integer.MAX_VALUE) %7B%0A            throw new IllegalArgumentException();%0A        %7D%0A        data = new BitSet((int) bitsCount);%0A        while (source.hasNext()) %7B%0A            data.set((int) (source.next() - min));%0A        %7D%0A    %7D%0A%0A
     @Override%0A  

add more ImmutableSingleTypeEntityIdBitSet constructors