// The BitOutputStream and BitInputStream classes provide the ability to
// write and read individual bits to a file in a compact form.  One major
// limitation of this approach is that the resulting file will always have
// a number of bits that is a multiple of 8.  In effect, whatever bits are
// output to the file are padded at the end with 0's to make the total
// number of bits a multiple of 8.
//
// BitInputStream has the following public methods:
//     public BitInputStream(String file)
//         opens an input stream with the given file name
//     public int nextBit()
//         reads the next bit from input (throws exception if at end of file)
//     public boolean hasNextBit()
//         returns true if there's another bit in the input stream to be read
//     public void close()
//         closes the input

import java.io.*;
import java.util.*;

@SuppressWarnings("deprecation")
public class BitInputStream {
    private FileInputStream input;
    private int currentByte;     // current set of bits (buffer)
    private int nextByte;        // next set of bits (buffer)
    private int numBits;         // how many bits from buffer have been used
    private int remainingAtEnd;  // how many bits will be remaining at the end
                                 // after we're done

    private static final int BYTE_SIZE = 8;  // bits per byte

    // pre : given file name is legal
    // post: creates a BitInputStream reading input from the file
    public BitInputStream(String file) {
        try {
            this.input = new FileInputStream(file);
            
            // Read in the number of remaining bits at the end
            this.remainingAtEnd = this.input.read();

            // Set up the nextByte field.
            this.nextByte = this.input.read();
        } catch (IOException ex) {
            throw new RuntimeException(ex.toString());
        }

        this.nextByte();
    }

    public boolean hasNextBit() {
        boolean atEnd = this.currentByte == -1;
        boolean onlyRemaining = this.nextByte == -1 
                && BYTE_SIZE - this.numBits == this.remainingAtEnd;
        return !atEnd && !onlyRemaining;
    }

    // post: reads next bit from input
    //       throws NoSuchElementException if there is no bit to return
    public int nextBit() {
        // if at eof, throw exception
        if (!this.hasNextBit()) {
            throw new NoSuchElementException();
        }
        int result = this.currentByte % 2;
        this.currentByte /= 2;
        this.numBits++;
        if (this.numBits == BYTE_SIZE) {
            this.nextByte();
        }
        return result;
    }

    // post: refreshes the internal buffer with the next BYTE_SIZE bits
    private void nextByte() {
        this.currentByte = this.nextByte;
        if (this.currentByte != -1) {
            try {
                this.nextByte = this.input.read();
            } catch (IOException e) {
                throw new RuntimeException(e.toString());
            }
        }

        this.numBits = 0;
    }

    // post: input is closed
    public void close() {
        try {
            this.input.close();
        } catch (IOException e) {
            throw new RuntimeException(e.toString());
        }
    }

    // included to ensure that the stream is closed
    protected void finalize() {
        this.close();
    }
}
