/*------------------------------------------------------------------------
 *
 *  File:	o1k_conf.cc
 *  Revision:	1.22
 *  Last Mod:	12:05:52, 05/21/97
 *
 *  Description:
 *     
 *      Implementation of conference O1kConf class. Contains
 *      routines for calling, joining and hanging up on a
 *      conference.
 *     
 *----------------------------------------------------------------------*/
#pragma ident	"@(#)o1k_conf.cc	1.22\t97/05/21  "
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <pwd.h>
#include <sys/utsname.h>

#include "o1k_conf.h"
#include "o1k_misc.h"
#include "packet.h"

extern void o1k_conf_grace(int signo);

/*
 * Global pointer to single O1kConf object
 */
O1kConf* conf;

/* Verbose flag for printing debugging info */
int verbose = 0;

/* Convenient strings for debugging */
static char* ConfStateStr[] = {
  "CONF_IDLE", "CONF_CALLING", "CONF_RECEIVED_CALL",
  "CONF_JOINING", "CONF_IN_CONF", "CONF_CLOSING"
};

/*------------------------------------------------------------------------
 *
 *  Function:	O1kConf::setState()
 *
 *  Description:
 *     Set conference state. Called whenever conference state is
 *     changed.
 *     
 *  Parameters:
 *     int s    : new state
 *     
 *----------------------------------------------------------------------*/

void O1kConf::setState(int s)
{
  int last_state = conf->state;
  state = s;

  if (verbose) printf(">>> Changing conference state from [%s] to [%s] <<<\n",
                      ConfStateStr[last_state],ConfStateStr[state]);
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kConf::O1kConf()
 *
 *  Description:
 *     Init conference state information. Called once on process init.
 *     Various conf objects are created here.
 *     
 *  Notes:
 *     Failure of O1kConf() is not currently being detected.
 *     
 *     
 *----------------------------------------------------------------------*/

O1kConf::O1kConf()
{
  int rc;
  struct passwd* passwdinfo;
  struct utsname unameinfo;
  
  /* set global O1kConf object pointer */
  conf = this;

  /* set state to idle */
  state = CONF_IDLE;
  loop_mode = FALSE;

  o1k_time_init();
  
  /* init audio/video/network state information */
  video = new O1kVideo();
  audio = new O1kAudio();
  network = new O1kNetwork();
  addressbook = new O1kAddrBook();
  
  /* determine name of user and hostname where this process is running */
  user_id = getuid();
  passwdinfo = getpwuid(user_id);
  strncpy(user_name, passwdinfo->pw_name, 80);
  rc = uname(&unameinfo);  
  strncpy(host_name, unameinfo.nodename, 80);
  sprintf(userhost, "%s@%s",user_name,host_name);  

  if (verbose) printf("You are %s\n",userhost);

  if (addressbook)
    addressbook->setuser(user_name);
  
  /* setup signal handler for closedown conditions */
  signal(SIGINT,   o1k_conf_exit);
  signal(SIGSEGV,  o1k_conf_grace);
  signal(SIGBUS,   o1k_conf_grace);

  memset(&incoming_call, 0, sizeof(CallInfo));  

  /*
   * Start the control network receive thread. This thread will 
   * 'listen' for others wanting to start a conference with us.
   */
  rc = network->startReceive(NET_ID_CNTRL);
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kConf::~O1kConf()
 *
 *  Description:
 *	
 *     Destructor for O1kConf object.
 *	
 *----------------------------------------------------------------------*/

O1kConf::~O1kConf()
{
  if (addressbook)
    delete addressbook;
  if (network)
    delete network;
  if (audio)
    delete audio;
  if (video)
    delete video;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kConf::settings()
 *
 *  Description:
 *     Print out conference settings.
 *     
 *----------------------------------------------------------------------*/

void O1kConf::settings()
{
  if (verbose){
    video->settings();
    audio->settings();
    network->settings();
  }
}

/*------------------------------------------------------------------------
 *
 *  Function:	o1k_conf_grace()
 *
 *  Description:
 *     Due to some multi-thread problems, this example has been known
 *     to segv - usually (almost always) in a malloc call when the
 *     conference starts up. Once a conference is in progress, the
 *     problem does not seem to occur. We try to gracefully exit here
 *     when a problem occurs.
 *     
 *----------------------------------------------------------------------*/

void o1k_conf_grace(int signo)
{
  fprintf(stderr,"Internal application problem detected.\n");
  fprintf(stderr,"Please try starting conference again.\n");  
  exit(0);
}

/*------------------------------------------------------------------------
 *
 *  Function:	o1k_conf_exit()
 *
 *  Description:
 *     Called on process exit (can be called via a signal,
 *     i.e. control-c). It is best to close down a conference
 *     gracefully (like telling the far end that you are hanging
 *     up on the conference).
 *     
 *----------------------------------------------------------------------*/

void o1k_conf_exit(int signo)
{
  if (verbose){
    if (verbose) printf("o1k_conf_exit(): closing down...\n");
    fflush(stdout);
  }

  /* stop recieve control thread. this is the only place this is done */
  conf->network->stopReceive(NET_ID_CNTRL);

  /* close any conference in progress */
  conf->hangup();

  if (verbose) printf("o1k_conf_exit(): closing down done...\n");

  exit(0);
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kConf::call()
 *
 *  Description:
 *     Call a conference. This routine will start a control 
 *     transmission to the far end telling the far end the
 *     desire for a conference. The conference parameters
 *     which must be the same on each end are passed along
 *     as well. We then wait for a response from the far end.
 *     
 *  Returns:
 *     
 *     Currently always return 0.
 *     
 *----------------------------------------------------------------------*/

int O1kConf::call()
{
  int netids = 0;
  CallInfo outgoing_call;
  memset(&outgoing_call, 0, sizeof(CallInfo));

  if (verbose) printf("o1k_conf_call()\n");

  /* debug check settings */
  settings();

  /* check for special loopback (calling ourselves) condition */
  if (strcmp(userhost, network->destination) == 0){
    if (verbose) printf("calling ourselves\n");
    loop_mode = TRUE;
  } else {
    loop_mode = FALSE;
  }
  setState(CONF_CALLING);
  
  
  /* Start control network trasnmit thread */
  network->startTransmit(NET_ID_CNTRL);

  /* Transmit our desire to start a conference */
  outgoing_call.ctype = CNTRL_CALLING;
  strcpy(outgoing_call.source, userhost);
  outgoing_call.atype       = audio->getFormat();
  outgoing_call.channels    = audio->getChannels();
  outgoing_call.sample_rate = audio->getSampleRate();
  outgoing_call.vtype       = video->getFormat();
  if (outgoing_call.vtype != VID_NONE){
    outgoing_call.size        = video->getSize();
    outgoing_call.framerate   = video->getFrameRate();
  } else {
    outgoing_call.size        = SIZE_NONE;
    outgoing_call.framerate   = 0;
  }

  /* we should probably check return from here */
  o1k_conf_network_transmit((unsigned char*)&outgoing_call, sizeof(CallInfo), 10000, NET_TYPE_CNTRL);

  /* set timer for timeout wainting on response */
  o1k_new_time(&tstart);
  
  return 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kConf::hangup()
 *
 *  Description:
 *     Hangup on a conference. This is called on demand when
 *     a conference is in progress and on closedown (exit).
 *     
 *  Returns:
 *     Currently always return 1.
 *     
 *----------------------------------------------------------------------*/

int O1kConf::hangup()
{
  int netids = 0;

  /* stop control transmission to far end */
  if (state == CONF_IN_CONF){
    CntrlType ctype;

    if (verbose) printf("O1kConf::hangup(): telling farside in process of hanging up\n");
    
    ctype = CNTRL_HANGUP;
    o1k_conf_network_transmit((unsigned char*)&ctype, sizeof(CntrlType), 10000, NET_TYPE_CNTRL);
  }
  
  network->stopTransmit(NET_ID_CNTRL);

  /* stop audio/video transmission/receive to/from far end */
  if (video->getFormat() != VID_NONE)
    netids |= NET_ID_VIDEO;
  if (audio->getFormat() != AUD_NONE)
    netids |= NET_ID_AUDIO;
  
  network->stopReceive(netids);
  network->stopTransmit(netids);

  /* set state to idle */
  setState(CONF_IDLE);
  loop_mode = FALSE;
  
  return 1;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kConf::callTimedout()
 *
 *  Description:
 *     Called when an attempt to start a conference with a far
 *     end client has timed out.
 *     
 *  Returns: 0
 *     
 *----------------------------------------------------------------------*/

int  O1kConf::callTimedout()
{
  if (verbose) printf("o1k_conf_call_timedout\n");
  
  network->stopTransmit(NET_ID_CNTRL);
  setState(CONF_IDLE);
  
  return 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kConf::callAccepted()
 *
 *  Description:
 *     Called when an attempt to start a conference with a far
 *     end client has resulted in the far end joining. We now
 *     can start sending audio/video as desired. Note that if
 *     we are in loop mode, the caller only sends audio/vide
 *     and the receiver (also ourselves) only receives. Other
 *     wise we both send and receive audio/video as desired.
 *     This routine is effectively the 'caller'.
 *     
 *  Returns: 0
 *     
 *----------------------------------------------------------------------*/

int O1kConf::callAccepted()
{
  int netids = 0;
  
  if (verbose) printf("o1k_conf_call_accepted\n");
  
  setState(CONF_IN_CONF);

  /* start audio/video as desired */
  if (video->getFormat() != VID_NONE)
    netids |= NET_ID_VIDEO;
  if (audio->getFormat() != AUD_NONE)
    netids |= NET_ID_AUDIO;

  if (!loop_mode)  
    network->startReceive(netids);
  network->startTransmit(netids);
  
  return 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kConf::joinAccept()
 *
 *  Description:
 *     Join a conference. This is called after we receive a request
 *     from the far end to join it in a conference and decide to
 *     join. There a certain common conference params which must
 *     be the same on each end. We set ours to match the far end.
 *     We then tell the far end we are joining. 
 *
 *     We can then start to send audio/video as desired. Note that
 *     if we are in loop mode, the caller only sends audio/video
 *     and the receiver (also ourselves) only receives. Other
 *     wise we both send and receive audio/video as desired.
 *     This routine is effectively the 'receiver'.
 *     
 *  Returns: 0
 *     
 *----------------------------------------------------------------------*/

int  O1kConf::joinAccept()
{
  CntrlType ctype;
  int netids = 0;
  
  /* set conference parameters to match far end */
  audio->setFormat(incoming_call.atype);
  audio->setChannels(incoming_call.channels);
  audio->setSampleRate(incoming_call.sample_rate);
  
  video->setFormat(incoming_call.vtype);
  switch (video->getFormat()) {
    case H261:
    {
      video->setH261Size(incoming_call.size);
      video->setH261FrameRate(incoming_call.framerate);
      break;
    }
    case CELLB:
    {
      video->setCellbSize(incoming_call.size);
      video->setCellbFrameRate(incoming_call.framerate);
      break;
    }
    case JPEG:
    {
      video->setJpegSize(incoming_call.size);
      video->setJpegFrameRate(incoming_call.framerate);
      break;
    }
  }

  /* debug print settings */
  settings();

  /* start transmitter to tell far end you've accepted */
  /* (note: transmitter does not need to be started in loop mode */
  if (!loop_mode)
    network->startTransmit(NET_ID_CNTRL);

  /* set conference state */
  setState(CONF_IN_CONF);

  ctype = CNTRL_JOINING;
  o1k_conf_network_transmit((unsigned char*)&ctype, sizeof(CntrlType), 10000, NET_TYPE_CNTRL);
      
  poll(0,0,200); /* wait for other side to note we are joining */

  /* start audio/video as desired */
  if (video->getFormat() != VID_NONE)
    netids |= NET_ID_VIDEO;
  if (audio->getFormat() != AUD_NONE)
    netids |= NET_ID_AUDIO;

  network->startReceive(netids);
  if (!loop_mode)
    network->startTransmit(netids);

  return 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kConf::joinDeny()
 *
 *  Description:
 *     Deny a conference. This is called after we receive a request
 *     from the far end to join it in a conference and decide to
 *     deny it. We can just ignore the request and let the far end
 *     timeout.
 *     
 *  Returns: 0
 *     
 *----------------------------------------------------------------------*/

int  O1kConf::joinDeny()
{
  /* just ignore to deny */

  /* if not in loop mode, set to idle. Otherwise let loop mode timeout */
  if (!loop_mode)
    setState(CONF_IDLE);

  return 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	 O1kConf::checkControl()
 *
 *  Description:
 *     This routine must be called by a timer of some sort. It is
 *     responsible for receiving control information for a far end
 *     transmission. The caller should check the control return type
 *     and act appropriately.
 *     
 *  Returns:
 *     The return value is a CntrlType. The value 0 maps to CNTRL_NONE.
 *     
 *----------------------------------------------------------------------*/

int  O1kConf::checkControl()
{
  static unsigned char data[MAX_FRAME_SIZE];
  static unsigned int  size;
  int rc;
  CntrlType ctype;

  if (verbose) printf("o1k_conf_check_control: state %s\n",ConfStateStr[state]);
  
  rc = o1k_conf_network_receive(data, &size, 0, NET_TYPE_CNTRL);

  if (rc == 0){
    /* a call came in */
    ctype = *((CntrlType*)data);
    switch(ctype){
      case CNTRL_CALLING:
      {
        /*
         * We receive CNTRL_CALLING when the far end is calling us to
         * start in a conference.
         */

        if (verbose) printf("control received CNTRL_CALLING\n");
        
        /* verify packet size */
        if (size != sizeof(CallInfo)){
          fprintf(stderr,"Control Error(%d): Invalid Size %d\n",ctype, size);
          return 0;
        }

        /* someone is calling us. if not idle, just ignore */
        if (state != CONF_IDLE && !loop_mode)
          break;
        
        setState(CONF_RECEIVED_CALL);

        if (!loop_mode)
          o1k_new_time(&tstart);
        
        memcpy((char*)&incoming_call, data, size);

        rc = CNTRL_CALLING;
        break;
      }
      case CNTRL_JOINING:
      {
        /*
         * We receive CNTRL_JOINING when the far end has accepted our
         * invite to start a conference.
         */
        
        if (verbose) printf("control received CNTRL_JOINING: now in conference\n");
        
        rc = CNTRL_ACCEPTED;
        break;
      }
      case CNTRL_HANGUP:
      {
        /*
         * We receive CNTRL_HANGUP when the far end is hanging up
         * our conference.
         */
        if (verbose) printf("control received CNTRL_HANGUP: hanging up conference\n");
        
        if (!loop_mode)
          rc = CNTRL_HANGUP;
        else
          return 0;
        break;
      }
    }
  } else {
    /* nothing received */
    rc = 0;
    
    if (state == CONF_CALLING ||
        (loop_mode && state == CONF_RECEIVED_CALL)){
      /* check for timeout */
      if (o1k_mtime_diff(&tstart) > 15.0){
        if (verbose) printf("call timed out\n");
        rc = CNTRL_TIMEOUT;
      }
    }
    if (!loop_mode && state == CONF_RECEIVED_CALL){
      /* check for timeout */
      if (o1k_mtime_diff(&tstart) > 10.0){
        if (verbose) printf("call timed out\n");
        rc = CNTRL_TIMEOUT;
      }
    }
  }
  
  return rc;
}

