package com.nifty.cloud.ncs;

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

/**
 * FilterInputStreamをラップするクラスです。
 *
 */
public final class NiftyCloudInputSubstream extends FilterInputStream
{
	private long currentPosition;
	private final long bufferOffset;
	private final long bufferSize;
	private long markedPosition = 0;
	private boolean closeSourceStream;

	/**
	 * 指定した入力ストリーム、オフセット、およびバッファサイズを使用して、NiftyCloudInputSubstreamを生成します。
	 *
	 * @param input
	 *  入力ストリーム
	 * @param bufferOffset
	 *  オフセット
	 * @param bufferSize
	 *  バッファサイズ
	 */
	public NiftyCloudInputSubstream(InputStream input, long bufferOffset, long bufferSize)
	{
		this(input, bufferOffset, bufferSize, true);
	}

	/**
	 * 指定した入力ストリーム、オフセット、およびバッファサイズを使用して、NiftyCloudInputSubstreamを生成します。
	 *
	 * @param input
	 *  入力ストリーム
	 * @param bufferOffset
	 *  オフセット
	 * @param bufferSize
	 *  バッファサイズ
	 * @param closeSourceStream
	 *  処理後にストリームをcloseするかどうかのフラグ
	 */
	public NiftyCloudInputSubstream(InputStream input, long bufferOffset, long bufferSize, boolean closeSourceStream)
	{
		super(input);

		this.currentPosition = 0;
		this.bufferSize = bufferSize;
		this.bufferOffset = bufferOffset;
		this.closeSourceStream = closeSourceStream;
	}

	@Override
	public int read() throws IOException {
		byte[] b = new byte[1];
		int bytesRead = read(b, 0, 1);

		if (bytesRead == -1)
			return bytesRead;
		return b[0];
	}

	@Override
	public int read(byte[] b, int off, int len) throws IOException
	{
		if (currentPosition < bufferOffset) {
			skip();
		}

		long bytesRemaining = (bufferSize + bufferOffset)
				- currentPosition;
		if (bytesRemaining <= 0)
			return -1;

		len = (int) Math.min(len, bytesRemaining);
		int bytesRead = super.read(b, off, len);
		currentPosition += bytesRead;

		return bytesRead;
	}

	@Override
	public synchronized void mark(int readlimit)
	{
		markedPosition = currentPosition;
		super.mark(readlimit);
	}

	@Override
	public synchronized void reset() throws IOException
	{
		currentPosition = markedPosition;
		super.reset();
	}

	@Override
	public int available() throws IOException
	{
		long bytesRemaining;
		if (currentPosition < bufferOffset)
			bytesRemaining = bufferSize;
		else
			bytesRemaining = (bufferSize + bufferOffset)
					- currentPosition;

		return (int) Math.min(bytesRemaining, super.available());
	}

	@Override
	public void close() throws IOException
	{
		if (closeSourceStream) {
			super.close();
		}
	}

	private long skip() throws IOException
	{
		while (currentPosition < bufferOffset) {
			long skippedBytes = skip(bufferOffset - currentPosition);
			currentPosition += skippedBytes;
		}

		return (currentPosition);
	}
}
