/*
 * BinaryInputBuffer.java - This file is part of the Jakstab project.
 * 
 * Copyright 2007-2015 Johannes Kinder <jk@jakstab.org>
 * Copyright (C) 2003 The University of Arizona
 *
 * The original code for this class was taken from "MBEL: The Microsoft 
 * Bytecode Engineering Library" and modified for use with Jakstab.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
 */

package org.jakstab.util;

/** 
 * This is an input stream that buffers an entire binary file in advance. 
 * All multi-byte integers are read little-endian.
 * 
 * @author Michael Stepp
 * @author Johannes Kinder
 */
public abstract class BinaryInputBuffer {

	protected int current;

	/** 
	 * Moves the file pointer to the given location.
	 */
	public void seek(long pos) throws java.io.IOException {
		// skips to absolute location 'point' in the file
		if (pos<0 || pos>=getSize())
			throw new java.io.IOException("BinaryInputBuffer.seek: Seek position outside of file bounds: "+pos);

		current = (int)pos;
	}

	/** 
	 * Reads a null-terminated ASCII string from the file starting 
	 * at the current location and ending at the next 0x00 ('\0') byte.
	 */
	public String readASCII() throws java.io.IOException {
		String result = "";
		int BYTE;
		while((BYTE=readBYTE()) != 0)
			result += (char)BYTE;
		return result;
	}

	/** 
	 * Returns the current file position of this input stream
	 */
	public long getCurrent() {
		return current;
	}

	/** 
	 * Asserts that the next bytes to be read from the input stream equal the 
	 * byte array given. If sucessful, returns true and advances the file pointer by
	 * bytes.length. If unsuccessful, will either throw an exception (if premature EOF)
	 * or will returns false. The file position after an unsuccessful match is undefined, 
	 * but will be somewhere between start and (start+bytes.length).
	 */
	public boolean match(byte[] bytes) throws java.io.IOException {
		if ((current + bytes.length) > getSize())
			return false;
		for (int i=0;i<bytes.length;i++){
			//if (bytes[i] != data[current++])
			if (bytes[i] != (byte)readBYTE())
				return false;
		}
		return true;
	}

	/** 
	 * Advances the file pointer by 'amount'.
	 * @param amount the amount to skip, may be negative.
	 * @return true iff the skip was successful
	 */
	public boolean skip(int amount) {
		if ((current + amount) > getSize() || (current + amount) < 0)
			return false;
		current += amount;
		return true;
	}

	/** 
	 * Moves the file pointer to the next higher multiple of 'a'.
	 * @param a the value to align to, must be positive
	 * @return true iff the align was successful
	 */
	public boolean align(int a) {
		// skips to the next multiple of 'a' bytes
		if (a<=0)
			return false;
		if (a==1)
			return true;
		int temp = current + (a-(current%a))%a;
		if (temp>getSize())
			return false;
		current = temp;
		return true;
	}

	/** 
	 * Reads from the current file pointer into the given array.
	 * @param bytes the array to read into. this will attempt to read bytes.length bytes from the file
	 */
	public void read(byte[] bytes) throws java.io.IOException {
		if (bytes==null)
			return;
		if ((current+bytes.length) > getSize())
			throw new java.io.IOException("BinaryInputBuffer.read: Premature EOF");

		for (int i=0; i<bytes.length; i++)
			//bytes[i] = data[current++];
			bytes[i] = (byte)readBYTE();
	}

	/** 
	 * Reads an unsigned byte from the file, returned in the lower 8 bits of an int.
	 * Advances the file pointer by 1.
	 */
	public abstract int readBYTE() throws java.io.IOException;
	
	/**
	 * Reads a single signed byte from the file without changing the file pointer.
	 * 
	 * @param fp Address to read from
	 * @return a signed byte value
	 */
	public abstract byte getByteAt(int fp);

		/**
	 *  Reads an unsigned 2-byte integer from the file, returned in the lower 2 bytes of an int
	 * Advances the file pointer by 2.
	 */
	public int readWORD() throws java.io.IOException {
		if (current+1>=getSize())
			throw new java.io.IOException("BinaryInputBuffer.readWORD: Premature EOF");

		int b1 = readBYTE();
		int b2 = readBYTE();
		int result = ((b1 & 0xFF) | ((b2 & 0xFF) << 8));
		return result;
	}

	/**
	 *  Reads an unsigned 4-byte integer from the file and returns it the lower 4 bytes of a long.
	 * Advances the file pointer by 4.
	 */
	public long readDWORD() throws java.io.IOException {
		if (current+3>=getSize())
			throw new java.io.IOException("BinaryInputBuffer.readDWORD: Premature EOF");
		int b1 = readBYTE();
		int b2 = readBYTE();
		int b3 = readBYTE();
		int b4 = readBYTE();

		long result = ((b1 & 0xFFL) | ((b2 & 0xFFL) << 8) 
				| ((b3 & 0xFFL) << 16) | ((b4 & 0xFFL) << 24));
		return result;
	}

	/** 
	 * Reads an 8-byte (signed) quantity and returns it in a long.
	 * Advances the file pointer by 8.
	 */
	public long readDDWORD() throws java.io.IOException {
		// throws java.io.IOException if EOF
		if (current+7>=getSize())
			throw new java.io.IOException("BinaryInputBuffer.readDWORD: Premature EOF");
		int b1 = readBYTE();
		int b2 = readBYTE();
		int b3 = readBYTE();
		int b4 = readBYTE();
		int b5 = readBYTE();
		int b6 = readBYTE();
		int b7 = readBYTE();
		int b8 = readBYTE();

		long result = ((b1 & 0xFFl) | ((b2 & 0xFFl) << 8) 
				| ((b3 & 0xFFl) << 16) | ((b4 & 0xFFl) << 24) 
				| ((b5 & 0xFFl) << 32) | ((b6 & 0xFFl) << 40) 
				| ((b7 & 0xFFl) << 48) | ((b8 & 0xFFl) << 56));
		return result;
	}

	/** 
	 * Reads an int64 (signed) from the file and returns it in a long.
	 * Advances the file pointer by 8.
	 */
	public long readINT64() throws java.io.IOException {
		return readDDWORD();
	}

	/** 
	 * Reads an int32 (signed) from the file and returns it in an int.
	 * Advances the file pointer by 4.
	 */
	public int readINT32() throws java.io.IOException {
		if (current+3>=getSize())
			throw new java.io.IOException("BinaryInputBuffer.readINT32: Premature EOF");

		int b1 = readBYTE();
		int b2 = readBYTE();
		int b3 = readBYTE();
		int b4 = readBYTE();

		int temp =  (b1 & 0xFF) | ((b2 & 0xFF)<<8) | 
		((b3 & 0xFF)<<16) | ((b4 & 0xFF)<<24);
		return temp;
	}

	/** 
	 * Reads an int16 (signed) from the file and returns it in an int.
	 * Advances the file pointer by 2.
	 */
	public int readINT16() throws java.io.IOException {
		short shorty=0;
		if (current+1>=getSize())
			throw new java.io.IOException("BinaryInputBuffer.readINT16: Premature EOF");
		int b1 = readBYTE();
		int b2 = readBYTE();

		shorty = (short)(((b1 & 0xFF) | (b2 & 0xFF)<<8) & 0xFFFF);
		return shorty;
	}

	/**
	 * Reads an int8 (signed) from the file and returns it in an int.
	 * Advances the file pointer by 1.
	 */
	public int readINT8() throws java.io.IOException {
		if (current>=getSize())
			throw new java.io.IOException("BinaryInputBuffer.readINT8: Premature EOF");
		return ((byte)readBYTE());
	}

	/**
	 * Reads an r4 from the file and returns it in a float.
	 * Advances the file pointer by 4.
	 */
	public float readR4() throws java.io.IOException {
		return Float.intBitsToFloat((int)readDWORD());
	}

	/** 
	 * Reads an r8 from the file and returns it in a double.
	 * Advances the file pointer by 8.
	 */
	public double readR8() throws java.io.IOException {
		return Double.longBitsToDouble(readDDWORD());
	}

	public abstract long getSize();

}
