package com.timepath.io;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.logging.Logger;

public class BitBuffer {

    private static final Logger LOG = Logger.getLogger(BitBuffer.class.getName());

    public static void main(String[] args) {
        BitBuffer scramble = new BitBuffer(ByteBuffer.wrap(
            new byte[] {(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE}));
        int shift = 1;
        scramble.position(0, shift);
        int first = scramble.get();
        scramble.position(0, shift);
        int second = scramble.get();
        assert first == second;
        System.out.println(first + " vs " + second);

        int number = 1;
        String expected = Long.toBinaryString(number);
        int bitLength = expected.length();

        for(int i = 0; i < 32 - bitLength; i++) {
            int n = number << i;
            ByteBuffer b = ByteBuffer.allocate(4);
            b.order(ByteOrder.LITTLE_ENDIAN);
            b.putInt(n);
            b.flip();
            BitBuffer bb = new BitBuffer(b);
            bb.getBits(i);
            long bits = bb.getBits(bitLength);
            System.out.println(Long.toBinaryString(bits) + " == " + expected + " ?");
        }

        number = (int) (Math.random() * Integer.MAX_VALUE);
        ByteBuffer b = ByteBuffer.allocate(4);
        b.order(ByteOrder.LITTLE_ENDIAN);
        b.putInt(number);
        b.flip();
        BitBuffer bb = new BitBuffer(b);
        String s1 = Integer.toBinaryString(number);
        String s2 = "";
        for(int i = 0; i < s1.length(); i++) {
            s2 = bb.getBits(1) + s2;
        }
        System.out.println(s1);
        System.out.println(s2);
    }

    /**
     * Internal field holding the current byte in the source buffer
     */
    private short b;

    /**
     * Total number of bits
     */
    private final int capacityBits;

    /**
     * Position in bits
     */
    private int position = 0;

    /**
     * Stores bit access offset
     */
    private int positionBit = 0;

    /**
     * Internal field holding the remaining bits in the current byte
     */
    private int remainingBits = 0;

    private final ByteBuffer source;

    public BitBuffer(ByteBuffer bytes) {
        this.source = bytes;
        this.capacityBits = source.capacity() * 8;
    }

    public int capacity() {
        return capacityBits / 8;
    }

    public byte get() {
        return getByte();
    }

    public void get(byte[] dst) {
        get(dst, 0, dst.length);
    }

    public void get(byte[] dst, int offset, int length) {
        for(int i = offset; i < offset + length; i++) {
            dst[i] = get();
        }
    }

    public long getBits(int n) {
        long data = 0;
        for(int i = 0; i < n; i++) {
            if(remainingBits == 0) {
                nextByte();
            }
            remainingBits--;
            int m = (1 << (positionBit++ % 8));
            if((b & m) != 0) {
                data |= 1 << i;
            }
        }
        position += n;
        return data;
    }

    public boolean getBoolean() {
        return getBits(1) != 0;
    }

    public byte getByte() {
        return (byte) getBits(8);
    }

    public double getDouble() {
        return Double.longBitsToDouble(getLong());
    }

    public float getFloat() {
        return Float.intBitsToFloat(getInt());
    }

    public int getInt() {
        return (int) getBits(32);
    }

    public long getLong() {
        return getBits(64);
    }

    public short getShort() {
        return (short) getBits(16);
    }

    @SuppressWarnings("empty-statement")
    public String getString() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        for(byte c; (c = getByte()) != 0; baos.write(c));
        return Charset.forName("UTF-8").decode(ByteBuffer.wrap(baos.toByteArray())).toString();
    }

    /**
     *
     * @return True if more than 1 byte is available
     */
    public boolean hasRemaining() {
        return remaining() > 0;
    }

    /**
     *
     * @return True if more than 1 bit is available
     */
    public boolean hasRemainingBits() {
        return remainingBits() > 0;
    }

    /**
     *
     * @return The limit in bytes
     */
    public int limit() {
        return capacityBits / 8;
    }

    public void order(ByteOrder bo) {

    }

    /**
     * Set the position
     * <p/>
     * @param newPosition
     */
    public void position(int newPosition) {
        position(newPosition, 0);
    }

    /**
     * Set the position
     * <p/>
     * @param newPosition Byte offset
     * @param bits        Bit offset
     */
    public void position(int newPosition, int bits) {
        source.position(newPosition);
        position = newPosition * 8;
        positionBit = bits;
        remainingBits = 0;
    }

    /**
     *
     * @return Position in bytes
     */
    public int position() {
        return positionBits() / 8;
    }

    /**
     *
     * @return Position in bits
     */
    public int positionBits() {
        return position;
    }

    /**
     *
     * @return Remaining bytes
     */
    public int remaining() {
        return remainingBits() / 8;
    }

    /**
     *
     * @return Remaining bits
     */
    public int remainingBits() {
        return capacityBits - position;
    }

    /**
     * Loads source data into internal byte
     */
    private void nextByte() {
        b = (short) (source.get() & 0xFF);
        remainingBits = 8;
    }

}

@@ -406,16 +406,20 @@
         
+    
 new byte
@@ -420,17 +420,16 @@
 w byte%5B%5D
- 
 %7B(byte) 
@@ -858,32 +858,33 @@
 ();%0A%0A        for
+ 
 (int i = 0; i %3C 
@@ -1612,32 +1612,33 @@
  %22%22;%0A        for
+ 
 (int i = 0; i %3C 
@@ -2662,32 +2662,33 @@
 h) %7B%0A        for
+ 
 (int i = offset;
@@ -2828,16 +2828,17 @@
      for
+ 
 (int i =
@@ -2869,16 +2869,17 @@
       if
+ 
 (remaini
@@ -3026,16 +3026,17 @@
       if
+ 
 ((b & m)
@@ -3735,24 +3735,33 @@
 g getString(
+int limit
 ) %7B%0A        
@@ -3829,16 +3829,17 @@
      for
+ 
 (byte c;
@@ -3873,24 +3873,112 @@
 .write(c));%0A
+        if (limit %3E 0) %7B%0A            this.get(new byte%5Blimit - baos.size()%5D);%0A        %7D%0A
         retu
@@ -4060,32 +4060,100 @@
 tring();%0A    %7D%0A%0A
+    public String getString() %7B%0A        return getString(0);%0A    %7D%0A%0A
     /**%0A     *%0A 
@@ -4854,24 +4854,24 @@
 Byte offset%0A
+
      * @para
@@ -4877,23 +4877,16 @@
 am bits 
-       
 Bit offs

Add a getString() method which always reads the given amount of bytes