import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * A class that wraps any InputStream to support bitwise reads.
 * @author Michael Goldwasser
 *
 */
public class BitReader {
	private int buf;
	private int numBitsBuffered;
	private InputStream stream;
	
	/**
	 * Creates a new BitInStream instance that wraps the given InputStream.
	 * @param s an underlying InputStream
	 */
	public BitReader(InputStream s) {
		stream = s;
		numBitsBuffered = 0;
	}
	
	/**
	 * Closes the underlying InputStream.
	 * @throws IOException if underlying InputStream throws such an exception
	 */
	public void close() throws IOException {
		stream.close();
	}
	
	/**
	 * Reads the next bit, returning true if 1, false if 0.
	 * @return true if next bit is 1; false otherwise
	 * @throws IOException if underlying InputStream throws such an exception
	 */
	public boolean readBit() throws IOException {
		if (numBitsBuffered == 0) {
			buf = stream.read();
			if (buf == -1)
				throw new IOException();
			numBitsBuffered = 8;
		}
		boolean result = (buf & (1 << (numBitsBuffered-1))) > 0;
		numBitsBuffered--;
		return result;
	}

	/**
	 * Reads the next n bits, and returns them as the
	 * least significant n bits of an int, with earliest
	 * read bit at high-end.
	 * 
	 * @param n Number of bits (must be 0 <= n <= 32)
	 * @return integer with least-significant n bits read
	 * @throws IOException if underlying InputStrea throws such an exception
	 */
	public int readBits(int n) throws IOException {
		if (n < 0 || n > 32)
			throw new IllegalArgumentException("must have 0 <= n <= 32");
		int result = 0;
		for (int k = n-1; k >= 0; k--)
			if (readBit())		
				result |= 1 << k;
		return result;
	}
	
	/**
	 * Unit test on file test.txt presumed to have contents "Hello."
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		java.io.FileInputStream fis = new java.io.FileInputStream("test.txt");
		BitReader bis = new BitReader(fis);
		boolean[] code = {false, true, false, false, true, false, false, false};
		for (int i=0; i < code.length; i++)
			if (bis.readBit() != code[i])
				System.out.println("Error on bit " + i);
		if (bis.readBits(8) != 101)
			System.out.println("Error reading 'e'");
		if (bis.readBit())
			System.out.println("Error on first 'l'");
		if (bis.readBits(8) != 216)
			System.out.println("Error on 'll' combo");
		if (bis.readBits(7) != 108)
			System.out.println("Error on second 'l'");
		if (bis.readBits(4) != 6)
			System.out.println("Error on first half of 'o'");
		if (bis.readBits(4) != 15)
			System.out.println("Error on second half of 'o'");
		if (bis.readBits(8) != 46)
			System.out.println("Error on '.'");
	}
	
}
