/*
 *	AudioInputStream.java
 *
 *	This file is part of Tritonus: http://www.tritonus.org/
 */

/*
 *  Copyright (c) 1999, 2000 by Matthias Pfisterer
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library 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 Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
|<---            this code is formatted to fit into 80 columns             --->|
*/

package javax.sound.sampled;

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

import org.tritonus.share.TDebug;
import org.tritonus.share.sampled.AudioUtils;




public class AudioInputStream
extends InputStream
{
	/**	Says whether we check lengths.
	 *	If true, lengths in bytes are asserted to be multiples
	 *	of the frame size.
	 */
	private static final boolean	CHECK_LENGTHS = true;


	/**	The stream that is backing the AudioInputStream.
	 *	Accessing this property, especially writing, is discouraged.
	 */
	protected InputStream		stream;



	/**	The format of this stream.
	 *	Accessing this property, especially writing, is discouraged.
	 *	You should use the method getFormat().
	 */
	protected AudioFormat		format;



	/**	The length of the audio data.
	 *
	 *  This value is in frames.  May have the value
	 *  AudioSystem.NOT_SPECIFIED to express that the length in frames
	 *  is unknown.  Accessing this property, especially writing, is
	 *  strongly discouraged.  You should use the method
	 *  getFrameLength().
	 */
	protected long			frameLength;



	/**	The size of an frame of audio data.
	 *
	 *	This value is in bytes.  May have the value
	 *	AudioSystem.NOT_SPECIFIED to express that the size of frames
	 *	is unknown.  Accessing this property, especially writing, is
	 *	strongly discouraged. Its semantics is not clearly defined.
	 */
	protected int			frameSize;



	/**	The size of an frame of audio data.
	 *
	 *	This value is used internally to check whether the number of
	 *	bytes requested in read() or skip() is a multiple of the frame
	 *	size.  This value is in bytes.  In contrary to frameSize,
	 *	which may be AudioSystem.NOT_SPECIFIED, this variable always
	 *	has a value >= 1. If the length in frames or the frame size of
	 *	the audio data is unknown, this value is set to 1.
	 */
	private int			m_nCheckFrameSize;



	/**	The read position in the stream.
	 *
	 *	This value is in frames.  NOTE: this property only has a
	 *	useful value if the encoding of the stream is PCM.  Accessing
	 *	this property, especially writing, is strongly
	 *	discouraged. Its semantics is not clearly defined.
	 */
	protected long			framePos;



	/**	The read position in the stream.
	 *
	 *	This value is in bytes.
	 */
	private long			m_lPosition;



	/**	The length of the audio data.
	 *
	 *	This value is in bytes.  May have the value
	 *	AudioSystem.NOT_SPECIFIED to express that the length is
	 *	unknown.
	 */
	private long			m_lLengthInBytes;





	/**	Position where the mark was set in the stream.
	 *
	 *	This value is in bytes.
	 */
	private long			m_lMarkedPosition;





	public AudioInputStream(InputStream inputStream,
							AudioFormat audioFormat,
							long lLengthInFrames)
	{
		if (TDebug.TraceAudioInputStream) { TDebug.out("AudioInputStream.<init>: inputStream: " + inputStream); }
		stream = inputStream;
		format = audioFormat;
		frameLength = lLengthInFrames;
		m_nCheckFrameSize = frameSize = audioFormat.getFrameSize();
		if (m_nCheckFrameSize < 1)
		{
			m_nCheckFrameSize = 1;
		}
		m_lLengthInBytes = AudioUtils.getLengthInBytes(
			audioFormat,
			lLengthInFrames);
		m_lPosition = 0;
		updateFramePosition();
	}



	public AudioInputStream(TargetDataLine targetDataLine)
	{
		this(new TargetDataLineInputStream(targetDataLine),
		     targetDataLine.getFormat(),
		     AudioSystem.NOT_SPECIFIED);
	}



	public AudioFormat getFormat()
	{
		return format;
	}



	public long getFrameLength()
	{
		return frameLength;
	}



	public int read()
		throws IOException
	{
		if (CHECK_LENGTHS)
		{
			if (getCheckFrameSize() != 1)
			{
				throw new IOException("frame size must be 1 to read a single byte");
			}
		}
		if (isEndReached())
		{
			return -1;
		}
		int nByte = stream.read();
		if (nByte != -1)
		{
			m_lPosition++;
			updateFramePosition();
		}
		return nByte;
	}



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



	public int read(byte[] abData, int nOffset, int nLength)
		throws IOException
	{
		//$$fb better to first check if end is reached.
		//otherwise, on un-aligned nLength, it will lead to a misleading exception
		// (as it occurs with DataInputStream.readFully)
		if (isEndReached())
		{
			return -1;
		}
		if (CHECK_LENGTHS)
		{
			if (nLength % getCheckFrameSize() != 0)
			{
				throw new IOException("length must be a multiple of the frame size (length="+nLength+", frameSize="+getCheckFrameSize()+")");
			}
		}
		if (m_lLengthInBytes != AudioSystem.NOT_SPECIFIED)
		{
			nLength = (int) Math.min(nLength, m_lLengthInBytes - m_lPosition);
		}
		//$$fb try to read frame-aligned (when stream returns unaligned data)
		//$$fb e.g. SequenceInputStream
		int nBytesRead=0;
		int thisRead;
		do
		{
			thisRead = stream.read(abData, nOffset, nLength);
			if (thisRead > 0)
			{
				nBytesRead += thisRead;
		  	nLength -= thisRead;
		  	nOffset += thisRead;
		  }
		}
		while (thisRead > 0 && nLength > 0);
		if (nBytesRead <= 0 && thisRead == -1)
		{
			nBytesRead =- 1;
		}
		else
		{
			m_lPosition += nBytesRead;
			updateFramePosition();
		}
		return nBytesRead;
	}



	public long skip(long nSkip)
		throws IOException
	{
		if (CHECK_LENGTHS)
		{
			if (nSkip % getCheckFrameSize() != 0)
			{
				throw new IOException("skip must be a multiple of the frame size");
			}
		}
		if (m_lLengthInBytes != AudioSystem.NOT_SPECIFIED)
		{
			nSkip = (int) Math.min(nSkip, m_lLengthInBytes - m_lPosition);
		}
		long	nSkipped = stream.skip(nSkip);
		m_lPosition += nSkipped;
		updateFramePosition();
		return nSkipped;
	}



	public int available()
		throws IOException
	{
		if (m_lLengthInBytes == AudioSystem.NOT_SPECIFIED)
		{
			return stream.available();
		}
		else
		{
			return (int) Math.min(stream.available(), m_lLengthInBytes - m_lPosition);
		}
	}



	public void close()
		throws IOException
	{
		stream.close();
	}



	public void mark(int readlimit)
	{
		stream.mark(readlimit);
		m_lMarkedPosition = m_lPosition;
	}



	public void reset()
		throws IOException
	{
		stream.reset();
		m_lPosition = m_lMarkedPosition;
		updateFramePosition();
	}



	public boolean markSupported()
	{
		return stream.markSupported();
	}



	/**	Returns the frame size for internal checks.
	 *
	 *	This number is is bytes.
	 */
	private int getCheckFrameSize()
	{
		return m_nCheckFrameSize;
	}



	private boolean isEndReached()
	{
		return m_lPosition >= m_lLengthInBytes && m_lLengthInBytes != AudioSystem.NOT_SPECIFIED;
	}



	/**	Calculates the position in frames from the position in bytes.
	 *
	 *	This only produces meaningful values if the encoding is PCM.
	 *	Maintaining framePos is only done for backwards compatibility
	 *	with really badly designed programs that directely access the
	 *	protected member framePos.
	 */
	private void updateFramePosition()
	{
		this.framePos = m_lPosition / getCheckFrameSize();
	}



	/**	Helper class to enable AudioInputStream to read from a TargetDataLine.
	 */
	private static class TargetDataLineInputStream
	extends InputStream
	{
		/**	The TargetDataLine from which to read.
		 */
		private TargetDataLine	m_targetDataLine;


		/**	A buffer used for reading single bytes.
		 *
		 *	It is allocated and used in read().
		 */
		private byte[]		m_abSingleByteBuffer = null;



		public TargetDataLineInputStream(TargetDataLine targetDataLine)
		{
			m_targetDataLine = targetDataLine;
		}



		public int read()
		{
			if (m_abSingleByteBuffer == null)
			{
				m_abSingleByteBuffer = new byte[1];
			}
			int nReturn = read(m_abSingleByteBuffer, 0, 1);
			if (nReturn < 0)
			{
				return -1;
			}
			else
			{
				return m_abSingleByteBuffer[0];
			}
		}



		public int read(byte[] abData, int nOffset, int nLength)
		{
			return m_targetDataLine.read(abData, nOffset, nLength);
		}
	}




}



/*** AudioInputStream.java ***/
