/* Copyright (c) 1991-2002 Doshita Lab. Speech Group, Kyoto University */
/* Copyright (c) 2000-2002 Speech and Acoustics Processing Lab., NAIST */
/* Copyright (c) 2000-2002 IPA */
/*   All rights reserved   */

/* msock.c --- communicate with SRM module (for IPA agent) */

#include <julius.h>
#include <stdarg.h>
#include <sys/poll.h>

#define MAXBUFLEN 4096

static int listen_sd = -1;		/* listen socket */
static int module_sd = -1;		/* socket */
static char mbuf[MAXBUFLEN];	/* message buffer */

/* status of Julian module */
/* set to TRUE is recognition is running */
/* if set to FALSE, process will stop at the beginning of next input */
static boolean process_active = TRUE;
/* if set to TRUE, disgard and stop recognition of the current processing
   input immediately */
static boolean process_want_terminate = FALSE;
/* if set to TRUE, segment the current input (if we are on 1st pass) */
static boolean process_want_reload = FALSE;

#ifdef USE_DFA
enum{SM_TERMINATE, SM_PAUSE, SM_WAIT};
static short gram_switch_input_method = SM_PAUSE;
#endif

/* ------------ SRM communication routines --------- */

void
main_module_loop()
{
  pid_t cid;
  
  /* prepare listen socket */
  if ((listen_sd = ready_as_server(module_port)) < 0) {
    j_error("Error: failed to bind socket\n");
  }

  j_printf  ("///////////////////////////////\n");
  j_printf  ("///  Module mode ready\n");
  j_printf  ("///  waiting client at %5d\n", module_port);
  j_printf  ("///////////////////////////////\n");
  j_printf  ("///  ");

  /* server loop */
  for(;;) {
    if ((module_sd = accept_from(listen_sd)) < 0) {
      j_error("Error: failed to accept connection\n");
    }
    /* fork process */
    if ((cid = fork()) != 0) {
      /* parent process */
      j_printf("///  forked [%d]\n", cid);
      close(module_sd);		/* parent not need socket to the client */
      continue;
    }
    /* child not need listen socket */
    close(listen_sd);
    /* call main recognition loop here */
    main_recognition_loop();
  }
}

/* disconnect client */
boolean
module_disconnect()
{
  if (close(module_sd) < 0) return FALSE;
  module_sd = -1;
  return TRUE;
}

/* return whether connected or not */
boolean
module_is_connected()
{
  if (module_sd < 0) return FALSE;
  return TRUE;
}


/* send message to SRM module */
int
module_send(char *fmt, ...)
{
  va_list ap;
  int ret;
  int len;
  va_start(ap,fmt);
  ret = vsnprintf(mbuf, MAXBUFLEN, fmt, ap);
  va_end(ap);
  if (ret > 0) {		/* success */
    if ((len = write(module_sd, mbuf, strlen(mbuf))) < 0) {
      perror("Error: module_send:");
    }
  }
  return(ret);
}


/* execute received commands */
/*
  MSOCK_COMMAND_DIE ,	 exit program 
  MSOCK_COMMAND_PAUSE ,	 stop recog. keeping the current input 
  MSOCK_COMMAND_TERMINATE , stop recog. dropping the current input 
  MSOCK_COMMAND_RESUME};	 resume the stopped process 
*/


#ifdef USE_DFA
/* read a new grammar via socket */
/* dfa and dict come in one stream:
   (DFAfile)..."DFAEND"...(DICTfile)..."DICEND" */
/* treat data as GRAM_INFO, and allocate new ones there */
static void
msock_read_grammar(DFA_INFO **ret_dfa, WORD_INFO **ret_winfo)
{
  DFA_INFO *dfa;
  WORD_INFO *winfo;

  /* load grammar: dfa and dict in turn */
  dfa = dfa_info_new();
  if (!rddfa_fd(module_sd, dfa)) {
    j_error("Error: msock: DFA format error\n");
  }
  winfo = word_info_new();
  /* ignore MONOTREE */
  if (!voca_load_htkdict_fd(module_sd, winfo, hmminfo, FALSE)) {
    j_error("Error: msock: dict format error\n");
  }
  *ret_dfa = dfa;
  *ret_winfo = winfo;
}

/* set flags to tell engine how/when to update whole global lexicon */
static void
set_grammar_switch_timing_flag()
{
  if (process_active) {
    /* if recognition is currently running, tell engine how/when to
       re-construct global lexicon. */
    switch(gram_switch_input_method) {
    case SM_TERMINATE:	/* discard input now and change (immediate) */
      process_want_terminate = TRUE;
      process_want_reload = TRUE;
      break;
    case SM_PAUSE:		/* segment input now, recognize it, and then change */
      process_want_terminate = FALSE;
      process_want_reload = TRUE;
      break;
    case SM_WAIT:		/* wait until the current input end and recognition completed */
      process_want_terminate = FALSE;
      process_want_reload = FALSE;
      break;
    }
    /* After the update, recognition will restart without sleeping. */
  } else {
    /* If recognition is currently not running, the received
       grammars are merely stored in memory here.  The re-construction of
       global lexicon will be delayed: it will be re-built just before
       the recognition process starts next time. */
  }
}
#endif

/* execute module command  */
void
msock_exec_command(char *command)
{
#ifdef USE_DFA
  DFA_INFO *new_dfa;
  WORD_INFO *new_winfo;
  static char *buf = NULL, *p;
  int gid;

  if (buf == NULL) buf = mymalloc(MAXBUFLEN);
#endif

  j_printf("[[%s]]\n",command);

  if (strmatch(command, "STATUS")) {
    if (process_active) {
      module_send("<SYSINFO PROCESS=\"ACTIVE\"/>\n.\n");
    } else {
      module_send("<SYSINFO PROCESS=\"SLEEP\"/>\n.\n");
    }
  } else if (strmatch(command, "DIE")) {
    j_exit();
  } else if (strmatch(command, "VERSION")) {
    module_send("<ENGINEINFO TYPE=\"%s\" VERSION=\"%s\" CONF=\"%s\"/>\n.\n",
		PRODUCTNAME, VERSION, SETUP);
  } else if (strmatch(command, "PAUSE")) {
    process_want_terminate = FALSE;
    process_want_reload = TRUE;
    process_active = FALSE;
  } else if (strmatch(command, "TERMINATE")) {
    process_want_terminate = TRUE;
    process_want_reload = TRUE;
    process_active = FALSE;
  } else if (strmatch(command, "RESUME")) {
    process_want_terminate = FALSE;
    process_active = TRUE;
#ifdef USE_DFA
  } else if (strmatch(command, "INPUTONCHANGE")) {
    if (getl_fd(buf, MAXBUFLEN, module_sd) == NULL) {
      j_error("Error: msock(INPUTONCHANGE): no argument\n");
    }
    if (strmatch(buf, "TERMINATE")) {
      gram_switch_input_method = SM_TERMINATE;
    } else if (strmatch(buf, "PAUSE")) {
      gram_switch_input_method = SM_PAUSE;
    } else if (strmatch(buf, "WAIT")) {
      gram_switch_input_method = SM_WAIT;
    } else {
      j_error("Error: msock(INPUTONCHANGE): unknown method [%s]\n", buf);
    }
  } else if (strmatch(command, "CHANGEGRAM")) {
    /* read a new grammar via socket */
    msock_read_grammar(&new_dfa, &new_winfo);
    /* delete all existing grammars */
    multigram_delete_all();
    /* register the new grammar to multi-gram tree */
    multigram_add(new_dfa, new_winfo);
    /* need to rebuild the global lexicon */
    /* set engine flag to tell how to switch the grammar when active */
    set_grammar_switch_timing_flag();
  } else if (strmatch(command, "ADDGRAM")) {
    /* read a new grammar via socket */
    msock_read_grammar(&new_dfa, &new_winfo);
    /* add it to multi-gram tree */
    multigram_add(new_dfa, new_winfo);
    /* need to rebuild the global lexicon */
    /* set engine flag to tell how to switch the grammar when active */
    set_grammar_switch_timing_flag();
  } else if (strmatch(command, "DELGRAM")) {
    /* read a list of grammar IDs to be deleted */
    if (getl_fd(buf, MAXBUFLEN, module_sd) == NULL) {
      j_error("Error: msock(DELGRAM): no argument\n");
    }
    /* extract IDs and mark them as delete
       (actual deletion will be performed on the next 
    */
    for(p=strtok(buf," ");p;p=strtok(NULL," ")) {
      gid = atoi(p);
      if (multigram_delete(gid) == FALSE) { /* deletion marking failed */
	j_printerr("Warning: msock(DELGRAM): gram #%d failed to delete, ignored\n", gid);
      }
    }
    /* need to rebuild the global lexicon */
    /* set engine flag to tell how to switch the grammar when active */
    set_grammar_switch_timing_flag();
  } else if (strmatch(command, "ACTIVATEGRAM")) {
    /* read a list of grammar IDs to be activated */
    if (getl_fd(buf, MAXBUFLEN, module_sd) == NULL) {
      j_error("Error: msock(ACTIVATEGRAM): no argument\n");
    }
    /* mark them as active */
    for(p=strtok(buf," ");p;p=strtok(NULL," ")) {
      gid = atoi(p);
      multigram_activate(gid);
    }
    /* tell engine when to change active status */
    set_grammar_switch_timing_flag();
  } else if (strmatch(command, "DEACTIVATEGRAM")) {
    /* read a list of grammar IDs to be de-activated */
    if (getl_fd(buf, MAXBUFLEN, module_sd) == NULL) {
      j_error("Error: msock(DEACTIVATEGRAM): no argument\n");
    }
    /* mark them as not active */
    for(p=strtok(buf," ");p;p=strtok(NULL," ")) {
      gid = atoi(p);
      multigram_deactivate(gid);
    }
    /* tell engine when to change active status */
    set_grammar_switch_timing_flag();
#endif
  }
}


/* return TRUE if julius is running */
boolean
module_is_active()
{
  return process_active;
}

/* return TRUE if julius has received a termination request */
boolean
module_wants_terminate()
{
  return process_want_terminate;
}

void
module_reset_reload()
{
  process_want_reload = FALSE;
}

/* process one incoming commands from SRM: block till a command comes */
void
msock_process_command()
{
  struct pollfd p;
  p.fd = module_sd;
  p.events = POLLIN;

  if (getl_fd(mbuf, MAXBUFLEN, module_sd) != NULL) {
    msock_exec_command(mbuf);
  }
}

/* check if any commands is in queue, and process them if any */
void
msock_check_and_process_command()
{
  struct pollfd p;
  int ret;

  /* check if some commands are waiting in queue */
  p.fd = module_sd;
  p.events = POLLIN;
  ret = poll(&p, 1, 0);		/* 0 msec timeout: return immediately */
  if (ret < 0) {
    perror("msock_check_and_process_command: cannot poll\n");
  }
  if (ret > 0) {
    /* there is data to read */
    /* process command and change status if necessaty */
    while(poll(&p, 1, 0) > 0 && getl_fd(mbuf, MAXBUFLEN, module_sd) != NULL) {
      msock_exec_command(mbuf);
    }
  }
}


/* check callback for adin: return -2 for force termination, -1
   for pause (only terminate when no output in buffer) */
int
msock_check_in_adin()
{
  if (module_is_connected()) {
    /* module: check command and terminate recording when requested */
    msock_check_and_process_command();
    if (module_wants_terminate()) {/* TERMINATE ... force termination */
      return(-2);
    }
    if (process_want_reload) {
      return(-1);
    }
  }
  return(0);
}
