/* ***************************************************************************+
 * 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.io.*;
import cnrg.itx.datax.*;

/**
 * This class contains the main Gateway control.  It also creates instances of other classes needed
 * for controlling individual Gateway channels, data flow, and the interface with signaling.
 * 
 * @author James Wann
 * @version 1.0a
 */
public class Gateway
{
	static
	{
		System.loadLibrary("Dialogic");
	}
	
	/**
	 * Size of voice packets transferred from phone to Gateway.
	 */
	protected static final int BUFFERSIZE = 512;
	
	/**
	 * Factor <code>BUFFERSIZE</code> is multiplied by to determine the size of voice packets transferred
	 * from Gateway to phone.
	 */
	protected static final int PXFERFACTOR = 2;
	
	/**
	 * Number of lines on the Gateway.
	 */
	private static final int LINECOUNT = 2;
	
	/**
	 * Number of channels on the Gateway.
	 * NOTE: A line could contain more than one channel.
	 */
	private static final int CHANNELCOUNT = LINECOUNT*2;
	
	/**
	 * Extension numbers used to call each line.  PBX uses these to make a connection between a line and
	 * a phone when Gateway calls a telephone.
	 */
	private static final String[] PHONENUMBER = {"45522", "45524"};
	
	/**
	 * Object used for mutex in stopping and restarting the thread responsible for playout on a phone.
	 */
	private Object[] DTMFMutex;
	
	/**
	 * Thread that listens for incoming phone calls.
	 */
	private ListenChannel[] gateListen;
	
	/**
	 * Thread that sends voice packets from a phone to a computer.
	 */
	private Thread[] gateRecord;
	
	/**
	 * Thread for playing out voice packets to a phone.
	 */
	private Thread[] gatePlay;
	
	/**
	 * Instance of SignalInterface.  For interfacing with the signaling component.
	 */
	private SignalInterface gateSignal;
	
	/**
	 * Array of Line objects.  Each Line object holds information concerning a Gateway line.
	 */
	private Line[] gateLine;
	
	/**
	 * Thread that waits for user input.  Terminates Gateway on Enter key event.
	 */
	private Thread myThreadWait;
	
	/**
    * Inner private class that waits for keyboard input.  It is a blocking wait, so does not interfere
    * with other processes that are running.  When it gets an Enter (or Return), it invokes the PBX
    * Server's finalize method. Extracted from PBX.
    */
   private class ThreadWait implements Runnable {
   
      public void run() {
         int i,r;
         System.out.println (
         "\n=======================================================");
		 System.out.println ( "Gateway: Press Enter to halt this Gateway");
         System.out.println (
         "=======================================================\n");
         try { System.in.read ( ); }
         catch ( IOException e ) {}
		 
		 System.out.println ("Gateway: shutting down");
		 int[] chDevArray = new int[CHANNELCOUNT];
		 
		 // Get all device handlers
		 if (CHANNELCOUNT == LINECOUNT)
			 for (i = 0; i < LINECOUNT; i++)
				 chDevArray[i*2] = gateLine[i].getChDev();
		 else
			 for (i = 0; i < LINECOUNT; i++)
			 {
				 chDevArray[i*2] = gateLine[i].getChDev();
				 chDevArray[i*2+1] = gateLine[i].getSecondDev();
			 }
		 
		 // Clean up gateway and native code
		 shutDown(chDevArray);
		 
		 // Unregister
         gateSignal.unregister();
		 System.exit(0);
      }
   }
	
	/**
	 * @param usePBX true if the gateway uses the PBX; false otherwise.
	 * @exception GatewayException If a channel could not be opened.
	 */
	private Gateway(boolean usePBX) throws GatewayException
	{
		int lineIndex;
		int[] chDev = new int[CHANNELCOUNT];
		gateSignal = new SignalInterface(this, usePBX);
		DTMFMutex = new Object[LINECOUNT];
		gateLine = new Line[LINECOUNT];
		gateListen = new ListenChannel[LINECOUNT];
		gateRecord = new Thread[LINECOUNT];
		gatePlay = new Thread[LINECOUNT];
		
		// Create user I/O thread for logout
		myThreadWait = new Thread(new ThreadWait());
		
		// Initialize the native Gateway parameters
		initialize(LINECOUNT, BUFFERSIZE, PXFERFACTOR);
		
		// Create Gateway state objects
		for (int i = 0; i < LINECOUNT; i++)
		{
			lineIndex = i + 1;
			chDev[i] = channelSetUp(lineIndex, 1);
			if (chDev[i] < 0)
				throw new GatewayException("<Channel: " + lineIndex + "> dx_open failed");
			
			// Set up individual channels
			if (CHANNELCOUNT == LINECOUNT)
				gateLine[i] = new Line(lineIndex, chDev[i], chDev[i], PHONENUMBER[i]);
			else
			{
				chDev[i + LINECOUNT] = channelSetUp(lineIndex + LINECOUNT, PXFERFACTOR);
				if (chDev[i + LINECOUNT] < 0)
					throw new GatewayException("<Channel: " + lineIndex + "> dx_open failed");
				gateLine[i] = new Line(lineIndex, chDev[i], chDev[i + LINECOUNT], PHONENUMBER[i]);
			}
			gateListen[i] = new ListenChannel(gateLine[i], this);
			DTMFMutex[i] = new Object();
		}
	}
	
	/**
	 * Starts the ThreadWait instance.  Waits for Enter key to be pressed.
	 * 
	 * @see ThreadWait
	 */
	private void startThreadWait()
	{
		myThreadWait.start();
	}
	
	/**
	 * Starts a thread for a Gateway channel.  This channel then listens for incoming calls from a phone.
	 * 
	 * @param index corresponds with each channel number.  index 0 is for channel 1.
	 * @see ListenChannel#run
	 */
	private void startListen(int index)
	{
		int chDev = gateLine[index].getChDev();
		
		// initialize the primary channel and start listening
		channelOnHook(chDev);
		unroute(gateLine[index].getChDev());
		gateListen[index].start();
	}
	
	/**
	 * Finds a line that can be used for a connection between a computer and a phone.  Initializes and
	 * reserves that line.
	 * 
	 * @return the <code>Line</code> instance of the available line.  Contains information concerning
	 * that line.
	 * @see Line
	 */
	protected synchronized Line getAvailableLine()
	{	
		for (int i = 0; i < LINECOUNT; i++)
		{
			if (gateLine[i].getAvailability())
			{
				if(useLine(gateLine[i]))
					return gateLine[i];
			}
		}
		
		return null;
	}
	
	/**
	 * Checks if the specified line can be used for a connection between a phone and computer.  If so,
	 * then the line is set offhook and reserved.
	 * 
	 * @param lineInfo the <code>Line</code> instance of the specific line being reserved.  Contains
	 * information concerning that line.
	 * @return true if the channel has been reserved; false otherwise.
	 */
	protected synchronized boolean useLine(Line lineInfo)
	{
		int lineIndex = lineInfo.getLineNumber() - 1;
		int chDev = lineInfo.getChDev();
		
		if (!gateLine[lineIndex].getAvailability())
		{
			return false;
		}
		else
		{
			gateLine[lineIndex].makeUnAvailable();
			channelOffHook(chDev);
			return true;
		}
	}
	
	/**
	 * Resets and unreserves a line for a future connection.
	 * 
	 * @param lineInfo the <code>Line</code> instance of the line being freed.  Contains information
	 * concerning that line.
	 */
	protected synchronized void freeLine(Line lineInfo)
	{
		int chDev = lineInfo.getChDev();
		
		//restore original state of channel (ready to accept calls and set to half-duplex
		channelOnHook(chDev);
		unroute(chDev);
		
		int lineIndex = lineInfo.getLineNumber() - 1;
		gateLine[lineIndex].makeAvailable();
	}
	
	/**
	 * Reinitializes a line for data flow.
	 * 
	 * @param lineInfo the <code>Line</code> instance of the line being reinitialized.  Contains
	 * information concerning that line.
	 */
	protected void resetSequence(Line lineInfo)
	{
		int lineNumber = lineInfo.getLineNumber();
		
		resetRecordSequence(lineNumber, true);
		resetPlaySequence(lineNumber, true);
	}
	
	/**
	 * Sets up a data connection between phone and computer. The connection is always through a Gateway
	 * line.
	 * 
	 * @param lineInfo the <code>Line</code> instance of the line where the connection is set up.
	 * Contains information concerning that line.
	 * @param c the <code>Connection</code> instance associated with the connection.  Provides the means
	 * to transfer voice data.
	 * @see cnrg.itx.datax.Connection
	 */
	protected void setUpConnection(Line lineInfo, Connection c)
	{
		int index = lineInfo.getLineNumber() - 1;
		int chDev = gateLine[index].getChDev();
		int secondChDev = gateLine[index].getSecondDev();
		
		// Create Record and Play threads
		gateRecord[index] = new Thread(new RecordChannel(gateLine[index], c, this));
		gatePlay[index] = new Thread(new PlayChannel(gateLine[index]));
		
		// Set line to full-duplex and begin transferring data
		reroute(chDev, secondChDev);
		gateRecord[index].start();
		gatePlay[index].start();
		gateListen[index].makeWait();
	}
	
	/**
	 * Used by <code>SignalInterface</code> to take down a data connection between computer and phone.
	 * The connection is always through a line.
	 * 
	 * @param lineInfo the <code>Line</code> instance of the line where the connection currently exists.
	 * Contains information concerning the line.
	 */
	protected void tearDownConnection(Line lineInfo)
	{
		int lineNumber = lineInfo.getLineNumber();
		int index = lineNumber - 1;

		// Terminate record session from phone to computer and wait until thread dies
		if ((gateRecord[index] != null) && (gatePlay[index] != null))
		{
			endRecordConnection(lineNumber);
			
			try
			{
				gateRecord[index].join();
			}
			catch (InterruptedException e)	
			{
			}
		}
	}
	
	/**
	 * Used by <code>RecordChannel</code> after a hangup to stop playout on a phone.  The connection on
	 * the line is terminated.
	 * 
	 * @param lineInfo the <code>Line</code> instance of the line where the connection currently exists.
	 * Contains information concerning that line.
	 */
	protected void tearDownPlayConnection(Line lineInfo)
	{
		int lineNumber = lineInfo.getLineNumber();
		int index = lineNumber - 1;
			
		synchronized(DTMFMutex[index])
		{
			endPlayConnection(lineNumber);
			
			// Wait until Play thread dies
			try
			{
				gatePlay[index].join();
			}
			catch (InterruptedException e)
			{
			}

			// Make sure that playout thread no longer exists
			gatePlay[index] = null;
		}
			// Free channel to accept calls
			freeLine(gateLine[index]);
			gateListen[index].restart();
	}
	
	/**
	 * Dials an computer application extension from ListenChannel.  Returns when call is either accepted
	 * or rejected.  If the call is accepted, a connection is then set up.
	 * 
	 * @param lineInfo The <code>Line</code> instance of the line from which the phone dial is being
	 * made.  Contains information concerning that line.
	 * @param ext extension number to dial.
	 * @return the <code>Connection</code> instance used to service data flow on a line.  Provides the
	 * means necessary for voice data transfer.
	 * @exception GatewayException If extension is invalid or call has been rejected.
	 */
	protected Connection dialComputer(Line lineInfo, String ext) throws GatewayException
	{
		if (ext == null)
			throw new GatewayException("not a number");
		else
		{
			// Remove the # sign from the extension number
			ext = ext.substring(0, ext.length() - 1);
			Connection c;
			try
			{
				c = gateSignal.dial(lineInfo, ext);
			}
			catch (GatewayException e)
			{
				throw e;
			}
			return c;
		}
	}	
	
	/**
	 * Called by a RecordChannel.  It informs the Gateway that the connected phone has hung up.  The
	 * Gateway then informs SignalInterface.
	 * 
	 * @param lineInfo the <code>Line</code> instance of the line that is disconnected.  Contains
	 * information concerning that line.
	 * @exception GatewayException If the hangup fails.
	 */
	protected void hangup(Line lineInfo) throws GatewayException
	{
		try {
			gateSignal.hangup(lineInfo);
		}
		catch (GatewayException e) {
			throw e;
		}
	}
	
	/**
	 * RecordChannel calls this to send any DTMF digits it received while sending voice data from a phone
	 * to a computer.
	 * 
	 * @param digits the DTMF digits that were detected from the phone.
	 * @param lineInfo the <code>Line</code> instance of the line on which the DTMF tones were detected.
	 * @exception GatewayException If something goes wrong in sending digits.
	 * */
	protected void sendDTMF(String digits, Line lineInfo) throws GatewayException
	{
		try {
			gateSignal.sendDTMF(digits, lineInfo);
		}
		catch (GatewayException e) {
			throw e;
		}
	}
	
	/**
	 * Used by SignalInterface to play DTMF tones onto the connected phone.
	 * 
	 * @param digits the DTMF tones being played.
	 * @param lineInfo the <code>Line</code> instance of the line where the DTMF tones are being played.
	 * Contains information concerning the line.
	 * @param c the <code>Connection</code> instance associated with the connection the line is on.
	 */
	protected void playDTMF(String digits, Line lineInfo, Connection c)
	{
		int lineNumber = lineInfo.getLineNumber();
		int chDev = lineInfo.getSecondDev();
		int index = lineNumber - 1;
		
		synchronized(DTMFMutex[index])
		{
			// Make sure there is an audio play thread
			if (gatePlay[index] != null)
			{
				// Stop audio playout and wait until it terminates
				endPlayConnection(lineNumber);
				try
				{
					gatePlay[index].join();
				}
				catch (InterruptedException e)
				{
				}
				
				// play tones and restart audio playout
				playTones(digits, chDev);
				resetPlaySequence(lineNumber, false);
				gatePlay[index] = new Thread(new PlayChannel(gateLine[index]));
				gatePlay[index].start();
			}
		}
	}
		
	/**
	 * The main entry point for the Gateway.
	 *
	 * @param args Array of parameters passed to the application via the command line.
	 */
	public static void main(String[] args)
	{
		Gateway gate;
		InputStreamReader isr = new InputStreamReader(System.in);
		
		// Check if PBX is being used and start the Gateway
		try
		{
			if (args.length > 0)
				if (args[0].equals("-pbx") || args[0].equals("-PBX"))
					gate = new Gateway(true);
				else
					gate = new Gateway(false);
			else
				gate = new Gateway(false);
			gate.startThreadWait();
			
			// Begin listening for incoming calls from the phone
			for (int i = 0; i < LINECOUNT; i++)
				gate.startListen(i);
		}
		catch (GatewayException e)
		{
			System.out.println(e.getMessage());
			e.printStackTrace();
			System.exit(-1);
		}
	}
	
	/**
	 * Initializes parameters in the .cpp file.  Called when Gateway starts.
	 * 
	 * @param numLines the number of lines in the gateway.
	 * @param bufferSize the packet transfer rate between the gateway and phone.
	 * @param pXferFactor factor bufferSize is multiplied by to define size of packets played to phone.
	 */
	private native void initialize(int numLines, int bufferSize, int pXferFactor);

	/**
	 * Closes all Gateway channels and cleans up memory.  Called at termination of the Gateway.
	 * 
	 * @param chDevArray array of all channel device handlers.
	 */
	private native void shutDown(int[] chDevArray);
	
	/**
	 * Used for channel initialization.  Called during the initialization of the Gateway.
	 * 
	 * @param channelNum the channel's designated number.
	 * @param xferFactor to be multiplied with the packet transfer rate.  Meant to increase transfer
	 * rate if desired.
	 * @return the channel's device handler.
	 */
	private native int channelSetUp(int channelNum, int xferFactor);
	
	/**
	 * Reroutes the second channel's playout to the primary channel's playout.  Necessary for a full
	 * duplex connection.
	 * 
	 * @param chDev the primary channel's device handler.
	 * @param secDev the second channel's device handler.  This channel is responsible for playout.
	 */
	private native void reroute(int chDev, int secDev);
	
	/**
	 * "Releases" the second channel from playing out on the primary channel.
	 * 
	 * @param chDev the primary channel's device handler.
	 */
	private native void unroute(int chDev);
	
	/**
	 * Sets the channel on hook.  Channel is available to receive incoming calls from a phone.
	 * 
	 * @param chDev the channel's device handler.
	 */
	private native void channelOnHook(int chDev);
	
	/**
	 * Sets the channel off hook to allow for passing voice data through.
	 * 
	 * @param chDev the channel's device handler.
	 */
	private native void channelOffHook(int chDev);
	
	/**
	 * Dials a phone.  Returns as soon as the phone starts ringing or something goes wrong.
	 * 
	 * @param digits the phone number being dialed.
	 * @param lineInfo the <code>Line</code> instance of the line where the connection is to be made.
	 * @exception GatewayException If the call to a phone fails or a busy signal is received.
	 */
	protected native void dialPhone(String digits, Line lineInfo) throws GatewayException;
	
	/**
	 * Plays DTMF tones to the telephone connected to the line.
	 * 
	 * @param digits the DTMF digits being played.
	 * @param chDev the channel's device handler.
	 */
	private native void playTones(String digits, int chDev);
	
	/**
	 * Resets handlers for sending data from phone to computer.  Handlers are set to designate that the
	 * connection is still alive.
	 * 
	 * @param lineNumber the channel's corresponding line number.
	 * @param isStartCall true if this is the beginning of a connection session; false otherwise.
	 */
	private native void resetRecordSequence(int lineNumber, boolean isStartCall);
	
	/**
	 * Resets handlers for a playout on a phone.  Handlers are set to designate that playout is still
	 * active.
	 * 
	 * @param lineNumber the channel's corresponding line number.
	 * @param isStartCall true if this is the beginning of a connection session; false otherwise.
	 */
	private native void resetPlaySequence(int lineNumber, boolean isStartCall);
	
	/**
	 * Terminates the data flow from phone to computer.
	 * 
	 * @param lineNumber the channel's corresponding line number.
	 */
	private native void endRecordConnection(int lineNumber);
	
	/**
	 * Stops audio playout from computer to phone
	 * 
	 * @param lineNumber the channel's corresponding line number.
	 */
	private native void endPlayConnection(int lineNumber);
}