package org.grobid.core.process;

import java.io.IOException;
import java.io.InputStream;

public class StreamProcess extends InputStream {
	
	protected void run() {
		byte[] buff = new byte[8192];

		while (true) {
			try {
				int avail = is.read(buff);

				// synchronized (synchronizer) {
					if (avail <= 0) {
						isEOF = true;
						// synchronizer.notifyAll();
						break;
					}

					int space_available = buffer.length - write_pos;

					if (space_available < avail) {
						/* compact/resize buffer */

						int unread_size = write_pos - read_pos;
						int need_space = unread_size + avail;

						byte[] new_buffer = buffer;

						if (need_space > buffer.length) {
							int inc = need_space / 3;
							inc = (inc < 256) ? 256 : inc;
							inc = (inc > 8192) ? 8192 : inc;
							new_buffer = new byte[need_space + inc];
						}

						if (unread_size > 0)
							System.arraycopy(buffer, read_pos, new_buffer, 0,
									unread_size);

						buffer = new_buffer;

						read_pos = 0;
						write_pos = unread_size;
					}

					System.arraycopy(buff, 0, buffer, write_pos, avail);
					write_pos += avail;

					//synchronizer.notifyAll();
				//}
			} catch (IOException e) {
				//synchronized (synchronizer) {
					exception = e;
					//synchronizer.notifyAll();
					break;
				}
			//}
		}
	}

	private InputStream is;

	// private final Object synchronizer = new Object();

	private boolean isEOF = false;
	private boolean isClosed = false;
	private IOException exception = null;

	private byte[] buffer = new byte[2048];
	private int read_pos = 0;
	private int write_pos = 0;

	public StreamProcess(InputStream is) {
		this.is = is;
		run();
	}

	@Override
	public int read() throws IOException {
		boolean wasInterrupted = false;

		//try {
			//synchronized (synchronizer) {
				if (isClosed)
					throw new IOException("This StreamGobbler is closed.");

				while (read_pos == write_pos) {
					if (exception != null)
						throw exception;

					if (isEOF)
						return -1;

					/*try {
						synchronizer.wait();
					} catch (InterruptedException e) {
						wasInterrupted = true;
					}*/
				}
				return buffer[read_pos++] & 0xff;
			}
	//	} 
	/*finally {
			 if (wasInterrupted)
				Thread.currentThread().interrupt();
		}*/
	//}

	@Override
	public int available() throws IOException {
		//synchronized (synchronizer) {
			if (isClosed)
				throw new IOException("This StreamGobbler is closed.");

			return write_pos - read_pos;
		//}
	}

	@Override
	public int read(byte[] b) throws IOException {
		return read(b, 0, b.length);
	}

	@Override
	public void close() throws IOException {
		//synchronized (synchronizer) {
			if (isClosed)
				return;
			isClosed = true;
			isEOF = true;
			//synchronizer.notifyAll();
			is.close();
		//}
	}

	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		if (b == null)
			throw new NullPointerException();

		if ((off < 0) || (len < 0) || ((off + len) > b.length)
				|| ((off + len) < 0) || (off > b.length))
			throw new IndexOutOfBoundsException();

		if (len == 0)
			return 0;

		boolean wasInterrupted = false;

		//try {
			//synchronized (synchronizer) {
				if (isClosed)
					throw new IOException("This StreamGobbler is closed.");

				while (read_pos == write_pos) {
					if (exception != null)
						throw exception;

					if (isEOF)
						return -1;

					/*try {
						synchronizer.wait();
					} catch (InterruptedException e) {
						wasInterrupted = true;
					}*/
				}

				int avail = write_pos - read_pos;

				avail = (avail > len) ? len : avail;

				System.arraycopy(buffer, read_pos, b, off, avail);

				read_pos += avail;

				return avail;
			}
		//} 
		/*finally {
			if (wasInterrupted)
				Thread.currentThread().interrupt();
		}*/
	//}
}
