package edu.berkeley.nlp.lm.bits;

public class CompressionUtils
{

	public static BitList variableCompress(final long n, final int radix) {
		final int numBits = getNumBits(n);
		final int bitsPerDigit = radix - 1;
		final int numDigits = getNumDigits(numBits, bitsPerDigit);
		final BitList bits = writeUnary(numDigits);
		final int numBitsToOutput = numDigits * bitsPerDigit;
		writeNormalBinary(n, bits, numBitsToOutput);
		assert bits.size() < Long.SIZE;

		return bits;
	}

	public static long variableDecompress(final BitStream input, final int radix) {
		final int numBitsInDigit = radix - 1;
		final int numDigits = readUnary(input);
		final long nextBits = input.next(numBitsInDigit * numDigits);
		return nextBits;
	}

	/**
	 * @param input
	 * @return
	 */
	private static int readUnary(final BitStream input) {
		return input.nextConsecutiveZeros();
	}

	/**
	 * @param n
	 * @param bits
	 * @param numBitsToOutput
	 */
	private static void writeNormalBinary(final long n, final BitList bits, final int numBitsToOutput) {
		long mask = 1L << (numBitsToOutput - 1);
		for (int i = 0; i < numBitsToOutput; ++i) {
			bits.add((n & mask) != 0);
			mask >>>= 1;
		}
	}

	/**
	 * @param numDigits
	 * @return
	 */
	private static BitList writeUnary(final int numDigits) {
		final BitList bits = new BitList();
		for (int i = 0; i < numDigits; ++i) {
			bits.add(i == numDigits - 1);
		}
		return bits;
	}

	/**
	 * @param delta
	 * @return
	 */
	private static int getNumBits(final long delta) {
		return Long.SIZE - Long.numberOfLeadingZeros(delta);
	}

	/**
	 * @param numBits
	 * @param bitsPerDigit
	 * @return
	 */
	private static int getNumDigits(final int numBits, final int bitsPerDigit) {
		int numDigits = (numBits / bitsPerDigit) + ((numBits % bitsPerDigit) == 0 ? 0 : 1);
		numDigits = Math.max(1, numDigits);
		return numDigits;
	}

}
