/* ***************************************************************************+
 * ITX package (cnrg.itx) for telephony application programming.              *
 * Copyright (c) 1999  Cornell University, Ithaca NY.                         *
 * A copy of the license is distributed with this package.  Look in the docs  *
 * directory, filename GPL.  Contact information: bergmark@cs.cornell.edu     *
 ******************************************************************************/


package cnrg.itx.datax.jaudio;

// Imports
import java.util.*;
import java.io.*;

public class JAudioDevice 
{
	//////////////////////////////////////////////////////////////////////
	//																	//
	//				 CLASS MEMBER VARIABLES								//
	//																	//
	//////////////////////////////////////////////////////////////////////

	// Global device information
	public static boolean debug = false;
	private static int numDevices;
	protected static String deviceNames[];
	protected static Vector devices[];
	protected static Hashtable hwoDevices = new Hashtable();
	protected static Hashtable hwiDevices = new Hashtable();
	private static JAudioMessageThread messageThread;
	private static boolean initialized = false;

	// Per JAudioDevice device information
	public int deviceID;
	public int mode;
	public String deviceName;
	public JAudioIn in;	
	public JAudioOut out;

	// Native variables
	private int lastError;
	private String lastErrorMsg;
	private int hwi,hwo;
	private boolean closed;

	// JAudio modes
	public final static int DUPLEX		= 0;
	public final static int INPUT		= 1;
	public final static int OUTPUT		= 2;
	public final static int SAMPLE_RATE = 8000;
	public final static int SAMPLE_BITS = 8;

	//////////////////////////////////////////////////////////////////////
	//																	//
	//					  CLASS METHODS									//
	//																	//
	//////////////////////////////////////////////////////////////////////

	//////////////////////////////////////////////////////////////////////
	//
	//	JAudioDevice::JAudioDevice()
	//
	//	Description:
	//		JAudioDevice constructor.
	//	
	//	Parameters:
	//		deviceID:	The ID number of the audio device to be opened.
	//		mode:		JAudio mode:
	//					DUPLEX - both recording and playback capabilities
	//					INPUT  - recording capability only
	//					OUTPUT - playback capability only
	//	Returns:
	//		Nothing.
	//
	//	Throws:
	//		JAudioException
	//
	//////////////////////////////////////////////////////////////////////
	public JAudioDevice(int deviceID) throws JAudioException
	{
		this(deviceID, DUPLEX);
	}

	public JAudioDevice(int deviceID, int mode) throws JAudioException 
	{
		initialize(this);
		closed = false;
		try 
		{
			synchronized (devices) 
			{
				// Make sure we were handed a valid deviceID
				if ((deviceID < 0) || (deviceID >= numDevices))
				{
					throw new JAudioException("Illegal JAudioDevice ID (" + deviceID + ")");
				}
				if (inUse(deviceID, mode)) 
				{
					throw new JAudioException("JAudioDevice ID (" + deviceID + ") already in use");
				}

				// Create a new message thread
				if (messageThread == null) 
				{
					messageThread = new JAudioMessageThread();
					synchronized (messageThread) 
					{
						messageThread.start();
						try 
						{
							// Wait for the message thread to start
							messageThread.wait();
						} 
						catch (InterruptedException e) 
						{
						}
					}
				}
				checkError();

				// Register the device
				devices[deviceID].addElement(this);
				this.deviceID = deviceID;
				this.deviceName = deviceNames[deviceID];
				this.mode = mode;
				initN(messageThread.windowsID, debug);
				checkError();

				// Create the input/output device(s)
				in = null;
				out = null;
				if ((mode == DUPLEX) || (mode == INPUT))
				{
					hwiDevices.put(new Integer(hwi), this);
					in = new JAudioIn(this);
				}
				if ((mode == DUPLEX) || (mode == OUTPUT))
				{
					hwoDevices.put(new Integer(hwo), this);
					out = new JAudioOut(this);
				}
			}
		} 
		catch (JAudioException e) 
		{
			try 
			{
				close();
			} 
			catch (Throwable e2) 
			{
			}
			throw e;
		}
	}

	//////////////////////////////////////////////////////////////////////
	//
	//	JAudioDevice::close()
	//
	//	Description:
	//		Closes an open JAudioDevice
	//	
	//	Parameters:
	//		None
	//
	//	Returns:
	//		Nothing.
	//
	//	Throws:
	//		JAudioException
	//
	//////////////////////////////////////////////////////////////////////
	public void close() throws JAudioException 
	{
		// Are we already closed?
		if (closed) 
		{
			return;
		}

		synchronized (devices) 
		{
		   if ((mode == DUPLEX) || (mode == INPUT))
		   {
		      in.device = null;
		      hwiDevices.remove(new Integer(hwi));
		   }
		   if ((mode == DUPLEX) || (mode == OUTPUT))
		   {
		      out.device = null;
		      hwoDevices.remove(new Integer(hwo));
		   }
		   closeN();
		   devices[deviceID].removeElement(this);
		   if (inUse() == 0) 
		   { 
		      messageThread.shutdown();
		      try
		      {
				messageThread.join();
		      }
		      catch (Exception e)
		      {
		      }
		      messageThread = null;
		   }  
		}
		closed = true;
		checkError();
	}


	//////////////////////////////////////////////////////////////////////
	//
	//	JAudioDevice::finalize()
	//
	//	Description:
	//		JAudioDevice finalizer: ensures that the audio device has been
	//		closed once the JAudioDevice has been GCed.
	//	
	//	Parameters:
	//		None
	//
	//	Returns:
	//		Nothing.
	//
	//////////////////////////////////////////////////////////////////////
	public void finalize() 
	{
		try 
		{
			close();
		}
		catch (JAudioException e) 
		{
		}
	}

	//////////////////////////////////////////////////////////////////////
	//																	//
	//					  CLASS HELPER METHODS							//
	//																	//
	//////////////////////////////////////////////////////////////////////

	// All native methods
	private native void initN(int msgThreadID, boolean debug);
	private native void closeN();

	// Is a conflicting device currently in use?
	public boolean inUse(int deviceID, int mode) 
	{
		JAudioDevice device;
		int size;

		// Check to see if the device slot is available
		if ((size = devices[deviceID].size()) == 0)
		{
			return false;
		}

		// Check to see if the exisiting device conflicts
		for (int i = 0; i < size; i++)
		{
			try
			{
				device = (JAudioDevice)devices[deviceID].elementAt(i);
				if ((device.mode == mode) || (device.mode == DUPLEX))
				{
					return true;
				}
			}
			catch (ArrayIndexOutOfBoundsException e)
			{
				return true;
			}
		}

		return false;
	}

	// How many devices are in use?
	public static int inUse() 
	{
		int c = 0;
		for (int i = 0; i < numDevices; i++) 
		{
			c = c + devices[i].size();
		}
		return c;
	}

	// Get the number of available audio devices
	public static int getNumDevices() 
	{
		JAudioInfo info = new JAudioInfo();
		return info.getNumDevices();
	}	

	// Look up a JAudioDevice based on output WAVEFORMATEX
	public static JAudioDevice getDeviceByHwo(int hwo) 
	{
		return (JAudioDevice)hwoDevices.get(new Integer(hwo));
	}

	// Look up a JAudioDevice based on input WAVEFORMATEX
	public static JAudioDevice getDeviceByHwi(int hwi) 
	{
		return (JAudioDevice)hwiDevices.get(new Integer(hwi));
	}

	// Print a formatted string
	public static void println(String s) 
	{ 
		if (debug) 
		{
			System.out.println("JAudioDevice: " + s);
		}
	}

	// Check for an error - if one occured, throw a JAudioException
	protected void checkError() throws JAudioException 
	{
		if (lastError != 0)	
		{
			lastError = 0;
			throw new JAudioException("JAudioDevice " + deviceID + ": " + lastErrorMsg);
		}
	}

	private static synchronized void initialize(JAudioDevice device)
	{
		if (initialized)
		{
			return;
		}
		
		try 
		{
			System.loadLibrary("jaudio");
			JAudioInfo info = new JAudioInfo();
			numDevices = info.getNumDevices();
			deviceNames = new String[numDevices];
			devices = new Vector[numDevices];
			for(int i = 0; i < numDevices; i++) 
			{
				deviceNames[i] = "JAudioDevice " + i;
				devices[i] = new Vector();
			}
		} 
		catch (Error e) 
		{
			e.printStackTrace();
			throw e;
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
		initialized = true;
	}
}
