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

/* multi_gram.c --- multiple grammar handling */

/* $Id: multi-gram.c,v 1.5 2003/01/06 09:11:02 ri Exp $ */

#include <julius.h>

#ifdef USE_DFA

#define MDEBUG			/* enable debug message */

/************************************************************************/
/************************************************************************/
/************************************************************************/

/* actual global DFA/WORD_INFO is "dfa" and "winfo" in global.h */
static DFA_INFO *global_dfa = NULL; /* global DFA (local) */
static WORD_INFO *global_winfo = NULL; /* global word dic. (local) */
static int gram_maxid = 0;
#define GRAM_MAXID_MAX 2147483647 /* (enough!) */
/* MULTIGRAM *gramlist ... made global */

/************************************************************************/
/* setup grammar for recognition */
/************************************************************************/
static void
multigram_setup(DFA_INFO *d, WORD_INFO *w)
{
  if (d == NULL || w == NULL) {
    /* no grammar */
    dfa = NULL;			/* reset */
    winfo = NULL;
    return;
  }

  /* set the global grammar and vocabulary pointer */
  dfa = d;
  winfo = w;

  /* re-build wchmm */
  if (wchmm != NULL) wchmm_free(wchmm);
  wchmm = wchmm_new();
  wchmm->dfa = d;
  wchmm->winfo = w;
  wchmm->hmminfo = hmminfo;
#ifdef CATEGORY_TREE
  if (old_tree_function_flag) {
    build_wchmm(wchmm);
  } else {
    build_wchmm2(wchmm);
  }
#else
  build_wchmm2(wchmm);
#endif /* CATEGORY_TREE */
  /* guess beam width from models, when not specified */
  if (trellis_beam_width <= 0) set_beam_width();
  if (trellis_beam_width > wchmm->n) trellis_beam_width = wchmm->n;

#ifdef USE_NGRAM
  /* reallocate factoring cache */
  max_successor_cache_free();
  max_successor_cache_init(wchmm);
#endif

  /* finished! */
}

/************************************************************************/
/* print grammar info to stdout */
/************************************************************************/
static char *hookstr[] = {"", "delete", "activate", "deactivate"};
static void 
print_all_gram()
{
  MULTIGRAM *m;

  j_printf("[grammars]\n");
  for(m=gramlist;m;m=m->next) {
    j_printf("  #%2d: [%-11s] %4d words, %3d categories, %4d nodes",
	     m->id,
	     m->active ? "active" : "inactive",
	     m->winfo->num, m->dfa->term_num, m->dfa->state_num);
    if (m->newbie) j_printf(" (new)");
    if (m->hook != MULTIGRAM_DEFAULT) {
      j_printf(" (next: %s)", hookstr[m->hook]);
    }
    j_printf("\n");
  }
  if (global_dfa != NULL) {
    j_printf("  Grobal:            %4d words, %3d categories, %4d nodes\n",
	     global_winfo->num, global_dfa->term_num, global_dfa->state_num);
  }
}

/************************************************************************/
/* send grammar info to module */
/************************************************************************/
static void
send_gram_info()
{
  MULTIGRAM *m;

  module_send("<GRAMINFO>\n");
  for(m=gramlist;m;m=m->next) {
    module_send("  #%2d: [%-11s] %4d words, %3d categories, %4d nodes",
		m->id,
		m->active ? "active" : "inactive",
		m->winfo->num, m->dfa->term_num, m->dfa->state_num);
    if (m->newbie) module_send(" (new)");
    if (m->hook != MULTIGRAM_DEFAULT) {
      module_send(" (next: %s)", hookstr[m->hook]);
    }
    module_send("\n");
  }
  if (global_dfa != NULL) {
    module_send("  Grobal:            %4d words, %3d categories, %4d nodes\n",
		global_winfo->num, global_dfa->term_num, global_dfa->state_num);
  }
  module_send("</GRAMINFO>\n.\n");
}

/************************************************************************/
/* concatinate grammars for multi-grammar */
/************************************************************************/
/* append grammar to the global grammar */
static void
multigram_build_append(DFA_INFO *gdfa, WORD_INFO *gwinfo, MULTIGRAM *m)
{
  /* where the new grammar 'm' will be appended to the gdfa */
  m->state_begin = gdfa->state_num;	/* initial state ID */
  m->cate_begin = gdfa->term_num;	/* initial terminal ID */
  m->word_begin = gwinfo->num;	/* initial word ID */
  
  /* append category ID and node number of src DFA */
  /* Julius allow multiple initial states: connect each initial node
     is not necesarry. */
  dfa_append(gdfa, m->dfa, m->state_begin, m->cate_begin);
  /* append words of src vocabulary to global winfo */
  voca_append(gwinfo, m->winfo, m->cate_begin, m->word_begin);
  /* append category->word mapping table */
  terminfo_append(&(gdfa->term), &(m->dfa->term), m->cate_begin, m->word_begin);
  /* append catergory-pair information */
  /* pause has already been considered on m->dfa, so just append here */
  cpair_append(gdfa, m->dfa, m->cate_begin);
  /* re-set noise entry by merging */
  dfa_pause_word_append(gdfa, m->dfa, m->cate_begin);
#ifdef MDEBUG
  j_printf("- Gram #%d: installed\n", m->id);
#endif
}

/************************************************************************/
/* add new grammar to list */
/************************************************************************/
void
multigram_add(DFA_INFO *dfa, WORD_INFO *winfo)
{
  MULTIGRAM *new;

  if (gram_maxid >= GRAM_MAXID_MAX) {
    j_error("Error: grammar ID has reached its limit!\n");
    if (module_mode) {
      module_send("<ERROR MESSAGE=\"grammar ID has reached its limit\"/>\n.\n");
    }
  }

  /* allocate new gram */
  new = (MULTIGRAM *)mymalloc(sizeof(MULTIGRAM));
  new->id = gram_maxid;
  new->dfa = dfa;
  new->winfo = winfo;
  new->hook = MULTIGRAM_DEFAULT;
  new->newbie = TRUE;		/* need to setup */
  new->active = TRUE;		/* default: active */
  new->next = gramlist;
  gramlist = new;

  j_printf("- Gram #%d: read\n", new->id);
  if (module_mode) {
    send_gram_info();
  }
#ifdef MDEBUG
  print_all_gram();
#endif
  gram_maxid++;
}
/************************************************************************/
/* mark grammar to be deleted in the next multigram_exec() */
/************************************************************************/
boolean
multigram_delete(int delid)
{
  MULTIGRAM *m;
  for(m=gramlist;m;m=m->next) {
    if (m->id == delid) {
      m->hook = MULTIGRAM_DELETE;
      j_printf("- Gram #%d: marked delete\n", m->id);
      break;
    }
  }
  if (! m) {
    j_printf("- Gram #%d: not found\n", delid);
    if (module_mode) {
      module_send("<ERROR MESSAGE=\"Gram #%d not found\"/>\n.\n", delid);
    }
    return FALSE;
  }
  return TRUE;
}

void
multigram_delete_all()
{
  MULTIGRAM *m;
  for(m=gramlist;m;m=m->next) {
    m->hook = MULTIGRAM_DELETE;
  }
}

/************************************************************************/
/* delete grammars marked as 'delete' */
/************************************************************************/
static boolean			/* return TRUE if any grammar deleted */
multigram_exec_delete()
{
  MULTIGRAM *m, *mtmp, *mprev;
  boolean ret_flag = FALSE;
#ifdef MDEBUG
  int n;
#endif

  /* exec delete */
  mprev = NULL;
  m = gramlist;
  while(m) {
    mtmp = m->next;
    if (m->hook == MULTIGRAM_DELETE) {
      /* if any grammar is deleted, we need to rebuild lexicons etc. */
      /* so tell it to the caller */
      if (! m->newbie) ret_flag = TRUE;
      dfa_info_free(m->dfa);
      word_info_free(m->winfo);
      n=m->id;
      free(m);
      j_printf("- Gram #%d: purged\n", n);
      if (mprev != NULL) {
	mprev->next = mtmp;
      } else {
	gramlist = mtmp;
      }
    } else {
      mprev = m;
    }
    m = mtmp;
  }

  return(ret_flag);
}

/************************************************************************/
/* activate/deactivate grammars */
/************************************************************************/
void
multigram_activate(int gid)	/* only mark */
{
  MULTIGRAM *m;
  for(m=gramlist;m;m=m->next) {
    if (m->id == gid) {
      if (m->hook == MULTIGRAM_ACTIVATE) {
	j_printf("- Gram #%d: already active\n", m->id);
	if (module_mode) {
	  module_send("<WARN MESSAGE=\"Gram #%d already active\"/>\n.\n", m->id);
	}
      } else {
	m->hook = MULTIGRAM_ACTIVATE;
	j_printf("- Gram #%d: marked activate\n", m->id);
      }
      break;
    }
  }
  if (! m) {
    j_printf("- Gram #%d: not found, activation ignored\n", gid);
    if (module_mode) {
      module_send("<WARN MESSAGE=\"Gram #%d not found\"/>\n.\n", gid);
    }
  }
}
void
multigram_deactivate(int gid)	/* only mark */
{
  MULTIGRAM *m;
  for(m=gramlist;m;m=m->next) {
    if (m->id == gid) {
      m->hook = MULTIGRAM_DEACTIVATE;
      j_printf("- Gram #%d: marked deactivate\n", m->id);
      break;
    }
  }
  if (! m) {
    j_printf("- Gram #%d: not found, deactivation ignored\n", gid);
    if (module_mode) {
      module_send("<WARN MESSAGE=\"Gram #%d not found\"/>\n.\n", gid);
    }
  }
}
/* execute activate/deactivate mark */
/* only change the active flags */
static boolean
multigram_exec_activate()
{
  MULTIGRAM *m;
  boolean modified;
  
  modified = FALSE;
  for(m=gramlist;m;m=m->next) {
    if (m->hook == MULTIGRAM_ACTIVATE) {
      m->hook = MULTIGRAM_DEFAULT;
      if (!m->active) {
	j_printf("- Gram #%d: turn on active\n", m->id);
      }
      m->active = TRUE;
      modified = TRUE;
    } else if (m->hook == MULTIGRAM_DEACTIVATE) {
      m->hook = MULTIGRAM_DEFAULT;
      if (m->active) {
	j_printf("- Gram #%d: turn off inactive\n, m->id");
      }
      m->active = FALSE;
      modified = TRUE;
    }
  }
  return(modified);
}
 
/************************************************************************/
/* update grammar if needed */
/************************************************************************/
boolean				/* return FALSE if no gram */
multigram_exec()
{
  MULTIGRAM *m;
  boolean global_modified = FALSE;
  boolean active_changed = FALSE;

#ifdef MDEBUG
  j_printf("- Grammar update check\n");
#endif

  /* setup additional grammar info of new ones */
  for(m=gramlist;m;m=m->next) {
    if (m->newbie) {
      /* map dict item to dfa terminal symbols */
      make_dfa_voca_ref(m->dfa, m->winfo);
      /* set dfa->sp_id and dfa->is_sp */
      dfa_find_pause_word(m->dfa, m->winfo, hmminfo);
      /* build catergory-pair information */
      extract_cpair(m->dfa);
    }
  }

  /* delete grammars marked as "delete" */
  if (multigram_exec_delete()) { /* some built grammars deleted */
    /* rebuild global grammar from scratch (including new) */
    /* active status not changed here (inactive grammar will also included) */
    /* activate/deactivate hook will be handled later, so just keep it here */
#ifdef MDEBUG
    j_printf("- Re-build whole global grammar...\n");
#endif
    if (global_dfa != NULL) {    /* free old global */
      dfa_info_free(global_dfa);
      word_info_free(global_winfo);
      global_dfa = NULL;
    }
    for(m=gramlist;m;m=m->next) {
      if (global_dfa == NULL) {
	global_dfa = dfa_info_new();
	dfa_state_init(global_dfa);
	global_winfo = word_info_new();
	winfo_init(global_winfo);
      }
      if (m->newbie) m->newbie = FALSE;
      multigram_build_append(global_dfa, global_winfo, m);
    }
    global_modified = TRUE;
  } else {			/* global not need changed by the deletion */
    /* append only new grammars */
    for(m=gramlist;m;m=m->next) {
      if (m->newbie) {
	if (global_dfa == NULL) {
	  global_dfa = dfa_info_new();
	  dfa_state_init(global_dfa);
	  global_winfo = word_info_new();
	  winfo_init(global_winfo);
	}
	if (m->newbie) m->newbie = FALSE;
	multigram_build_append(global_dfa, global_winfo, m);
	global_modified = TRUE;
      }
    }
  }

  /* process activate/deactivate hook */
  active_changed = multigram_exec_activate();

  if (global_modified) {		/* if global lexicon has changed */
    /* now global grammar info has been updated, */
    /* build up tree lexicon for recognition process */
    multigram_setup(global_dfa, global_winfo);
#ifdef MDEBUG
    j_printf("- update completed\n");
#endif
  }
  
  /* output grammar info when any change has been made */
  if (global_modified || active_changed) {
    print_all_gram();
    if (module_mode) {
      send_gram_info();
    }
  }

  return(TRUE);
}

#endif /* USE_DFA */
