/*
 * Copyright (C) 2005 Luca Veltri - University of Parma - Italy
 * 
 * This source code 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.
 * 
 * This source code 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.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this source code; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Author(s):
 * Luca Veltri (luca.veltri@unipr.it)
 */

package local.ua;


import org.zoolu.sip.address.*;
import org.zoolu.sip.provider.SipStack;
import org.zoolu.sip.provider.SipProvider;
import org.zoolu.net.SocketAddress;
import org.zoolu.tools.Log;
import org.zoolu.tools.LogLevel;

import java.io.*;


/** Simple command-line-based SIP user agent (UA).
  * It includes audio/video applications.
  * <p>It can use external audio/video tools as media applications.
  * Currently only RAT (Robust Audio Tool) and VIC are supported as external applications.
  */
public class CommandLineUA implements UserAgentListener, RegisterAgentListener
{           

   /** Event logger. */
   Log log;
   
   /** User Agent */
   UserAgent ua;

   /** Register Agent */
   RegisterAgent ra;
   
   /** UserAgentProfile */
   UserAgentProfile user_profile;
         
   /** Standard input */
   BufferedReader stdin=null; 
         
   /** Standard output */
   PrintStream stdout=null; 

        
   /** Costructs a UA with a default media port */
   public CommandLineUA(SipProvider sip_provider, UserAgentProfile user_profile)
   {  log=sip_provider.getLog();
      this.user_profile=user_profile;

      ua=new UserAgent(sip_provider,user_profile,this);      
      ra=new RegisterAgent(sip_provider,user_profile.from_url,user_profile.contact_url,user_profile.username,user_profile.realm,user_profile.passwd,this);

      if (!user_profile.no_prompt) stdin=new BufferedReader(new InputStreamReader(System.in)); 
      if (!user_profile.no_prompt) stdout=System.out;
      
      run();
   }


   /** Register with the registrar server.
     * @param expire_time expiration time in seconds */
   public void register(int expire_time)
   {  if (ra.isRegistering()) ra.halt();
      ra.register(expire_time);
   }


   /** Periodically registers the contact address with the registrar server.
     * @param expire_time expiration time in seconds
     * @param renew_time renew time in seconds
     * @param keepalive_time keep-alive packet rate (inter-arrival time) in milliseconds */
   public void loopRegister(int expire_time, int renew_time, long keepalive_time)
   {  if (ra.isRegistering()) ra.halt();
      ra.loopRegister(expire_time,renew_time,keepalive_time);
   }


   /** Unregister with the registrar server */
   public void unregister()
   {  if (ra.isRegistering()) ra.halt();
      ra.unregister();
   }


   /** Unregister all contacts with the registrar server */
   public void unregisterall()
   {  if (ra.isRegistering()) ra.halt();
      ra.unregisterall();
   }


   /** Makes a new call */
   public void call(String target_url)
   {  ua.hangup();
      ua.printLog("UAC: CALLING "+target_url);
      if (!ua.user_profile.audio && !ua.user_profile.video) ua.printLog("ONLY SIGNALING, NO MEDIA");       
      ua.call(target_url);       
   } 
         
         
   /** Receives incoming calls (auto accept) */
   public void listen()
   {  ua.printLog("UAS: WAITING FOR INCOMING CALL");
      if (!ua.user_profile.audio && !ua.user_profile.video) ua.printLog("ONLY SIGNALING, NO MEDIA");       
      ua.listen(); 
      printOut("digit the callee's URL to make a call or press 'enter' to exit");
   } 


   /** Starts the UA */
   void run()
   {
      try
      {  // Set the re-invite
         if (user_profile.re_invite_time>0)
         {  ua.reInvite(user_profile.contact_url,user_profile.re_invite_time);
         }

         // Set the transfer (REFER)
         if (user_profile.transfer_to!=null && user_profile.transfer_time>0)
         {  ua.callTransfer(user_profile.transfer_to,user_profile.transfer_time);
         }

         if (user_profile.do_unregister_all)
         // ########## unregisters ALL contact URLs
         {  ua.printLog("UNREGISTER ALL contact URLs");
            unregisterall();
         } 

         if (user_profile.do_unregister)
         // unregisters the contact URL
         {  ua.printLog("UNREGISTER the contact URL");
            unregister();
         } 

         if (user_profile.do_register)
         // ########## registers the contact URL with the registrar server
         {  ua.printLog("REGISTRATION");
            loopRegister(user_profile.expires,user_profile.expires/2,user_profile.keepalive_time);
         }         
         
         if (user_profile.call_to!=null)
         {  // UAC
            call(user_profile.call_to); 
            printOut("press 'enter' to hangup");
            readLine();
            ua.hangup();
            exit();
         }
         else
         {  // UAS
            if (user_profile.accept_time>=0) ua.printLog("UAS: AUTO ACCEPT MODE");
            listen();
            while (stdin!=null)
            {  String line=readLine();
               if (ua.statusIs(UserAgent.UA_INCOMING_CALL))
               {  if (line.toLowerCase().startsWith("n"))
                  {  ua.hangup();
                  }
                  else
                  {  ua.accept();             
                  }
               }
               else
               if (ua.statusIs(UserAgent.UA_IDLE))
               {  if (line!=null && line.length()>0)
                  {  call(line);
                  }
                  else
                  {  exit();
                  }
               }
               else
               if (ua.statusIs(UserAgent.UA_ONCALL))
               {  ua.hangup();
               }
            }
         }
      }
      catch (Exception e)  {  e.printStackTrace(); System.exit(0);  }
   }


   /** Exits */
   public void exit()
   {  try {  Thread.sleep(1000);  } catch (Exception e) {}
      System.exit(0);
   }


   // ******************* UserAgent callback functions ******************

   /** When a new call is incoming */
   public void onUaCallIncoming(UserAgent ua, NameAddress callee, NameAddress caller)
   {  if (ua.user_profile.redirect_to!=null) // redirect the call
      {  ua.redirect(ua.user_profile.redirect_to);
         printOut("call redirected to "+ua.user_profile.redirect_to);
      }         
      else
      if (ua.user_profile.accept_time>=0) // automatically accept the call
      {  //ua.accept();
         //printOut("press 'enter' to hangup"); 
         ua.automaticAccept(ua.user_profile.accept_time);
      }
      else         
      {  printOut("incoming call from "+caller.toString());
         printOut("accept? [yes/no]");
      }
   }
   
   /** When an ougoing call is remotly ringing */
   public void onUaCallRinging(UserAgent ua)
   {  
   }

   /** When an ougoing call has been accepted */
   public void onUaCallAccepted(UserAgent ua)
   {
   }
   
   /** When a call has been trasferred */
   public void onUaCallTrasferred(UserAgent ua)
   {  
   }

   /** When an incoming call has been cancelled */
   public void onUaCallCancelled(UserAgent ua)
   {  listen();
   }

   /** When an ougoing call has been refused or timeout */
   public void onUaCallFailed(UserAgent ua)
   {  if (ua.user_profile.call_to!=null) exit();
      else listen();
   }

   /** When a call has been locally or remotely closed */
   public void onUaCallClosed(UserAgent ua)
   {  if (ua.user_profile.call_to!=null) exit();
      else listen();     
   }


   // **************** RegisterAgent callback functions *****************

   /** When a UA has been successfully (un)registered. */
   public void onUaRegistrationSuccess(RegisterAgent ra, NameAddress target, NameAddress contact, String result)
   {  ua.printLog("Registration success: "+result,LogLevel.HIGH);
   }

   /** When a UA failed on (un)registering. */
   public void onUaRegistrationFailure(RegisterAgent ra, NameAddress target, NameAddress contact, String result)
   {  ua.printLog("Registration failure: "+result,LogLevel.HIGH);
   }
   

   // ***************************** MAIN *****************************


   /** The main method. */
   public static void main(String[] args)
   {         
      String file=null;
      boolean opt_regist=false;
      boolean opt_unregist=false;
      boolean opt_unregist_all=false;
      int     opt_expires=-1;
      long    opt_keepalive_time=-1;
      boolean opt_no_offer=false;
      String  opt_call_to=null;      
      int     opt_accept_time=-1;      
      int     opt_hangup_time=-1;
      String  opt_redirect_to=null;
      String  opt_transfer_to=null;
      int     opt_transfer_time=-1;
      int     opt_re_invite_time=-1;
      boolean opt_audio=false;
      boolean opt_video=false;
      int     opt_media_port=0;
      boolean opt_recv_only=false;
      boolean opt_send_only=false;
      boolean opt_send_tone=false;
      String  opt_send_file=null;
      String  opt_recv_file=null;
      boolean opt_no_prompt=false;
 
      String opt_from_url=null;
      String opt_contact_url=null;
      String opt_username=null;
      String opt_realm=null;
      String opt_passwd=null;

      int opt_debug_level=-1;
      String opt_log_path=null;
      String opt_outbound_proxy=null;
      String opt_via_addr=SipProvider.AUTO_CONFIGURATION;
      int opt_host_port=SipStack.default_port;

      try
      {  
         for (int i=0; i<args.length; i++)
         {
            if (args[i].equals("-f") && args.length>(i+1))
            {  file=args[++i];
               continue;
            }
            if (args[i].equals("-g") && args.length>(i+1)) // registrate the contact url
            {  opt_regist=true;
               String time=args[++i];
               if (time.charAt(time.length()-1)=='h') opt_expires=Integer.parseInt(time.substring(0,time.length()-1))*3600;
               else opt_expires=Integer.parseInt(time);
               continue;
            }
            if (args[i].equals("-u")) // unregistrate the contact url
            {  opt_unregist=true;
               continue;
            }
            if (args[i].equals("-z")) // unregistrate all contact urls
            {  opt_unregist_all=true;
               continue;
            }
            if (args[i].equals("-n")) // no offer in the invite
            {  opt_no_offer=true;
               continue;
            }
            if (args[i].equals("-c") && args.length>(i+1)) // make a call with a remote user (url)
            {  opt_call_to=args[++i];
               continue;
            }
            if (args[i].equals("-y") && args.length>(i+1)) // set automatic accept time
            {  opt_accept_time=Integer.parseInt(args[++i]);
               continue;
            }
            if (args[i].equals("-t") && args.length>(i+1)) // set the call duration
            {  opt_hangup_time=Integer.parseInt(args[++i]);
               continue;
            }
            if (args[i].equals("-i") && args.length>(i+1)) // set the re-invite time
            {  opt_re_invite_time=Integer.parseInt(args[++i]);
               continue;
            }
            if (args[i].equals("-r") && args.length>(i+1)) // redirect the call to a new url
            {  opt_accept_time=0;
               opt_redirect_to=args[++i];
               continue;
            }
            if (args[i].equals("-q") && args.length>(i+1)) // transfers the call to a new user (REFER)
            {  opt_transfer_to=args[++i];
               opt_transfer_time=Integer.parseInt(args[++i]);
               continue;
            }
            if (args[i].equals("-a")) // use audio
            {  opt_audio=true;
               continue;
            }
            if (args[i].equals("-v")) // use video
            {  opt_video=true;
               continue;
            }
            if (args[i].equals("-m") && args.length>(i+1)) // set the local media port
            {  opt_media_port=Integer.parseInt(args[++i]);
               continue;
            }
            if (args[i].equals("-o") && args.length>(i+1)) // outbound proxy
            {  opt_outbound_proxy=args[++i];
               continue;
            }
            if (args[i].equals("--via-addr") && args.length>(i+1)) // via addr
            {  opt_via_addr=args[++i];
               continue;
            }
            if (args[i].equals("-p") && args.length>(i+1)) // set the local sip port
            {  opt_host_port=Integer.parseInt(args[++i]);
               continue;
            }
            if (args[i].equals("--keep-alive") && args.length>(i+1)) // keep-alive
            {  opt_keepalive_time=Integer.parseInt(args[++i]);
               continue;
            }
            if (args[i].equals("--from-url") && args.length>(i+1)) // user's AOR
            {  opt_from_url=args[++i];
               continue;
            }
            if (args[i].equals("--contact-url") && args.length>(i+1)) // user's contact_url
            {  opt_contact_url=args[++i];
               continue;
            }
            if (args[i].equals("--username") && args.length>(i+1)) // username
            {  opt_username=args[++i];
               continue;
            }
            if (args[i].equals("--realm") && args.length>(i+1)) // realm
            {  opt_realm=args[++i];
               continue;
            }
            if (args[i].equals("--passwd") && args.length>(i+1)) // passwd
            {  opt_passwd=args[++i];
               continue;
            }
            if (args[i].equals("--recv-only")) // receive only mode
            {  opt_recv_only=true;
               continue;
            }
            if (args[i].equals("--send-only")) // send only mode
            {  opt_send_only=true;
               continue;
            }
            if (args[i].equals("--send-tone")) // send only mode
            {  opt_send_only=true;
               opt_send_tone=true;
               continue;
            }
            if (args[i].equals("--send-file") && args.length>(i+1)) // send audio file
            {  opt_send_file=args[++i];
               continue;
            }
            if (args[i].equals("--recv-file") && args.length>(i+1)) // receive audio file
            {  opt_recv_file=args[++i];
               continue;
            }
            if (args[i].equals("--debug-level") && args.length>(i+1)) // debug level
            {  opt_debug_level=Integer.parseInt(args[++i]);
               continue;
            }
            if (args[i].equals("--log-path") && args.length>(i+1)) // log path
            {  opt_log_path=args[++i];
               continue;
            }
            if (args[i].equals("--no-prompt")) // do not prompt
            {  opt_no_prompt=true;
               continue;
            }
            
            // else, do:
            if (!args[i].equals("-h"))
               System.out.println("unrecognized param '"+args[i]+"'\n");
            
            System.out.println("usage:\n   java CommandLineUA [options]");
            System.out.println("   options:");
            System.out.println("   -h              this help");
            System.out.println("   -f <file>       specifies a configuration file");
            System.out.println("   -t <secs>       auto hangup time (0 means manual hangup)");
            System.out.println("   -g <time>       registers the contact URL with the registrar server");
            System.out.println("                   where time is the duration of the registration, and can be");
            System.out.println("                   in seconds (default) or hours (-g 7200 is the same as -g 2h)");
            System.out.println("   -u              unregisters the contact URL with the registrar server");
            System.out.println("                   (is the same as -g 0)");
            System.out.println("   -z              unregisters ALL the contact URLs");
            System.out.println("   -n              no offer in invite (offer/answer in 2xx/ack)");
            System.out.println("   -c <call_to>    calls a remote user");
            System.out.println("   -y <secs>       auto answer time");
            System.out.println("   -i <secs>       re-invite after <secs> seconds");
            System.out.println("   -r <url>        redirects the call to new user <url>");
            System.out.println("   -q <url> <secs> transfers the call to <url> after <secs> seconds");
            System.out.println("   -a              audio");
            System.out.println("   -v              video");
            System.out.println("   -m <port>       (first) local media port");
            System.out.println("   -p <port>       local SIP port, used ONLY without -f option");
            System.out.println("   -o <addr>[:<port>]  use the specified outbound proxy");
            System.out.println("   --via-addr <addr>   host via address, used ONLY without -f option");
            System.out.println("   --keep-alive <millisecs>   send keep-alive packets each <millisecs>");
            System.out.println("   --from-url <url>    user's address-of-record (AOR)");
            System.out.println("   --contact-url <url> user's contact url");
            System.out.println("   --username <name>   user name used for authentication");
            System.out.println("   --realm <realm>     realm used for authentication");
            System.out.println("   --passwd <passwd>   passwd used for authentication");
            System.out.println("   --recv-only         receive only mode, no media is sent");
            System.out.println("   --send-only         send only mode, no media is received");
            System.out.println("   --send-tone         send only mode, an audio test tone is generated");
            System.out.println("   --send-file <file>  audio is played from the specified file");
            System.out.println("   --recv-file <file>  audio is recorded to the specified file");
            System.out.println("   --debug-level <n>   debug level (level=0 means no log)");
            System.out.println("   --log-path <path>   base path for all logs (./log is the default value)");
            System.out.println("   --no-prompt         do not prompt");
            System.exit(0);
         }
                     
         SipStack.init(file);
         if (opt_debug_level>=0) SipStack.debug_level=opt_debug_level;
         if (opt_log_path!=null) SipStack.log_path=opt_log_path;
         SipProvider sip_provider;
         if (file!=null) sip_provider=new SipProvider(file); else sip_provider=new SipProvider(opt_via_addr,opt_host_port);
         if (opt_outbound_proxy!=null) sip_provider.setOutboundProxy(new SocketAddress(opt_outbound_proxy));
         UserAgentProfile user_profile=new UserAgentProfile(file);
         
         if (opt_regist) user_profile.do_register=true;
         if (opt_unregist) user_profile.do_unregister=true;
         if (opt_unregist_all) user_profile.do_unregister_all=true;
         if (opt_expires>0) user_profile.expires=opt_expires;
         if (opt_keepalive_time>=0) user_profile.keepalive_time=opt_keepalive_time;
         if (opt_no_offer) user_profile.no_offer=true;
         if (opt_call_to!=null) user_profile.call_to=opt_call_to;
         if (opt_accept_time>=0) user_profile.accept_time=opt_accept_time;
         if (opt_hangup_time>0) user_profile.hangup_time=opt_hangup_time;
         if (opt_redirect_to!=null) user_profile.redirect_to=opt_redirect_to;
         if (opt_re_invite_time>0) user_profile.re_invite_time=opt_re_invite_time;
         if (opt_transfer_to!=null) user_profile.transfer_to=opt_transfer_to;
         if (opt_transfer_time>0) user_profile.transfer_time=opt_transfer_time;
         if (opt_audio) user_profile.audio=true;
         if (opt_video) user_profile.video=true;
         if (opt_media_port>0) user_profile.video_port=(user_profile.audio_port=opt_media_port)+2;
         if (opt_from_url!=null) user_profile.from_url=opt_from_url;
         if (opt_contact_url!=null) user_profile.contact_url=opt_contact_url;
         if (opt_username!=null) user_profile.username=opt_username;
         if (opt_realm!=null) user_profile.realm=opt_realm;
         if (opt_passwd!=null) user_profile.passwd=opt_passwd;
         if (opt_recv_only) user_profile.recv_only=true;
         if (opt_send_only) user_profile.send_only=true;             
         if (opt_send_tone) user_profile.send_tone=true;
         if (opt_send_file!=null) user_profile.send_file=opt_send_file;
         if (opt_recv_file!=null) user_profile.recv_file=opt_recv_file;
         if (opt_no_prompt) user_profile.no_prompt=true;
         
         // use audio as default media in case of..
         if ((opt_recv_only || opt_send_only || opt_send_tone || opt_send_file!=null || opt_recv_file!=null) && !opt_video) user_profile.audio=true;

         new CommandLineUA(sip_provider,user_profile);
      }
      catch (Exception e)  {  e.printStackTrace(); System.exit(0);  }
   }    
   

   // ****************************** Logs *****************************

   /** Read a new line from stantard input. */
   protected String readLine()
   {  try { if (stdin!=null) return stdin.readLine(); } catch (IOException e) {}
      return null;
   }

   /** Print to stantard output. */
   protected void printOut(String str)
   {  if (stdout!=null) System.out.println(str);
   }

   /** Adds a new string to the default Log */
   void printLog(String str)
   {  printLog(str,LogLevel.HIGH);
   }

   /** Adds a new string to the default Log */
   void printLog(String str, int level)
   {  if (log!=null) log.println("CommandLineUA: "+str,level+SipStack.LOG_LEVEL_UA);  
   }

   /** Adds the Exception message to the default Log */
   void printException(Exception e,int level)
   {  if (log!=null) log.printException(e,level+SipStack.LOG_LEVEL_UA);
   }

}
