package com.github.ponkin.bloom;

import java.nio.charset.StandardCharsets;

import static java.lang.Math.log;

/**
 * Utility functions and constants
 *
 * @author Alexey Ponkin
 */
class Utils {

  /**
   * masks for tags - masks[i]
   * where i is number of bits in tag
   */
  public static final long[] MASKS = {
    0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, // 0 bits 
    0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000001L, // 1 bit
    0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000011L, // 2 bit ...
    0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00001111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00011111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_00000011_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_00000111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_00001111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_00011111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_00111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_01111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000000_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000001_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000011_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00000111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00001111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00011111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_00111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_01111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000000_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000001_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000011_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00000111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00001111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00011111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_00111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_01111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000000_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000001_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000011_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00000111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00001111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00011111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_00111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_01111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000000_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000001_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000011_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00000111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00001111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00011111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_00111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_01111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000000_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000001_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000011_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00000111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00001111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00011111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_00111111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_01111111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000000_11111111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000001_11111111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000011_11111111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00000111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00001111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00011111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b00111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b01111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L,
    0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L // 64 bit
  };

  /**
   * @return byte[] in UTF-8 string representation, or null is str == null
   */ 
  public static byte[] getBytesFromUTF8String(String str) {
    byte[] bytes = null;
    if(str != null) {
      bytes = str.getBytes(StandardCharsets.UTF_8);
    }
    return bytes;
  }

  /**
   * Computes the optimal k (number of hashes per item inserted in Bloom filter), given the
   * expected insertions and total number of bits in the Bloom filter.
   *
   * See http://en.wikipedia.org/wiki/File:Bloom_filter_fp_probability.svg for the formula.
   *
   * @param n expected insertions (must be positive)
   * @param m total number of bits in Bloom filter (must be positive)
   */
  public static int optimalNumOfHashFunctions(long n, long m) {
    // (m / n) * log(2), but avoid truncation due to division!
    return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
  }

  /**
   * Computes m (total bits of Bloom filter) which is expected to achieve, for the specified
   * expected insertions, the required false positive probability.
   *
   * See http://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives for the formula.
   *
   * @param n expected insertions (must be positive)
   * @param p false positive rate (must be 0 < p < 1)
   */
  public static long optimalNumOfBits(long n, double p) {
    return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
  }

  public static final double DEFAULT_FPP = 0.03;

  /**
   * Check condition.
   * if <code>conf</code> is false
   * than IllegalArgument exception is thrown
   * with provided errorMessage
   *
   * @param cond condition to check
   * @param errorMessage message to pass in IllegalArgumentException
   */
  public static void checkArgument(boolean cond, String errorMessage) {
    if (!cond) {
      throw new IllegalArgumentException(errorMessage);
    }
  }

  private static final double LN_2 = log(2);

  /**
   * Calculate log2 - copy from
   * com.google.common.math.DoubleMath.log2 (guava)
   */
  public static double log2(double x) {
    return log(x) / LN_2;
  }

  /**
   * Returns modulus
   * the same as com.google.common.math.LongMath.mod (guava)
   */
  public static long mod(long x, long m) {
    long result = x % m;
    return (result >= 0) ? result : result + m;
  }
}
