package zemberek.lm.compression;

// TODO: experimental work.
public class DataInfo {

  public static final double LOG_2 = Math.log(2);
  // total amount ot finger print bits. fingerprint data starts from index 0.
  public final int fpBits;
  // This represents the last byte right shift count to retrieve fp data from byte array.
  // For example if fpBits is 13 int (int representation: MSB|---abcdefghijklmn|LSB )
  // Then  it will be put to the byte array as : B0=|abcdefgh| B1=|ijklmn---| So the fpLastByteRightShiftCount = 3
  public final int fpLastByteRightShiftCount;
  //The index of last byte that contains fingerprint data.
  public final int fpEndByte;
  public final int probBits;
  //The index of first byte that contains probability data.
  public final int probStartByte;

  // This represents the amount of left shift required for getting the probability data from the last byte.
  // public final int probLastByteRightShiftCount;
  // this defines amount of higher bits to truncate from the first byte of probability.
  // Suppose first byte of the probability data is : |---abcde| then mask needs to truncate most significant 3 bits.
  public final int probFirstByteMask;
  //The index of last byte that contains probability data.
  public final int probEndByte;
  public final int backoffBits;
  public final int byteCount;

  DataInfo(int minfingerPrintBits, int probBits, int backoffBits) {

    this.probBits = probBits;
    this.backoffBits = backoffBits;

    int total = minfingerPrintBits + probBits + backoffBits;
    if (total % 8 != 0) {
      total = ((total / 8) + 1) * 8;
      minfingerPrintBits = total - (probBits + backoffBits);
    }
    this.fpBits = minfingerPrintBits;
    byteCount = (fpBits + probBits + backoffBits) / 8;
    this.fpLastByteRightShiftCount = fpBits % 8;

    this.fpEndByte = fpBits / 8;

    if (fpLastByteRightShiftCount != 0) {
      probStartByte = fpEndByte;
      probFirstByteMask = 1 << (7 - fpLastByteRightShiftCount);
    } else {
      probStartByte = fpEndByte + 1;
      probFirstByteMask = 0xff;
    }

    int probLastByteEndBitIndex = (fpBits + probBits) % 8;

    probEndByte = (fpBits + probBits) / 8;


  }

  static DataInfo fromCounts(int minfingerPrintBits, int probCount, int backoffCount) {
    int probBits = probCount == 0 ? 0 : minBitCount(probCount);
    int backoffBits = backoffCount == 0 ? 0 : minBitCount(backoffCount);
    return new DataInfo(minfingerPrintBits, probBits, backoffBits);
  }

  static DataInfo fromCountsAndExpectedBits(
      int minfingerPrintBits,
      int probCount,
      int probBitsDesired,
      int backoffCount,
      int backoffBitsDesired) {
    DataInfo initial = fromCounts(minfingerPrintBits, probCount, backoffCount);
    int bb = backoffBitsDesired;
    if (initial.backoffBits < bb) {
      bb = initial.backoffBits;
    }
    int pb = probBitsDesired;
    if (initial.probBits < pb) {
      pb = initial.probBits;
    }
    return new DataInfo(minfingerPrintBits, pb, bb);
  }

  /**
   * Calculates 2 base logarithm
   *
   * @param input value to calculate log
   * @return 2 base logarithm of the input
   */
  public static double log2(double input) {
    return Math.log(input) / LOG_2;
  }

  public static boolean powerOfTwo(int k) {
    if (k < 0) {
      throw new IllegalArgumentException("Cannot calculate negative numbers:" + k);
    }
    return (k & (k - 1)) == 0;
  }

  public static int minBitCount(int a) {
    int probBits = (int) log2(a);
    if (!powerOfTwo(a)) {
      probBits++;
    }
    return probBits;
  }

  byte[] encode(int fp, int probIndex, int backoffIndex) {
    long k = backoffIndex;
    k = k << probBits;
    k |= probIndex;
    k = k << fpBits;
    k |= fp;
    byte[] result = new byte[byteCount];
    for (int i = 0; i < byteCount; i++) {
      result[i] = (byte) (k & 0xff);
      k = k >>> 8;
    }
    return result;
  }

  byte[] encode2(int fp, int prob, int backoff) {
    byte[] arr = new byte[byteCount];
    return arr;
  }

  @Override
  public String toString() {
    return "DataInfo{" +
        "fpBits=" + fpBits +
        ", probBits=" + probBits +
        ", backoffBits=" + backoffBits +
        ", byteCount=" + byteCount +
        '}';
  }
}
