/*
 * Copyright 2013 Yuichiro Moriguchi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.morilib.sh.misc;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class PipeBuffer {

	private class Out extends OutputStream {

		@Override
		public void write(int b) throws IOException {
			checkOutput();
			if(tmpfile == null && point + 1 < buffer.length) {
				buffer[point++] = (byte)b;
			} else {
				createtmp();
				tous.write(b);
			}
		}

		@Override
		public void close() throws IOException {
			checkOutput();
			if(tmpfile != null)  tous.close();
			output = null;
		}

		@Override
		public void flush() throws IOException {
			checkOutput();
			if(tmpfile != null)  tous.flush();
		}

		@Override
		public void write(
				byte[] b, int off, int len) throws IOException {
			checkOutput();
			if(tmpfile == null && point + len < buffer.length) {
				System.arraycopy(b, off, buffer, point, len);
				point += len;
			} else {
				createtmp();
				tous.write(b, off, len);
			}
		}

		@Override
		public void write(byte[] b) throws IOException {
			write(b, 0, b.length);
		}

	}

	private class In extends InputStream {

		@Override
		public int read() throws IOException {
			checkInput();
			if(tmpfile != null) {
				return tins.read();
			} else if(readp < point) {
				return buffer[readp++];
			} else {
				return -1;
			}
		}

		@Override
		public int available() throws IOException {
			checkInput();
			return tmpfile != null ? tins.available() : point - readp;
		}

		@Override
		public void close() throws IOException {
			checkInput();
			if(tmpfile != null)  tins.close();
			input  = null;
		}

		@Override
		public int read(byte[] b, int off, int l) throws IOException {
			int p = readp;

			checkInput();
			if(tmpfile != null) {
				return tins.read(b, off, l);
			} else if(readp + l < point) {
				System.arraycopy(buffer, readp, b, off, l);
				readp += l;
				return l;
			} else if(readp < point) {
				System.arraycopy(buffer, readp, b, off, point - readp);
				readp += l;
				return point - p;
			} else {
				return -1;
			}
		}

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

		@Override
		public long skip(long n) throws IOException {
			int p = readp;

			checkInput();
			if(tmpfile != null) {
				return tins.skip(n);
			} else if(readp + n < point) {
				readp += n;
				return n;
			} else if(readp < point) {
				readp += n;
				return point - p;
			} else {
				return -1;
			}
		}

	}

	private static final int BUFSIZE = 8192;

	private int    point = 0;
	private int    readp = 0;
	private byte[] buffer;
	private File   tmpfile = null;
	private InputStream  tins;
	private OutputStream tous;

	private InputStream  input  = null;
	private OutputStream output = new Out();

	/**
	 * 
	 * @param size
	 */
	public PipeBuffer(int size) {
		buffer = new byte[size];
	}

	/**
	 * 
	 */
	public PipeBuffer() {
		this(BUFSIZE);
	}

	private void createtmp() throws IOException {
		if(tmpfile == null) {
			tmpfile = File.createTempFile("jsh", ",tmp");
			tous = new FileOutputStream(tmpfile);
			tous.write(buffer, 0, point);
		}
	}

	private void checkOutput() {
		if(output == null)  throw new IllegalStateException();
	}

	private void checkInput() {
		if(input == null)  throw new IllegalStateException();
	}

	/**
	 * 
	 * @return
	 */
	public OutputStream getOutputStream() {
		if(output == null) {
			throw new IllegalStateException();
		} else {
			return output;
		}
	}

	/**
	 * 
	 * @return
	 */
	public InputStream getInputStream() {
		try {
			if(input != null) {
				return input;
			} else if(output != null) {
				throw new IllegalStateException();
			} else {
				if(tmpfile != null) {
					tins = new FileInputStream(tmpfile);
				}
				return input = new In();
			}
		} catch(FileNotFoundException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 
	 */
	public void clear() {
		if(input != null || output != null) {
			throw new IllegalStateException();
		}
		readp = point = 0;
		tmpfile = null;
	}

	/**
	 * 
	 */
	public void close() {
		try {
			if(tins != null)  tins.close();
			if(tous != null)  tous.close();
			tins = null;
			tous = null;
		} catch(IOException e) {
			throw new RuntimeException(e);
		}
	}

}
