content: package btools.codec;
  
  import java.util.Map;
  import java.util.TreeMap;
  
  import btools.util.BitCoderContext;
  
  public final class StatCoderContext extends BitCoderContext {
    private static Map<String, long[]> statsPerName;
    private long lastbitpos = 0;
  
  
    private static final int[] noisy_bits = new int[1024];
  
    static {
      // noisybits lookup
      for (int i = 0; i < 1024; i++) {
        int p = i;
        int noisybits = 0;
        while (p > 2) {
          noisybits++;
          p >>= 1;
        }
        noisy_bits[i] = noisybits;
      }
    }
  
  
    public StatCoderContext(byte[] ab) {
      super(ab);
    }
  
    /**
     * assign the de-/encoded bits since the last call assignBits to the given
     * name. Used for encoding statistics
     *
     * @see #getBitReport
     */
    public void assignBits(String name) {
      long bitpos = getWritingBitPosition();
      if (statsPerName == null) {
        statsPerName = new TreeMap<>();
      }
      long[] stats = statsPerName.get(name);
      if (stats == null) {
        stats = new long[2];
        statsPerName.put(name, stats);
      }
      stats[0] += bitpos - lastbitpos;
      stats[1] += 1;
      lastbitpos = bitpos;
    }
  
    /**
     * Get a textual report on the bit-statistics
     *
     * @see #assignBits
     */
    public static String getBitReport() {
      if (statsPerName == null) {
        return "<empty bit report>";
      }
      StringBuilder sb = new StringBuilder();
      for (String name : statsPerName.keySet()) {
        long[] stats = statsPerName.get(name);
        sb.append(name + " count=" + stats[1] + " bits=" + stats[0] + "\n");
      }
      statsPerName = null;
      return sb.toString();
    }
  
    /**
     * encode an unsigned integer with some of of least significant bits
     * considered noisy
     *
     * @see #decodeNoisyNumber
     */
    public void encodeNoisyNumber(int value, int noisybits) {
      if (value < 0) {
        throw new IllegalArgumentException("encodeVarBits expects positive value");
      }
      if (noisybits > 0) {
        int mask = 0xffffffff >>> (32 - noisybits);
        encodeBounded(mask, value & mask);
        value >>= noisybits;
      }
      encodeVarBits(value);
    }
  
    /**
     * decode an unsigned integer with some of of least significant bits
     * considered noisy
     *
     * @see #encodeNoisyNumber
     */
    public int decodeNoisyNumber(int noisybits) {
      int value = decodeBits(noisybits);
      return value | (decodeVarBits() << noisybits);
    }
  
    /**
     * encode a signed integer with some of of least significant bits considered
     * noisy
     *
     * @see #decodeNoisyDiff
     */
    public void encodeNoisyDiff(int value, int noisybits) {
      if (noisybits > 0) {
        value += 1 << (noisybits - 1);
        int mask = 0xffffffff >>> (32 - noisybits);
        encodeBounded(mask, value & mask);
        value >>= noisybits;
      }
      encodeVarBits(value < 0 ? -value : value);
      if (value != 0) {
        encodeBit(value < 0);
      }
    }
  
    /**
     * decode a signed integer with some of of least significant bits considered
     * noisy
     *
     * @see #encodeNoisyDiff
     */
    public int decodeNoisyDiff(int noisybits) {
      int value = 0;
      if (noisybits > 0) {
        value = decodeBits(noisybits) - (1 << (noisybits - 1));
      }
      int val2 = decodeVarBits() << noisybits;
      if (val2 != 0) {
        if (decodeBit()) {
          val2 = -val2;
        }
      }
      return value + val2;
    }
  
    /**
     * encode a signed integer with the typical range and median taken from the
     * predicted value
     *
     * @see #decodePredictedValue
     */
    public void encodePredictedValue(int value, int predictor) {
      int p = predictor < 0 ? -predictor : predictor;
      int noisybits = 0;
  
      while (p > 2) {
        noisybits++;
        p >>= 1;
      }
      encodeNoisyDiff(value - predictor, noisybits);
    }
  
    /**
     * decode a signed integer with the typical range and median taken from the
     * predicted value
     *
     * @see #encodePredictedValue
     */
    public int decodePredictedValue(int predictor) {
      int p = predictor < 0 ? -predictor : predictor;
      int noisybits = 0;
      while (p > 1023) {
        noisybits++;
        p >>= 1;
      }
      return predictor + decodeNoisyDiff(noisybits + noisy_bits[p]);
    }
  
    /**
     * encode an integer-array making use of the fact that it is sorted. This is
     * done, starting with the most significant bit, by recursively encoding the
     * number of values with the current bit being 0. This yields an number of
     * bits per value that only depends on the typical distance between subsequent
     * values and also benefits
     *
     * @param values  the array to encode
     * @param offset  position in this array where to start
     * @param subsize number of values to encode
     * @param nextbit bitmask with the most significant bit set to 1
     * @param mask    should be 0
     */
    public void encodeSortedArray(int[] values, int offset, int subsize, int nextbit, int mask) {
      if (subsize == 1) { // last-choice shortcut
        while (nextbit != 0) {
          encodeBit((values[offset] & nextbit) != 0);
          nextbit >>= 1;
        }
      }
      if (nextbit == 0) {
        return;
      }
  
      int data = mask & values[offset];
      mask |= nextbit;
  
      // count 0-bit-fraction
      int i = offset;
      int end = subsize + offset;
      for (; i < end; i++) {
        if ((values[i] & mask) != data) {
          break;
        }
      }
      int size1 = i - offset;
      int size2 = subsize - size1;
  
      encodeBounded(subsize, size1);
      if (size1 > 0) {
        encodeSortedArray(values, offset, size1, nextbit >> 1, mask);
      }
      if (size2 > 0) {
        encodeSortedArray(values, i, size2, nextbit >> 1, mask);
      }
    }
  
    /**
     * @param values  the array to encode
     * @param offset  position in this array where to start
     * @param subsize number of values to encode
     * @param nextbit bitmask with the most significant bit set to 1
     * @param value   should be 0
     * @see #encodeSortedArray
     */
    public void decodeSortedArray(int[] values, int offset, int subsize, int nextbitpos, int value) {
      if (subsize == 1) { // last-choice shortcut
        if (nextbitpos >= 0) {
          value |= decodeBitsReverse(nextbitpos + 1);
        }
        values[offset] = value;
        return;
      }
      if (nextbitpos < 0) {
        while (subsize-- > 0) {
          values[offset++] = value;
        }
        return;
      }
  
      int size1 = decodeBounded(subsize);
      int size2 = subsize - size1;
  
      if (size1 > 0) {
        decodeSortedArray(values, offset, size1, nextbitpos - 1, value);
      }
      if (size2 > 0) {
        decodeSortedArray(values, offset + size1, size2, nextbitpos - 1, value | (1 << nextbitpos));
      }
    }
  
  }
  