/***************************************************************************
	Copyright          : (C) 2002 by Neoworks Limited. All rights reserved
	URL                : http://www.neoworks.com
 ***************************************************************************/
/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
package com.neoworks.util;

import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.apache.log4j.Category;

/**
 * Push back InputStream. This is an InputStream (not standard) that allows data to be pushed
 * back and subsequently reread.
 *
 * @author Nigel Atkinson (<a href="mailto:nigel@neoworks.com">nigel@neoworks.com</a>)
 * @author Original author unknown
 */
public class PushBackInputStream{
	private static final Category log = Category.getInstance(PushBackInputStream.class.getName());
	private static final boolean logDebugEnabled = log.isDebugEnabled();
	private static final boolean logInfoEnabled = log.isInfoEnabled();

	private byte[] buf;
	private InputStream in;
	private long pos = 0;

	/**
	 * Public constructor
	 *
	 * @param in The InputStream to wrap
	 */
	public PushBackInputStream(InputStream in)
	{
		this.in = in;
		this.buf = null;
	}

	/**
	 * Get the position in the Stream
	 *
	 * @return the position in the stream
	 */
	public long getPos()
	{
		return pos;
	}

	/**
	 * Read bytes from the stream into an array
	 *
	 * @param b The byte array to read into
	 * @param off The offset into the byte array to begin reading into
	 * @param len The number of bytes to read
	 * @exception IOException
	 * @return The number of bytes read
	 */
	public int read (byte [] b, int off, int len) throws IOException
	{
		//if (logDebugEnabled) log.debug("Reading " + len + " bytes into buffer at offset " + off);
		int retVal = 0;
		// there is enough data in the pushback buffer to fulfill this read
		if (buf != null)
		{
			//if (logDebugEnabled) log.debug("Buffer is " + buf.length + " bytes");
			ByteArrayInputStream bais = new ByteArrayInputStream(buf);
			if (bais.available() > 0)
			{
				//if (logDebugEnabled) log.debug("Reading " + Math.min(len,bais.available()) + " bytes from buffer");
				// read bytes from the buffer if they are available
				retVal = bais.read(b,off,len);
			}
			if (bais.available() > 0)
			{
				// if there are any left then create a new buffer
				byte [] tmp = new byte[bais.available()];
				bais.read(tmp,0,bais.available());
				buf = tmp;
				//if (logDebugEnabled) log.debug("Buffer is " + buf.length + " bytes");
			} else {
				buf = null;
				//if (logDebugEnabled) log.debug("Buffer is 0 bytes");
			}
		}
		if (retVal < len)
		{
			// no data left in the buffer, go get it from the stream
			int remainingLength = len - retVal;
			//if (logDebugEnabled) log.debug("Reading " + remainingLength + " bytes from stream");
			retVal += in.read(b, off + retVal, remainingLength);
		}
		pos += retVal;
		return retVal;
	}

	/**
	 * Unread bytes from the stream.
	 *
	 * @param b An array of bytes to push back onto the stream
	 * @param off The offset into the byte array to push back from
	 * @param len The number of bytes to push back.
	 */
	public void unread(byte[] b, int off, int len)
	{
		//if (logDebugEnabled) log.debug("Unreading " + len + " bytes from offset " + off);
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		if (buf != null && buf.length > 0)
		{
			//if (logDebugEnabled) log.debug("Saving " + buf.length + " bytes of existing buffer");
			// save the existing buffer
			baos.write(buf, 0, buf.length);
		}
		baos.write(b, off, len);
		buf = baos.toByteArray();
		pos -= len;
		//if (logDebugEnabled) log.debug("Buffer is " + buf.length + " bytes");
	}

	/**
	 * Close the stream.
	 *
	 * @exception IOException
	 */
	public synchronized void close() throws IOException
	{
		if (in == null)
			return;
		in.close();
		in = null;
		buf = null;
	}
}
