// ByteArray.java

public class ByteArray extends java.lang.Object {
	private byte[] data;
	private int size;
	private int start;
	private int length;
	private int multiplier = 2;

	public ByteArray() {
		size = 1;
		data = new byte[size];
	}

	public ByteArray(int new_size) throws ArrayIndexOutOfBoundsException {
		if (new_size < 1)
			throw new ArrayIndexOutOfBoundsException();

		size = new_size;
		data = new byte[size];
	}

	public void setSize(int new_size) throws ArrayIndexOutOfBoundsException {
		int new_start = start;
		int new_length = length;

		if (new_size < 1)
			throw new ArrayIndexOutOfBoundsException();
		if (new_size < start)
			new_start = 0;
		if (new_size < (new_start + length))
			new_length = new_size;

		byte[] new_data = new byte[new_size];
		System.arraycopy(data, start, new_data, new_start, new_length);
		data = new_data;
		size = new_size;
		length = new_length;
		start = new_start;
	}

	public void setSizeRightJustify(int new_size) throws ArrayIndexOutOfBoundsException {
		int new_start = start;
		int new_length = length;

		if (new_size < 1)
			throw new ArrayIndexOutOfBoundsException();
		if (new_size < start)
			new_start = 0;
		if (new_size < (new_start + length))
			new_length = new_size;

		new_start = new_size - new_length;
		byte[] new_data = new byte[new_size];
		System.arraycopy(data, start, new_data, new_start, new_length);
		data = new_data;
		size = new_size;
		length = new_length;
		start = new_start;
	}

	public void setLength(int new_length) throws ArrayIndexOutOfBoundsException {
		int new_start = start;

		if (new_length < 0)
			throw new ArrayIndexOutOfBoundsException();
		if (size < new_length) {
			setSize(new_length * multiplier);
			return;
		}
		if (size < (start + new_length))
			new_start = size - new_length;

		System.arraycopy(data, start, data, new_start, new_length);
		start = new_start;
		length = new_length;
	}

	public void setStart(int new_start) throws ArrayIndexOutOfBoundsException {
		if (new_start < 0)
			throw new ArrayIndexOutOfBoundsException();
		if (size < (new_start + length)) {
			setSize((new_start + length) * multiplier);
			return;
		}

		System.arraycopy(data, start, data, new_start, length);
		start = new_start;
	}

	public void setMultiplier(int new_multiplier) throws ArrayIndexOutOfBoundsException {
		if (new_multiplier < 1)
			throw new ArrayIndexOutOfBoundsException();
		multiplier = new_multiplier;
	}

	public int size() {
		return size;
	}

	public int length() {
		return length;
	}

	public int start() {
		return start;
	}

	public byte[] data() {
		return data;
	}

	public int multiplier() {
		return multiplier;
	}

	public int getByteAt(int index) throws ArrayIndexOutOfBoundsException {
		if (index < 0)
			throw new ArrayIndexOutOfBoundsException();
		if (length < (start + index))
			throw new ArrayIndexOutOfBoundsException();
		return data[start + index];
	}

	public void setByteAt(int index, byte new_byte) throws ArrayIndexOutOfBoundsException {
		if (index < 0)
			throw new ArrayIndexOutOfBoundsException();
		if (length < (start + index))
			throw new ArrayIndexOutOfBoundsException();
		data[start + index] = new_byte;
	}

	public void setByteAt(int index, int new_byte) throws ArrayIndexOutOfBoundsException {
		if (index < 0)
			throw new ArrayIndexOutOfBoundsException();
		if (length < (start + index))
			throw new ArrayIndexOutOfBoundsException();
		data[start + index] = (byte)new_byte;
	}

	public byte[] getBytesAt(int index, int new_length)
		throws ArrayIndexOutOfBoundsException {
		if (index < 0)
			throw new ArrayIndexOutOfBoundsException();
		if (length < (start + index))
			throw new ArrayIndexOutOfBoundsException();
		if (length < (index + new_length))
			throw new ArrayIndexOutOfBoundsException();
		byte[] new_data = new byte[new_length];
		System.arraycopy(data, start + index, new_data, 0, new_data.length);
		return new_data;
	}

	public byte[] getBytesAt(int index) throws ArrayIndexOutOfBoundsException {
		if (index < 0)
			throw new ArrayIndexOutOfBoundsException();
		if (length < (start + index))
			throw new ArrayIndexOutOfBoundsException();
		byte[] new_data = new byte[length - index];
		System.arraycopy(data, start + index, new_data, 0, new_data.length);
		return new_data;
	}

	public byte[] getBytes() throws ArrayIndexOutOfBoundsException {
		byte[] new_data = new byte[length];
		System.arraycopy(data, start, new_data, 0, new_data.length);
		return new_data;
	}

	public void setBytesAt(int index, byte[] new_data)
		throws ArrayIndexOutOfBoundsException {
		if (index < 0)
			throw new ArrayIndexOutOfBoundsException();
		if (length < (start + index))
			throw new ArrayIndexOutOfBoundsException();
		if (length < (index + new_data.length))
			throw new ArrayIndexOutOfBoundsException();
		System.arraycopy(new_data, 0, data, start + index, new_data.length);
	}

	//XXX needs insertBytesAt, and deleteBytesAt

	public void append(byte[] new_data) throws ArrayIndexOutOfBoundsException {
		if (size < (length + new_data.length))
			setSize((length + new_data.length) * multiplier);
		if (size < (start + length + new_data.length)) {
			int new_start = size - (length + new_data.length);
			if (new_start != start)
				System.arraycopy(data, start, data, new_start, length);
			start = new_start;
		}
		System.arraycopy(new_data, 0, data, start + length, new_data.length);
		length = length + new_data.length;
	}

	public void append(int new_int) throws ArrayIndexOutOfBoundsException {
		if (size < (length + 1))
			setSize((length + 1) * multiplier);
		if (size < (start + length + 1)) {
			int new_start = size - (length + 1);
			if (new_start != start)
				System.arraycopy(data, start, data, new_start, length);
			start = new_start;
		}
		data[start + length] = (byte)new_int;
		length = length + 1;
	}

	public void append(byte new_byte) throws ArrayIndexOutOfBoundsException {
		if (size < (length + 1))
			setSize((length + 1) * multiplier);
		if (size < (start + length + 1)) {
			int new_start = size - (length + 1);
			if (new_start != start)
				System.arraycopy(data, start, data, new_start, length);
			start = new_start;
		}
		data[start + length] = new_byte;
		length = length + 1;
	}

	public void prepend(byte[] new_data) throws ArrayIndexOutOfBoundsException {
		if (size < (length + new_data.length))
			setSizeRightJustify((length + new_data.length) * multiplier);
		if (start < new_data.length) {
			int new_start = size - (length + new_data.length);
			if (new_start != start - new_data.length)
				System.arraycopy(data, start, data,
					new_start + new_data.length, length);
			start = new_start;
		}
		else
			start = start - new_data.length;
		System.arraycopy(new_data, 0, data, start, new_data.length);
		length = length + new_data.length;
	}

	public void prepend(int new_int) throws ArrayIndexOutOfBoundsException {
		if (size < (length + 1))
			setSizeRightJustify((length + 1) * multiplier);
		if (start < 1) {
			int new_start = size - (length + 1);
			if (new_start != start - 1)
				System.arraycopy(data, start, data, new_start + 1, length);
			start = new_start;
		}
		else
			start = start - 1;
		data[start] = (byte)new_int;
		length = length + 1;
	}

	//XXX for testing only; the function immediately above is 20 times faster
	public void prependWrapper(int new_int) throws ArrayIndexOutOfBoundsException {
		byte[] temp = { (byte)new_int };
		prepend(temp);
	}

	public void prepend(byte new_byte) throws ArrayIndexOutOfBoundsException {
		if (size < (length + 1))
			setSizeRightJustify((length + 1) * multiplier);
		if (start < 1) {
			int new_start = size - (length + 1);
			if (new_start != start - 1)
				System.arraycopy(data, start, data, new_start + 1, length);
			start = new_start;
		}
		else
			start = start - 1;
		data[start] = new_byte;
		length = length + 1;
	}

}
