package org.roaringbitmap.buffer;


import org.roaringbitmap.IntIterator;

import java.nio.CharBuffer;
import java.nio.LongBuffer;
import java.util.Arrays;
import java.util.BitSet;

import static java.lang.Long.numberOfTrailingZeros;


/***
 *
 * This class provides convenience functions to manipulate BitSet and MutableRoaringBitmap objects.
 *
 */
public class BufferBitSetUtil {
  // todo: add a method to convert an ImmutableRoaringBitmap to a BitSet using BitSet.valueOf

  // a block consists has a maximum of 1024 words, each representing 64 bits,
  // thus representing at maximum 65536 bits
  static final private int BLOCK_LENGTH = MappeableBitmapContainer.MAX_CAPACITY / Long.SIZE; //
  // 64-bit
  // word

  private static MappeableArrayContainer arrayContainerOf(final int from, final int to,
      final int cardinality, final long[] words) {
    // precondition: cardinality is max 4096
    final char[] content = new char[cardinality];
    int index = 0;

    for (int i = from, socket = 0; i < to; ++i, socket += Long.SIZE) {
      long word = words[i];
      while (word != 0) {
        content[index++] = (char) (socket + numberOfTrailingZeros(word));
        word &= (word - 1);
      }
    }
    return new MappeableArrayContainer(CharBuffer.wrap(content), cardinality);
  }


  /**
   * Generate a MutableRoaringBitmap out of a BitSet
   *
   * @param bitSet original bitset (will not be modified)
   * @return roaring bitmap equivalent to BitSet
   */
  public static MutableRoaringBitmap bitmapOf(final BitSet bitSet) {
    return bitmapOf(bitSet.toLongArray());
  }

  /**
   * Generate a MutableRoaringBitmap out of a long[], each long using little-endian representation
   * of its bits
   *
   * @see BitSet#toLongArray() for an equivalent
   * @param words array of longs (will not be modified)
   * @return roaring bitmap
   */
  public static MutableRoaringBitmap bitmapOf(final long[] words) {
    // split long[] into blocks.
    // each block becomes a single container, if any bit is set
    final MutableRoaringBitmap ans = new MutableRoaringBitmap();
    int containerIndex = 0;
    for (int from = 0; from < words.length; from += BLOCK_LENGTH) {
      final int to = Math.min(from + BLOCK_LENGTH, words.length);
      final int blockCardinality = cardinality(from, to, words);
      if (blockCardinality > 0) {
        ((MutableRoaringArray) ans.highLowContainer).insertNewKeyValueAt(containerIndex++,
            BufferUtil.highbits(from * Long.SIZE),
            BufferBitSetUtil.containerOf(from, to, blockCardinality, words));
      }
    }
    return ans;
  }

  private static int cardinality(final int from, final int to, final long[] words) {
    int sum = 0;
    for (int i = from; i < to; i++) {
      sum += Long.bitCount(words[i]);
    }
    return sum;
  }


  private static MappeableContainer containerOf(final int from, final int to,
      final int blockCardinality, final long[] words) {
    // find the best container available
    if (blockCardinality <= MappeableArrayContainer.DEFAULT_MAX_SIZE) {
      // containers with DEFAULT_MAX_SIZE or less integers should be
      // ArrayContainers
      return arrayContainerOf(from, to, blockCardinality, words);
    } else {
      // otherwise use bitmap container
      return new MappeableBitmapContainer(
          LongBuffer.wrap(Arrays.copyOfRange(words, from, from + BLOCK_LENGTH)), blockCardinality);
    }
  }


  /**
   * Compares a RoaringBitmap and a BitSet. They are equal if and only if they contain the same set
   * of integers.
   *
   * @param bitset first object to be compared
   * @param bitmap second object to be compared
   * @return whether they are equal
   */
  public static boolean equals(final BitSet bitset, final ImmutableRoaringBitmap bitmap) {
    if (bitset.cardinality() != bitmap.getCardinality()) {
      return false;
    }
    final IntIterator it = bitmap.getIntIterator();
    while (it.hasNext()) {
      int val = it.next();
      if (!bitset.get(val)) {
        return false;
      }
    }
    return true;
  }
}