package net.pms.io;

/*
 * Input stream wrapper with a byte limit.
 * Copyright (C) 2004 Stephen Ostermiller
 * http://ostermiller.org/contact.pl?regarding=Java+Utilities
 *
 * 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.
 *
 * This program 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 for more details.
 *
 * See COPYING.TXT for details.
 */

import java.io.*;

/**
 * An input stream wrapper that will read only a set number of bytes from the
 * underlying stream.
 * 
 * @author Stephen Ostermiller
 *         http://ostermiller.org/contact.pl?regarding=Java+Utilities
 * @since ostermillerutils 1.04.00
 */
public class SizeLimitInputStream extends InputStream {

	/**
	 * The input stream that is being protected. All methods should be forwarded
	 * to it, after checking the size that has been read.
	 * 
	 * @since ostermillerutils 1.04.00
	 */
	protected InputStream in;

	/**
	 * The number of bytes to read at most from this Stream. Read methods should
	 * check to ensure that bytesRead never exceeds maxBytesToRead.
	 * 
	 * @since ostermillerutils 1.04.00
	 */
	protected long maxBytesToRead = 0;

	/**
	 * The number of bytes that have been read from this stream. Read methods
	 * should check to ensure that bytesRead never exceeds maxBytesToRead.
	 * 
	 * @since ostermillerutils 1.04.00
	 */
	protected long bytesRead = 0;

	/**
	 * The number of bytes that have been read from this stream since mark() was
	 * called.
	 * 
	 * @since ostermillerutils 1.04.00
	 */
	protected long bytesReadSinceMark = 0;

	/**
	 * The number of bytes the user has request to have been marked for reset.
	 * 
	 * @since ostermillerutils 1.04.00
	 */
	protected long markReadLimitBytes = -1;

	/**
	 * Get the number of bytes actually read from this stream.
	 * 
	 * @return number of bytes that have already been taken from this stream.
	 * 
	 * @since ostermillerutils 1.04.00
	 */
	public long getBytesRead() {
		return bytesRead;
	}

	/**
	 * Get the maximum number of bytes left to read before the limit (set in the
	 * constructor) is reached.
	 * 
	 * @return The number of bytes that (at a maximum) are left to be taken from
	 *         this stream.
	 * 
	 * @since ostermillerutils 1.04.00
	 */
	public long getBytesLeft() {
		return maxBytesToRead - bytesRead;
	}

	/**
	 * Tell whether the number of bytes specified in the constructor have been
	 * read yet.
	 * 
	 * @return true iff the specified number of bytes have all been read.
	 * 
	 * @since ostermillerutils 1.04.00
	 */
	public boolean allBytesRead() {
		return getBytesLeft() == 0;
	}

	/**
	 * Get the number of total bytes (including bytes already read) that can be
	 * read from this stream (as set in the constructor).
	 * 
	 * @return Maximum bytes that can be read until the size limit runs out
	 * 
	 * @since ostermillerutils 1.04.00
	 */
	public long getMaxBytesToRead() {
		return maxBytesToRead;
	}

	/**
	 * Create a new size limit input stream from another stream given a size
	 * limit.
	 * 
	 * @param in
	 *            The input stream.
	 * @param maxBytesToRead
	 *            the max number of bytes to allow to be read from the
	 *            underlying stream.
	 * 
	 * @since ostermillerutils 1.04.00
	 */
	public SizeLimitInputStream(InputStream in, long maxBytesToRead) {
		this.in = in;
		this.maxBytesToRead = maxBytesToRead;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int read() throws IOException {
		if (bytesRead >= maxBytesToRead) {
			return -1;
		}
		int b = in.read();
		if (b != -1) {
			bytesRead++;
			bytesReadSinceMark++;
		}
		return b;
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		if (bytesRead >= maxBytesToRead) {
			return -1;
		}
		long bytesLeft = getBytesLeft();
		if (len > bytesLeft) {
			len = (int) bytesLeft;
		}
		int bytesJustRead = in.read(b, off, len);
		bytesRead += bytesJustRead;
		bytesReadSinceMark += bytesJustRead;
		return bytesJustRead;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public long skip(long n) throws IOException {
		if (bytesRead >= maxBytesToRead) {
			return -1;
		}
		long bytesLeft = getBytesLeft();
		if (n > bytesLeft) {
			n = bytesLeft;
		}
		return in.skip(n);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int available() throws IOException {
		int available = in.available();
		long bytesLeft = getBytesLeft();
		if (available > bytesLeft) {
			available = (int) bytesLeft;
		}
		return available;
	}

	/**
	 * Close this stream and underlying streams. Calling this method may make
	 * data on the underlying stream unavailable.
	 * <p>
	 * Consider wrapping this stream in a NoCloseStream so that clients can call
	 * close() with no effect.
	 * 
	 * @since ostermillerutils 1.04.00
	 */
	@Override
	public void close() throws IOException {
		in.close();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void mark(int readlimit) {
		if (in.markSupported()) {
			markReadLimitBytes = readlimit;
			bytesReadSinceMark = 0;
			in.mark(readlimit);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void reset() throws IOException {
		if (in.markSupported() && bytesReadSinceMark <= markReadLimitBytes) {
			bytesRead -= bytesReadSinceMark;
			in.reset();
			bytesReadSinceMark = 0;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean markSupported() {
		return in.markSupported();
	}
}

