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

	
	/**
	 * 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);
		myDSS.start();
		myLoc = new Location("I", myName+":"+Integer.toString(myServerPort), myDesc);
		try{
			if (config == null)
				myDirS = new DirectoryService();
			else
				myDirS = new DirectoryService(config);			
			Register();
		}
		catch(DirectoryServiceException dse){
			myDSS.killServer();
			throw dse;
		}
	}
	
	/**
	 * 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 {
		Channel cInput = new Channel();
		Channel cOutput = new Channel();

		cInput.setSource(new NetworkSource( cInput, Channel.SAMPLE_SIZE ));
		cInput.addDestination (new SpeakerDestination() );
		
		cOutput.setSource( new MicrophoneSource(cOutput) );
		cOutput.addDestination (new NetworkDestination () );
		return Dial(userid, cInput, cOutput);
	}
	
	/**
	 * 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;
				}
				switch(ret.getResult()){
					case SignalID.ACCEPT://exit
						seq = ret.getSeq();
						sc = (SignalConnection) myConnectionList.get(seq);
						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 = (SignalConnection) myConnectionList.get(seq);
								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
	 * @exception  DirectoryServiceException
	 * 
	 * @see		cnrg.itx.signal.SignalConnectionObserver
	 * @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, Location userLoc, SignalConnectionObserver sco) throws DataException, DesktopSignalingException, DirectoryServiceException{
		Channel cInput = new Channel();
		Channel cOutput = new Channel();

		cInput.setSource(new NetworkSource( cInput, Channel.SAMPLE_SIZE ));
		cInput.addDestination (new SpeakerDestination() );
		
		cOutput.setSource( new MicrophoneSource(cOutput) );
		cOutput.addDestination (new NetworkDestination () );
		return Dial(userid, userLoc, cInput, cOutput, 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
	 * @exception  DirectoryServiceException
	 * 
	 * @see		cnrg.itx.signal.SignalConnectionObserver
	 * @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, Location userLoc, Channel cInput, Channel cOutput, SignalConnectionObserver sco) 
		throws DataException, DesktopSignalingException, DirectoryServiceException
	{	
		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;
	}
	
	/**
	 * 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();
		KeepAlive ka = sc.getKeepAlive();
		
		if(ka != null)
			ka.cleanup();
		myConnectionList.remove(seq);
		
		InvitePacket ip = new InvitePacket(myUID, myLoc, myDesc, SignalID.HANGUP);
		ip.setSeqNumber(seq);	
		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);
		
		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 unregisters the Signaling Component with Directory Services and kills the 
	 * server thread.
	 * 
	 * @param   None.
	 * @return  void
	 */
	public void logout(){
		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);
				
		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)
					c.close();
				else
					ac.close();
				de.printStackTrace();
			}				

			ise.setPropertiesCollection(pc);
			InvitePacket result = ise.getInvitePacket();
			sender = result.getSenderLoc();
			
			seq = new Long(myConnSeqNumber);
			myConnSeqNumber++;
			result.setPacketType(SignalID.RESULT);
			result.setSeqNumber(seq);//send my seq number
						
			InvitePacket confirm = null;
			try{
				sendPacket(result, so);
			}
			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());
					}
					peerseq = new Long(confirm.getSeqNumber().longValue());//get peer seq number
					sc.setSeqNumber(seq);
					sc.setPeerSeqNumber(peerseq);					
					myConnectionList.put(seq, sc);						
					myApp.onStartCall(sc);
					sc.startKeepAlive(this);
					return;
				}
				else{					
					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
			InvitePacket result = ise.getInvitePacket();
			result.setPacketType(SignalID.RESULT);
			try{
				sendPacket(result, so);
				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 = (SignalConnection) myConnectionList.get(seq);
		if (sc == null){
			System.out.println("Hangup accepted.  Connection removed from Connection List.");			
		}
		else {
			try{
				myConnectionList.remove(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 = (SignalConnection) myConnectionList.get(seq);
		if(sc == null){
			System.out.println("SignalConnection inactive.  Dropping DTMF");
		}
		else{
			dtmfse.setSignalConnection(sc);
			myApp.onDTMF(dtmfse);	
		}
		
		if (s != null){
			try{
				s.close();
			}
			catch(IOException ioe){
				ioe.printStackTrace();
			}				
		}
		System.out.println("DTMF String accepted.");			
	}
	
	/** 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);
		}
		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 hadnshake is returned
	 *  to the callee method.
	 */
	protected Result tryDialSequence(UserID destUID, Location destAdd, Connection ac) throws ConnectException
	{
		SignalConnection sc = null;
		Long peerseq = null;
		Long seq = 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);

		try{
			sock = sendNewPacket(ip, destAdd.getIP(), destAdd.getPort());
		}
		catch(ConnectException ce){
			System.out.println("Could not send initial invite packet to peer.");
			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());
					seq = new Long(myConnSeqNumber);
					myConnSeqNumber++;
					sc.setSeqNumber(seq);
					sc.setPeerSeqNumber(peerseq);
					myConnectionList.put(seq, sc);	
					
					ip.confirm();
					ip.setPacketType(SignalID.CONFIRM);
					ip.setSeqNumber(seq);//send my seq number
					sendPacket(ip, sock);
					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 (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.
	 * @return  void
	 * @exception DesktopSignalingException
	 */
	protected void sendPacket(SigPacket send, Socket sock) 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 (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;
	
		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."); 
		}		
		return (SigPacket) obj;
	}
	
	/** 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;	
	}
	
	
	/** 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;
		}
	}
}


