/* ***************************************************************************+
 * 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 License 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.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
{
	/**
	 * 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", "45525"};
	
	/**
	 * Size of voice packets transferred in the computer network.
	 */
	private static final int DATACOUNT = Channel.SAMPLE_SIZE;
	
	/**
	 * 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;
	
	/**
	 * Instance of ChannelControl.  For calling methods that control the Gateway hardware.
	 */
	private ChannelControl myControl;
	
	/**
	 * 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;
	
	/**
	 * @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);
		myControl = new ChannelControl();
		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
		myControl.initialize(LINECOUNT, BUFFERSIZE, DATACOUNT, PXFERFACTOR);
		
		// Create Gateway state objects
		for (int i = 0; i < LINECOUNT; i++)
		{
			lineIndex = i + 1;
			chDev[i] = myControl.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] = myControl.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], myControl, 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
		myControl.channelOnHook(chDev);
		myControl.unroute(gateLine[index].getChDev());
		gateListen[index].start();
	}
	
	/**
	 * Called to receive the <code>ChannelControl</code> object responsible for controlling the Gateway
	 * hardware.
	 * 
	 * @return Gateway's instance of <code>ChannelControl</code>.
	 * @see ChannelControl
	 */
	protected ChannelControl getChannelControl()
	{
		return myControl;
	}
		
	/**
	 * 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();
			myControl.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
		myControl.channelOnHook(chDev);
		myControl.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();
		
		myControl.resetRecordSequence(lineNumber, true);
		myControl.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, myControl, this));
		gatePlay[index] = new Thread(new PlayChannel(gateLine[index], myControl));
		
		// Set line to full-duplex and begin transferring data
		myControl.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))
		{
			myControl.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])
		{
			myControl.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 place a call to a phone.  If phone rings, a connection through the
	 * Gateway line is created.
	 * 
	 * @param digits the number of the phone being called
	 * @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 void dialPhone(String digits, Line lineInfo) throws GatewayException
	{
		boolean isAccepted;
		int chDev = lineInfo.getChDev();
		
		isAccepted = myControl.dialPhone(digits, chDev);
		if (isAccepted == false)
			throw new GatewayException("Call could not go through");
	}
	
	/**
	 * 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
				myControl.endPlayConnection(lineNumber);
				try
				{
					gatePlay[index].join();
				}
				catch (InterruptedException e)
				{
				}
				
				// play tones and restart audio playout
				myControl.playDTMF(digits, chDev);
				myControl.resetPlaySequence(lineNumber, false);
				gatePlay[index] = new Thread(new PlayChannel(gateLine[index], myControl));
				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);
		}
	}
	
   /**
    * 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
		 myControl.shutDown(chDevArray);
		 
		 // Unregister
         gateSignal.unregister();
		 System.exit(0);
      }
   }
}