/* ***************************************************************************+
 * 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 client;

import shared.*;
import cnrg.itx.datax.*;
import cnrg.itx.datax.devices.*;
import cnrg.itx.ds.*;
import cnrg.itx.signal.*;
import cnrg.itx.signal.SignalEvent.*;
import java.io.*;
import java.net.*;
import java.util.*;

/**
 * The <code>Client</code> class wraps around all SPOT client functionality.
 * All client GUI code resides in the <code>ClientGUI</code> class.  This
 * separation of GUI from functionality allows the GUI to be interchanged without
 * major code changes in the base client code.
 * 
 * @version 1.0, 12/15/1998
 * @author Jason Howes
 * @see cnrg.itx.signal.client.SignalingObserver
 */
public class Client implements SignalingObserver
{	
	/**
	 * Username and password
	 */
	private String mITXUser;
	private String mITXPassword;
	private String mITXDestination;
	private boolean mITXDestinationSpecified;
	
	/**
	 * Client status variables
	 */
	private boolean mClientInitialized;
	private int mClientMode;

	/**
	 * The client presentation control
	 */
	private ClientPresentationControl mClientControl;

	/**
	 * The current slide presentation server session
	 */
	private ClientSession mClientSession;

	/**
	 * All ITX telephony/datax objects and state
	 */
	private DesktopSignaling mTelephonySignaling;
	private boolean mActiveConnection;
	private SignalConnection mSignalConnection;
	private UserID mUserID;
	private boolean mTelephonyEnabled;
	private Object mCallEvent;
	private Object mHangupEvent;

	/**
	 * Presentation status variables
	 */
	private PresentationInfo mPresentationInfo;
	private boolean mPresentationInProgress;
	private boolean mPresentationPaused;
	private int mNumPresentationSlides;
	private Vector mPresentationTopics;

	/**
	 * PAM control object local playback
	 */
	private PAM mPAM;

	/**
	 * Available slide presentation servers
	 */
	private Vector mServerList;
	private Vector mServerPortList;

	/**
	 * Exception messages
	 */
	static final String INVALID_INTIALIZATION_FILE		= "Invalid initialization file";
	static final String SERVER_CONNECTION_ERROR			= "Error connecting to server";
	static final String SESSION_INITIALIZATION_ERROR	= "Error initializing session";
	static final String SESSION_TERMINATION_ERROR		= "Error terminating session";
	static final String SPT_PARSE_ERROR					= "SPT parse error";
	static final String PAM_PARSE_ERROR					= "PAM parse error";
	static final String SESSION_IN_PROGRESS				= "Session in progress";
	static final String NO_SESSION_IN_PROGRESS			= "No session in progress";
	static final String FILE_CREATION_ERROR				= "File creation error";
	static final String FILE_CLOSE_ERROR				= "File close error";
	static final String FILE_WRITE_ERROR				= "File write error";
	static final String FILE_READ_ERROR					= "File read error";
	static final String RECEIVE_ERROR					= "Receive error";
	static final String SEND_ERROR						= "Send error";
	static final String SERVER_ERROR					= "Server error";
	static final String PAM_UNINITIALIZED				= "PAM uninitialized";
	static final String CLIENT_INITIALIZED				= "Client initialized";
	static final String CLIENT_UNINITIALIZED			= "Client uninitialized";
	static final String PRESENTATION_IN_PROGRESS		= "Presentation in progress";
	static final String PRESENTATION_NOT_IN_PROGRESS	= "Presentation not in progress";
	static final String CONNECTION_ESTABLISHED			= "Connection established";
	static final String CONNECTION_NOT_ESTABLISHED		= "Connection not established";
	static final String USER_LOGIN_FAILED				= "User failed to login";

	/**
	 * Other definitions
	 */
	public static final String SPOT_HELP_STRING					= "?";

	/**
	 * Class constructor.
	 */
	public Client()
	{
		// Initilaize client status
		mClientInitialized = false;

		// Initialize all presentation-specific variables
		mPresentationInfo = new PresentationInfo(SPOTDefinitions.DEFAULT_PRESENTATION_NAME);
		mPresentationInProgress = false;
		mPresentationPaused = false;
		mPresentationTopics = new Vector();

		// Create the server / port lists
		mServerList = new Vector();
		mServerPortList = new Vector();

		// Create the ClientSession
		mClientSession = new ClientSession();

		// Initialize telephony flags
		mTelephonyEnabled = false;
		mActiveConnection = false;
		
		// Reset ITX user/password
		mITXUser = "";
		mITXPassword = "";
	}

	/**
	 * Initializes the client.
	 * 
	 * @param filename the name of the SPT or PAM file with which to 
	 * initialize the client.
	 * @throws <code>ClientException</code> on error.
	 */
	public void initialize(String filename) throws ClientException
	{
		int filenameLength;
		String filenameSuffix;

		// Is the client already initialized?
		if (mClientInitialized)
		{
			throw new ClientException(CLIENT_INITIALIZED);
		}

		// Were we passed a SPT or PAM filename?
		if (SPOTDefinitions.isSPTFile(filename))
		{
			parseSPTFile(filename);
		}
		else if (SPOTDefinitions.isPAMFile(filename))
		{
			parsePAMFile(filename);
		}
		else
		{
			throw new ClientException(INVALID_INTIALIZATION_FILE);
		}

		// The client is now initialized
		mClientInitialized = true;
	}

	/**
	 * Resets the client (closes presentation, disconnects from server, etc.).  This
	 * function MUST be called before Client exit!
	 * 
	 * @throws <code>ClientException</code>, <code>PowerPointControlException</code>,
	 * <code>ClientSessionException</code> on error
	 */
	public void reset() throws ClientException, 
		PowerPointControlException,
		ClientSessionException
	{
		// Is the client initialized?
		if (!mClientInitialized)
		{
			return;
		}

		// Is there a presentation in progress?
		if (mPresentationInProgress)
		{
			// Stop the presentation
			stopPresentation();
		}

		// Is there an active session?
		if (mClientSession.sessionActive())
		{
			// End the session
			endSession();
		}
		
		// Shutdown telephony subsystem
		if (mTelephonyEnabled)
		{
			shutdownTelephony();
		}

		// Client is now uninitialized
		mClientInitialized = false;
	}

	/**
	 * Opens a session with a SPOT server.  Note: when finished, a user thread
	 * should call <code>endSession()</code>.
	 * 
	 * @param serverIndex:	index of the SPOT server to connect to
	 * @throws <code>ClientException</code>, <code>ClientSessionException</code> on error.
	 */
	public void startSession(int serverIndex) throws ClientException, ClientSessionException
	{
		String serverName;
		int serverPort;

		// Is the client initialized?
		if ((!mClientInitialized) || (mClientMode != ClientPresentationControl.SPOT_CONTROL))
		{
			throw new ClientException(CLIENT_UNINITIALIZED);
		}

		// Get the name and port of the requested server
		try
		{
			serverName = getServerInList(serverIndex);
			serverPort = getServerPortInList(serverIndex);
		}
		catch (ArrayIndexOutOfBoundsException e)
		{
			throw new ClientException(SESSION_INITIALIZATION_ERROR);
		}

		// Start the session
		mClientSession.startSession(serverName, serverPort);
	}

	/**
	 * Ends the currently open session.
	 * 
	 * @throws <code>ClientSessionException</code> on error
	 */
	public void endSession() throws ClientSessionException
	{	
		mClientSession.endSession(true);
	}

	/**
	 * Retrieves the slide presentation from the SPOT server.
	 * 
	 * @param presentationHome root directory of the local SPOT directory hierarchy
	 * @throws <code>ClientException</code>, <code>ClientSessionException</code> on error
	 */
	public void getPresentation(String presentationHome) throws ClientException, 
		ClientSessionException
	{
		SPOTMessage message;
		String presentationName;
		RandomAccessFile presentationFile;

		// Construct the presentation file
		presentationName = mPresentationInfo.getPresentationFilename(presentationHome);
		try
		{
			presentationFile = new RandomAccessFile(presentationName, "rw");
		}
		catch (Exception e)
		{
			throw new ClientException(FILE_CREATION_ERROR);
		}
		
		// Send a request for the number of presentation slides
		mClientSession.sendMessage(SPOTMessage.REQUEST_NUM_PRESENTATION_SLIDES, mPresentationInfo);
		
		// Read the server's response
		message = mClientSession.receiveMessage();
		if (message.mType != SPOTMessage.NUM_PRESENTATION_SLIDES)
		{
			throw new ClientException(SERVER_ERROR);
		}
		mNumPresentationSlides = ((Integer)message.mObject).intValue();
		
		// Send a request for the presentation topics
		mClientSession.sendMessage(SPOTMessage.REQUEST_PRESENTATION_TOPICS, mPresentationInfo);
		
		// Read the server's response
		message = mClientSession.receiveMessage();
		if (message.mType != SPOTMessage.PRESENTATION_TOPICS)
		{
			throw new ClientException(SERVER_ERROR);
		}
		mPresentationTopics = (Vector)message.mObject;

		// Send a request for the presentation
		mClientSession.sendMessage(SPOTMessage.REQUEST_PRESENTATION, mPresentationInfo);

		// Read the server's response
		message = mClientSession.receiveMessage();
		if (message.mType != SPOTMessage.BEGIN_PRESENTATION_TRANSFER)
		{
			throw new ClientException(SERVER_ERROR);
		}

		// Write the contents of the presentation file to disk
		message = mClientSession.receiveMessage();
		while (message.mType != SPOTMessage.END_PRESENTATION_TRANSFER)
		{
			try
			{
				presentationFile.write((byte [])message.mObject);
			}
			catch (IOException e)
			{
				throw new ClientException(FILE_WRITE_ERROR);
			}
			
			message = mClientSession.receiveMessage();
		}

		// Close the presentation file
		try
		{
			presentationFile.close();
		}
		catch (IOException e)
		{
			throw new ClientException(FILE_CLOSE_ERROR);
		}
	}
	
	/**
	 * Returns a Vector of available presentation topics (in String form).
	 * 
	 * @return a Vector of available presentation topics
	 * @throws <code>ClientException</code> on error.
	 */
	public Vector getPresentationTopics() throws ClientException, ClientSessionException
	{
		SPOTMessage message;
		
		if (!mClientInitialized)
		{
			throw new ClientException(CLIENT_UNINITIALIZED);
		}

		// Is the client running a local presentation?
		if (mClientMode == ClientPresentationControl.PAM_CONTROL)
		{
			return mPAM.getTopics();
		}
		else
		{		
			// Ask the SPOT server for the topics
			mClientSession.sendMessage(SPOTMessage.REQUEST_PRESENTATION_TOPICS, mPresentationInfo);
			
			// Read the server's response
			message = mClientSession.receiveMessage();
			if (message.mType != SPOTMessage.PRESENTATION_TOPICS)
			{
				throw new ClientException(SERVER_ERROR);
			}
			
			mPresentationTopics = (Vector)message.mObject;
		}
		
		return (Vector)mPresentationTopics.clone();
	}	

	/**
	 * Starts the presentation.
	  * 
	 * @param presentationHome root directory of the local SPOT directory hierarchy
	 * @param w the horizontal size of the PPT presentation window (in pixels)
	 * @param h the vertical size of the PPT presentation window (in pixels)
	 * @param x the horizontal screen position of the PPT presentation window
	 * @param y the vertical screen position of the PPT presentation window
	 * @throws <code>ClientException</code>, <code>ClientSessionException</code>,
	 * <code>PowerPointException</code>, <code>DataException</code> on error
	 */
	public void startPresentation(String presentationHome,
		int width, 
		int height, 
		float x, 
		float y) throws ClientException, ClientSessionException, PowerPointControlException, DataException
	{
		String presentationName;
		SPOTMessage message;
		File file;
		boolean canRead;

		// Is the client initialized?
		if (!mClientInitialized)
		{
			throw new ClientException(CLIENT_UNINITIALIZED);
		}

		// Is there a presentation in progress?
		if (mPresentationInProgress)
		{
			throw new ClientException(PRESENTATION_IN_PROGRESS);
		}

		// Construct the presentation name
		if (mClientMode == ClientPresentationControl.PAM_CONTROL)
		{
			presentationName = mPresentationInfo.getPresentationFilename(null);
		}
		else
		{
			presentationName = mPresentationInfo.getPresentationFilename(presentationHome);
		}
		
		// Make sure the presentation file exists and is readable
		try
		{
			file = new File(presentationName);
			canRead = file.canRead();
		}
		catch (Exception e)
		{
			throw new ClientException(e.getMessage());
		}
		if (!canRead)
		{
			throw new ClientException(FILE_READ_ERROR);
		}
		
		// Construct a new ClientPresentationControl based on client mode
		if (mClientMode == ClientPresentationControl.PAM_CONTROL)
		{
			mClientControl = new ClientPAMControl(mPAM, mPresentationInfo.getRADFilename(null));
		}
		else
		{
			// Send a request for the number of presentation slides
			mClientSession.sendMessage(SPOTMessage.REQUEST_NUM_PRESENTATION_SLIDES, mPresentationInfo);
		
			// Read the server's response
			message = mClientSession.receiveMessage();
			if (message.mType != SPOTMessage.NUM_PRESENTATION_SLIDES)
			{
				throw new ClientException(SERVER_ERROR);
			}
			mNumPresentationSlides = ((Integer)message.mObject).intValue();
			
			mClientControl = new ClientSPOTControl(this, mClientSession, mPresentationInfo, mNumPresentationSlides);
		}
		
		// Set up the presentation and data connection on the SPOT server
		if (mClientMode == ClientPresentationControl.SPOT_CONTROL)
		{
			// Open the presentation
			mClientSession.sendMessage(SPOTMessage.OPEN_PRESENTATION, mPresentationInfo);
			
			// Read the server's response
			message = mClientSession.receiveMessage();
			if (message.mType != SPOTMessage.ACK)
			{
				throw new ClientException(SERVER_ERROR);
			}			
			
			// Create the data connection
			if (mTelephonyEnabled)
			{
				initiateTelephonyConnection();
			}
			else
			{
				// TODO: Non ITX support
			}		
		}		
		
		// Start the presentation
		mClientControl.startPresentation(presentationName, width, height, x, y);

		// Success!
		mPresentationInProgress = true;
	}
	
	/**
	 * Stops the slide presentation.
	 * 
	 * @throws <code>ClientSessionException</code> on error.
	 */
	public void stopPresentation() throws ClientSessionException
	{
		SPOTMessage message;
		
		// Is there a presentation in progress?
		if (!mPresentationInProgress)
		{
			return;
		}
		
		// Stop the presentation
		try
		{
			mClientControl.stopPresentation();
		}
		catch (ClientSessionException e)
		{	
			if (mTelephonyEnabled)
			{
				try
				{
					mTelephonySignaling.Hangup(mSignalConnection);
				}
				catch (Exception exp)
				{
				}
			}
			else
			{
				// TODO: Non ITX support
			}
			
			mActiveConnection = false;
			mClientSession.endSession(false);
			throw e;
		}		
		
		// If we are in SPOT mode, wait for the telephony call to hangup
		if (mClientMode == ClientPresentationControl.SPOT_CONTROL)
		{				
			if (mTelephonyEnabled)
			{
				closeTelephonyConnection();
			}
			else
			{
				// TODO: Non ITX support
			}
		}
		
		// Success!
		mPresentationInProgress = false;			
	}	
	
	/**
	 * Synchronizes the PPT presentation and audio with the first 
	 * presentation slide.
	 * 
	 * @throws <code>ClientException</code>, <code>ClientSessionException</code> on error.
	 */
	public void gotoFirstPresentationSlide() throws ClientException, ClientSessionException
	{
		// Is there a presentation in progress?
		if (!mPresentationInProgress)
		{
			return;
		}

		mClientControl.gotoFirstPresentationSlide();
	}
	
	/**
	 * Synchronizes the PPT presentation and audio with the last 
	 * presentation slide.
	 * 
	 * @throws <code>ClientException</code>, <code>ClientSessionException</code> on error.
	 */
	public void gotoLastPresentationSlide() throws ClientException, ClientSessionException
	{
		// Is there a presentation in progress?
		if (!mPresentationInProgress)
		{
			return;
		}

		mClientControl.gotoLastPresentationSlide();
	}	

	/**
	 * Synchronizes the PPT presentation and audio with the next 
	 * presentation slide.
	 * 
	 * @throws <code>ClientException</code>, <code>ClientSessionException</code> on error.
	 */
	public void gotoNextPresentationSlide() throws ClientException, ClientSessionException
	{
		// Is there a presentation in progress?
		if (!mPresentationInProgress)
		{
			return;
		}

		mClientControl.gotoNextPresentationSlide();
	}
	
	/**
	 * Synchronizes the PPT presentation and audio with the previous 
	 * presentation slide.
	 * 
	 * @throws <code>ClientException</code>, <code>ClientSessionException</code> on error.
	 */	
	public void gotoPreviousPresentationSlide() throws ClientException, ClientSessionException
	{
		// Is there a presentation in progress?
		if (!mPresentationInProgress)
		{
			return;
		}

		mClientControl.gotoPreviousPresentationSlide();
	}
	
	/**
	 * Synchronizes the PPT presentation and audio with a specified
	 * presentation topic.
	 * 
	 * @param topic the desired topic with which to synchronize.
	 * @throws <code>ClientException</code>, <code>ClientSessionException</code> on error.
	 */	
	public void gotoPresentationTopic(String topic) throws ClientException, ClientSessionException
	{
		// Is there a presentation in progress?
		if (!mPresentationInProgress)
		{
			return;
		}

		mClientControl.gotoPresentationTopic(topic);	
	}	

	/**
	 * Pauses the slide presentation.
	 * 
	 * @throws <code>ClientSessionException</code> on error.
	 */	
	public void pausePresentation() throws ClientSessionException
	{
		// Is there a presentation in progress?
		if (!mPresentationInProgress)
		{
			return;
		}

		mClientControl.pausePresentation();	
	}

	/**
	 * Pauses the slide presentation.
	 * 
	 * @throws <code>ClientException</code>, <code>ClientSessionException</code> on error.
	 */
	public void resumePresentation() throws ClientException, ClientSessionException
	{
		// Is there a presentation in progress?
		if (!mPresentationInProgress)
		{
			return;
		}

		mClientControl.resumePresentation();		
	}
	
	/**
	 * Searches a local SPOT archive for the current presentation file.
	 * 
	 * @param presentationHome root directory of the local SPOT directory hierarchy to search
	 * @throws <code>ClientException</code> on error
	 */
	public boolean searchLocalArchive(String presentationHome) throws ClientException
	{
		String filename;
		File file;

		// Is the client initialized?
		if (!mClientInitialized)
		{
			throw new ClientException(CLIENT_UNINITIALIZED);
		}

		// Create the presentation file name and file object
		filename = mPresentationInfo.getPresentationFilename(presentationHome);
		file = new File(filename);

		// See if the file exists
		try
		{
			if (!file.exists())
			{
				return false;
			}
		}
		catch (Exception e)
		{
			throw new ClientException(e.getMessage());
		}

		return true;
	}
	
	/**
	 * Get the number of SPOT servers.
	 * 
	 * @return number of SPOT servers
	 */
	public int getNumServers()
	{
		return mServerList.size();
	}
	
	/**
	 * Get a particular SPOT server name.
	 * 
	 * @param index index of the desired server
	 * @throws <code>ArrayIndexOutOfBoundsException</code> if index is invalid
	 * @return SPOT server name
	 */	
	public String getServerInList(int index) throws ArrayIndexOutOfBoundsException
	{
		return new String((String)mServerList.elementAt(index));
	}
	
	/**
	 * Get a particular SPOT server port.
	 * 
	 * @param index index of the desired server
	 * @throws <code>ArrayIndexOutOfBoundsException</code> if index is invalid
	 * @return SPOT server port
	 */		
	public int getServerPortInList(int index) throws ArrayIndexOutOfBoundsException
	{
		return ((Integer)mServerPortList.elementAt(index)).intValue();
	}
	
	/**
	 * Returns the presentation path of the current slide presentation.
	 * 
	 * @return the presentation path
	 */
	public String getPresentationPath()
	{
		return mPresentationInfo.getPresentationPath();
	}
	
	/**
	 * Returns the current client mode.
	 * 
	 * @return the client mode
	 */
	public int getClientMode()
	{
		return mClientMode;
	}
	
	/**
	 * Determines whether or not the client is initialized.
	 * 
	 * @return <code>true</code> if the client is initialized, <code>false</code> otherwise.
	 */
	public boolean isInitialized()
	{
		return mClientInitialized;
	}
	
	/**
	 * This method informs the application that a peer application's invitation
	 * has been received.
	 * 
	 * @param ise is the InviteSignalEvent that contains all the information about
	 * the caller application.
	 * @return  void
	 * 
	 * @see cnrg.itx.signal.SignalEvent.InviteSignalEvent
	 * @see cnrg.itx.signal.client.SignalingObserver	 
	 */
	public void onInvite(InviteSignalEvent ise)
	{	
		if (mActiveConnection)
		{
			ise.busy();
		}
		else
		{
			// Create a half duplex audio channel
			try
			{
				Channel newInputChannel = new Channel();
				NetworkSource newSource = new NetworkSource(newInputChannel, Channel.SAMPLE_SIZE);
				newInputChannel.setSource(newSource);
				newInputChannel.addDestination(new SpeakerDestination());			
				
				ise.accept(new AudioConnection(newInputChannel, null));
			}
			catch (Exception e)
			{
				ise.reject("Failed to allocate resources");
				return;
			}
		}
	}

	/**
	 * This method informs the application that a peer application has sent
	 * a confirmation and the call setup is complete.
	 * 
	 * @param  c the SignalConnection the Application should use for data exchange.
	 *         The connection within SignalConnection may be instantiated by the application.
	 * 
	 * @see cnrg.itx.signal.client.SignalConnection
	 * @see cnrg.itx.signal.client.SignalingObserver	 
	 */
	public void onStartCall(SignalConnection c)
	{
		if (mActiveConnection)
		{
			try
			{
				mTelephonySignaling.Hangup(c);
			}
			catch (Exception e)
			{
			}
		}
		
		// Notify any waiting threads
		try
		{
			synchronized (mCallEvent)
			{
				mActiveConnection = true;
				mSignalConnection = c;
				mCallEvent.notifyAll();
			}
		}
		catch (Exception e)
		{
			// What are you going to do?
		}
	}
	
	/**
	 * This method informs the application that it should abort the call it was
	 * waiting for.
	 * 
	 * @param ase ithe AbortSignalEvent describing the reason for the abort and
	 * which indicates the user that aborted the invite and returns the connection object
	 * the onInvite call gave signaling, if any.
	 * 
	 * @see cnrg.itx.signal.SignalEvent.AbortSignalEvent
	 * @see cnrg.itx.signal.client.SignalingObserver 
	 */
	public void onAbortCall(AbortSignalEvent ase)
	{	
		// Notify any waiting threads
		try
		{
			synchronized (mCallEvent)
			{
				mActiveConnection = false;
				mCallEvent.notifyAll();
			}
		}
		catch (Exception e)
		{
			// What are you going to do?
		}
	}
	
	/**
	 * This method informs the application that a peer application has hung up.
	 * 
	 * @param hse the HangupSignalEvent that contains all the information about
	 * the application that has hung up.
	 * 
	 * @see cnrg.itx.signal.SignalEvent.HangupSignalEvent
	 * @see cnrg.itx.signal.client.SignalingObserver
	 */
	public void onHangup(HangupSignalEvent hse)
	{		
		// Notify any waiting threads
		try
		{
			synchronized (mHangupEvent)
			{
				mActiveConnection = false;
				mHangupEvent.notifyAll();
			}
		}
		catch (Exception e)
		{
			// What are you going to do?
		}		
	}
	
	/**
	 * This method informs the application that a DTMF has been received.
	 * 
	 * @param dse the DTMFSignalEvent that contains the tone(s).
	 * 
	 * @see cnrg.itx.signal.SignalEvent.DTMFSignalEvent
	 * @see cnrg.itx.signal.client.SignalingObserver
	 */
	public void onDTMF(DTMFSignalEvent dtmfse)
	{
		SignalConnection connection = dtmfse.getSignalConnection();
	}	
	
	/**
	 * Called in response to an UNEXPECTED asynchronous server disconnect.
	 */
	protected void onServerDisconnect()
	{	
		try
		{				
			mClientSession.endSession(false);
		}
		catch (Exception e)
		{
		}
		
		// Reset all state
		mPresentationInProgress = false;
	}
	
	/**
	 * Called in response to an UNEXPECTED asynchronous server hangup.
	 */
	protected void onServerHangup()
	{
		if (!mPresentationInProgress)
		{
			return;
		}
		
		// Reset all state
		mPresentationInProgress = false;
	}		
	
	/**
	 * Parses a SPT parameter file to extract the presentation path and
	 * list of available host server(s)/port(s).  Calling this function
	 * sets the client to SPOT mode.
	 * 
	 * @param SPTFilename name of the SPT file
	 * @throws <code>ClientException</code> on error
	 */
	private void parseSPTFile(String SPTFilename) throws ClientException
	{
		BufferedReader inputStream;
		String currentLine;
		int index;

		// Try to open the parameter file
		try
		{
			inputStream = new BufferedReader(new InputStreamReader(new FileInputStream(SPTFilename)));
		}
		catch (Exception e)
		{
			throw new ClientException(SPT_PARSE_ERROR);
		}

		// Get the parameters
		mServerList.removeAllElements();
		mServerPortList.removeAllElements();
		try
		{
			// Get the first line
			currentLine = inputStream.readLine();

			// Create presentation information
			mPresentationInfo = new PresentationInfo(currentLine);

			// Servers / ports
			while ((currentLine = inputStream.readLine()) != (String)null)
			{
				// Parse the next parameter line
				index = currentLine.lastIndexOf(':');
				if (index == -1)
				{
					throw new IOException();
				}

				// Get the server and port
				mServerList.addElement(currentLine.substring(0, index));
				mServerPortList.addElement(Integer.valueOf(currentLine.substring(index + 1)));
			}
		}
		catch (EOFException e)
		{
		}
		catch (Exception e)
		{
			throw new ClientException(SPT_PARSE_ERROR);
		}
		
		// Initialize the ITX telephony subsystem
		initializeTelephony();

		// The client is now initialized
		mClientMode = ClientPresentationControl.SPOT_CONTROL;
	}

	/**
	 * Parses a PAM file to extract the PAM and presentation path.  Calling this
	 * function sets the client mode to PAM_CONTROL.
	 * 
	 * @param PAMFilename name of the PAM file
	 * @throws <code>ClientException</code> on error
	 */
	private void parsePAMFile(String PAMFilename) throws ClientException
	{
		File PAMFile;
		FileInputStream	fileInput;
		ObjectInputStream objectInput;

		// Create a file object around the PAM file
		try
		{
			PAMFile = new File(PAMFilename);
		}
		catch (Exception e)
		{
			throw new ClientException(PAM_PARSE_ERROR);
		}

		// Get the PAM object from the PAM file
		try
		{
			fileInput = new FileInputStream(PAMFile);
			objectInput = new ObjectInputStream(fileInput);
			mPAM = (PAM)objectInput.readObject();
		}
		catch (Exception e)
		{
			throw new ClientException(PAM_PARSE_ERROR);
		}

		// Get the presentation info
		mPresentationInfo = new PresentationInfo(PAMFile.getAbsolutePath(),
												 SPOTDefinitions.PAM_FILE_SUFFIX);

		// The client is now initialized
		mClientMode = ClientPresentationControl.PAM_CONTROL;
	}
	
	/**
	 * Initializes the ITX telephony subsystem.
	 * 
	 * @throws <code>DirectoryServiceException</code> on error
	 */
	private void initializeTelephony() throws ClientException
	{
		boolean loginFinished = false;
		SPOTPreferences preferences = new SPOTPreferences();
		
		while (!loginFinished)
		{
			// Bring up the user login GUI
			ClientLoginGUI loginGUI = new ClientLoginGUI();
		
			// Did the user login?
			if (!loginGUI.userLoggedIn())
			{
				throw new ClientException(USER_LOGIN_FAILED);
			}
		
			// Get the telephony settings
			mITXUser = loginGUI.getUserID();
			mITXPassword = loginGUI.getUserPassword();
		
			if (mITXDestinationSpecified = loginGUI.destinationSpecified())
			{
				mITXDestination = loginGUI.getDestinationID();
				
				// Make sure the user didn't specify his own ID
				if (mITXDestination.equals(mITXUser))
				{
					mITXDestinationSpecified = false;
				}
			}
		
			// Create the telephony signaling component
			try
			{
				mTelephonySignaling = new DesktopSignaling(this, mITXUser, mITXPassword, SPOTDefinitions.SPOT_CLIENT, preferences.getSPOTDirectoryService());
				loginFinished = true;
			}
			catch (DirectoryServiceException e)
			{
			}
		}
		
		// Create telephony event objects
		mCallEvent = new Object();
		mHangupEvent = new Object();
		
		mTelephonyEnabled = true;
	}
	
	/**
	 * Shuts down the ITX telephony subsystem.
	 */
	private void shutdownTelephony()
	{
		// Logout
		mTelephonySignaling.logout();
	}

	/**
	 * Initiates the telephony connection with the SPOT server.
	 * 
	 * @param presentationHome root directory of the local SPOT directory hierarchy
	 * throws <code>ClientException</code>, <code>ClientSessionException</code> on error
	 */
	private void initiateTelephonyConnection() throws ClientException, ClientSessionException
	{
		SPOTMessage message;
		
		// Notify the server that we are ready to accept the ITX call
		if (mITXDestinationSpecified)
		{
			mClientSession.sendMessage(SPOTMessage.CONNECT, new UserID(mITXDestination));
		}
		else
		{
			mClientSession.sendMessage(SPOTMessage.CONNECT, new UserID(mITXUser));
		}
		
		// Read the server's response
		message = mClientSession.receiveMessage();
		if (message.mType != SPOTMessage.ACK)
		{
			throw new ClientException(SERVER_ERROR);
		}
		
		// Wait for the call
		if (!mITXDestinationSpecified)
		{
			try
			{		
				synchronized (mCallEvent)
				{
					if (!mActiveConnection)
					{
						mCallEvent.wait();
					}
				}
			}
			catch (InterruptedException e)
			{
				throw new ClientException(e.getMessage());
			}
		
			// Was it a success?
			if (!mActiveConnection)
			{
				throw new ClientException(CONNECTION_NOT_ESTABLISHED);
			}
			
			// Open the connection
			try
			{
				mSignalConnection.getConnection().open();
			}
			catch (DataException e)
			{
				throw new ClientException(e.getMessage());
			}
		}
		else
		{
			// Done
			mActiveConnection = true;
		}
	}
	
	/**
	 * Stops the telephony connection with the SPOT server.
	 */
	private void closeTelephonyConnection()
	{
		// Wait for the server to hangup
		if (!mITXDestinationSpecified)
		{
			try
			{		
				synchronized (mHangupEvent)
				{
					if (mActiveConnection)
					{
						mHangupEvent.wait();
					}
				}
			}
			catch (InterruptedException e)
			{
			}
		}
		else
		{
			// Done
			mActiveConnection = false;
		}
	}	

	/**
	 * Entry point for the SPOT client application:
	 * <p>
	 * client [SPOT filename][PAM filename][?]
	 * 
	 * @param args command line arguments
	 */
	public static void main(String[] args)
	{
		// 'The' client object and GUI
		Client theClient = new Client();

		// Test for arguments
		if (args.length >= 1)
		{
			if (args[0].compareTo(SPOT_HELP_STRING) == 0)
			{
				System.out.println("The SPOT client\n\nUsage : client [SPOT filename][PAM filename][?]");
				return;
			}
			try
			{
				theClient.initialize(args[0]);
			}
			catch (Exception e)
			{
				ClientGUI.showErrorDialog("Error initializing Client: " + e.getMessage());
				System.exit(-1);
			}
		}

		// Layout the Client GUI
		ClientGUI theClientGUI = new ClientGUI(theClient);
		theClientGUI.start();
	}
}

/**
 * A <code>ClientException</code> is an exception thrown by a <code>Client</code>.
 * 
 * @version 1.0, 12/15/1998
 * @author Jason Howes
 * @see	cnrg.apps.spot.client.Client
 */
class ClientException extends Exception
{
	/**
	 * Class constructor.
	 * 
	 * @param msg exception information.
	 */
	public ClientException(String msg)
	{
		super("<ClientException> :: " + msg);
	}

	/**
	 * Class constructor.
	 */
	public ClientException()
	{
		super("<ClientException>");
	}
}