/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the "Elastic License
 * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
 * Public License v 1"; you may not use this file except in compliance with, at
 * your election, the "Elastic License 2.0", the "GNU Affero General Public
 * License v3.0 only", or the "Server Side Public License, v 1".
 */

package org.elasticsearch.lucene.util;

import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.util.BitSet;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.FixedBitSet;
import org.apache.lucene.util.SparseFixedBitSet;
import org.elasticsearch.test.ESTestCase;

public class CombinedBitsTests extends ESTestCase {
    public void testEmpty() {
        for (float percent : new float[] { 0f, 0.1f, 0.5f, 0.9f, 1f }) {
            testCase(randomIntBetween(1, 10000), 0f, percent);
            testCase(randomIntBetween(1, 10000), percent, 0f);
        }
    }

    public void testSparse() {
        for (float percent : new float[] { 0f, 0.1f, 0.5f, 0.9f, 1f }) {
            testCase(randomIntBetween(1, 10000), 0.1f, percent);
            testCase(randomIntBetween(1, 10000), percent, 0.1f);
        }
    }

    public void testDense() {
        for (float percent : new float[] { 0f, 0.1f, 0.5f, 0.9f, 1f }) {
            testCase(randomIntBetween(1, 10000), 0.9f, percent);
            testCase(randomIntBetween(1, 10000), percent, 0.9f);
        }
    }

    public void testRandom() {
        int iterations = atLeast(10);
        for (int i = 0; i < iterations; i++) {
            testCase(randomIntBetween(1, 10000), randomFloat(), randomFloat());
        }
    }

    private void testCase(int numBits, float percent1, float percent2) {
        BitSet first = randomSet(numBits, percent1);
        BitSet second = randomSet(numBits, percent2);
        CombinedBits actual = new CombinedBits(first, second);
        FixedBitSet expected = new FixedBitSet(numBits);
        or(expected, first);
        and(expected, second);
        assertEquals(expected, actual, numBits);
    }

    private void or(BitSet set1, BitSet set2) {
        int next = 0;
        while (next < set2.length() && (next = set2.nextSetBit(next)) != DocIdSetIterator.NO_MORE_DOCS) {
            set1.set(next);
            next += 1;
        }
    }

    private void and(BitSet set1, BitSet set2) {
        int next = 0;
        while (next < set1.length() && (next = set1.nextSetBit(next)) != DocIdSetIterator.NO_MORE_DOCS) {
            if (set2.get(next) == false) {
                set1.clear(next);
            }
            next += 1;
        }
    }

    private void assertEquals(Bits set1, Bits set2, int maxDoc) {
        for (int i = 0; i < maxDoc; ++i) {
            assertEquals("Different at " + i, set1.get(i), set2.get(i));
        }

        FixedBitSet bitSet1 = new FixedBitSet(100);
        FixedBitSet bitSet2 = new FixedBitSet(100);
        for (int from = 0; from < maxDoc; from += bitSet1.length()) {
            bitSet1.set(0, bitSet1.length());
            bitSet2.set(0, bitSet1.length());
            if (from + bitSet1.length() > maxDoc) {
                bitSet1.clear(maxDoc - from, bitSet1.length());
                bitSet2.clear(maxDoc - from, bitSet1.length());
            }
            set1.applyMask(bitSet1, from);
            set2.applyMask(bitSet2, from);
            assertEquals(bitSet1, bitSet2);
        }
    }

    private BitSet randomSet(int numBits, float percentSet) {
        return randomSet(numBits, (int) (percentSet * numBits));
    }

    private BitSet randomSet(int numBits, int numBitsSet) {
        assert numBitsSet <= numBits;
        final BitSet set = randomBoolean() ? new SparseFixedBitSet(numBits) : new FixedBitSet(numBits);
        for (int i = 0; i < numBitsSet; ++i) {
            while (true) {
                final int o = random().nextInt(numBits);
                if (set.get(o) == false) {
                    set.set(o);
                    break;
                }
            }
        }
        return set;
    }
}
