/*
 *     Copyright (C) 2015  higherfrequencytrading.com
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.openhft.lang.collection;

import net.openhft.lang.io.ByteBufferBytes;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;

import static net.openhft.lang.collection.DirectBitSet.Bits;
import static org.junit.Assert.*;

@RunWith(value = Parameterized.class)
public class DirectBitSetTest {

    private static final int[] INDICES = new int[]{0, 50, 100, 127, 128, 255};
    private DirectBitSet bs;
    private boolean singleThreaded;
    private SingleThreadedDirectBitSet st;

    public DirectBitSetTest(DirectBitSet bs) {
        this.bs = bs;
        singleThreaded = bs instanceof SingleThreadedDirectBitSet;
        if (singleThreaded)
            st = (SingleThreadedDirectBitSet) bs;
        assertTrue(bs.size() >= 256);
    }

    @Parameterized.Parameters
    public static Collection<Object[]> data() {
        int capacityInBytes = 256 / 8;
        return Arrays.asList(new Object[][]{
                {
                        ATSDirectBitSet.wrap(ByteBufferBytes.wrap(
                                ByteBuffer.allocate(capacityInBytes)))
                },
                {
                        new SingleThreadedDirectBitSet(ByteBufferBytes.wrap(
                                ByteBuffer.allocate(capacityInBytes)))
                },
                {
                        ATSDirectBitSet.wrap(ByteBufferBytes.wrap(
                                ByteBuffer.allocateDirect(capacityInBytes)))
                },
                {
                        new SingleThreadedDirectBitSet(ByteBufferBytes.wrap(
                                ByteBuffer.allocateDirect(capacityInBytes)))
                }
        });
    }

    private void setIndices() {
        bs.clear();
        for (int i : INDICES) {
            bs.set(i);
        }
    }

    private void setIndicesComplement() {
        setIndices();
        bs.flip(0, bs.size());
    }

    private void assertRangeIsClear(long from, long to) {
        for (long i = from; i < to; i++) {
            assertFalse(bs.get(i));
        }
    }

    private void assertRangeIsClear(String message, long from, long to) {
        for (long i = from; i < to; i++) {
            assertFalse(message + ", bit: " + i, bs.get(i));
        }
    }

    private void assertRangeIsSet(long from, long to) {
        for (long i = from; i < to; i++) {
            assertTrue(bs.get(i));
        }
    }

    private void assertRangeIsSet(String message, long from, long to) {
        for (long i = from; i < to; i++) {
            assertTrue(message + ", bit: " + i, bs.get(i));
        }
    }

    @Test
    public void testGetSetClearAndCardinality() {
        bs.clear();
        assertEquals(0, bs.cardinality());
        int c = 0;
        for (int i : INDICES) {
            c++;
            assertFalse("At index " + i, bs.get(i));
            assertFalse("At index " + i, bs.isSet(i));
            assertTrue("At index " + i, bs.isClear(i));
            bs.set(i);
            assertTrue("At index " + i, bs.get(i));
            assertTrue("At index " + i, bs.isSet(i));
            assertFalse("At index " + i, bs.isClear(i));
            assertEquals(c, bs.cardinality());
        }

        for (int i : INDICES) {
            assertTrue("At index " + i, bs.get(i));
            assertTrue("At index " + i, bs.isSet(i));
            assertFalse("At index " + i, bs.isClear(i));
            bs.clear(i);
            assertFalse("At index " + i, bs.get(i));
            assertFalse("At index " + i, bs.isSet(i));
            assertTrue("At index " + i, bs.isClear(i));
        }

        for (int i : INDICES) {
            assertEquals("At index " + i, true, bs.setIfClear(i));
            assertEquals("At index " + i, false, bs.setIfClear(i));
        }

        for (int i : INDICES) {
            assertEquals("At index " + i, true, bs.clearIfSet(i));
            assertEquals("At index " + i, false, bs.clearIfSet(i));
        }
    }

    @Test
    public void testGetLong() {
        setIndices();
        long l0 = 1L | (1L << 50);
        assertEquals(l0, bs.getLong(0));
        long l1 = (1L << (100 - 64)) | (1L << (127 - 64));
        assertEquals(l1, bs.getLong(1));
    }

    @Test
    public void testFlip() {
        bs.clear();
        for (int i : INDICES) {
            assertEquals("At index " + i, false, bs.get(i));
            bs.flip(i);
            assertEquals("At index " + i, true, bs.get(i));
            bs.flip(i);
            assertEquals("At index " + i, false, bs.get(i));
        }
    }

    @Test
    public void testNextSetBit() {
        setIndices();
        int order = 0;
        for (long i = bs.nextSetBit(0L); i >= 0; i = bs.nextSetBit(i + 1)) {
            assertEquals(INDICES[order], i);
            order++;
        }
        assertEquals(-1, bs.nextSetBit(bs.size()));

        bs.clear();
        assertEquals(-1, bs.nextSetBit(0L));
    }

    @Test
    public void testSetBitsIteration() {
        setIndices();
        int order = 0;
        Bits bits = bs.setBits();
        long i;
        while ((i = bits.next()) >= 0) {
            assertEquals(INDICES[order], i);
            order++;
        }
        assertEquals(-1, bits.next());

        bs.clear();
        assertEquals(-1, bs.setBits().next());
    }

    @Test
    public void testClearNextSetBit() {
        setIndices();
        long cardinality = bs.cardinality();
        int order = 0;
        for (long i = bs.clearNextSetBit(0L); i >= 0;
             i = bs.clearNextSetBit(i + 1)) {
            assertEquals(INDICES[order], i);
            assertFalse(bs.get(i));
            order++;
            cardinality--;
            assertEquals(cardinality, bs.cardinality());
        }
        assertEquals(-1, bs.clearNextSetBit(bs.size()));
        assertEquals(0, bs.cardinality());
        assertEquals(-1, bs.clearNextSetBit(0L));
    }

    @Test
    public void testClearNext1SetBit() {
        setIndices();
        long cardinality = bs.cardinality();
        int order = 0;
        for (long i = bs.clearNextNContinuousSetBits(0L, 1); i >= 0;
             i = bs.clearNextNContinuousSetBits(i + 1, 1)) {
            assertEquals(INDICES[order], i);
            assertFalse(bs.get(i));
            order++;
            cardinality--;
            assertEquals(cardinality, bs.cardinality());
        }
        assertEquals(-1, bs.clearNextNContinuousSetBits(bs.size(), 1));
        assertEquals(0, bs.cardinality());
        assertEquals(-1, bs.clearNextNContinuousSetBits(0L, 1));
    }

    @Test
    public void testNextSetLong() {
        bs.clear();
        bs.set(0);
        bs.set(255);
        long[] setLongs = {0, 3};
        int order = 0;
        for (long i = bs.nextSetLong(0L); i >= 0; i = bs.nextSetLong(i + 1)) {
            assertEquals(setLongs[order], i);
            order++;
        }
        assertEquals(-1, bs.nextSetLong(bs.size() / 64));

        bs.clear();
        assertEquals(-1, bs.nextSetLong(0L));
    }

    @Test
    public void testNextClearBit() {
        setIndicesComplement();
        int order = 0;
        for (long i = bs.nextClearBit(0L); i >= 0; i = bs.nextClearBit(i + 1)) {
            assertEquals(INDICES[order], i);
            order++;
        }
        assertEquals(-1, bs.nextClearBit(bs.size()));

        bs.setAll();
        assertEquals(-1, bs.nextClearBit(0L));
    }

    @Test
    public void testSetNextClearBit() {
        setIndicesComplement();
        long cardinality = bs.cardinality();
        int order = 0;
        for (long i = bs.setNextClearBit(0L); i >= 0;
             i = bs.setNextClearBit(i + 1)) {
            assertEquals(INDICES[order], i);
            assertTrue(bs.get(i));
            order++;
            cardinality++;
            assertEquals(cardinality, bs.cardinality());
        }
        assertEquals(-1, bs.setNextClearBit(bs.size()));
        assertEquals(bs.size(), bs.cardinality());
        assertEquals(-1, bs.setNextClearBit(0L));
    }

    @Test
    public void testSetNext1ClearBit() {
        setIndicesComplement();
        long cardinality = bs.cardinality();
        int order = 0;
        for (long i = bs.setNextNContinuousClearBits(0L, 1); i >= 0;
             i = bs.setNextNContinuousClearBits(i + 1, 1)) {
            assertEquals(INDICES[order], i);
            assertTrue(bs.get(i));
            order++;
            cardinality++;
            assertEquals(cardinality, bs.cardinality());
        }
        assertEquals(-1, bs.setNextNContinuousClearBits(bs.size(), 1));
        assertEquals(bs.size(), bs.cardinality());
        assertEquals(-1, bs.setNextNContinuousClearBits(0L, 1));
    }

    @Test
    public void testNextClearLong() {
        bs.setAll();
        bs.clear(0);
        bs.clear(255);
        long[] clearLongs = {0, 3};
        int order = 0;
        for (long i = bs.nextClearLong(0L); i >= 0; i = bs.nextClearLong(i + 1)) {
            assertEquals(clearLongs[order], i);
            order++;
        }
        assertEquals(-1, bs.nextClearLong(bs.size() / 64));

        bs.setAll();
        assertEquals(-1, bs.nextClearLong(0L));
    }

    @Test
    public void testPreviousSetBit() {
        setIndices();
        int order = INDICES.length;
        for (long i = bs.size(); (i = bs.previousSetBit(i - 1)) >= 0; ) {
            order--;
            assertEquals(INDICES[order], i);
        }
        assertEquals(-1, bs.previousSetBit(-1));

        bs.clear();
        assertEquals(-1, bs.previousSetBit(bs.size()));
    }

    @Test
    public void testClearPreviousSetBit() {
        setIndices();
        long cardinality = bs.cardinality();
        int order = INDICES.length;
        for (long i = bs.size(); (i = bs.clearPreviousSetBit(i - 1)) >= 0; ) {
            order--;
            cardinality--;
            assertEquals(INDICES[order], i);
            assertFalse(bs.get(i));
            assertEquals(cardinality, bs.cardinality());
        }
        assertEquals(-1, bs.clearPreviousSetBit(-1));
        assertEquals(0, bs.cardinality());
        assertEquals(-1, bs.clearPreviousSetBit(bs.size()));
    }

    @Test
    public void testClearPrevious1SetBit() {
        setIndices();
        long cardinality = bs.cardinality();
        int order = INDICES.length;
        for (long i = bs.size();
             (i = bs.clearPreviousNContinuousSetBits(i - 1, 1)) >= 0; ) {
            order--;
            cardinality--;
            assertEquals(INDICES[order], i);
            assertFalse(bs.get(i));
            assertEquals(cardinality, bs.cardinality());
        }
        assertEquals(-1, bs.clearPreviousNContinuousSetBits(-1, 1));
        assertEquals(0, bs.cardinality());
        assertEquals(-1, bs.clearPreviousNContinuousSetBits(bs.size(), 1));
    }

    @Test
    public void testPreviousSetLong() {
        bs.clear();
        bs.set(0);
        bs.set(255);
        long[] setLongs = {0, 3};
        int order = setLongs.length;
        for (long i = bs.size() / 64; (i = bs.previousSetLong(i - 1)) >= 0; ) {
            order--;
            assertEquals(setLongs[order], i);
        }
        assertEquals(-1, bs.previousSetLong(-1));

        bs.clear();
        assertEquals(-1, bs.previousSetLong(bs.size() / 64));
    }

    @Test
    public void testPreviousClearBit() {
        setIndicesComplement();
        int order = INDICES.length;
        for (long i = bs.size(); (i = bs.previousClearBit(i - 1)) >= 0; ) {
            order--;
            assertEquals(INDICES[order], i);
        }
        assertEquals(-1, bs.previousClearBit(-1));

        bs.setAll();
        assertEquals(-1, bs.previousClearBit(bs.size()));
    }

    @Test
    public void testSetPreviousClearBit() {
        setIndicesComplement();
        long cardinality = bs.cardinality();
        int order = INDICES.length;
        for (long i = bs.size(); (i = bs.setPreviousClearBit(i - 1)) >= 0; ) {
            order--;
            cardinality++;
            assertEquals(INDICES[order], i);
            assertTrue(bs.get(i));
            assertEquals(cardinality, bs.cardinality());
        }
        assertEquals(-1, bs.setPreviousClearBit(-1));
        assertEquals(bs.size(), bs.cardinality());
        assertEquals(-1, bs.setPreviousClearBit(bs.size()));
    }

    @Test
    public void testSetPrevious1ClearBit() {
        setIndicesComplement();
        long cardinality = bs.cardinality();
        int order = INDICES.length;
        for (long i = bs.size();
             (i = bs.setPreviousNContinuousClearBits(i - 1, 1)) >= 0; ) {
            order--;
            cardinality++;
            assertEquals(INDICES[order], i);
            assertTrue(bs.get(i));
            assertEquals(cardinality, bs.cardinality());
        }
        assertEquals(-1, bs.setPreviousNContinuousClearBits(-1, 1));
        assertEquals(bs.size(), bs.cardinality());
        assertEquals(-1, bs.setPreviousNContinuousClearBits(bs.size(), 1));
    }

    @Test
    public void testPreviousClearLong() {
        bs.setAll();
        bs.clear(0);
        bs.clear(255);
        long[] clearLongs = {0, 3};
        int order = clearLongs.length;
        for (long i = bs.size() / 64; (i = bs.previousClearLong(i - 1)) >= 0; ) {
            order--;
            assertEquals(clearLongs[order], i);
        }
        assertEquals(-1, bs.previousClearLong(-1));

        bs.setAll();
        assertEquals(-1, bs.previousClearLong(bs.size() / 64));
    }

    @Test
    public void testSetAll() {
        bs.clear();
        bs.setAll();
        assertEquals(bs.size(), bs.cardinality());
    }

    @Test
    public void testRangeOpsWithinLongCase() {
        bs.clear();
        if (singleThreaded) {
            assertTrue(st.allClear(0, 0));
            assertTrue(st.allClear(63, 63));
            assertTrue(st.allSet(0, 0));
            assertTrue(st.allSet(63, 63));
        }

        bs.flip(0, 0);
        assertEquals(false, bs.get(0));
        assertEquals(0, bs.cardinality());
        bs.flip(0, 1);
        assertEquals(true, bs.get(0));
        assertEquals(1, bs.cardinality());
        if (singleThreaded) {
            assertTrue(st.allSet(0, 1));
            assertFalse(st.allSet(0, 2));
            assertFalse(st.allClear(0, 1));
        }

        bs.clear(0, 0);
        assertEquals(true, bs.get(0));
        assertEquals(1, bs.cardinality());
        bs.clear(0, 1);
        assertEquals(false, bs.get(0));
        assertEquals(0, bs.cardinality());

        bs.set(0, 0);
        assertEquals(false, bs.get(0));
        assertEquals(0, bs.cardinality());
        bs.set(0, 1);
        assertEquals(true, bs.get(0));
        assertEquals(1, bs.cardinality());
    }

    @Test
    public void testRangeOpsCrossLongCase() {
        bs.clear();

        bs.flip(63, 64);
        assertEquals(true, bs.get(63));
        assertEquals(false, bs.get(64));
        assertEquals(1, bs.cardinality());
        if (singleThreaded) {
            assertFalse(st.allSet(63, 65));
            assertFalse(st.allClear(63, 65));
        }
        bs.flip(63, 65);
        assertEquals(false, bs.get(63));
        assertEquals(true, bs.get(64));
        assertEquals(1, bs.cardinality());
        if (singleThreaded) {
            assertFalse(st.allSet(63, 65));
            assertFalse(st.allClear(63, 65));
        }

        bs.clear(64);
        bs.set(63, 64);
        assertEquals(true, bs.get(63));
        assertEquals(false, bs.get(64));
        assertEquals(1, bs.cardinality());

        bs.set(64);
        bs.clear(63, 64);
        assertEquals(false, bs.get(63));
        assertEquals(true, bs.get(64));
        assertEquals(1, bs.cardinality());

        bs.clear(64);
        bs.set(63, 65);
        assertEquals(true, bs.get(63));
        assertEquals(true, bs.get(64));
        assertEquals(2, bs.cardinality());
        if (singleThreaded) {
            assertTrue(st.allSet(63, 65));
            assertFalse(st.allClear(63, 65));
        }

        bs.clear(63, 65);
        assertEquals(false, bs.get(63));
        assertEquals(false, bs.get(64));
        assertEquals(0, bs.cardinality());
        if (singleThreaded) {
            assertFalse(st.allSet(63, 65));
            assertTrue(st.allClear(63, 65));
        }
    }

    @Test
    public void testRangeOpsSpanLongCase() {
        bs.clear();
        if (singleThreaded) {
            assertTrue(st.allClear(0, st.size()));
            assertFalse(st.allSet(0, st.size()));
        }

        bs.set(0, bs.size());
        assertEquals(bs.size(), bs.cardinality());
        if (singleThreaded) {
            assertFalse(st.allClear(0, st.size()));
            assertTrue(st.allSet(0, st.size()));
        }

        bs.clear(0, bs.size());
        assertEquals(0, bs.cardinality());

        bs.flip(0, bs.size());
        assertEquals(bs.size(), bs.cardinality());
    }

    private String m(int n) {
        return "N: " + n + ", " + bs.getClass().getSimpleName();
    }

    @Test
    public void testSetNextNContinuousClearBitsWithinLongCase() {
        long size = (bs.size() + 63) / 64 * 64;
        for (int n = 1; n <= 64; n *= 2) {
            bs.clear();
            for (int i = 0; i < size / n; i++) {
                assertRangeIsClear(i * n, i * n + n);
                assertEquals(m(n), i * n, bs.setNextNContinuousClearBits(0L, n));
                assertRangeIsSet(i * n, i * n + n);
                assertEquals(i * n + n, bs.cardinality());
            }
        }
        for (int n = 2; n <= 64; n *= 2) {
            bs.setAll();
            bs.clear(size - n, size);
            assertEquals(size - n, bs.setNextNContinuousClearBits(0L, n));
            assertRangeIsSet(size - n, size);

            long offset = (64 - n) / 2;
            long from = size - n - offset;
            long to = size - offset;
            bs.clear(from, to);
            assertEquals(from, bs.setNextNContinuousClearBits(from, n));
            assertRangeIsSet(from, to);

            bs.clear(from, to);
            for (long i = from - 2; i >= 0; i -= 2) {
                bs.clear(i);
            }
            long cardinality = bs.cardinality();
            assertEquals(from, bs.setNextNContinuousClearBits(0, n));
            assertEquals(cardinality + n, bs.cardinality());
        }
    }

    @Test
    public void testSetNextNContinuousClearBitsCrossLongCase() {
        if (bs instanceof ATSDirectBitSet)
            return;
        long size = bs.size();
        for (int n : new int[]{3, 7, 13, 31, 33, 63, 65, 100, 127, 128, 129, 254, 255}) {
            bs.clear();
            for (int i = 0; i < size / n; i++) {
                assertRangeIsClear(i * n, i * n + n);
                assertEquals(m(n), i * n, bs.setNextNContinuousClearBits(0L, n));
                assertRangeIsSet(i * n, i * n + n);
                assertEquals(i * n + n, bs.cardinality());
            }
        }
        long lastBound = size - (size % 64 == 0 ? 64 : size % 64);
        for (int n : new int[]{2, 3, 7, 13, 31, 33, 63, 64, 65, 100, 127, 128, 129}) {
            bs.setAll();
            long from = n <= 64 ? lastBound - (n / 2) : 30;
            long to = from + n;
            bs.clear(from, to);
            assertEquals("" + n, from, bs.setNextNContinuousClearBits(0L, n));
            assertRangeIsSet(from, to);

            bs.clear(from, to);
            for (long i = from - 2; i >= 0; i -= 2) {
                bs.clear(i);
            }
            for (long i = to + 1; i < bs.size(); i += 2) {
                bs.clear(i);
            }
            long cardinality = bs.cardinality();
            assertEquals(from, bs.setNextNContinuousClearBits(from, n));
            assertEquals(cardinality + n, bs.cardinality());
        }
    }

    @Test
    public void testClearNextNContinuousSetBitsWithinLongCase() {
        long size = (bs.size() + 63) / 64 * 64;
        for (int n = 1; n <= 64; n *= 2) {
            bs.setAll();
            long cardinality = bs.cardinality();
            for (int i = 0; i < size / n; i++) {
                assertRangeIsSet(i * n, i * n + n);
                assertEquals(m(n), i * n, bs.clearNextNContinuousSetBits(0L, n));
                assertRangeIsClear(i * n, i * n + n);
                assertEquals(cardinality - (i * n + n), bs.cardinality());
            }
        }
        for (int n = 2; n <= 64; n *= 2) {
            bs.clear();
            bs.set(size - n, size);
            assertEquals(size - n, bs.clearNextNContinuousSetBits(0L, n));
            assertRangeIsClear(size - n, size);

            long offset = (64 - n) / 2;
            long from = size - n - offset;
            long to = size - offset;
            bs.set(from, to);
            assertEquals(from, bs.clearNextNContinuousSetBits(from, n));
            assertRangeIsClear(from, to);

            bs.set(from, to);
            for (long i = from - 2; i >= 0; i -= 2) {
                bs.set(i);
            }
            long cardinality = bs.cardinality();
            assertEquals(from, bs.clearNextNContinuousSetBits(0, n));
            assertEquals(cardinality - n, bs.cardinality());
        }
    }

    @Test
    public void testClearNextNContinuousSetBitsCrossLongCase() {
        if (bs instanceof ATSDirectBitSet)
            return;
        long size = bs.size();
        for (int n : new int[]{3, 7, 13, 31, 33, 63}) {
            bs.setAll();
            long cardinality = bs.cardinality();
            for (int i = 0; i < size / n; i++) {
                assertRangeIsSet(i * n, i * n + n);
                assertEquals(m(n), i * n, bs.clearNextNContinuousSetBits(0L, n));
                assertRangeIsClear(i * n, i * n + n);
                assertEquals(cardinality -= n, bs.cardinality());
            }
        }
        long lastBound = size - (size % 64 == 0 ? 64 : size % 64);
        for (int n : new int[]{2, 3, 7, 13, 31, 33, 63, 64}) {
            bs.clear();
            long from = lastBound - (n / 2);
            long to = from + n;
            bs.set(from, to);
            assertEquals(from, bs.clearNextNContinuousSetBits(0L, n));
            assertRangeIsClear(from, to);

            bs.set(from, to);
            for (long i = from - 2; i >= 0; i -= 2) {
                bs.set(i);
            }
            for (long i = to + 1; i < bs.size(); i += 2) {
                bs.set(i);
            }
            long cardinality = bs.cardinality();
            assertEquals(from, bs.clearNextNContinuousSetBits(from, n));
            assertEquals(cardinality - n, bs.cardinality());
        }
    }

    @Test
    public void testSetPreviousNContinuousClearBitsWithinLongCase() {
        long size = (bs.size() + 63) / 64 * 64;
        for (int n = 1; n <= 64; n *= 2) {
            bs.clear();
            long cardinality = 0;
            for (long i = size / n - 1; i >= 0; i--) {
                assertRangeIsClear(i * n, i * n + n);
                assertEquals(m(n), i * n, bs.setPreviousNContinuousClearBits(size, n));
                assertRangeIsSet(i * n, i * n + n);
                assertEquals(cardinality += n, bs.cardinality());
            }
        }
        for (int n = 2; n <= 64; n *= 2) {
            bs.setAll();
            bs.clear(0, n);
            assertEquals(0, bs.setPreviousNContinuousClearBits(bs.size(), n));
            assertRangeIsSet(0, n);

            long from = (64 - n) / 2;
            long to = from + n;
            bs.clear(from, to);
            assertEquals(from, bs.setPreviousNContinuousClearBits(to - 1, n));
            assertRangeIsSet(from, to);

            bs.clear(from, to);
            for (long i = to + 1; i < bs.size(); i += 2) {
                bs.clear(i);
            }
            long cardinality = bs.cardinality();
            assertEquals(from, bs.setPreviousNContinuousClearBits(bs.size(), n));
            assertEquals(cardinality + n, bs.cardinality());
        }
    }

    @Test
    public void testSetPreviousNContinuousClearBitsCrossLongCase() {
        if (bs instanceof ATSDirectBitSet)
            return;
        long size = bs.size();
        for (int n : new int[]{3, 7, 13, 31, 33, 63}) {
            bs.clear();
            long cardinality = 0;
            for (long from = size - n; from >= 0; from -= n) {
                assertRangeIsClear(from, from + n);
                assertEquals(m(n), from, bs.setPreviousNContinuousClearBits(size, n));
                assertRangeIsSet(from, from + n);
                assertEquals(cardinality += n, bs.cardinality());
            }
        }
        for (int n : new int[]{2, 3, 7, 13, 31, 33, 63, 64}) {
            bs.setAll();
            long from = 64 - (n / 2);
            long to = from + n;
            bs.clear(from, to);
            assertEquals(from, bs.setPreviousNContinuousClearBits(size, n));
            assertRangeIsSet(from, to);

            bs.clear(from, to);
            for (long i = from - 2; i >= 0; i -= 2) {
                bs.clear(i);
            }
            for (long i = to + 1; i < bs.size(); i += 2) {
                bs.clear(i);
            }
            long cardinality = bs.cardinality();
            assertEquals(from, bs.setPreviousNContinuousClearBits(to - 1, n));
            assertEquals(cardinality + n, bs.cardinality());
        }
    }

    @Test
    public void testClearPreviousNContinuousSetBitsWithinLongCase() {
        long size = (bs.size() + 63) / 64 * 64;
        for (int n = 1; n <= 64; n *= 2) {
            bs.setAll();
            long cardinality = bs.cardinality();
            for (long i = size / n - 1; i >= 0; i--) {
                assertRangeIsSet(i * n, i * n + n);
                assertEquals(m(n), i * n, bs.clearPreviousNContinuousSetBits(size, n));
                assertRangeIsClear(m(n), i * n, i * n + n);
                assertEquals(cardinality -= n, bs.cardinality());
            }
        }
        for (int n = 2; n <= 64; n *= 2) {
            bs.clear();
            bs.set(0, n);
            assertEquals(0, bs.clearPreviousNContinuousSetBits(bs.size(), n));
            assertRangeIsClear(0, n);

            long from = (64 - n) / 2;
            long to = from + n;
            bs.set(from, to);
            assertEquals(from, bs.clearPreviousNContinuousSetBits(to - 1, n));
            assertRangeIsClear(from, to);

            bs.set(from, to);
            for (long i = to + 1; i < bs.size(); i += 2) {
                bs.set(i);
            }
            long cardinality = bs.cardinality();
            assertEquals(from, bs.clearPreviousNContinuousSetBits(bs.size(), n));
            assertEquals(cardinality - n, bs.cardinality());
        }
    }

    @Test
    public void testClearPreviousNContinuousSetBitsCrossLongCase() {
        if (bs instanceof ATSDirectBitSet)
            return;
        long size = bs.size();
        for (int n : new int[]{3, 7, 13, 31, 33, 63}) {
            bs.setAll();
            long cardinality = bs.cardinality();
            for (long from = size - n; from >= 0; from -= n) {
                assertRangeIsSet(from, from + n);
                assertEquals(m(n), from, bs.clearPreviousNContinuousSetBits(size, n));
                assertRangeIsClear(from, from + n);
                assertEquals(cardinality -= n, bs.cardinality());
            }
        }
        for (int n : new int[]{2, 3, 7, 13, 31, 33, 63, 64}) {
            bs.clear();
            long from = 64 - (n / 2);
            long to = from + n;
            bs.set(from, to);
            assertEquals(from, bs.clearPreviousNContinuousSetBits(size, n));
            assertRangeIsClear(from, to);

            bs.set(from, to);
            for (long i = from - 2; i >= 0; i -= 2) {
                bs.set(i);
            }
            for (long i = to + 1; i < bs.size(); i += 2) {
                bs.set(i);
            }
            long cardinality = bs.cardinality();
            assertEquals(from, bs.clearPreviousNContinuousSetBits(to - 1, n));
            assertEquals(cardinality - n, bs.cardinality());
        }
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeGetNegative() {
        bs.get(-1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeGetOverCapacity() {
        bs.get(bs.size());
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeSetNegative() {
        bs.set(-1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeSetOverCapacity() {
        bs.set(bs.size());
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeSetIfClearNegative() {
        bs.setIfClear(-1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeSetIfClearOverCapacity() {
        bs.setIfClear(bs.size());
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeClearIfSetNegative() {
        bs.clearIfSet(-1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeClearIfSetOverCapacity() {
        bs.clearIfSet(bs.size());
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeFlipNegative() {
        bs.flip(-1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeFlipOverCapacity() {
        bs.flip(bs.size());
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeGetLongNegative() {
        bs.getLong(-1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeGetLongOverCapacity() {
        bs.getLong((bs.size() + 63) / 64);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeNextSetBit() {
        bs.nextSetBit(-1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeNextSetLong() {
        bs.nextSetLong(-1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeNextClearBit() {
        bs.nextClearBit(-1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeNextClearLong() {
        bs.nextClearLong(-1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobePreviousSetBit() {
        bs.previousSetBit(-2);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobePreviousSetLong() {
        bs.previousSetLong(-2);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobePreviousClearBit() {
        bs.previousClearBit(-2);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobePreviousClearLong() {
        bs.previousClearLong(-2);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeClearNextSetBit() {
        bs.clearNextSetBit(-1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeClearNextNContinuousSetBits() {
        bs.clearNextNContinuousSetBits(-1, 2);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeSetNextClearBit() {
        bs.setNextClearBit(-1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeSetNextNContinuousClearBits() {
        bs.setNextNContinuousClearBits(-1, 2);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeClearPreviousSetBit() {
        bs.clearPreviousSetBit(-2);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeClearPreviousNContinuousSetBit() {
        bs.clearPreviousNContinuousSetBits(-2, 2);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeSetPreviousClearBit() {
        bs.setPreviousClearBit(-2);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeSetPreviousNContinuousClearBit() {
        bs.setPreviousNContinuousClearBits(-2, 2);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeSetRangeFromNegative() {
        bs.set(-1, 0);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeSetRangeFromOverTo() {
        bs.set(1, 0);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeSetRangeToOverCapacity() {
        bs.set(0, bs.size() + 1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeClearRangeFromNegative() {
        bs.clear(-1, 0);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeClearRangeFromOverTo() {
        bs.clear(1, 0);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeClearRangeToOverCapacity() {
        bs.clear(0, bs.size() + 1);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeFlipRangeFromNegative() {
        bs.flip(-1, 0);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeFlipRangeFromOverTo() {
        bs.flip(1, 0);
    }

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIoobeFlipRangeToOverCapacity() {
        bs.flip(0, bs.size() + 1);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testIaeClearNextNContinuousSetBits() {
        bs.clearNextNContinuousSetBits(0, 0);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testIaeSetNextNContinuousClearBits() {
        bs.setNextNContinuousClearBits(0, 0);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testIaeClearPreviousNContinuousSetBits() {
        bs.clearPreviousNContinuousSetBits(bs.size(), 0);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testIaeSetPreviousNContinuousClearBits() {
        bs.setPreviousNContinuousClearBits(bs.size(), 0);
    }
}
