/**
 *	An input stream where the bytes are read from the contents of a string.
 */
function HexInputStream(hex) {
	this.hex = hex;
	this.pos = 0;
}

/**
 *	Reads the next byte from the hex stream. Returns a value from 0-255 or -1
 *	if an error occurs.
 */
HexInputStream.prototype.read = function() {
	try {
		var out = parseInt(this.hex.substr(this.pos, 2), 16);
		if (isNaN(out)) {
			return -1;
		} else {
			this.pos += 2;
			return out;
		}
	} catch (e) {
		return -1;
	}
};

/**
 *	Skips over the required number of bytes.
 */
HexInputStream.prototype.skip = function(n) {
	this.pos += n * 2;
};

/**
 *	Returns the number of bytes available.
 */
HexInputStream.prototype.available = function() {
	return Math.floor((this.hex.length - this.pos) / 2);
};

/**
 *	Starts reading from the beginning of the hex stream.
 */
HexInputStream.prototype.reset = function() {
	this.pos = 0;
};

HexInputStream.prototype.close = function() {};

/****************************************************************************/

function DataInputStream(stream) {
	this.stream = stream;
}

DataInputStream.prototype.read = function() {
	return this.stream.read();
};

DataInputStream.prototype.readUnsignedByte = function() {
	var a = this.stream.read();
	if (a < 0) {
		throw "Whoa!";
	}
	return a;
};

DataInputStream.prototype.readByte = function() {
	var n = this.readUnsignedByte();
	if (n < 128) {
		return n;
	} else {
		return n - 256;
	}
};

DataInputStream.prototype.readUnsignedShort = function() {
	var a = this.stream.read();
	var b = this.stream.read();
	if ((a | b) < 0) {
		throw "Whoa!";
	}
	return b | (a << 8);
};

DataInputStream.prototype.readShort = function() {
	var n = this.readUnsignedShort();
	if (n < 32768) {
		return n;
	} else {
		return n - 65536;
	}
};

DataInputStream.prototype.readInt = function() {
	var a = this.stream.read();
	var b = this.stream.read();
	var c = this.stream.read();
	var d = this.stream.read();
	if ((a | b | c | d) < 0) {
		throw "Whoa!";
	}
	return (a << 24) | (b << 16) | (c << 8) | d;
};

DataInputStream.prototype.readUTF = function() {
	var out = "";
	var len = this.readUnsignedShort();
	for (var n = 0; n < len; n++) {
		out += String.fromCharCode(this.readUnsignedByte());
	}
	return out;
};

DataInputStream.prototype.skip = function(n) {
	this.stream.skip(n);
};

DataInputStream.prototype.reset = function() {
	this.stream.reset();
};

DataInputStream.prototype.close = function() {
	this.stream.close();
};

/****************************************************************************/

function BitInputStream(stream) {
	this.stream = stream;
	this.buffer = 0;
	this.offset = 0;
}

BitInputStream.BYTE_SIZE = 8;
BitInputStream.INT_SIZE = 32;

BitInputStream.prototype.read = function(bits) {
	if (bits < 0) {
		alert("Cannot read less than zero bits");
		throw "Cannot read less than zero bits";
	}
	if (bits <= BitInputStream.INT_SIZE - BitInputStream.BYTE_SIZE) {
		while (this.offset < bits) {
			this.buffer <<= BitInputStream.BYTE_SIZE;
			this.buffer |= this.stream.read();
			this.offset += BitInputStream.BYTE_SIZE;
		}
		this.offset -= bits;
		return (this.buffer >> this.offset) & ((1 << bits) - 1);
	} else if (bits <= BitInputStream.INT_SIZE) {
		return this.read(BitInputStream.INT_SIZE - BitInputStream.BYTE_SIZE)
			| this.read(bits - BitInputStream.INT_SIZE
				- BitInputStream.BYTE_SIZE);
	} else {
		alert("Cannot read more bits than an int contains: " + BitInputStream.INT_SIZE);
		throw "Cannot read more bits than an int contains";
	}
};

BitInputStream.prototype.align = function() {
	this.buffer = 0;
	this.offset = 0;
};

BitInputStream.prototype.close = function() {
	this.stream.close();
};

/****************************************************************************/

function RLEInputStream(stream) {
	this.stream = new BitInputStream(stream);
	this.bits = 0;
	this.flag = 0;
	this.last = 0;
	this.repeat = -1;
	this.reset();
}

RLEInputStream.prototype.reset = function() {
	this.bits = this.stream.read(3) + 1;
	this.flag = (1 << this.bits) - 1;
};

RLEInputStream.prototype.read = function() {
	if (this.repeat <= 0) {
		this.last = this.stream.read(this.bits);
		if (this.last == this.flag) {
			this.repeat = this.stream.read(8);
			this.last = this.stream.read(this.bits);
		}
	} else {
		this.repeat--;
	}
	return this.last;
}

RLEInputStream.prototype.close = function() {
	this.stream.close();
};

RLEInputStream.prototype.toString = function() {
	return "BitInputStream: [bits: " + this.bits + ", flag: " + this.flag + "]";
};