package cnrg.itx.gtwy.pbx;
/* ***************************************************************************+
 * 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     *
 ******************************************************************************/

// Based on Masc.java created in Fall 1999 by Madhav Ranjan and
// Abhideep Singh for CS519, Engineering the Internet.  Modified
// to work with new distributed version of Signaling.

// Launch this
// by typing "jview cnrg.itx.signal.pbx.PBXSignalingServer" in NT, 
// or "java PBXSignalingServer" in Unix.
// There must be a "tsapi.pro" file in the current directory.
// Also, there must be a "resolve.conf" file in the current directory.
// GatewayManager (in cnrg.itx.gtwy) must be running for anything to happen.

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

// Directory Services
import cnrg.itx.ds.*;

// For standard SigPacket stuff
import cnrg.itx.signal.SignalID;
import cnrg.itx.signal.SigPacket;
import cnrg.itx.signal.InvitePacket;
import cnrg.itx.signal.SignalEvent.*;

import cnrg.itx.gtwy.Line;

/**
 * This is the main program class for PBXSignaling.  The server's functions
 * are: receive packets from a gateway signaling component;
 * own a PBXSignaling object ("myPBX") which executes JTAPI functions; 
 * A PBX could handle more than one gateway; a gateway may use different
 * PBXs for different calls.
 * decode incoming packets and invoke the proper routines in "myPBX" 
 * for handling INVITE packets and HANGUP requests. START packets are ignored.
 */
public class PBXSignalingServer 
{
// Here are options for PBXSignalingServer and for logging on with the
// Tserver running on a node mentioned in the "tsapi.pro" file.
        private static boolean LOG=false; // true for debugging output
        private static final String ME = "PBXSignalingServer: ";

// Here are credentials for PBXSignaling
        private static String LOGIN = "cs519";
        private static String PASWD = "cs519";

// Variables related to this PBXSignalingServer
    private ServerSocket recvSock = null;
    private int myPort = -1;      // Server thread waits on this port
    private Location myLoc = null;     // IP address of here for D.S.
    private InetAddress myIAdd = null; // IP address of here for sockets
    private UserID myUID = new UserID("pbxsrv"); // who we are
    private Password myPIN = new Password("itxpbx");

// The PBX controller and Directory Services
    private PBXSignaling myPBX = null;
    private DirectoryService myDS = null;

    // main entry point.  
    public static void main (String[] args) {

       PBXSignalingServer myPBXSS = new PBXSignalingServer ( args );
    }
    
    
/**
 * Constructor is invoked by main.
 * It gets a PBXSignaling object, and registers self with Directory Services.
 * @param args from the command line
 */
    public PBXSignalingServer( String[] args ){

    // process command-line arguments
    for (int i = 0; i < args.length; i++ ) {
       if (args[i].equals ( "log" ) ) LOG = true;
    }

    if (LOG) System.out.println (ME+"being launched.");

    // Construct a PBXSignaling object.  Throws an exception if
    // Provider could not be obtained.

       if (LOG) System.out.println (ME + "getting a PBXSignaling object");

       try { myPBX = new PBXSignaling(LOG, LOGIN, PASWD); } 
       catch ( PBXSignalingException e) 
       { 
          System.out.println (ME + e);
          System.out.println ( ME + 
             "Exiting ... Could not obtain a working PBXSignaling object");
          System.exit(0);
       }

       if (LOG) System.out.println (ME + "got a PBXSignaling object");

    // Set up a socket and wait on it for packets from the gateway
        try{
            recvSock = new ServerSocket(0, 20);
            myPort = recvSock.getLocalPort();
        }
        catch(IOException ioe){
           ioe.printStackTrace();
           System.out.println (ME + "could not get a server port "
              + "on this computer");
           System.exit(0);
        }

    // If we get to here, we have a PBX and we have a socket port.

    // Figure out my hostname 
        try {
           myIAdd = InetAddress.getLocalHost();
        } catch (UnknownHostException e) { 
           System.out.println(ME+
           "Caught UnkownHostException while getting server host address");
           System.exit(0);
        }
        // following is sometimes a.b.c/x.y.z.n. Strip off prefix
        StringTokenizer st = new StringTokenizer( myIAdd.toString(), "/" );
        String myName = st.nextToken();
               myName = st.nextToken();

    // Construct a Location object indicating where we are running
        myLoc = new Location ("I", myName + ":" + myPort, "PBXsrv");
        if (LOG) {
           System.out.println (ME + "registering as " + myLoc);
           System.out.println(ME+" listening on port " + myPort);
        }

    // Get a DirectoryService object and authenticate ourselves.
           try {myDS = new DirectoryService();}
           catch (Exception e){
              e.printStackTrace();
              System.out.println(ME+"Exiting...No Directory Service");
              System.exit(0);
           }
           if (LOG) System.out.println ( ME + "got a DirectoryService");
           try { 
              myDS.declareIdentity ( myUID, myPIN );
           // Register our Location with Directory services so that 
           // Gateways can find us.
              myDS.registerLocation ( Location.ROAMING, myLoc ); 
           }
           catch (AuthenticationException e){ 
              e.printStackTrace(); 
              System.out.println(ME + e); 
           } catch (AccessDeniedException e){ e.printStackTrace(); 
           } catch (RecordNotFoundException e){ e.printStackTrace(); 
           }

       handlePackets();    

    } // end of PBXSignalingServer constructor

    /**
     * Destructor for PBXSignalingServer - stop all running Dialers,
     * unregister from Directory Server, and exit
     */
   protected void finalize() {

      if (LOG) System.out.println ( ME + "in finalize");

      // stop all running Dialers
      myPBX.stopDialers();

      boolean res = unRegister();
      if ( res )
         System.out.println ("Unregister of PBXsrv is complete.");
      else
         System.out.println ("Unregister of PBXsrv failed.");
      System.exit(0);

   } // end finalize

// ------------ Private methods for PBXSignalingServer ---------------------

    private void handlePackets () {
    // Go ahead and wait for a packet from the Gateway Desktop Signaling
    // Thread t calls finalize() to unregister us

        gateObject obj = null;   // deserialized InvitePacket & socket
        
        // this runs continuously until Enter is pressed
        Thread t = new ThreadWait( this );
        t.start();

        while(true){

            if (LOG) System.out.println(ME+"waiting for an incoming packet");

            try {
               obj = waitForPacket ( recvSock );
            } catch (InterruptedIOException iioe) { this.finalize(); }

            if ( obj == null ) {
               System.out.println ( ME + "failed to handle incoming packet "
               + "--dropped");
            }
            else {
               try {
                  handleIncomingPacket( obj.getSigPacket(), obj.getGateSocket() );
               } catch(PBXSignalingException dse){
                  System.out.println ( ME + "failed to handle incoming packet "
                     + "--dropped");
               }
            }

        } // go around and wait for something else

    }  // end of handlePackets

    // Wait for a packet to come in from some Gateway
    private gateObject waitForPacket ( ServerSocket recvSock )
    throws InterruptedIOException {

       Object obj = null;
       Socket rcvS = null;    // socket that Gateway will sent to us on

       try{
          rcvS = recvSock.accept();  // create a new socket

          if (LOG) 
             System.out.println(ME+"Received something...");

          DataInputStream in = new DataInputStream(rcvS.getInputStream());
          ObjectInputStream s = new ObjectInputStream(in);
          obj = s.readObject();
          return new gateObject ( (SigPacket) obj, rcvS ); 
       }
       catch(ClassNotFoundException cnf){
           System.out.println (ME + cnf);
           System.out.println (ME+"could not deserialize the packet");
           cnf.printStackTrace();
           return (gateObject) null;
       } catch (Exception e){
          System.out.println (ME + e);
          e.printStackTrace();
          return (gateObject) null;
       } 

    } // end waitForPacket 

    /**
     * This method decodes a SignalPacket and calls the required method 
     * in PBXSignaling.  
     * 
     * @param sp is the SignalPacket object received by the server thread.
     * @param gateSocket is the socket going to our Gateway
     * @return void
     */
    private void handleIncomingPacket( SigPacket sp, Socket gateSocket ) 
    throws PBXSignalingException{
        int mID;//method ID
        int pID;//packet ID

        if (LOG) System.out.println(ME+"got an incoming packet");
        
        if (sp == null)
            throw new PBXSignalingException
            ("Error DSS: Received a null packet.  Dropping.\n");
        mID = sp.getMethodID();
        pID = sp.getPacketID();
        
/* INVITE packet invokes the DIAL and HANGUP methods on our PBXSignaling */
        if(sp.isInvitePacket()){
            if (LOG) System.out.println ( ME + "got INVITE packet " );
            switch(mID){
                case SignalID.DIAL:
                    if (LOG) System.out.println (ME + "It is a Dial packet");
                    myPBX.handleDialInvite( (InvitePacket) sp,
                       gateSocket );
                    break;
                case SignalID.HANGUP:
                    if (LOG) System.out.println (ME + "is Hangup packet");
                    myPBX.handleHangupInvite( (InvitePacket) sp);
                    break;
                case SignalID.SENDDTMF:
                    if (LOG) {
                       System.out.println (ME + "is SendDTMF packet");
                       System.out.println (
                       ME+" does not handle SENDDTMF request packets");
                    }
                    throw new PBXSignalingException(
                    ME+"does not expect to get SENDDTMF request packets");
                case SignalID.ALIVEQUERY:
                    if (LOG) {
                       System.out.println (ME + "isAlive  packet");
                       System.out.println (
                          ME+" does not handle ALIVEQUERY request packets");
                    }
                    throw new PBXSignalingException(
                       ME+" got an ALIVEQUERY request packet");
                default:
                    if (LOG) System.out.println (ME + "unknown packet");
                    throw new PBXSignalingException(
                    ME+" got a request packet with an unknown method id");
            }
        }
        else if (sp.isResultPacket()){
            if (LOG) {
               System.out.println ( ME + "got a result packet");
               switch(mID) {
                  case SignalID.ACCEPT:
                     System.out.println ( ME + "accept packet");
                     break;
                  case SignalID.REJECT:
                     System.out.println ( ME + "reject packet");
                     break;
                  case SignalID.BUSY:
                     System.out.println ( ME + "busy packet");
                     break;
                  case SignalID.CONFIRMED:
                     System.out.println ( ME + "confirmed packet");
                     break;
                  case SignalID.TIMEOUT:
                     System.out.println ( ME + "timeout packet");
                     break;
                  case SignalID.INCOMPATIBLE:
                     System.out.println ( ME + "incompatible packet");
                     break;
                  default:
                     if (LOG) System.out.println (ME + "unknown packet");
               }
               System.out.println ( ME + "ignores result packets");
            }
            throw new PBXSignalingException(
ME+"Received an result SigPacket.  Dropping packet...\nDropped.\n");
        }
        else if (sp.isConfirmPacket()) {
           switch(mID) {
              case SignalID.DIAL:
                 if (LOG) System.out.println ( ME + 
                    "confirm type packet, with the DIAL method");
                 // Just ignore CONFIRM packets
                 break;
              default:
                 throw new PBXSignalingException(
       ME+"received a confirm packet of unknown method type");
           }
        }
        else
            throw new PBXSignalingException(
ME+"Received an unknown SigPacket.  Dropping packet...\nDropped.\n");
    
    }    

    /** 
     * This method unregisters this PBXsrv from Directory Service
     * @return true successful deregistration, false otherwise
     */
   private boolean unRegister() {
      if ( LOG ) System.out.println ( ME + "in unRegister()");
         try { 
            myDS.unregisterLocation( Location.ROAMING, myLoc ); 
            return true;
         } catch (Exception e) {
            System.out.println ( ME + e ); 
            return false;
         }
   }

   /**
    * Inner private class that puts a SigPacket and a Socket into
    * one object
    */
   private class gateObject {

      private SigPacket mySigPacket = null;
      private Socket myGateSocket = null;

      private gateObject ( SigPacket sp, Socket gs ) {
         mySigPacket = sp;
         myGateSocket = gs;
      }

      private SigPacket getSigPacket () { return mySigPacket; }

      private Socket getGateSocket () { return myGateSocket; }
   }

   /**
    * 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 and
    * Enter (or Return), it invokes the PBX Server's finalize
    * method.
    */
   protected class ThreadWait extends Thread {

      PBXSignalingServer myPBXSS = null;  // our PBX Signaling Server

      protected ThreadWait ( PBXSignalingServer myPBXSS_ ) {
         myPBXSS = myPBXSS_;
      }
   
      public void run() {
         int r;
         System.out.println (
         "\n=======================================================");
         System.out.println ( ME + "Press Enter to halt this PBX Server");
         System.out.println (
         "=======================================================\n");
         try { System.in.read ( ); }
         catch ( IOException e ) {}
         if ( LOG )
            System.out.println (ME + "ThreadWait is calling finalize");
         myPBXSS.finalize();
      }
   }  // ends ThreadWait inner class

}
