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

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

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

/**
 * Used as an interface between the Gateway and the signaling component.
 * 
 * @author James Wann and Jason Howes
 * @version 1.0a
 * @see Gateway
 * @see cnrg.itx.signal.DesktopSignaling
 */
public class SignalInterface implements SignalingObserver	
{
	private boolean usePBX;
	private static int TABLE_SIZE			= 7;
	private static float TABLE_LOAD_FACTOR	= (float)0.75;
	
	/**
	 * The instance of DesktopSignaling associated with the Gateway.
	 */
	private DesktopSignaling mySignaling;
	
	/**
	 * The instance of the Gateway class.
	 */
	private Gateway myGate;
	
	/**
	 * Hash table that provides mappings between SignalConnection(s) and userID(s)
	 */
	private Hashtable gateSignalEntries;
	
	/**
	 * @param g the <code>Gateway</code> that constructed SignalInterface.
	 * @param ifPBX true if a PBX is being used; false otherwise.
	 * @exception GatewayException If there a null pointer to the <code>Gateway</code> class was passed
	 * in.
	 */
	protected SignalInterface(Gateway g, boolean ifPBX) throws GatewayException
	{
		myGate = g;
		usePBX = ifPBX;
		try 
		{
			mySignaling = new DesktopSignaling(this, "gatewaysrv", "itxgw", "Gateway with Dialogic card", "resolv.conf");
		} 
		catch (DirectoryServiceException e) 
		{
			myGate = null;
			throw new GatewayException(e.getMessage());
		}
		
		gateSignalEntries = new Hashtable(TABLE_SIZE, TABLE_LOAD_FACTOR);
	}
	
	/**
	 * Used to dial a computer when an extension number is received from a phone.
	 * NOTE: This function assumes that all Gateway resources have been allocated!
	 * 
	 * @param lineInfo the <code>Line</code> instance of the line that received the extension number.
	 * Contains information concerning that line.
	 * @param ext the extension number of the computer application being dialed.
	 * @return an AudioConnection instance associated with the call.  Used for voice packet transfer.
	 * @exception GatewayException If something went wrong during the dial.
	 * @see Gateway#dialComputer(Line, String)
	 * @see cnrg.itx.datax.AudioConnection
	 */
	protected AudioConnection dial(Line lineInfo, String ext) throws GatewayException
	{
		SignalConnection c;
		Channel newInputChannel = new Channel(Gateway.BUFFERSIZE*Gateway.PXFERFACTOR);
		Channel newOutputChannel = new Channel();
		UserID userID;
		
		try
		{
			// Create the connection audio infrastructure
			RecordSource newRecord = new RecordSource(newOutputChannel, lineInfo);
			PlayDestination newPlay = new PlayDestination(lineInfo);
			NetworkSource newSource = new NetworkSource(newInputChannel, Channel.SAMPLE_SIZE);
			NetworkDestination newDestination = new NetworkDestination();
			
			// Attach the sources and destinations to the datax Channels
			newInputChannel.setSource(newSource);
			newInputChannel.addDestination(newPlay);
			newOutputChannel.setSource(newRecord);
			newOutputChannel.addDestination(newDestination);
		}
		catch (DataException e)
		{
			throw new GatewayException(e.getMessage());
		}
		
		// Look up the username with the extension number
		try
		{
			userID = mySignaling.getDirectory().getID(new Digits(ext));
		}
		catch (Exception e)
		{
			throw new GatewayException(e.getMessage());
		}
		
		// Dial the user
		try 
		{
		   c = mySignaling.Dial(userID.toString(), newInputChannel, newOutputChannel);
		} 
		catch (Exception e) 
		{
		   throw new GatewayException(e.getMessage());
		}
		
		// Create a mapping between extension and SignalConnection associated with this call
		try
		{
			gateSignalEntries.put(lineInfo, c);
		}
		catch (NullPointerException e)
		{
			try
			{
				mySignaling.Hangup(c);
			}
			catch (Exception exp)
			{
			}
			
			throw new GatewayException(e.getMessage());
		}
		
		myGate.resetSequence(lineInfo);
		
		// Open the data connection
		try
		{
			c.open();
		}
		catch (DataException e)
		{
			try
			{
				mySignaling.Hangup(c);
			}
			catch (Exception exp)
			{
			}
			
			throw new GatewayException(e.getMessage());			
		}
	
		return (AudioConnection)c.getConnection();
	}
	
	/**
	 * Gateway calls this method to tell signaling that the phone has hung up.
	 * 
	 * @param lineInfo the <code>Line</code> instance of the line being hung up.  Contains information
	 * regarding that line.
	 * @exception GatewayException If something went wrong with the hangup.
	 * @see Gateway#hangup(Line)
	 */
	protected void hangup(Line lineInfo) throws GatewayException
	{
		SignalConnection signalConnection;
		
		// Look up the SignalConnection associated with the line
		if ((signalConnection = (SignalConnection)gateSignalEntries.get(lineInfo)) != null)
		{
			// Remove the signal connection entry from the gateSignalEntries hash table
			gateSignalEntries.remove(lineInfo);
		
			// Hangup the connection
			try
			{
				mySignaling.Hangup(signalConnection);
			}
			catch (Exception e)
			{
				throw new GatewayException(e.getMessage());
			}
		}
	}
	
	/**
	 * Sends DTMF tones picked up on a channel from <code>Gateway</code> to a computer.
	 * 
	 * @param s a string represenation of the DTMF tones.
	 * @param lineInfo the <code>Line</code> instance of the line that received the DTMF tones.  Contains
	 * information regarding that line.
	 * @exception GatewayException If something goes wrong in sending DTMF.
	 * @see Gateway#sendDTMF(String, Line)
	 */
	protected void sendDTMF(String s, Line lineInfo) throws GatewayException
	{
		SignalConnection signalConnection;
		
		// Look up the SignalConnection associated with the line
		if ((signalConnection = (SignalConnection)gateSignalEntries.get(lineInfo)) != null)
		{
			// Send the DTMF
			try
			{
				mySignaling.sendDTMF(s, signalConnection);
			}
			catch (Exception e)
			{
				throw new GatewayException(e.getMessage());
			}
		}
	}
	
	/**
	 * Informs the gateway that a computer application wishes to connect with a phone.  If there is an
	 * available line to make a connection with, the gateway accepts and gives the line's status
	 * information.  Otherwise, it rejects the call.
	 * 
	 * @param ise the <code>InviteSignalEvent</code> instance that contains all the information about the
	 * calling application.
	 * @see cnrg.itx.signal.SignalingObserver
	 * @see cnrg.itx.signal.SignalEvent.InviteSignalEvent
	 */
	public void onInvite(InviteSignalEvent ise)
	{
		LocationList ll;
		
		// Get an available Gateway line and create an audio connection
		Line availableLine = myGate.getAvailableLine();
		AudioConnection newConnection;
		
		// Did we get a line?
		if (availableLine == null)
			rejectInvite(availableLine, ise);
		else
		{	
			boolean calledUser;
			InvitePacket myInvite = ise.getInvitePacket();
			if (usePBX == false)
			{
				calledUser = true;
				String phoneNum = myInvite.getDestination().toString();
				
				try
				{
					myGate.dialPhone(phoneNum, availableLine);
				}
				catch (GatewayException e)
				{
					System.out.println("<Channel: " + availableLine.getLineNumber() + "> -> " + e.getMessage());
					calledUser = false;
				}
			}
			else
			{
				calledUser = false;
				myInvite.setCustomObject((Object) availableLine);
				
				// find all the PBX's
				DirectoryService myDS = mySignaling.getDirectory();
				try 
				{
					ll = myDS.getLocationListByID(new UserID("pbxsrv"));
				}
				catch (DirectoryServiceException dse) 
				{
					ise.reject("Can't find pbx");
					return;
				}
				if(ll != null) 
				{
					Location l = ll.first();
					while(l != null) 
					{
						String PBXIP = l.getIP();
						int PBXPort = l.getPort();
						try 
						{
							Socket PBXSocket = new Socket(InetAddress.getByName(PBXIP), PBXPort);
							DataOutputStream f = new DataOutputStream(PBXSocket.getOutputStream());
							ObjectOutputStream s = new ObjectOutputStream(f);
							s.writeObject(myInvite);
							s.flush();
							DataInputStream in = new DataInputStream(PBXSocket.getInputStream());
							ObjectInputStream si = new ObjectInputStream(in);
							myInvite = (InvitePacket)si.readObject();
							PBXSocket.close();
							if(myInvite.wasAccepted()) {
								calledUser = true;
								break;
							}
						}
						catch (Exception e) 
						{
						}
						l = ll.next();
					}
				}
			} 
			if(calledUser == false)
				rejectInvite(availableLine, ise);
			else
			{
				try
				{
					// Create the connection audio infrastructure
					Channel newInputChannel = new Channel(Gateway.BUFFERSIZE*Gateway.PXFERFACTOR);
					Channel newOutputChannel = new Channel();
					RecordSource newRecord = new RecordSource(newOutputChannel, availableLine);
					PlayDestination newPlay = new PlayDestination(availableLine);
					NetworkSource newSource = new NetworkSource(newInputChannel, Channel.SAMPLE_SIZE);
					NetworkDestination newDestination = new NetworkDestination();
			
					// Attach the sources and destinations to the channels
					newInputChannel.setSource(newSource);
					newInputChannel.addDestination(newPlay);
					newOutputChannel.setSource(newRecord);
					newOutputChannel.addDestination(newDestination);
			
					// Create the AudioConnection associated with the call
					newConnection = new AudioConnection(newInputChannel, newOutputChannel);
					ise.getInvitePacket().setCustomObject(null);
					ise.accept(newConnection);
				}
				catch (DataException e)
				{
					rejectInvite(availableLine, ise);
				}
			}
		}	
	}
	
	/**
	 * Called to reject an onInvite call.
	 * 
	 * @param reservedLine the <code>Line</code> instance of the line that needs to be freed.  Contains
	 * information regarding that line.
	 * @param ise the <code>InviteSignalEvent</code> instance passed in by <code>DesktopSignaling</code>.
	 * @see #onInvite(InviteSignalEvent)
	 */
	private void rejectInvite(Line reservedLine, InviteSignalEvent ise)
	{
		if (reservedLine != null)
			myGate.freeLine(reservedLine);
		ise.getInvitePacket().setCustomObject(null);
		ise.busy();
	}
	
	/**
	 * Informs the gateway that the call setup from computer to phone is complete.  The gateway opens up
	 * the data connection at its end.
	 * 
	 * @param sc is the <code>SignalConnection</code> the computer application should use for data
	 * transfer.
	 * @see cnrg.itx.signal.SignalingObserver
	 * @see cnrg.itx.signal.SignalConnection
	 */
	public void onStartCall(SignalConnection sc)
	{
		Line gwLine;
		AudioConnection gwConnection;
		RecordSource gwSource;
		
		// Extract various objects from the HangupSignalEvent
		gwConnection = (AudioConnection)sc.getConnection();
		gwSource = (RecordSource)gwConnection.getOutputChannel().getSource();
		gwLine = gwSource.getLine();
		
		myGate.resetSequence(gwLine);
		
		// Create a mapping between extension and SignalConnection associated with this call
		try
		{
			gateSignalEntries.put(gwLine, sc);
			sc.open();
		}
		catch (NullPointerException e)
		{
			myGate.freeLine(gwLine);
			try
			{
				mySignaling.Hangup(sc);
			}
			catch (Exception exp)
			{
			}
			return;
		}
		catch (DataException e)
		{
			myGate.freeLine(gwLine);
			gateSignalEntries.remove(gwLine);
			try
			{
				mySignaling.Hangup(sc);
			}
			catch (Exception exp)
			{
			}
			return;
		}
		
		myGate.setUpConnection(gwLine, sc.getConnection());
	}	
	
	/**
	 * Informs the Gateway that it should abort the call it was waiting for.
	 * 
	 * @param ase an event describing the abort action.
	 * @see cnrg.itx.signal.SignalingObserver
	 * @see cnrg.itx.signal.SignalEvent.AbortSignalEvent
	 */
	public void onAbortCall(AbortSignalEvent ase)
	{
		Line gwLine;
		AudioConnection gwConnection;
		RecordSource gwSource;
		
		// Extract various objects from the HangupSignalEvent
		gwConnection = (AudioConnection)ase.getConnection();
		gwSource = (RecordSource)gwConnection.getOutputChannel().getSource();
		gwLine = gwSource.getLine();		
		
		// Free up the gateway channel
		myGate.freeLine(gwLine);
	}
	
	/**
	 * Informs the gateway that the computer has hung up.  Gateway frees up the line computer is
	 * connected to and tears down the data connection.
	 * 
	 * @param hse the <code>HangupSignalEvent</code> instance that contains all the information about the
	 * connection that has hung up.
	 * @see cnrg.itx.signal.SignalingObserver
	 * @see cnrg.itx.signal.SignalEvent.HangupSignalEvent
	 */
	public void onHangup(HangupSignalEvent hse)
	{
		SignalConnection signalConnection;		
		Line gwLine;
		AudioConnection gwConnection;
		RecordSource gwSource;
		
		// Extract various objects from the HangupSignalEvent
		signalConnection = hse.getSignalConnection();
		gwConnection = (AudioConnection)signalConnection.getConnection();
		gwSource = (RecordSource)gwConnection.getOutputChannel().getSource();
		gwLine = gwSource.getLine();
		
		// Remove the signal connection entry from the gateSignalEntries hash table
		gateSignalEntries.remove(gwLine);
		
		// Tear down the Gateway connection
		myGate.tearDownConnection(gwLine);
	}
	
	/**
	 * Called to play DTMF digits on a phone.  These digits were received from the computer connected to
	 * the phone.
	 * 
	 * @param dse the <code>DTMFSignalEvent</code> instance that contains the digits.
	 * @see cnrg.itx.signal.SignalingObserver
	 * @see cnrg.itx.signal.SignalEvent.DTMFSignalEvent
	 */
	public void onDTMF(DTMFSignalEvent dse) 
	{
		String DTMFToPlay;
		Line gwLine;
		SignalConnection gwSignalConnection;
		AudioConnection gwConnection;
		RecordSource gwSource;
		
		DTMFToPlay = dse.getDTMF();
		gwSignalConnection = dse.getSignalConnection();
		gwConnection = (AudioConnection)gwSignalConnection.getConnection();
		gwSource = (RecordSource)gwConnection.getOutputChannel().getSource();
		gwLine = gwSource.getLine();
	
		myGate.playDTMF(DTMFToPlay,gwLine,gwConnection);
	}
	
	/**
	 * Unregisters the gateway.
	 */
	protected void unregister()
	{
		mySignaling.logout();
	}
}