/*
 * hear.c
 *
 * $Revision: 0.1 $
 *
 * Wait for particular phrase and exit
 *
 * Copyright (c) 2002, Michael Kosowsky
 * 
 * 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.
 *
 * This program 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 program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
 * Takes a list of strings as arguments, and returns with errorlevel
 * set to indicate which string was heard, e.g.
 * 
 *    hear horton "hears a who"
 * 
 * will exit with 1 if the user says "horton"
 * and 2 if the user says "hears a who".
 * 
 * Also, it exits with -1 if error, and 0 if another
 * app takes the voice focus.
 * 
 * The -a and -e flags are here to match xvoice; it appears that
 * every ViaVoice client must request the same audio lib,
 * and I don't see how you can esk the engine what the
 * current choice is.
 * 
 * The -x flag prevents a ViaVoice navigator application (e.g.
 * xvoice running in Navigator mode) from listening.  This is
 * called "enabling an exclusive vocabulary" in the ViaVoice docs.
 * 
 * Since we're not a GUI app, we do just about everything synchronously.
 * 
 * 
 * compile with -I/usr/lib/ViaVoice/include -lsmapi -lpopt
 *
*/



#include <stdio.h>
#include <smapi.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <unistd.h>
#include <popt.h>

#ifndef TRUE
#define TRUE (1)
#endif
#ifndef FALSE
#define FALSE (0)
#endif


#define appName "hear"
#define vocabName appName
#define DEBUG


#ifdef DEBUG
#define dbgprintf(x) do {		\
  printf("%s: ", __FUNCTION__);		\
    printf x ;				\
} while (0)
#else
#define dbgprintf(x)
#endif


#define  TRY(p)	CHECK(p, SM_RC_OK)

#define CHECK(p, c)				\
    do {					\
      rc = (p);					\
      if (rc != (c)) {				\
	dbgprintf(("%s: %d\n", #p, rc));	\
        exit (-1); }				\
    } while (0)

#define CHECK2(p, c1, c2)			\
    do {					\
      rc = (p);					\
      if (rc != (c1) && rc != (c2)) {		\
	dbgprintf(("%s: %d\n", #p, rc));	\
        exit (-1); }				\
    } while (0)


static int exit_val = 0;


/* These are passed to us from the ViaVoice lib
 * through the notifier callback, and we use them to
 * talk to the engine.  In this app we don't
 * ever actually call smapi_fn.
 */

static int smapi_socket = 0;
static int ( * smapi_fn ) ( ) = NULL;
static void * smapi_data = NULL;


int main (int argc, const char **argv)
{
  fd_set        rfds;
  int           retval, rc;
  SM_MSG        reply;
  static int    Connect(char* lib);
  static int    DefineWordsVocab(const char **words);
  SM_WORD       *firm;
  unsigned long i, num_firm;
  int           j, lib, exclusive, esd, arts;
  const char    **words;

  poptContext optCon;
  struct poptOption options[] = {
    {"esd",   'e', POPT_ARG_NONE,  &esd, 0, "Use esound",     NULL   },
    {"arts",  'a', POPT_ARG_NONE,  &arts, 0, "Use aRts",       NULL   },
    {"exclusive",  'x', POPT_ARG_NONE,  &exclusive, 0,       "Use exclusive vocabulary", NULL   },
    POPT_AUTOHELP
    {NULL, '\0', 0, NULL, 0}};

  exclusive = FALSE;
  esd = FALSE;
  arts = FALSE;
  optCon = poptGetContext(NULL, argc, argv, options, 0);
  poptSetOtherOptionHelp(optCon, "<phrase 1>  [phrase2 ...]");

  /* read all args */
  while ((j = poptGetNextOpt(optCon)) >= 0);
  if (j != -1) {
    fprintf(stderr, "%s: %s\n",
	    poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
	    poptStrerror(j));
    exit(-1); }
    
  /* any remaining strings are what we're hoping to hear */
  words = poptGetArgs(optCon);
  if (words == NULL || words[0] == NULL) {
    poptPrintUsage(optCon, stderr, 0);
    exit(-1); }


  if (    Connect(esd? "pcm;libxvesd.so;;foo" : arts? "pcm;libxvarts.so;;foo" : "pcm;audoss.so;;foo") != 0) exit(-1);
  TRY(    DefineWordsVocab(words)          );
  TRY(    SmEnableVocab(vocabName, &reply) );
  CHECK2( SmRequestFocus(&reply),          SM_RC_OK, SM_RC_FOCUS_GRANTED);
  CHECK2( SmMicOn(&reply),                 SM_RC_OK, SM_RC_MIC_ALREADY_ON);
  if (exclusive)
    TRY(  SmSet(SM_ENABLE_EXCLUSIVE_VOCABS, TRUE, &reply)   );
  TRY(    SmRecognizeNextWord(&reply)      );

  while (1) {
    /* wait for input on smapi_socket, then read it with SmReceiveMsg,
       rather than bother with callbacks */
    FD_ZERO(&rfds);
    FD_SET(smapi_socket, & rfds);
    if (!select(smapi_socket + 1, &rfds, NULL, NULL, (struct timeval *) NULL))
      continue;

    TRY( SmReceiveMsg(0, &reply) );
    TRY( SmGetMsgType(reply, &j) );

    if (j == SM_FOCUS_LOST) {
      dbgprintf(("focus lost\n"));
      exit(0); }
    if (j != SM_RECOGNIZED_WORD) {
      dbgprintf(("message %d\n", j));
      continue; }

    TRY( SmGetFirmWords(reply, &num_firm, &firm) );
    for (i = 0 ; i < num_firm; i++)
      if (firm[i].spelling[0] != '\0')
	for (j = 0; words[j] != NULL; j++)
	  if (!strcmp(firm[i].spelling, words[j])) {
	    dbgprintf(("exiting %d on %s\n", j+1, words[j]));
	    exit(j+1); }

    TRY( SmRecognizeNextWord(&reply) );
  }
}


static int notifier ( int socket_handle, int ( * recv_fn ) ( ), void * recv_data, void * client_data )
{
  smapi_socket = socket_handle;
  smapi_fn     = recv_fn;
  smapi_data   = recv_data;

  return ( 0 );
}


static int Connect(char* lib)
{
  static int first = TRUE;
  int        rc;
  int        smc;
  SmArg      smargs [ 30 ];
  char       * cp;
  static int input_id;
  SM_MSG     reply;

  static SmHandler ConnectCB     ( SM_MSG reply, void * client, void * call_data );
  static SmHandler DisconnectCB  ( SM_MSG reply, void * client, void * call_data );
  static SmHandler SetCB         ( SM_MSG reply, void * client, void * call_data );
  static SmHandler MicOnCB       ( SM_MSG reply, void * client, void * call_data );
  static SmHandler MicOffCB      ( SM_MSG reply, void * client, void * call_data );
  static SmHandler DefineVocabCB ( SM_MSG reply, void * client, void * call_data );
  static SmHandler EnableVocabCB ( SM_MSG reply, void * client, void * call_data );
  static SmHandler GetNextWordCB ( SM_MSG reply, void * client, void * call_data );
  static SmHandler RecoWordCB    ( SM_MSG reply, void * client, void * call_data );
  static SmHandler RecoTextCB    ( SM_MSG reply, void * client, void * call_data );
  static SmHandler UtteranceCB   ( SM_MSG reply, void * client, void * call_data );
  static SmHandler NotifyFocusStateCB    ( SM_MSG reply, void * client, void * call_data );

  dbgprintf(("started\n"));

  if ( first )
  {
    smc = 0;
    SmSetArg ( smargs [ smc ], SmNapplicationName,  appName );  smc++;
    SmSetArg ( smargs [ smc ], SmNexternalNotifier, notifier ); smc++;

    rc = SmOpen ( smc, smargs );
    if ( rc != SM_RC_OK )
    {
      dbgprintf(("SmOpen %d\n", rc ));
      return rc;
    }

    /* SmAddCallback ( SmNconnectCallback,             ConnectCB,       NULL ); */
    /* SmAddCallback ( SmNdisconnectCallback,          DisconnectCB,    NULL ); */
    /* SmAddCallback ( SmNsetCallback,                 SetCB,           NULL ); */
    /* SmAddCallback ( SmNmicOnCallback,               MicOnCB,         NULL ); */
    /* SmAddCallback ( SmNmicOffCallback,              MicOffCB,        NULL ); */
    /* SmAddCallback ( SmNenableVocabCallback,         EnableVocabCB,   NULL ); */
    /* SmAddCallback ( SmNdefineVocabCallback,         DefineVocabCB,   NULL ); */
    /* SmAddCallback ( SmNrecognizeNextWordCallback,   GetNextWordCB,   NULL ); */
    /* SmAddCallback ( SmNrecognizedWordCallback,      RecoWordCB,      NULL ); */
    /* SmAddCallback ( SmNrecognizedTextCallback,      RecoTextCB,      NULL ); */
    /* SmAddCallback ( SmNutteranceCompletedCallback,  UtteranceCB,     NULL ); */
    /* SmAddCallback ( SmNfocusStateCallback,          NotifyFocusStateCB, NULL ); */

    first = FALSE;
  }

  smc = 0;
  SmSetArg ( smargs [ smc ], SmNuserId,       SM_USE_CURRENT );  smc++;
  SmSetArg ( smargs [ smc ], SmNenrollId,     SM_USE_CURRENT );  smc++;
  SmSetArg ( smargs [ smc ], SmNtask,         SM_USE_CURRENT );  smc++;
  SmSetArg ( smargs [ smc ], SmNrecognize,    TRUE           );  smc++;
  SmSetArg ( smargs [ smc ], SmNoverrideLock, TRUE           );  smc++;
  SmSetArg ( smargs [ smc ], SmNaudioHost,    lib            );  smc++;


  rc = SmConnect(smc, smargs, &reply);
  if (rc != SM_RC_OK) {
    dbgprintf(("SmConnect %d\n", rc));
    if (rc == SM_RC_ENOSERVER)
      fprintf(stderr, "%s", "Did you . vvsetenv?\n");
    return rc;
  }
  return 0;
}


static int DefineWordsVocab(const char **words)
{
  SM_MSG reply;
  SM_VOCWORD *voc_words;
  SM_VOCWORD **voc_ptrs;
  int i, count;
  int rc;
  static SmHandler DefineVocabCB ( SM_MSG reply, void * client, void * call_data );

  for (count = 0; words[count] != NULL; ++count);
  dbgprintf(("words %d\n", count));

  voc_words = (SM_VOCWORD *)malloc(count*sizeof(SM_VOCWORD));
  voc_ptrs = (SM_VOCWORD **)malloc(count*sizeof(SM_VOCWORD));
  /*
   * The SmDefineVocab call expects an array of pointers to SM_VOCWORD
   * structures rather than the SM_VOCWORDs themself
   */
  for (i = 0; i < count; i++) {
    dbgprintf(("word %d: %s\n", i, words[i]));
    voc_ptrs[i] = &(voc_words[i]);
    voc_words[i].spelling = (char *)words[i];
    voc_words[i].spelling_size = strlen(voc_words[i].spelling);
    voc_words[i].flags = 0;
  }

  rc = SmDefineVocab(vocabName, i, voc_ptrs, &reply);
  free(voc_words);
  free(voc_ptrs);

  return rc;
}
