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

import cnrg.itx.ds.*;
import cnrg.itx.datax.*;
import cnrg.itx.datax.devices.*;
import cnrg.itx.signal.SignalEvent.*;		   

import java.util.*;
import java.net.*;
import java.io.*;

/** The primary Signaling Object that allows the application to access all telephony services.
 */
public class DesktopSignaling
{
	/******************** Application specific information *******************/
	
	/** email address or phone number **/
	protected UserID myUID = null;
	/** user's password **/
	protected Password myPassword = null;
	/**  Description of application **/
	protected String myDesc = "";
	/** String representation of IP Address **/
	protected String myName = "";
	
	/******************** Object Handles *******************/
	
	/** Directory Service Handle **/
	protected DirectoryService myDirS = null;
	/** DesktopSignalingServer Handle **/
	protected DesktopSignalingServer myDSS = null;
	/** Application's (that implements SignalingObserver) Handle **/
	protected SignalingObserver myApp = null;

	/******************** Internal variables *******************/
	
	/** Hashtable of all Signal connections **/
	protected Hashtable myConnectionList = null;
	/** Unique sequence number **/
	protected long myConnSeqNumber = 0;
	
	/******************** Location descriptors *******************/
	
	/** IP Address in InetAddress format **/
	protected InetAddress myIAdd = null;
	/** Location of this machine; Used by Server Thread, DS & App. **/
	protected Location myLoc = null;
	/** Port Server Thread will listen on. **/
	protected int myServerPort = -1;
	
	/** Timeout value for all Signaling Sockets using TCP  **/
	protected static int TIMEOUT = 20000;

	/******************** SIP parameters *******************/
	
	/** true if this DesktopSignaling implements SIP; false otherwise **/
    protected static boolean useSIP = false;
	/** the SIP version this DesktopSignaling implements **/
    protected static String SIPVersion;
	/** the transport protocol used when sending SIP packets **/
    protected static String SIPTransport;
	/** the type of media used for the body **/
	protected static String SIPContentType;
	/** the number of digits that define a long distance phone call **/
    protected static int globalPhoneValue = 10;
	
	/**
	 * Constructor for a DesktopSignaling component for an application with no specified description and config file.
	 * 
	 * @param  so the SignalingObserver handle for the Application
	 * @param  uid the UserID of the user
	 * @param  pass the password of the user
	 * 
	 * @exception DirectoryServiceException
	 * @see cnrg.itx.ds.DirectoryServiceException
	 */
	public DesktopSignaling(SignalingObserver so, String uid, String pass) throws DirectoryServiceException{
		this(so, uid, pass, "UNKNOWN", null);
	}
	
	/**
	 * Constructor for DesktopSignaling with an app description and config file for
	 * Directory Service
	 *
	 * @param  so the SignalingObserver handle for the Application
	 * @param  uid the UserID of the user
	 * @param  pass the password of the user
	 * @param  desc the String representing the type of application instantiating this component
	 * @param  config is the name of the configuration file to use for Directory Service
	 * 
	 * @exception DirectoryServiceException
	 * @see cnrg.itx.ds.DirectoryServiceException	 
	*/
	public DesktopSignaling(SignalingObserver so, String uid, String pass, String desc, String config) throws DirectoryServiceException{
		StringTokenizer st = null;
		myConnectionList = new Hashtable();
		myDesc = desc;	
		myUID = new UserID(uid);
		myPassword = new Password(pass);
		myApp = so;
		try{	
			myIAdd = InetAddress.getLocalHost();
		}
		catch(UnknownHostException uhe){
			uhe.printStackTrace();
		}
		st = new StringTokenizer(myIAdd.toString(), "/");
		myName = st.nextToken();
		myName = st.nextToken();

		myDSS = new DesktopSignalingServer(this);
		myLoc = new Location("I", myName+":"+Integer.toString(myServerPort), myDesc);
		try{
			if (config == null)
				myDirS = new DirectoryService();
			else
				myDirS = new DirectoryService(config);			
			Register();
			myDSS.start();
		}
		catch(DirectoryServiceException dse){
			throw dse;
		}
	}
	
	/**
	 * Constructor for DesktopSignaling as above with a boolean stating if this uses the SIP protocol
	 *
	 * @param  so the SignalingObserver handle for the Application
	 * @param  uid the UserID of the user
	 * @param  pass the password of the user
	 * @param  desc the String representing the type of application instantiating this component
	 * @param  config is the name of the configuration file to use for Directory Service
	 * @param  ifSIP true if DesktopSignaling uses SIP; false otherwise
	 * 
	 * @exception DirectoryServiceException
	 * @see cnrg.itx.ds.DirectoryServiceException	 
	*/
	public DesktopSignaling(SignalingObserver so, String uid, String pass, String desc, String config, boolean ifSIP) 
		throws DirectoryServiceException
	{
		useSIP = ifSIP;
		StringTokenizer st = null;
		myConnectionList = new Hashtable();
		myDesc = desc;	
		myUID = new UserID(uid);
		myPassword = new Password(pass);
		myApp = so;
		try{	
			myIAdd = InetAddress.getLocalHost();
		}
		catch(UnknownHostException uhe){
			uhe.printStackTrace();
		}
		st = new StringTokenizer(myIAdd.toString(), "/");
		myName = st.nextToken();
		myName = st.nextToken();

		myDSS = new DesktopSignalingServer(this,useSIP);
		myLoc = new Location("I", myName+":"+Integer.toString(myServerPort), myDesc);
		try{
			if (config == null)
				myDirS = new DirectoryService();
			else
				myDirS = new DirectoryService(config);			
			Register();
			myDSS.start();
		}
		catch(DirectoryServiceException dse){
			throw dse;
		}
		SIPVersion = new String("SIP/2.0");
		SIPTransport = new String("/TCP");
		SIPContentType = new String("text/plain");
	}

        /**
         * Constructor for DesktopSignaling for applications that run
         * without a Directory Service. They don't register themselves
         * nor do they do directory lookups.  This version of the constructor
	     * listens on a free port selected by the Java library.
         *
         * @param so - SignalingObserver that will handle SigEvents
	 */
	 public DesktopSignaling (SignalingObserver so) {
            initDeskSig(so);
	    myDSS = new DesktopSignalingServer(this);
	    myLoc = new Location("I", myName+":"+Integer.toString(myServerPort), "");
	    myDSS.start();
	 }

        /**
         * Constructor for DesktopSignaling for applications that run
         * without a Directory Service. They don't register themselves
         * nor do they do directory lookups.  This version listens on a
	     * specified port.
         *
         * @param this - SignalingObserver that will handle SigEvents
	     * @param port - the port on which the server should listen.  (If 0,
	     *     the server will listen on any free port)
	     */
	 public DesktopSignaling (SignalingObserver so, int port) {
            initDeskSig(so);
	    myDSS = new DesktopSignalingServer(this,port);
	    myLoc = new Location("I", myName+":"+Integer.toString(myServerPort), "");
	    myDSS.start();
	 }
	  
	 /**
         * Constructor for DesktopSignaling for applications that run
         * without a Directory Service.  They don't register themselves
         * nor do they do directory lookups.  This version listens on a
		 * specified port and specifies a the name of the user.
         *
         * @param this - SignalingObserver that will handle SigEvents
         * @param uid - String representing the identity of the client.
	     * @param port - the port on which the server should listen.  (If 0,
	     *     the server will listen on any free port)
	     */
	 public DesktopSignaling (SignalingObserver so, String uid, int port) {
            initDeskSig(so);
		myUID = new UserID(uid);
		myDSS = new DesktopSignalingServer(this,port);
		myLoc = new Location("I", myName+":"+Integer.toString(myServerPort), "");
	    myDSS.start();
	 }

	 /**
	  * Sets up selected fields in the DesktopSignaling object.
	  * Invoked by the various constructors.
	  *
          * @param so - SignalingObserver that will handle SigEvents
	  * @return None
	  */
	 private void initDeskSig (SignalingObserver so) {
	    StringTokenizer st = null;
	    myConnectionList = new Hashtable();
	    myUID = new UserID("anonymous");
	    myApp = so;
	    try { myIAdd = InetAddress.getLocalHost(); }
	    catch(UnknownHostException uhe){ uhe.printStackTrace(); }
	    st = new StringTokenizer (myIAdd.toString(), "/");
	    myName = st.nextToken();
	    myName = st.nextToken();
	 }

	/**
	 * Dials a peer application and returns a SignalConnection containing the 
	 * connection object to use for communication.  This dial is blocking.
	 * It automatically tries all locations for the given user and returns the first established connection.
	 * 
	 * @param   userid the String representing the email address or phone number or userID to call
	 * @return  a SignalConnection object for data transfer
	 * @exception  DesktopSignalingException
	 * @exception  DataException
	 * @exception  DirectoryServiceException
	 * 
	 * @see     cnrg.itx.signal.SignalConnection
	 * @see		cnrg.itx.datax.DataException
	 * @see     cnrg.itx.signal.DesktopSignalingException
	 * @see		cnrg.itx.ds.DirectoryServiceException
	 */
	public SignalConnection Dial(String userid) throws 
	DataException, DesktopSignalingException, DirectoryServiceException {
	   return Dial(userid, getDefaultInputChannel(), getDefaultOutputChannel());
	}
	
	/**
	 * Returns a SignalConnection containing the connection object to use for communication. This dial is blocking.
	 * It automatically tries all locations for the given user and returns the first established connection.
	 * The application provides the input and output channels to use for the call.
	 * 
	 * @param userid the String representing the email address or phone number or userID to call
	 * @param cInput Input Channel to add to result
	 * @param cOutput Output Channel to add to result
	 * @return  a SignalConnection object for data transfer
	 * @exception  DesktopSignalingException
	 * @exception  DataException
	 * @exception  DirectoryServiceException
	 * 
	 * @see     cnrg.itx.signal.SignalConnection
	 * @see		cnrg.itx.datax.DataException
	 * @see     cnrg.itx.signal.DesktopSignalingException
	 * @see		cnrg.itx.ds.DirectoryServiceException
	 */
	public SignalConnection Dial(String userid, Channel cInput, Channel cOutput) throws DataException, DesktopSignalingException, DirectoryServiceException
	{
		Location destAdd;
		Result ret;
		SignalConnection sc = null;
		Long seq = null;
		boolean busyFlag = false;

				
		UserID destUID = new UserID(userid);
		LocationList dest = null;
		try{
			dest = myDirS.getLocationListByID(destUID);
		}
		catch(DirectoryServiceException dse){
			cInput.close();
			cOutput.close();
			throw dse;
		}
		
		boolean go = false;
				
		//main outer loop vars
		boolean outLast = false;
		int outCount = dest.count();
		
		if (outCount == 0) {
			cInput.close();
			cOutput.close();
			throw new DirectoryServiceException("The user you are trying is not logged on to our network.\n");
		}
		
		AudioConnection ac = new AudioConnection(cInput, cOutput);
		//inner loop vars
		int inCount = -1;//number of items in inner list
		
		for(int i=0; i < outCount; i++){ //Dial all destinations until successful
			if (i == outCount-1)
				outLast = true;
			
			if(i == 0)
				destAdd = dest.first();
			else
				destAdd = dest.next();
			
			go = destAdd.isDialable();
			if (go)
			{
				try {
					ret = tryDialSequence(destUID, destAdd, ac);
				}
				catch (ConnectException ce) 
				{
					if(outLast){
						ac.close();
						throw ce;
					}
					else
						continue;
				}
// TODO (DJB) replace the switch statement with the following code:
/* 
    sc = processResult(ret,ac,outLast,busyFlag,userid)
    // see if we should try again
    if (sc != null) return sc;
    busyFlag=(ret.getResult() == SignalID.BUSY);
*/
				switch(ret.getResult()){
					case SignalID.ACCEPT://exit
						seq = ret.getSeq();
						sc = getConnection(seq);
						if (useSIP == false)
							sc.startKeepAlive(this);
						return sc;						
					case SignalID.REJECT://exit
						ac.close();
						throw new InviteRejectException("The user "+ userid + " has not accepted your call.  Please try again later.");
						
					case SignalID.INVALID://exit
						ac.close();
						throw new InviteInvalidException("Fatal Application Error.");
						
					case SignalID.BUSY:
						busyFlag = true;
						if(outLast){//exit
							ac.close();
							throw new InviteBusyException("The user "+ userid + " is currently busy.  Please try again later.");
						}
						else//try next
							break;
						
					case SignalID.TIMEOUT:
						if(outLast){//exit
							ac.close();
							throw new InviteTimeoutException("The user "+ userid + " is not responding.  Please try again later.");
						}
						else//try next
							break;
					case SignalID.INCOMPATIBLE:
						if(outLast){//exit
							ac.close();
							if(busyFlag){
								throw new InviteBusyException("The user "+ userid + " is currently busy.  Please try again later.");
							}
							else{
								throw new InviteIncompatibleException("The user "+ userid + " is not compatible with your application.");
							}
						}
						else//try next
							break;						
				}				
			}
			else {//We do not have a list of IP's, get new LocationList based on phone #, email or userID
				UserID newUID = destAdd.getUID();				
				LocationList ll = null;
				Location newDest = null;
				try{
					ll = myDirS.getLocationListByID(newUID);
				}
				catch(DirectoryServiceException dse){
					cInput.close();
					cOutput.close();
					throw dse;
				}

				
				if (ll != null)
					inCount = ll.count();
				
				if (inCount == 0)
					throw new DirectoryServiceException("The user you are trying is not logged on to our network.\n");

				for (int j=0; j < inCount; j++){				
					if (j==0)
						newDest = ll.first();
					else
						newDest = ll.next();
					
					if (newDest.isDialable()){
						try{
							ret = tryDialSequence(newUID, newDest, ac);
						}
						catch(ConnectException ce){
							ac.close();
							throw ce;
						}
				
						switch(ret.getResult()){
							case SignalID.ACCEPT:
								seq = ret.getSeq();
								sc = getConnection(seq);
								if (useSIP == false)
									sc.startKeepAlive(this);
								return sc;									
							case SignalID.REJECT://exit loop, equivalent to 1st level busy, so we try next item in main list.
								j = inCount;
								break;
									
							case SignalID.INVALID://exit loop
								ac.close();
								throw new InviteInvalidException("Fatal Application Error.");
									
							case SignalID.BUSY://try next in inner loop
									break;
										
							case SignalID.TIMEOUT://try next in inner loop
									break;
						}//inner switch
					}//if it is Dialable
				}//for - inner for loop
			}//else	- try new list based on a new UID	
		}//for - main outer for loop
		return null;
	}


	/**
	 * Returns a SignalConnection containing the connection object to use for communication.
	 * This dial is non-blocking and requires the application to implement the SignalConnectionObserver interface.
	 * The method returns immediately with a SignalConnection.  The SignalConnection can then be
	 * used to control the call (eg. startCall, abortCall etc.)
	 * 
	 * @param userid the String representing the email address or phone number or userID to call
	 * @param userLoc the Location object that needs to be dialed.
	 * @param sco the SignalConnectionObserver object that is used by the application to control the dial process
	 * @return  a SignalConnection object for the call.
	 * @exception  DesktopSignalingException
	 * @exception  DataException
	 * 
	 * @see		cnrg.itx.signal.SignalConnectionObserver
	 * @see     cnrg.itx.signal.SignalConnection
	 * @see		cnrg.itx.datax.DataException
	 * @see     cnrg.itx.signal.DesktopSignalingException
	 */
	public SignalConnection Dial(String userid, Location userLoc, SignalConnectionObserver sco) throws DataException, DesktopSignalingException {
		return Dial(userid, userLoc, getDefaultInputChannel(), 
		   getDefaultOutputChannel(), sco);	
	}


	/**
	 * Returns a SignalConnection containing the connection object to use for communication.
	 * This dial is non-blocking and requires the application to implement the SignalConnectionObserver interface.
	 * This method allows the app to specify the input and output channels to use for data exchange.
	 * The method returns immediately with a SignalConnection.  The SignalConnection can then be
	 * used to control the call (eg. startCall, abortCall etc.)
	 * 
	 * @param userid the String representing the email address or phone number or userID to call
	 * @param userLoc the Location object that needs to be dialed
	 * @param cInput the input channel to use for data exchange
	 * @param cOutput the output channel to use for data exchange
	 * @param sco the SignalConnectionObserver object that is used by the application to control the dial process
	 * @return  a SignalConnection object for the call.
	 * @exception  DesktopSignalingException
	 * @exception  DataException
	 * 
	 * @see		cnrg.itx.signal.SignalConnectionObserver
	 * @see     cnrg.itx.signal.SignalConnection
	 * @see		cnrg.itx.datax.DataException
	 * @see     cnrg.itx.signal.DesktopSignalingException
	 */
	public SignalConnection Dial(String userid, Location userLoc, Channel cInput, Channel cOutput, SignalConnectionObserver sco) throws DataException, DesktopSignalingException {	
		AudioConnection ac = new AudioConnection(cInput, cOutput);
		SignalConnection sc = new SignalConnection();
		DialThread d = new DialThread(new UserID(userid), userLoc, ac, sco, sc, this);
		sc.setDialer(d);
		return sc;
	}


	// Here are some Location-based Dial() methods

	/**
	 *  Returns a SignalConnection containing the data connection
	 *  object to be used to transferring sound data.  This dial
	 *  is blocking and returns to the user only when the invite
	 *  has received a reply from the peer, or if we timeout.
	 *
	 *  The default name of our peer application is 
	 *  "anonymous server", and default Channels are used for
	 *  data communication (mic, speakers, network)
	 *
	 * @param peerLoc - the Location of our peer
	 *
	 * @exception DesktopSignalingException is thrown when
	 * @exception DataException is thrown when default audio
	 *    connection could not be set up for some reason
	 */
	public SignalConnection Dial ( Location peerLoc ) 
	throws DataException, DesktopSignalingException 
	{
	   return Dial ("anonymous server", peerLoc );
	}

	/**
	 *  Returns a SignalConnection containing the data connection
	 *  object to be used to transferring sound data.  This dial
	 *  is blocking and returns to the user only when the invite
	 *  has received a reply from the peer, or if we timeout.
	 *
	 *  The default name of our peer application is 
	 *  "anonymous server".
	 *
	 * @param peerLoc - the Location of our peer
	 * @param cInput - Input channel to be used for data communications
	 * @param cOuput - output channel to be used for data communications
	 *
	 * @exception DesktopSignalingException is thrown when
	 */
	public SignalConnection Dial ( Location peerLoc,
	   Channel cInput, Channel cOutput ) 
	   throws DataException,  DesktopSignalingException 
	{
	   return Dial ("anonymous server", peerLoc, cInput, cOutput );
	}

	/**
	 *  Returns a SignalConnection containing the data connection
	 *  object to be used to transferring sound data.  This dial
	 *  is blocking and returns to the user only when the invite
	 *  has received a reply from the peer, or if we timeout.
	 *
	 *  Default channels are used for data communication
	 * (mic, speakers, and network)
	 *
	 * @param userid - name for server app, our peer
	 * @param peerLoc - the Location of our peer
	 *
	 * @exception DesktopSignalingException is thrown when
	 * @exception DataException is thrown when the default audio
	 * connection could not be set up for some reason
	 */
	public SignalConnection Dial (String userid,  Location peerLoc ) 
	throws DataException, DesktopSignalingException 
	{
	   Channel cInput = getDefaultInputChannel();
	   Channel cOutput = getDefaultOutputChannel();
	   return Dial (userid, peerLoc, cInput, cOutput );
	}

	/**
	 *  This is the primary Location-based, blocking, Dial method.
	 *  Returns a SignalConnection containing the data connection
	 *  object to be used to transferring sound data.  This dial
	 *  is blocking and returns to the user only when the invite
	 *  has received a reply from the peer, or if we timeout.
	 *
	 * @param userid - name for server app, our peer
	 * @param destAdd - the Location of our peer
	 * @param cInput - Input channel to be used for data communications
	 * @param cOuput - output channel to be used for data communications
	 *
	 * @exception DesktopSignalingException is thrown when
	 * @exception DataException is thrown when
	 */
	public SignalConnection Dial (String userid,  Location destAdd,
	   Channel cInput, Channel cOutput ) 
	   throws DataException, DesktopSignalingException 
	{
	   SignalConnection sc = null;
	   Result ret = null;
	   UserID destUID = new UserID(userid);
	   AudioConnection ac = new AudioConnection(cInput,cOutput);

           try { ret = tryDialSequence(destUID, destAdd, ac); }
           catch (ConnectException ce) { ac.close(); throw ce; }

	   // The Dial sequence completed with result in "ret".
	   // The following will throw an exception or return an sc.
	   sc = processResult(ret,ac,false,false,userid);
	   return sc;
	}

	/**
	 *  Returns a SignalConnection containing the data connection
	 *  object to be used to transferring sound data.  This dial
	 *  is non-blocking and requires the application to implement
	 *  the SignalConnectionObserver interface.  This method returns
	 *  immediately with a SignalConnection.  The SignalConnection
	 *  can then be used to control the call (e.g. startCall, abortCall, etc.)
	 *
	 *  Default channels are used for data communication
	 *  (mic, speakers, and network).  Default userid of "anonymous server" 
	 *  is used.
	 *
	 * @param peerLoc - the Location of our peer
	 * @param sco the SignalConnectionObserver object that is used by the
	 *    application to control the dial process
	 * @return a SignalConnection object for the call.
	 * @exception DesktopSignalingException is thrown when?
	 * @exception DataException is thrown when the default audio
	 *    connection could not be set up for some reason.
	 */
	public SignalConnection Dial (Location peerLoc, SignalConnectionObserver sco ) 
	throws DataException, DesktopSignalingException 
	{
	   Channel cInput = getDefaultInputChannel();
	   Channel cOutput = getDefaultOutputChannel();
	   return Dial ("anonymous server", peerLoc, cInput, cOutput, sco );
	}

	/**
	 *  Returns a SignalConnection containing the data connection
	 *  object to be used to transferring sound data.  This dial
	 *  is non-blocking and requires the application to implement
	 *  the SignalConnectionObserver interface.  This method returns
	 *  immediately with a SignalConnection.  The SignalConnection
	 *  can then be used to control the call (e.g. startCall, abortCall, etc.)
	 *
	 *  Default userid of "anonymous server" is used.
	 *
	 * @param peerLoc - the Location of our peer
	 * @param cInput - Input channel to be used for data communications
	 * @param cOutput - Output channel to be used for data communications
	 * @param sco the SignalConnectionObserver object that is used by the
	 *    application to control the dial process
	 * @return a SignalConnection object for the call.
	 * @exception DesktopSignalingException is thrown when?
	 * @exception DataException is thrown when the default audio
	 *    connection could not be set up for some reason.
	 */
	public SignalConnection Dial (Location peerLoc, Channel cInput,
	   Channel cOutput, SignalConnectionObserver sco ) 
	throws DataException, DesktopSignalingException 
	{
	   return Dial ("anonymous server", peerLoc, cInput, cOutput, sco );
	}
	
	/**
	 * Hangs up a given SignalConnection being used for a call.
	 * 
	 * @param  sc the SignalConnection to hangup
	 * @return void
	 * @exception  ConnectException
	 */
	public void Hangup(SignalConnection sc)  throws ConnectException {
		Socket sock = null;
		try{			
			sc.close();			
		}
		catch (DataException de){
			de.printStackTrace();
		}		
		
		String ipadd = sc.getIP();
		int p = sc.getPort();
		Long seq = sc.getPeerSeqNumber();
		Long mySeq = sc.getSeqNumber();
		InvitePacket oldInvite = sc.getInvite();
		KeepAlive ka = sc.getKeepAlive();
		
		if(ka != null)
			ka.cleanup();
		removeConnection(seq);
		
		InvitePacket ip = new InvitePacket(myUID, myLoc, myDesc, SignalID.HANGUP);
		ip.setSeqNumber(seq);
		if (useSIP == true)
		{
			UserID dest = null;
			if (sc.checkIfMyInvite() == true)
				dest = oldInvite.getDestination();
			else
				dest = oldInvite.getSenderID();
			ip.setDestination(dest);
			String id = oldInvite.getCallID();
			ip.setCallID(id);
			ip.setCSeq(new Long(mySeq.intValue() + 1));
		}
		try{			
			sock = sendNewPacket(ip, ipadd, p);
			sock.close();			
		}
		catch (IOException ioe){			
			ioe.printStackTrace();			
		}
	}
	
	/** This method sends a DTMF tone, as a string, to the destination on a given SignalConnection
	 * 
	 * @param   s is the string representation of the DTMF tone.
	 * @param   sc is the SignalConnection currently in use with the peer application.
	 * @return  void
	 * 
	 * @exception  ConnectException
	 */
	public void sendDTMF(String s, SignalConnection sc)  throws ConnectException{
		Socket sock = null;
		if (sc == null)
			return;
		
		String ipadd = sc.getIP();
		int p = sc.getPort();
		Long seq = sc.getPeerSeqNumber();
		
		InvitePacket ip = new InvitePacket(myUID, myLoc, myDesc, SignalID.SENDDTMF);
		ip.setCustomObject(s);
		ip.setSeqNumber(seq);
		
		if (useSIP == true)
		{
			InvitePacket oldInvite = sc.getInvite();
			UserID dest = null;
			if (sc.checkIfMyInvite() == true)
				dest = oldInvite.getDestination();
			else
				dest = oldInvite.getSenderID();
			ip.setDestination(dest);
			String id = oldInvite.getCallID();
			ip.setCallID(id);
			Long mySeq = sc.getSeqNumber();
			ip.setCSeq(mySeq);
		}
		
		try{
			sock = sendNewPacket(ip, ipadd, p);
			sock.close();
		}
		catch(DesktopSignalingException dse){
			dse.printStackTrace();
		}
		catch (IOException ioe){
			ioe.printStackTrace();			
		}
	}

	/** This method queries a peer application to check if it is still running.  If the peer application does not respond
	 *  then the data connection between them is closed and removed.
	 * 
	 * @param   sc is the SignalConnection currently in use with the peer application.
	 * @return  TRUE if the application is still running, FALSE otherwise
	 * 
	 * @exception  ConnectException
	 */	
	public boolean isAlive(SignalConnection sc) throws ConnectException{
		Socket sock = null; 
		if (sc == null)
			return false;

		String ipadd = sc.getIP();
		int p = sc.getPort();
		Long seq = sc.getPeerSeqNumber();

		InvitePacket ip = new InvitePacket(myUID, myLoc, myDesc, SignalID.ALIVEQUERY);
		ip.setSeqNumber(seq);		
		
		try{
			sock = sendNewPacket(ip, ipadd, p);
		}
		catch (ConnectException ce){
			handlePeerNotAlive(sc);
			return false;
		}	
		
		try{
			ip = (InvitePacket) waitForPacket(sock);
		}
		catch (InterruptedIOException iioe){
			handlePeerNotAlive(sc);
			return false;
		}
			
		if(ip.isResultPacket() && ip.wasConfirmed()){//peer app. is alive
				return true;
		}
		else{
			handlePeerNotAlive(sc);
			return false;
		}
	}
	
	/** This method allows application to get a handle to a ready-to-use Directory Service.  The application needs to
	 *  know the Directory Service API.
	 * 
	 * @param   None.
	 * @return  A handle to a DirectoryService Object
	 */
	public DirectoryService getDirectory(){
		return myDirS;
	}

	/** This method allows an application to get a list of possible locations for a
	 *  given user.  This should be used in conjuntion with the non-blocking dial to call
	 * specific locations.
	 * 
	 * @param  userid is the String representation of a user.
	 * @return  A LocationList for the given userid.
	 * @exception DirectoryServiceException
	 */	
	public LocationList getLocationList(String userid) throws DirectoryServiceException{
		UserID destUID = new UserID(userid);
		return myDirS.getLocationListByID(destUID);		
	}
	
	/** This method allows the application to set the timeout value to use for socket connections.
	 * 
	 * @param  t is the timeout in milliseconds to be used for Sockets.
	 * @return  void
	 */	
	public void setTimeout(int t){
		TIMEOUT = t;
	}
	
	/** This method returns the timeout value currently being used for socket connections by Signaling.
	 * 
	 * @param  None
	 * @return  the integer value of the timeout
	 */	
	public int getTimeout(){
		return TIMEOUT;
	}

	/**
	 * This method returns the port on which the SignalingServer is listening
	 * @param None
	 * @return the integer value of port on which we are listening
	 */
       public int getPort () {
	  return myServerPort;
       }

       /**
	* Returns the name of the computer on which we are running
	 * @param None
	 * @return the string value of the the IP address of the
	 *     computer on which we are running
	*/
      public String getName () {
	 return myName;
      }
						   
	/**
         * This method unregisters the Signaling Component with Directory Services
         * (if myDirS is not null) and kills the server thread.
	 * 
	 * @param   None.
	 * @return  void
	 */
	public void logout(){
		if (myDirS != null) {
			int res = unRegister();
			if (res == -1){
				System.out.println("Unregister Failed!\n");
			}
		}
		myDSS.killServer();
	}

/**************************** Internal Protected Methods for Signaling ***************************/	

	
	/** This method is used to handle an incoming call.
	 * 
	 * @param  ise is the InviteSignalEvent that contains all the necessary information about the peer
	 * @param  so is the socket that should be used for communication.
	 * @return void
	 * @exception DesktopSignalingException
	 * 
	 * @see  cnrg.itx.signal.SignalEvent.InviteSignalEvent
	 */
	protected void handleDialInvite(InviteSignalEvent ise, Socket so) throws DesktopSignalingException, DataException{
		PropertiesCollection pc = null;
		AudioConnection ac = null;
		Connection c = null;
		SignalConnection sc = null;
		Location sender = null;
		Long seq = null;
		Long peerseq = null;
				
		myApp.onInvite(ise);
		
		// Get set to send our reply
		InvitePacket result = ise.getInvitePacket();
		String method = SIPInterface.translatePacket(result.getPacketID(),result.getMethodID());
		result.setPacketType(SignalID.RESULT);
				
		if(ise.wasAccepted()){
			c = ise.getConnection();
			pc = ise.getPropertiesCollection();
			try{
				if (c != null){
					c.setPeerProperties(pc);
					pc = c.getProperties();
				}
				else{//Make a new default Audio Connection
						ac = AudioConnection.getDefaultAudioConnection();
						ac.setPeerProperties(pc);
						pc = ac.getProperties();
				}
			}
			catch(DataException de){
			    if (c == null && ac == null) {
			       String reason =
			          "Could not get audio connection, no other connection specified";
			       ise.reject(reason);
				   try{
				      sendPacket(result, so, (new Integer(ErrorResponse.INTERNALERROR)).toString() + reason, method);
				      so.close();
			       } catch(ConnectException ce){
				       ce.printStackTrace();
			       } catch(IOException ioe){
				       ioe.printStackTrace();
		           }
			       myApp.onAbortCall(
                      new AbortSignalEvent(ise.getInvitePacket(), 
                             (Connection)null, reason));
                   return; // all done with this dial invite
                }
                // close the bad connection and go on with the call
				if (c != null)
					c.close();
				else
					ac.close();
                de.printStackTrace();
			}				

            // Assert we have non-null connection
			ise.setPropertiesCollection(pc);
			sender = result.getSenderLoc();
			
			seq = getSequence();
			result.setSeqNumber(seq);//send my seq number
						
			InvitePacket confirm = null;
			try{
				sendPacket(result, so, (new Integer(InfoResponse.OK)).toString(), method);
			}
			catch(ConnectException ce){
				if(ac == null)
					c.close();
				else
					ac.close();
				myApp.onAbortCall(new AbortSignalEvent(ise.getInvitePacket(), ise.getConnection(), "Connection Timed Out"));
				return;
			}
				
			try{
				confirm = (InvitePacket) waitForPacket(so);
				so.close();
			}
			catch (InterruptedIOException iioe){
				if(ac == null)
					c.close();
				else
					ac.close();
				myApp.onAbortCall(new AbortSignalEvent(ise.getInvitePacket(), ise.getConnection(), "Connection Timed Out"));
				return;
			}
			catch(IOException ioe){
					if(ac == null)
						c.close();
					else
						ac.close();
				myApp.onAbortCall(new AbortSignalEvent(ise.getInvitePacket(), ise.getConnection(), "Connection Lost"));
				return;
			}
		
			if(confirm.isConfirmPacket()){
				if(confirm.wasConfirmed()){
					if (ac == null){
						sc = new SignalConnection(c, sender.getIP(), sender.getPort());
					}
					else{
						sc = new SignalConnection(ac, sender.getIP(), sender.getPort());
					}
					if (useSIP == true)
						peerseq = confirm.getCSeq();
					else
						peerseq = new Long(confirm.getSeqNumber().longValue());//get peer seq number
					sc.setSeqNumber(seq);
					sc.setPeerSeqNumber(peerseq);
					sc.setInvite(confirm);
					sc.setSourceInvite(false);
					putConnection(seq, sc);						
					myApp.onStartCall(sc);
					if (useSIP == false)
						sc.startKeepAlive(this);
					return;
				}
				else{		// our peer did not confirm			
					if(ac == null)
						c.close();
					else
						ac.close();
					myApp.onAbortCall(new AbortSignalEvent(ise.getInvitePacket(), ise.getConnection(), "Confirm packet was not confirmed"));
					return;
				}					
			}
			else{
				if(ac == null)
					c.close();
				else
					ac.close();

				myApp.onAbortCall(new AbortSignalEvent(ise.getInvitePacket(), ise.getConnection(), "Did not receive confirm packet"));
			}
		}	
		else{//call was rejected by user
			String rejectReason = null;
			if (result.wasBusy())
				rejectReason = (new Integer(ErrorResponse.BUSYHERE)).toString();
			else
				rejectReason = (new Integer(ErrorResponse.DECLINE)).toString() + " " + result.getReason();
			try{
				sendPacket(result, so, rejectReason, method);
				so.close();
			}
			catch(ConnectException ce){
				ce.printStackTrace();
			}
			catch(IOException ioe){
				ioe.printStackTrace();
			}
		}			
	}
		
	/** This method is used to handle a hangup request.
	 * 
	 * @param  hse is the HangupSignalEvent that contains all the necessary information about the peer
	 * @param  s is the socket that should be used for communication.
	 * @return void
	 * 
	 * @see  cnrg.itx.signal.SignalEvent.HangupSignalEvent
	 */
	protected void handleHangupInvite(HangupSignalEvent hse, Socket s){
		InvitePacket ip = null;
		Long seq = null;
		SignalConnection sc = null;
		KeepAlive ka = null;

		ip = hse.getInvitePacket();
		seq = ip.getSeqNumber();
		sc = getConnection(seq);
		if (sc == null){
			System.out.println("Hangup accepted.  Connection removed from Connection List.");			
		}
		else {
			try{
				removeConnection(seq);			
				sc.close();			
			}
			catch(DataException de){
				System.out.println("Connection could not be closed properly.");
				de.printStackTrace();
			}
			hse.setSignalConnection(sc);
			myApp.onHangup(hse);
			ka = sc.getKeepAlive();
			if(ka != null)
				ka.cleanup();
		}
		
		if(s != null){
			try{
				s.close();
			}
			catch(IOException ioe){
				ioe.printStackTrace();
			}
		}
		System.out.println("Hangup accepted.  Connection removed from Connection List.");
	}
	
	/** This method is used to handle an incoming DTMF tone.
	 * 
	 * @param  dtmfse is the DTMFSignalEvent that contains all the necessary information about the peer
	 * @param  s is the socket that should be used for communication.
	 * @return void
	 * 
	 * @see  cnrg.itx.signal.SignalEvent.DTMFSignalEvent
	 */
	protected void handleSendDTMFInvite(DTMFSignalEvent dtmfse, Socket s){
		InvitePacket ip = null;
		Long seq = null;
		SignalConnection sc = null;
			
		ip = dtmfse.getInvitePacket();
		seq = ip.getSeqNumber();
		sc = getConnection(seq);
		if(sc == null){
			System.out.println("SignalConnection inactive.  Dropping DTMF");
		}
		else{
			dtmfse.setSignalConnection(sc);
			myApp.onDTMF(dtmfse); 
			System.out.println("DTMF String accepted.");		
		}
		
		if (s != null){
			try{
				s.close();
			}
			catch(IOException ioe){
				ioe.printStackTrace();
			}				
		}		
	}
	
	/** This method is used to handle an alive request.
	 * 
	 * @param  ase is the AliveSignalEvent that contains all the necessary information about the peer
	 * @param  s is the socket that should be used for communication.
	 * @return void
	 * 
	 * @see  cnrg.itx.signal.SignalEvent.AliveSignalEvent
	 */
	protected void handleAliveInvite(AliveSignalEvent ase, Socket s){
		InvitePacket ip = null;
		
		ase.confirm();
		ip = ase.getInvitePacket();
		ip.setPacketType(SignalID.RESULT);
		
		try{
			sendPacket(ip, s, null, null);
		}
		catch(ConnectException ce){
			System.out.println("Connection to peer lost.");
			ce.printStackTrace();
		}
		if(s != null){		
			try{
				s.close();
			}
			catch(IOException ioe){
				ioe.printStackTrace();
			}		
		}
		System.out.println("Alive Request Handled.");					
	}
		
	/** This method is used to clear up resources if the peer application is no longer active.
	 * 
	 * @param  sc is the SignalConnection that needs to be closed and removed
	 * @return void
	 * 
	 * @see  cnrg.itx.signal.client.SignalConnection
	 */
	protected void handlePeerNotAlive(SignalConnection sc){
		InvitePacket ip = new InvitePacket(null, null, null, SignalID.HANGUP);
		Long seq = sc.getSeqNumber();
		ip.setSeqNumber(seq);
		HangupSignalEvent hse = new HangupSignalEvent(ip);
		handleHangupInvite(hse, null);
	}
	
	/** The DesktopSignalingServer thread uses this method to register its port with
	 *  DesktopSignaling.
	 * 
	 * @param p is the port the Server thread is listening for requests.
	 * 
	 */
	protected void setServerPort(int p){
		myServerPort = p;
	}
	
	/** This method performs a Dial Sequence including the 3-way handshake.
         *  The result of the handshake is returned
	 *  to the callee method.
	 * @param destUID - the UserID of the callee
	 * @param destAdd - the IP address of the destination
	 * @param ac - the data connection that will be used to transmit data
	 * @exception ConnectException is thrown if 
	 * @returns the result of the sequence as a Result object (inner class)
	 */
	protected Result tryDialSequence(UserID destUID, Location destAdd, Connection ac) throws ConnectException
	{
		SignalConnection sc = null;
		Long peerseq = null;
		Long seq = null;
		Integer cSeq = null;
        String callID = null;
		Socket sock = null;
		
		System.out.println("Dial-> Source: " + myUID.toString() + " & Dest: " + destUID.toString());

		InvitePacket ip = new InvitePacket(myUID, myLoc, myDesc, SignalID.DIAL);
		PropertiesCollection pc = ac.getProperties();
		ip.setPropertiesCollection(pc);
		ip.setDestination(destUID);
		if (useSIP == true)
		{
			seq = getSequence();
			ip.setCSeq(seq);
			callID = (new Integer(myServerPort)).toString() + "-" + seq.toString() + "@" + myName;
			ip.setCallID(callID);
		}
		try{
			sock = sendNewPacket(ip, destAdd.getIP(), destAdd.getPort());
		}
		catch(ConnectException ce){
			System.out.println("Could not send initial invite packet to peer,"
			+ "because " + ce.toString());
			throw ce;
		}
		
		try{
			ip = (InvitePacket) waitForPacket(sock);
		}
		catch(InterruptedIOException iioe){
			System.out.println("User not responding.  Timing out...");
			try{
				sock.close();
			}
			catch(IOException ioe){
				ioe.printStackTrace();
			}			
			return new Result(SignalID.TIMEOUT);
		}
		
		try{
			if (ip.isResultPacket()){
				if(ip.wasAccepted()){
					pc = ip.getPropertiesCollection();
					ac.setPeerProperties(pc);
					peerseq = ip.getSeqNumber();
					sc = new SignalConnection(ac, destAdd.getIP(), destAdd.getPort());
					if (useSIP == false)
					{
						seq = getSequence();
						ip.setSeqNumber(seq);//send my seq number
					}
					sc.setSeqNumber(seq);
					sc.setPeerSeqNumber(peerseq);
					sc.setInvite(ip);
					sc.setSourceInvite(true);
					putConnection(seq, sc);	
					
					ip.confirm();
					ip.setPacketType(SignalID.CONFIRM);
					sendPacket(ip, sock, null, null);
					sock.close();
					return new Result(SignalID.ACCEPT, seq);
				}
				else if(ip.wasRejected()){
					sock.close();
					return new Result(SignalID.REJECT);
				}
				else if(ip.wasBusy()){
					sock.close();
					return new Result(SignalID.BUSY);
				}					
			}
			else{//Wrong Sequence/Packet
				sock.close();
				return new Result(SignalID.INVALID);
			}
		}
		catch (IOException ioe){
			System.out.println("Socket already closed.");
			ioe.printStackTrace();
		}
		catch(DataException de){			
			de.printStackTrace();
			throw new ConnectException("Peer Audio connection not valid");
		}
		
		return new Result(SignalID.INVALID);
	}      
	
	/** This method sends a packet over a TCP connection.  A new socket is created.
	 * 
	 * @param   send is the SigPacket to send to the peer application
	 * @param   ip is the String representation of the IP address of the destination
	 * @param   p is the port of the destination application
	 * 
	 * @return  Socket that was created and used to send the packet
	 * 
	 * @exception DesktopSignalingException
	 */
	protected Socket sendNewPacket(SigPacket send, String ip, int p) throws ConnectException{		
		InetAddress add = null;
		Socket sock = null;
		
		try{
			add = InetAddress.getByName(ip);
		}
		catch(UnknownHostException uhe){
			throw new ConnectException("Could not acquire Inetaddress of peer application");			
		}
		
		try{
			sock = new Socket(add, p);
			sock.setSoTimeout(TIMEOUT);
			DataOutputStream f = new DataOutputStream(sock.getOutputStream());
			ObjectOutputStream s = new ObjectOutputStream(f);
			
			if (useSIP == true)
			{
				if (send != null)
				{
					String sipRequest = null;
					try
					{
						sipRequest = SIPInterface.makeSIPRequest((InvitePacket)send,ip,p);
					}
					catch (SIPException se)
					{
						throw new ConnectException(se.toString());
					}
					if (sipRequest != null)
						s.writeUTF(sipRequest);
					else
						throw new ConnectNullObjectSentException("Error: Bad packet type or method");
				}
				else
					throw new ConnectNullObjectSentException("Error: Null object being sent to peer");
				s.flush();
			}
			else
			{
				if (send != null)
					s.writeObject(send);
				else 
					throw new ConnectNullObjectSentException("Error: Null object being sent to peer");
				s.flush();
			}
		}
		catch(IOException ioe){
			if (sock != null){
				try{
					sock.close();
				}
				catch(IOException e){
				}
			}
			throw new ConnectFailedToOpenSocketException("Socket connection to " + add + ":" + p + " failed.");
		}		
		System.out.println("Sent SigPacket to " + ip + ":" + p + "\n");
		return sock;
	}
	
	/** This method sends a packet over an TCP socket.  No new socket is created.
	 * 
	 * @param   send is the SigPacket to send to the peer application
	 * @param   sock is the existing socket to use
	 * @param   topHeader the header for a SIP response; null if packet is a SIP request or DesktopSignaling does not use SIP
	 * @param   method the SIP method involved; null if DesktopSignaling does not use SIP
	 * @return  void
	 * @exception DesktopSignalingException
	 */
	protected void sendPacket(SigPacket send, Socket sock, String topHeader, String method) throws ConnectException{			
		if (sock == null)
			throw new ConnectNullSocketException("Error:  Cannot send packets on a null socket!\n");
		try{
			DataOutputStream f = new DataOutputStream(sock.getOutputStream());
			ObjectOutputStream s = new ObjectOutputStream(f);
		
			if (useSIP == true)
			{
				if (send != null)
				{
					String sipPacket = null;
					if (topHeader == null)
					{
						try
						{
							sipPacket = SIPInterface.makeSIPRequest((InvitePacket)send,myName,myServerPort);
						}
						catch (SIPException se)
						{
							throw new ConnectException(se.toString());
						}
					}
					else
					{
						try
						{
							sipPacket = SIPInterface.makeSIPResponse((InvitePacket)send,topHeader,method,myName,myServerPort);
						}
						catch (SIPException se)
						{
							throw new ConnectException(se.toString());
						}
					}
					s.writeUTF(sipPacket);
				}
				else 
					throw new ConnectNullObjectSentException("Error: Null object being sent to peer");		
				s.flush();
			}
			else
			{
				if (send != null)
					s.writeObject(send);
				else 
					throw new ConnectNullObjectSentException("Error: Null object being sent to peer");		
				s.flush();
			}
		}
		catch(IOException ioe){
			throw new ConnectException("Socket at destination has been closed.");
		}
		System.out.println("Sent SigPacket on connected socket\n");
	}
	
	/** This method waits for a packet on a given TCP socket.  The wait times out after the
	 *  specified number of seconds (in DesktopSignaling.TIMEOUT) have passed.
	 * 
	 * @param   sock is the existing socket to wait on
	 * @return  SigPacket that is received
	 * @exception InterruptedIOException
	 */
	protected SigPacket waitForPacket(Socket sock) throws InterruptedIOException{
		Object obj = null;
		String sipPacket = null;
		SigPacket mySP = null;
	
		if (useSIP == true)
		{
			try 
			{
				DataInputStream in = new DataInputStream(sock.getInputStream());
				ObjectInputStream s = new ObjectInputStream(in);
				sipPacket = s.readUTF();
			}
			catch(IOException ioe)
			{
				throw new InterruptedIOException("Socket at destination has been closed.");
			}
			try
			{
				mySP = SIPInterface.makeSigPacket(sipPacket);
			}
			catch (SIPException se)
			{
				throw new InterruptedIOException(se.toString());
			}
		}
		else
		{
			try {
				DataInputStream in = new DataInputStream(sock.getInputStream());
				ObjectInputStream s = new ObjectInputStream(in);
				obj = s.readObject();
			}	 
			catch (ClassNotFoundException ce) {
				ce.printStackTrace();
			}
			catch (InterruptedIOException iioe){
				try{
					sock.close();
				}
				catch(IOException ioe){
					ioe.printStackTrace();
				}
				throw iioe;
			}
			catch(IOException ioe){
				throw new InterruptedIOException("Socket at destination has been closed."); 
			}
			mySP = (SigPacket) obj;
		}
		return mySP;
	}
	
	/** This method registers the DesktopSignaling Component with DirectoryService.
	 * 
	 * @param   None.
	 * @return  Void.
	 */
	protected void Register() throws DirectoryServiceException {
		myDirS.declareIdentity(myUID, myPassword);
		myDirS.registerLocation(Location.ROAMING, myLoc);			
	}
	
	/** This method Unregisters the DesktopSignaling Component on DirectoryService.
	 * 
	 * @param   None.
	 * @return  integer indicating whether the unregistration succeeded
	 *          1 - successfully unregistered
	 *          -1 - unregister failed
	 */
	protected int unRegister(){
		try{
			myDirS.unregisterLocation(Location.ROAMING, myLoc);
		}
		catch(DirectoryServiceException dse){
			return -1;
		}
		return 1;	
	}
	
	/**
	 * Gets the current connection's sequence number.
	 * 
	 * @return the sequence number
	 */
	private synchronized Long getSequence()
	{
		Long seq;
		
		seq = new Long(myConnSeqNumber);
		myConnSeqNumber++;
		return seq;
	}
	
	/**
	 * Gets the SignalConnection object from the hashtable corresponding with a sequence number.
	 * 
	 * @param seq the sequence number of the connection
	 * @return the SignalConnection object
	 */
	private synchronized SignalConnection getConnection(Long seq)
	{
		SignalConnection sc;
		
		sc = (SignalConnection) myConnectionList.get(seq);
		return sc;
	}
	
	/**
	 * Places a SignalConnection object into the hashtable.
	 * 
	 * @param seq the sequence number of the connection
	 * @param sc the SignalConnection to be placed into the hashtable
	 */
	private synchronized void putConnection(Long seq, SignalConnection sc)
	{
		myConnectionList.put(seq, sc);
	}
	
	/**
	 * Removes a SignalConnection object from the hashtable.
	 * 
	 * @param seq the sequence number of the connection
	 */
	private synchronized void removeConnection(Long seq)
	{
		myConnectionList.remove(seq);
	}
	

	/**
	 * returns the default input channel for data communications
	 * which is NetworkSource to speakers
	 *
	 * @param None
	 * @exception DataException thrown when trying to add the same
	 * destination to a channel twice, or if there is no free port
	 * for the NetworkSource.
	 */
	private Channel getDefaultInputChannel() 
	throws DataException {
	   Channel cInput = new Channel();
	   cInput.setSource(new NetworkSource( cInput, Channel.SAMPLE_SIZE ));
	   cInput.addDestination (new SpeakerDestination() );
	   return cInput;
	}

	/**
	 * returns the default output channel for data communications
	 * which is microphone to NetworkDestination
	 *
	 * @param None
	 * @exception DataException thrown when trying to add the same
	 * destination to a channel twice, or microphone resources could
	 * not be allocated
	 */
	private Channel getDefaultOutputChannel() 
	throws DataException {
	   Channel cOutput = new Channel();
	   cOutput.setSource(new MicrophoneSource(cOutput) );
	   cOutput.addDestination (new NetworkDestination() );
	   return cOutput;
	}

	/**
	 * processes the Result from tryDialSequence, which encapsulates
	 * our peer's reaction to our Invite.  This method either (1)
	 * returns with a valid SignalConnection or (2) throws an exception.
	 *
	 * @param ret - the Result of the tryDialSequence
	 * @param ac - the AudioConnection we intended to use.  This
	 *    will be closed if the result is not an Accept and this
	 *    is the last location to be tried
	 * @param outLast - true if this is the last Location to be tried
	 * @param busyFlag - true if a previously dialled location was busy.  
	 *    This parameter carries history from a previous call.  
	 * @param userid - userid string for use in exception message
	 * @exception DesktopSignalingException is thrown if the Dial
	 *    was unsuccessful.  The contained message explains why.
	 * @returns , if successful, with a SignalConnection that has a KeepAlive 
	 *    running on it.  If not successful, but the call hasn't exactly
	 *    been rejected, and there are more possibilities to try (i.e.
	 *    outLast is false), then a null SignalConnection is returned.
	 */
	 private SignalConnection processResult ( Result ret,
	    AudioConnection ac,  boolean outLast, boolean busyFlag, String userid ) 
	    throws DesktopSignalingException {

	    SignalConnection sc = null;
	    Long seq = null;

            switch(ret.getResult()){
               case SignalID.ACCEPT://exit
                  seq = ret.getSeq();
                  sc = getConnection(seq);
				  if (useSIP == false)
					sc.startKeepAlive(this);
                  return sc;						
               case SignalID.REJECT://exit
                  ac.close();
                  throw new InviteRejectException("The user "+ userid + 
		  " has not accepted your call.  Please try again later.");
               case SignalID.INVALID://exit
                  ac.close();
                  throw new InviteInvalidException("Fatal Application Error.");
               case SignalID.BUSY:
                  busyFlag = true;
                  if(outLast){//exit
                     ac.close();
                     throw new InviteBusyException("The user "+ userid + 
		     " is currently busy.  Please try again later.");
                  } else break;    //try next
               case SignalID.TIMEOUT:
                  if(outLast){//exit
                     ac.close();
                     throw new InviteTimeoutException("The user "+ userid + 
		     " is not responding.  Please try again later.");
                  } else break;   //try next
               case SignalID.INCOMPATIBLE:
                  if(outLast){//exit
                     ac.close();
                     if(busyFlag){ throw new InviteBusyException("The user "+ 
		        userid + " is currently busy.  Please try again later.");
                     } 
		     else {
                        throw new InviteIncompatibleException("The user "+ 
		        userid + " is not compatible with your application.");
                     }
                  } else break;   //try next
	       default:
		  System.out.println("In DesktopSignaling:processResult, got " +
		     "an unknown result - " + ret.getResult() + ". ");
		  throw new DesktopSignalingException ("bad result in " +
		     "DesktopSignaling:processResult()");
            } // end switch on ret.getResult

	    return sc;
	 } // ends processResult

	
	/** Inner Class containing the result of a Dial Sequence.
	 *  Used only by Signaling in the Dial Sequence
	 */
	public class Result{
		/** Sequence Number used to put the SignalConnection in the hashtable **/
		Long seq = null;
		/** Integer result of Dial from cnrg.itx.signal.SignalID **/
		int res = -1;
	
		/**
		 * Constructor
		 * 
		 * @param i is the result of the Dial sequence
		 */
		public Result(int i){
			this(i, null);
		}
		
		/**
		 * Constructor
		 * 
		 * @param i is the result of the Dial sequence
		 * @param l is the sequence number for the SignalConnection created
		 */
		public Result(int i, Long l){
			seq = l;
			res = i;
		}
		
		/**
		 * @return the result of the Dial Sequence
		 */
		public int getResult(){
			return res;
		}
		
		/**
		 * @return the sequence number associated with the SignalConnection
		 */
		public Long getSeq(){
			return seq;
		}
	}
}


