import java.io.IOException;
import java.io.OutputStream;

/**
 * Writes single bits to a binary output stream.  Does not buffer more than a
 * single byte at a time (though the underlying output stream might itself be
 * buffered).  Do not intersperse writes to the underlying stream with writes
 * through this BitWriter.
 * <p>
 * IMPORTANT: Since only whole bytes can be written to the underlying output
 * stream, you need to indicate that you are done writing bits.  Call flush()
 * after you write your last bit to be sure any last few bits get sent to the
 * output stream.
 *
 * @author Zach Tomaszewski
 * @since 15 Nov 2012
 */
public class BitWriter {

  byte buffer = 0;
  int bits = 0;  //bits in the buffer
  OutputStream out;

  /*
   * Implementation note:
   * Although the buffer is conceptually filled in left-to-right:
   *
   * Adding 1, 0, 1, 1:
   * 1???????
   * 10??????
   * 101?????
   * 1011????
   *
   * we'll instead append new bits on the right, thereby pushing any
   * existing bits to the left:
   *
   * ???????1
   * ??????10
   * ?????101
   * ????1011
   *
   * Either way, the byte looks the same after 8 additions.
   */


  /**
   * Constructs a BitWriter that will write to the given output stream.
   */
  public BitWriter(OutputStream out) {
    this.out = out;
  }

  /**
   * Writes the given boolean as a bit to the underlying stream.
   * <code>true</code> == 1 and <code>false</code> == 0.
   *
   * @throws IOException  If cannot write to underlying stream.
   */
  public void write(boolean bit) throws IOException {
    this.write((bit) ? 1 : 0);
  }

  /**
   * Writes the given int value as a bit to the underlying stream.
   * 0 is a 0 bit and any other int value is treated as a 1 bit.
   *
   * @throws IOException  If cannot write to underlying stream.
   */
  public void write(int bit) throws IOException {
    bit &= 0x1;    //mask all but last of 32 bits so bit is only 1 or 0
    buffer <<= 1;  //shift buffer over 1 bit
    buffer |= bit; //merge bit into buffer
    bits++;
    if (bits == 8) {
      this.flush();  //time to write out; resets buffer and bits
    }
  }

  /**
   * Writes the given byte one bit a time.
   * This is convenience method that allows a user to also write more
   * traditional-sized data through this BitWriter.
   *
   * @throws IOException  If cannot write to stream.
   */
  public void writeByte(byte b) throws IOException {
    /* would have used a java.util.BitSet here, but valueOf not in Java 1.6 */
    //manually cycle through bits in b...
    for (int i = 7; i >= 0; i--) {  //loop through bit positions: 76543210
      int mask = 0x01 << i;  // mask out all except bit at position i
      int bit = b & mask;
      bit >>>= i;            // shift remaining bit to position 0 (right-most)
      this.write(bit);
    }
  }

  /**
   * Writes the bits in the byte-long buffer to the underlying output stream.
   * If the buffer is not full yet, pads the remainder of the byte with 0 bits.
   *
   * @throws IOException  If cannot write to stream.
   */
  public void flush() throws IOException {
    // conceptually, may need to "pad" remainder of byte first.
    // (Actually, just shift left, since 0s will come in on right)
    int padding = 8 - bits;
    buffer <<= padding;
    out.write(buffer);
    buffer = 0;
    bits = 0;
  }
}