/*
 * Copyright (C) 2008 Steve Ratcliffe
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 * 
 * Author: Steve Ratcliffe
 * Create date: 29-Aug-2008
 */
package uk.me.parabola.imgfmt.app;

/**
 * Read an array as a bit stream.
 *
 * @author Steve Ratcliffe
 */
public class BitReader {
	private final byte[] buf;
	private int bitPosition;

	public BitReader(byte[] buf) {
		this.buf = buf;
	}

	public boolean get1() {
		int off = bitPosition % 8;
		byte b = buf[bitPosition / 8];

		bitPosition++;
		return ((b >> off) & 1) == 1;
	}

	public int get(int n) {
		int res = 0;

		int pos = 0;
		while (pos < n) {
			int index = bitPosition / 8;
			int off = bitPosition % 8;

			byte b = buf[index];
			b >>= off;
			int nbits = n - pos;
			if (nbits > (8-off))
				nbits = 8 - off;

			int mask = ((1 << nbits) - 1);
			res |= ((b & mask) << pos);
			pos += nbits;
			bitPosition += nbits;
		}

		return res;
	}

	/**
	 * Get a signed quantity.
	 *
	 * The sign bit is the last bit in the field.
	 *
	 * @param n The field width, including the sign bit.
	 * @return A signed number.
	 */
	public int sget(int n) {
		int res = get(n);
		int top = 1 << (n - 1);

		if ((res & top) != 0) {
			int mask = top - 1;
			res = ~mask | res;
		}
		return res;
	}

	/**
	 * Get a signed n-bit value, treating 1 << (n-1) as a flag to read another signed n-bit value
	 * for extended range.
	 */
	public int sget2(int n) {
		int top = 1 << (n - 1);
		int mask = top - 1;
		int base = 0;

		int res = get(n);
		while (res == top) {
			// Add to the base value, and read another
			base += mask;
			res = get(n);
		}

		// The final byte determines the sign of the result. Add or subtract the base as
		// appropriate.
		if ((res & top) == 0)
			res += base;
		else
			res = (res | ~mask) - base; // Make negative and subtract the base

		return res;
	}

	public int getBitPosition() {
		return bitPosition;
	}

	public int getNumberOfBits() {
		return buf.length * 8;
	}

	/**
	 * Debugging routine that returns the remainder of the stream as a string.
	 * The bits are in little endian order, so that numbers can be read from left
	 * to right, although the whole string has to read from right to left.
	 *
	 * @return A string in binary.
	 */
	public String remainder() {
		int save = bitPosition;
		StringBuilder sb = new StringBuilder();
		while (bitPosition < buf.length * 8) {
			sb.insert(0, get1() ? "1" : "0");
		}
		bitPosition = save;
		return sb.toString();
	}
}
