/* ***************************************************************************+
 * ITX package (cnrg.itx) for telephony application programming.              *
 * Copyright (c) 1999  Cornell University, Ithaca NY                          *
 *                                                                            *
 * This program is free software; you can redistribute it and/or modify       *
 * it under the terms of the GNU General Public Liense as published by        *
 * the Free Software Foundation, either version 2 of the License, or (at      * 
 * your option) any later version.                                            *
 *                                                                            *
 * The ITX package 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.                                                          * 
 *                                                                            *
 * A copy of the license is distributed with this package.  Look in the docs  *
 * directory, filename GPL.                                                   *
 *                                                                            * 
 * Contact information:                                                       *
 * Donna Bergmark                                                             *
 * 484 Rhodes Hall                                                            *
 * Cornell University                                                         *
 * Ithaca, NY 14853-3801                                                      *
 * bergmark@cs.cornell.edu                                                    *
 ******************************************************************************/
package shared;

import java.io.*;

/**
 * A <code>RADInputStream</code> represents a stream of raw audio data (RAD).  It wraps
 * around both a RAD file and an <code>InputStream</code> and provides both the usual
 * functionality of the <code>InputStream</code> as well as some specialized stream
 * functionality (go to a certain offset, wait for an offset, etc.).
 * 
 * @author Jason Howes
 * @version 1.0, 2/12/1999
 * @see java.io.FileInputStream
 */
public class RADInputStream extends InputStream
{
	/**
	 * Is the stream open?
	 */
	private boolean mOpen = false;
	
	/**
	 * Is the stream paused?
	 */
	private boolean mPaused = false;
	
	/**
	 * Holds the length (in bytes) of the RAD file.
	 */
	private long mFileLength = 0;
	
	/**
	 * Holds the current offset into the RAD file.
	 */
	private long mCurrentOffset = 0;
	
	/**
	 * Is someone waiting for a specific offset?
	 */
	private boolean mWaitingForOffset = false;
	private	boolean	mWaitForOffsetRetVal = false;
	private long mOffsetToWaitFor = 0;
	
	/**
	 * Semaphores
	 */
	private Object mWaitForOffset = new Object();
	private Object mSema = new Object();
	
	/**
	 * A wrapper around the RAD file (used for seeks)
	 */
	private RandomAccessFile mRADFile;
	
	/**
	 * Silent data.
	 */
	private byte SILENCE = (byte)127;
	
	/**
	 * Class constructor.
	 * 
	 * @param path full path of the RAD file.
	 * @exception <code>SecurityException, FileNotFoundException</code> on error.
	 */
	public RADInputStream(String path) throws SecurityException, IOException
	{
		try
		{
			mRADFile = new RandomAccessFile(path, "r");
		}
		catch (IllegalArgumentException e)
		{
		}
		mFileLength = mRADFile.length();
		mOpen = true;
	}
	
	/**
	 * Class constructor.
	 * 
	 * @param file file representing the RAD file.
	 * @exception <code>SecurityException, FileNotFoundException</code> on error.
	 */
	public RADInputStream(File file) throws SecurityException, IOException
	{
		try
		{
			mRADFile = new RandomAccessFile(file, "r");
		}
		catch (IllegalArgumentException e)
		{
		}
		mFileLength = mRADFile.length();
		mOpen = true;
	}
	
	/**
	 * Reads one byte from the <code>RADInputStream</code>.
	 * 
	 * @return the byte read.
	 * @exception <code>IOException</code> on read error.
	 */
	public int read() throws IOException
	{
		int returnVal;
		
		// Has the stream been closed?
		if (!mOpen)
		{
			throw new IOException();
		}
		
		// Is the stream paused?
		if (mPaused)
		{
			return SILENCE;
		}
		
		// Enter object semaphore.
		synchronized (mSema)
		{
			// Send silence on EOF
			if ((returnVal = mRADFile.read()) == -1)
			{
				returnVal = (int)SILENCE;
				mCurrentOffset = mFileLength;
			}
			else
			{
				mCurrentOffset++;
			}
			
			// Notify waiting thread if necessary.
			notifyWaitingThread(false);			
		}
		// Leave object semaphore.

		return returnVal;
	}
	
	/**
	 * Reads bytes from the <code>RADInputStream</code>.
	 * 
	 * @param b buffer array to fill with data.
	 * @return the number of bytes read.
	 * @exception <code>IOException</code> on read error, <code>NullPointerException</code> if b is null.
	 */
	public int read(byte[] b) throws IOException, NullPointerException
	{
		int bytesToRead = b.length;
		int bytesRead;
		int bytesToFill;
		
		// Has the stream been closed?
		if (!mOpen)
		{
			throw new IOException();
		}
		
		// Is the stream paused?
		if (mPaused)
		{
			for (int i = 0; i < b.length; i++)
			{
				b[i] = SILENCE;
			}
			
			return b.length;
		}		
		
		// Enter object semaphore.
		synchronized (mSema)
		{
			// See if we will overrun the EOF.
			if (mCurrentOffset + bytesToRead > mFileLength)
			{
				bytesRead = (int)(mFileLength - mCurrentOffset);
				bytesToFill = bytesToRead - bytesRead;
			}
			else
			{
				bytesRead = bytesToRead;
				bytesToFill = 0;
			}
		
			// Read as many bytes from the file as we can.
			mRADFile.read(b);
		
			// Fill in silence if necessary
			for (int i = 0; i < bytesToFill; i++)
			{
				b[bytesRead + i] = SILENCE;
			}
		
			// Update mCurrentOffset
			if (bytesToFill > 0)
			{
				mCurrentOffset = mFileLength;
			}
			else
			{
				mCurrentOffset += bytesRead;
			}
			
			// Notify waiting thread if necessary.
			notifyWaitingThread(false);
		}
		// Leave object semaphore.

		return bytesToRead;
	}
	
	/**
	 * Reads bytes from the <code>RADInputStream</code>.
	 * 
	 * @param b buffer array to fill with data.
	 * @param off offset into the buffer array where data will be written.
	 * @param len number of bytes to read.
	 * @return the number of bytes read.
	 * @exception <code>IOException</code>, <code>NullPointerException</code>, <code>IndexOutOfBoundException</code>.
	 */
	public int read(byte[] b, int off, int len) throws IOException, 
													   NullPointerException,
													   IndexOutOfBoundsException
	{
		int bytesToRead = len;
		int bytesRead;
		int bytesToFill;
		
		// Has the stream been closed?
		if (!mOpen)
		{
			throw new IOException();
		}
		
		// Is the stream paused?
		if (mPaused)
		{
			for (int i = off; i < len; i++)
			{
				b[i] = SILENCE;
			}
			
			return len;
		}		
		
		// Check bounds.
		if (off + len > b.length)
		{
			throw new IndexOutOfBoundsException();
		}
		
		// Enter object semaphore.
		synchronized (mSema)
		{		
			// See if we will overrun the EOF.
			if (mCurrentOffset + bytesToRead > mFileLength)
			{
				bytesRead = (int)(mFileLength - mCurrentOffset);
				bytesToFill = bytesToRead - bytesRead;
			}
			else
			{
				bytesRead = bytesToRead;
				bytesToFill = 0;
			}
		
			// Read as many bytes from the file as we can.
			mRADFile.read(b, off, len);
		
			// Fill in silence if necessary
			for (int i = 0; i < bytesToFill; i++)
			{
				b[off + bytesRead + i] = SILENCE;
			}
		
			// Update mCurrentOffset
			if (bytesToFill > 0)
			{
				mCurrentOffset = mFileLength;
			}
			else
			{
				mCurrentOffset += bytesRead;
			}
			
			// Notify waiting thread if necessary.
			notifyWaitingThread(false);			
		}
		// Leave object semaphore.

		return bytesToRead;
	}
	
	/**
	 * Skips over a number of bytes in the stream.
	 * 
	 * @param n the number of bytes to skip.
	 */
	public long skip(long n) throws IOException
	{
		long newOffset;
		
		// Is the stream open?
		if (!mOpen)
		{
			throw new IOException();
		}
		
		// Enter object semaphore.
		synchronized (mSema)
		{		
			// Compute the new offset
			if (mCurrentOffset + n > mFileLength)
			{
				newOffset = mFileLength;
			}
			else
			{
				newOffset = mCurrentOffset + n;
			}
		
			// Seek to the position within the file.
			mRADFile.seek(newOffset);
		
			// Update mCurrentOffset
			mCurrentOffset = newOffset;
			
			// Notify waiting thread if necessary.
			notifyWaitingThread(false);			
		}
		// Leave object semaphore.
		
		return n;
	}
	
	/**
	 * Returns the number of bytes that are currently available to be read.
	 * 
	 * @return the number of bytes that can be read without blocking.
	 */
	public int available() throws IOException
	{
		// Is the stream open?
		if (!mOpen)
		{
			throw new IOException();
		}
		
		// Enter object semaphore.
		synchronized (mSema)
		{		
			return (int)(mFileLength - mCurrentOffset);
		}
		// Leave object semaphore.
	}	
	
	/**
	 * Closes the <code>RADInputStream</code>.
	 * 
	 * @exception <code>IOException</code> on error.
	 */	
	public void close() throws IOException
	{
		// Is the stream open?
		if (!mOpen)
		{
			//throw new IOException();
			return;
		}
		
		// Enter object semaphore.
		try
		{
			synchronized (mSema)
			{
				// Close the RAD file.
				mRADFile.close();
				
				// Alert any waiting thread.
				notifyWaitingThread(true);
			}
		}
		catch (IllegalMonitorStateException e)
		{
		}
		
		mOpen = false;
	}
	
	/**
	 * Finalize method.
	 * 
	 * @exception <code>Throwable</code> on error.
	 */
	protected void finalize() throws Throwable
	{
		super.finalize();
		close();
	}
	
	/** 
	 * Gets the current offset in the audio stream (in bytes).
	 *
	 * @return The current offset in bytes.
	 */
	public long getStreamOffset()
	{
		// Enter object semaphore.
		synchronized (mSema)
		{		
			return mCurrentOffset;
		}
		// Leave object semaphore.
	}

	/**
	 * Waits for the current offset to be exceeded.
	 * <p>
	 * <i>Note 1:</i> The calling thread will be put to sleep and woken up
	 * when one of three conditions is met:
	 *  1) The current offset in the RAD file has exceeded the given offset.<p>
	 *  2) The function setOffset() is called.<p>
	 *  3) close() is called.<p>
	 * <p>
	 * <i>Note 2:</i> Only one thread may call this function.
	 *
	 * @param offset the offset to wait for (from the beginning of the RAD file).
	 * @return true if condition <1> was met, or false otherwise.
	 * @exception <code>InterruptedException</code> if a wait was interrupted.
	 */
	public boolean waitForOffset(long offset) throws InterruptedException
	{
		try
		{
			// Enter wait for offset semaphore.
			synchronized (mWaitForOffset)
			{
				mWaitForOffsetRetVal = false;
				
				// Are we at or past the given offset?
				synchronized (mSema)
				{
					if (mCurrentOffset >= offset)
					{
						return true;
					}
				}
				// Leave object semaphore.

				// Wait for the offset.
				mOffsetToWaitFor = offset;
				mWaitingForOffset = true;
				mWaitForOffset.wait();
				
				return mWaitForOffsetRetVal;
			}
			// Leave wait for offset semaphore.
		}
		catch (IllegalMonitorStateException e)
		{
		}
		
		return false;
	}

	/**
	 * Sets the current offset in the RAD stream from the beginning.
	 * 
	 * @param offset offset into the RAD stream.
	 * @exception <code>IOException</code> on I/O error.
	 */
	public void setOffset(long offset) throws IOException
	{
		try
		{
			// Enter object semaphore.
			synchronized(mSema)
			{
				// Bounds check
				if (offset > mFileLength)
				{
					offset = mFileLength;
				}
				
				// Seek to the desired offset
				mRADFile.seek(offset);
				mCurrentOffset = offset;

				// Notify any waiting threads
				notifyWaitingThread(true);
			}
			// Leave object semaphore.
		}
		catch (IllegalMonitorStateException e)
		{
		}
	}
	
	/**
	 * Sets the stream pause state).
	 */
	public void setPaused(boolean paused)
	{
		mPaused = paused;
	}	
	
	/**
	 * Notifies threads that are waiting for a RAD file offset.
	 * <p>
	 * Note: must be called from within an mSema semaphore.
	 * 
	 * @param manual forces the function to ALWAYS notify waiting threads.
	 */
	private void notifyWaitingThread(boolean manual) throws IllegalMonitorStateException
	{
		// Enter wait for offset semaphore.
		synchronized (mWaitForOffset)
		{
			// Is there a waiting thread?
			if (!mWaitingForOffset)
			{
				return;
			}
		
			// Check the manual flag
			if (manual)
			{
				mWaitingForOffset = false;
				mWaitForOffsetRetVal = false;
				mWaitForOffset.notify();			
			}
			// Else check the normal wait for offset condition.
			else if (mCurrentOffset >= mOffsetToWaitFor)
			{
				mWaitingForOffset = false;
				mWaitForOffsetRetVal = true;
				mWaitForOffset.notify();
			}
		}
		// Leave wait for offset semaphore.
	}
}