package Tools;

/**
 * The byte[] analog of StringBuffer. Lets a tree of data structures
 * recursively generate a linearized representation of the data
 * structure without repeated byte[] allocations, copies, and
 * deletions. This code actually does reallocate and copy the byte[],
 * of course, but only log n times, and starting with a pretty big
 * buffer.
 *
 * @author jonh@cs.dartmouth.edu
 */

public class ByteBuffer {
	protected byte[] buf;
	protected int off;	// not used yet, but will be if I support prepend()
	protected int len;	// amount of space in buf actually used by data

	public ByteBuffer() {
		this(1024);		// very generous. I'm Assuming memory pages are
						// cheaper than object reallocations.
	}

	public ByteBuffer(int initialAllocation) {
		buf = new byte[initialAllocation];
		off = 0;
		len = 0;
	}

	/**
	 * Returns a new byte[] containing the contents of this buffer,
	 * trimmed to length.
	 */
	public byte[] toByteArray() {
		byte[] outBuf = new byte[len];
		return toByteArray(outBuf, 0);
	}

	/**
	 * Copies this.length() bytes into outBuf starting at outOff.
	 *
	 * @returns outBuf.
	 */
	public byte[] toByteArray(byte[] outBuf, int outOff) {
		System.arraycopy(buf, off, outBuf, outOff, len);
		return outBuf;
	}

	/**
	 * These three methods let you get at the byte array itself without
	 * making a data copy. Useful, for example, if you just want to
	 * write() it directly to a socket.
	 *
	 * Note that getRawBytes() has reference semantics: if you dink
	 * around with the returned buffer, you'll change the contents
	 * of this ByteBuffer object.
	 * TODO: Perhaps I should make these methods 'protected' in this class,
	 * and create a subclass RawByteBuffer that exposes them?
	 */
	public byte[] getRawBytes() {
		return buf;
	}
	public int getRawOffset() {
		return off;
	}
	public int length() {
		return len;
	}

	/**
	 * mutation primitives
	 */

	/**
	 * Append a single byte to the end of the buffer.
	 */
	public void append(byte b) {
		if (buf.length-len-off < 1) {
			reallocate(off+len+1);
		}
		buf[off+len] = b;
		len += 1;
	}

	/**
	 * Append a byte array to the end of the buffer.
	 */
	public void append(byte[] inBuf) {
		append(inBuf, 0, inBuf.length);
	}

	/**
	 * Append part of a byte array to the end of the buffer.
	 */
	public void append(byte[] inBuf, int inOff, int inLen) {
		if (buf.length-len-off < inLen) {
			reallocate(off+len+inLen);
		}
		System.arraycopy(inBuf, inOff, buf, off+len, inLen);
		len += inLen;
	}

	/**
	 * Reallocate the internal buffer.
	 * Invariant: when this call returns, buf.length >= minLength,
	 * and off hasn't changed.
	 *
	 * @params minLength: the append() methods set this to make sure
	 * that buf.length will have enough room to add their data, counting
	 * any unused <code>off</code> space at the beginning of the buffer.
	 */
	protected void reallocate(int minLength) {
		int newLength = 2*minLength;
			// leave *lots* of room for growth.
		byte[] newBuf = new byte[newLength];
		System.arraycopy(buf, off, newBuf, off, len);
			// don't bother copying bytes that aren't used.
		buf = newBuf;
	}

	public boolean equals(Object o) {
		byte[] obuf;
		int ooff, olen;

		if (o==this) {
			return true;
		}
		if (o instanceof ByteBuffer) {
			ByteBuffer obb = (ByteBuffer) o;
			obuf = obb.buf;
			ooff = obb.off;
			olen = obb.len;
		} else if (o instanceof byte[]) {
			obuf = (byte[]) o;
			ooff = 0;
			olen = obuf.length;
		} else {
			return false;
		}

		if (olen!=len) {
			return false;
		}
		int stop = off+len;
		for (int i=off, j=ooff; i<stop; i++, j++) {
			if (buf[i]!=obuf[j]) {
				return false;
			}
		}
		// same length & contents
		return true;
	}

	public int hashCode() {
		int hash = -91;		// because jonh *likes* -91
		int stop = off+len;
		for (int i=off; i<stop; i++) {
			hash = ((hash<<5) | (hash>>>(32-5))) ^ buf[i];
		}
		return hash;
	}
}
