// file: $isip/util/speech/isip_model_converter.cc
// version: $Id: isip_model_converter.cc 8977 2003-01-12 06:37:52Z jelinek $
//
// isip include files
//
#include "isip_model_converter.h"

// isip_model_converter: converts the prototype models to the
//                       production system formats
//
int main(int argc, const char** argv) {

  // configure the command line parser
  //
  CommandLine cmdl;

  cmdl.setUsage(
#include "usage_message.text"
                );
  cmdl.setHelp(
#include "help_message.text"
               );

  // parse the command line
  //
  if (!cmdl.parse(argc,argv)) {
    return Integral::exit();
  }
  
  // declare string to hold file names
  //
  String tmp;
  String param_filename;
  String mono_filename;
  String lexicon_filename;
  String exclude_filename;
  String lat_filename;
  String states_filename;
  String transitions_filename;
  String models_filename;
  String phones_filename;
  String output_lm_filename;      
  String output_am_filename;      
  String output_config_filename;      

  // declare number of states, models and features
  //
  long num_states = (long)0;
  long num_models = (long)0;
  long num_features = (long)0;
  long syms = 0;

  // declare SearchSymbols and Hashtables
  //
  Vector<SearchSymbol> monophone_syms;
  Vector<SearchSymbol> context_syms;
  Vector<SearchSymbol> word_syms;  
  HashTable<String, Ulong> monophone_hash(DstrBase::USER);
  HashTable<String, Ulong> lex_hash(DstrBase::USER);  
  Ulong* item = (Ulong*)NULL;

  // declare a pronunciation graph
  //
  Vector<DiGraph<Ulong> > pron_graph;
  Vector<DiGraph<Ulong> > wrd_graph;

  // read the file name
  //
  if (!cmdl.getArgument(param_filename, 0)) {
    cmdl.printUsage();
    return Integral::exit();
  }
  
  // read and decipher the parameter file
  // store the relevant information
  //
  File param_file;
  File mono_file;
  File lexicon_file;
  File lat_file;
  long output_mode = (long)IMC_DEF_OUTPUT_MODE;
  long context_mode = (long)IMC_DEF_CONTEXT_MODE;

  read_params_cc(param_filename, param_file, mono_filename, lexicon_filename,
		 exclude_filename, lat_filename,
		 states_filename, transitions_filename,
		 models_filename, phones_filename, context_mode,
		 output_lm_filename, output_am_filename,
		 output_config_filename, output_mode);
    
  // read the monophone file
  //
  read_mono_cc(mono_filename, mono_file, monophone_syms, monophone_hash);

  // read the lexicon and construct the pronunciation graph
  //
  read_lexicon_cc(lexicon_filename, lexicon_file, word_syms, pron_graph,
		  lex_hash, monophone_hash);

  // add dummy symbol to the word symbol table and lexicon hash table
  //
  word_syms.setLength(word_syms.length() + 1);
  word_syms(word_syms.length() - 1).assign(L"!DUMMY");
  
  Ulong* p = new Ulong(lex_hash.getNumItems());
  lex_hash.insert(L"!DUMMY", p);

  // decide on the lattice or N-gram mode
  //
  if (lat_filename.length() != 0) {
    
    // read the lattice and construct the word graph
    //
    read_lat_cc(lat_filename, lat_file, lex_hash, wrd_graph);
  }
  else if (exclude_filename.length() != 0) {

    // create the full loop grammar, do not include words from exclude file
    //
    create_loop_grammar_cc(exclude_filename, lex_hash, wrd_graph);
  }
  else {
    String out(L"Error: Neither exclude_file (for N-gram decoding), nor lattice_file (for network decoding) was specified in the parameter file.");
    Console::put(out);
    exit(Integral::exit());
  }

  // read the states data
  //
  State** states = read_states_cc(num_states, num_features, states_filename);

  // *****************************************
  //
  // map the states of the HMMs
  //
  // *****************************************
  //
  // create an array of StatisticalModels which match the states file
  //
  Vector<StatisticalModel> stat_models(num_states - 1);

  // copy the state information to the statistical models
  //
  for (long i = 1; i < num_states; i++) {

    // get the state parameters
    //
    float_4* weights = states[i]->get_weights_cc();
    float_4** means = states[i]->get_mean_cc();
    float_4** covars = states[i]->get_covar_cc();
    long mean_dimension = states[i]->get_num_features_cc();
    long num_mixtures = states[i]->get_num_mixtures_cc();

    // set the mixture weights
    //
    VectorFloat converted_weights(num_mixtures);
    for (long j = 0; j < num_mixtures; j++) {
      converted_weights(j) = exp(weights[j]);
    }
    stat_models(i-1).setType(StatisticalModel::MIXTURE_MODEL);
    MixtureModel mix_model;
    mix_model.setMode(StatisticalModel::NONE);
    mix_model.setWeights(converted_weights); 

    // loop over each mixture and create a mean and covariance for it
    //
    for (long j = 0; j < num_mixtures; j++) {

      // invert the covariances
      //
      for (long k = 0; k < mean_dimension; k++) {
	covars[j][k] = 1.0 / covars[j][k];
      }
    
      // create a new Gaussian
      //
      GaussianModel gauss;

      // create the mean and diagonal covariance
      //
      VectorFloat converted_mean(mean_dimension);
      converted_mean.assign(mean_dimension, means[j]);
      MatrixFloat converted_covar;
      converted_covar.assign(mean_dimension, mean_dimension, covars[j],
			     Integral::DIAGONAL);
      gauss.setMean(converted_mean);
      gauss.setCovariance(converted_covar);
      mix_model.add(gauss);
    }

    // assign the mixture model to the StatisticalModel
    //
    stat_models(i-1).assign(mix_model);
  }

  // read the transition matrices
  //
  Vector<MatrixFloat> trans_matrices;
  read_trans_cc(transitions_filename, trans_matrices);
  
  // *****************************************
  //
  // map the states of the HMMs to production
  // system data structures for later use
  //
  // *****************************************
  //

  // loop over each prototype model and create the state and transition map
  //
  Vector<VectorLong> model_state_map;
  VectorLong model_trans_map;
  Vector<String> model_names;
      
  // read the basic HMM models data
  //
  if (!read_models_cc(models_filename, model_state_map, model_trans_map,
		      model_names)) {
    String out(L"Error parsing model file: ");
    out.concat(models_filename);
    Console::put(out);
    exit(Integral::exit());
  }

  // get number of models
  //
  num_models = model_names.length();

  // construct the HMM graph for each model
  //
  Vector<DiGraph<Ulong> > hmm_graph(num_models);

  // declare output variables for context mapping
  //
  Vector<ContextMap> context_maps;
  long left_context_length = 0;
  long right_context_length = 0;

  printf("\n");
  if (context_mode != IMC_MONOPHONE) {

    // declare variables for context dependent models reading
    //
    Vector<VectorLong> phone_mono_map;
    VectorLong phone_model_map;
    Vector<String> phone_names;
    
    // read phones if we use context dependent phones
    //
    left_context_length = 1;
    right_context_length = 1;

    read_phones_cc(phones_filename, phone_mono_map, phone_model_map,
		   phone_names, monophone_syms.length());

    context_maps.setLength(phone_mono_map.length());
    printf("\n\nphone_mono_map length: %ld\n", phone_mono_map.length());

    // loop over phone_names to find indices of sp and sil
    //
    long sp_model_ind = -1;
    long sil_model_ind = -1;
    long sp_mono_ind = -1;
    long sil_mono_ind = -1;
    long sp_phone_ind = -1;
    long sil_phone_ind = -1;
    for (long i = 0; i < phone_mono_map.length(); i++) {
      if (phone_names(i).eq(L"sp")) {
	sp_model_ind = phone_model_map(i);
	sp_mono_ind = phone_mono_map(i)(1);
	sp_phone_ind = i;
	printf("found sp phone: %ld, mono: %ld\n", sp_model_ind, sp_mono_ind);
      }
      else if (phone_names(i).eq(L"sil")) {
	sil_model_ind = phone_model_map(i);
	sil_mono_ind = phone_mono_map(i)(1);
	sil_phone_ind = i;
	printf("found sil phone: %ld, mono: %ld\n", sil_model_ind, sil_mono_ind);
      }
    }
    
    // this vector holds 0 or 1 for each phone
    //   0 if the phone was end phone of the word
    //   1 if the phone was not end phone of the word
    // goal is to avoid duplicity of symbols as "c-sil+!NR"
    // it is used for word-internal mode
    //
    Vector<Boolean> was_end_phone(monophone_syms.length());
    was_end_phone.setRange(0, monophone_syms.length(), false);

    // goal is to avoid duplicity of !NL-a+sil generated for both "a"
    // and "a+sil"
    //
    Vector<Boolean> was_single_phone(monophone_syms.length());
    was_single_phone.setRange(0, monophone_syms.length(), false);

    // goal is to avoid duplicity of a-b+sil generated for both "a-b"
    // and "a-b+sil"
    //
    Vector<Vector<Boolean> > was_first_two(monophone_syms.length());
    for (long i = 0; i < monophone_syms.length(); i++) {
      was_first_two(i).setLength(monophone_syms.length());
      was_first_two(i).setRange(0, monophone_syms.length(), false);
    }

    // loop over the phone map and create context maps
    //
    long i_map = 0;
    for (long i = 0; i < phone_mono_map.length(); i++) {
      printf("context map index: %ld\n", i);
      phone_names(i).debug(L"context map for: ");

      // set the length to 3 and loop over 3
      //
      context_maps(i_map).getContext().setLength(IMC_TRIPHONE_SIZE);
      long j = 0;
      for(; j < phone_mono_map(i).length(); j++) {
	context_maps(i_map).getContext()(j) =
	  monophone_syms(phone_mono_map(i)(j));
      }
      // set the index of the model for context 
      //
      context_maps(i_map).setContextIndex(phone_model_map(i));

      if (context_mode == IMC_WRD_INTERNAL) {

	// if i is index of "sil" or "sp",
	// then generate only "!NL-sil+!NR" or "!NL-sp+!NR"
	//
	if ((i == sil_phone_ind) || (i == sp_phone_ind)) {
	  context_maps(i_map).getContext()(0) = SearchSymbol::NO_LEFT_CONTEXT;
	  context_maps(i_map).getContext()(2) = SearchSymbol::NO_RIGHT_CONTEXT;
	}
	
	// if both left and right context symbol is nothing, e.g "ow" phone
	// then generate a) "!NL-ow+!NR"
	////             b) "!NL-ow+sp" and c) "!NL-ow+sil"
	//
	else if ((phone_mono_map(i)(0) == (long)0) &&
		 (phone_mono_map(i)(2) == (long)0)) {

	  was_single_phone(phone_mono_map(i)(1)) = true;
							   
	  // a)
	  context_maps(i_map).getContext()(0) = SearchSymbol::NO_LEFT_CONTEXT;
	  context_maps(i_map).getContext()(2) = SearchSymbol::NO_RIGHT_CONTEXT;
	    
	  context_maps.setLength(context_maps.length() + 2);
	  for (long k = 1; k <= 2; k++) {
	    context_maps(i_map + k).getContext().setLength(IMC_TRIPHONE_SIZE);
	  }

	  // b)
	  ++i_map;
	  context_maps(i_map).getContext()(0) = SearchSymbol::NO_LEFT_CONTEXT;
	  context_maps(i_map).getContext()(1) =
	    context_maps(i_map - 1).getContext()(1);
	  context_maps(i_map).getContext()(2) =
	    monophone_syms(sp_mono_ind);
	  context_maps(i_map).setContextIndex(phone_model_map(i));

	  // c)
	  ++i_map;
	  context_maps(i_map).getContext()(0) = SearchSymbol::NO_LEFT_CONTEXT;
	  context_maps(i_map).getContext()(1) =
	    context_maps(i_map - 1).getContext()(1);
	  context_maps(i_map).getContext()(2) =
	    monophone_syms(sil_mono_ind);
	  context_maps(i_map).setContextIndex(phone_model_map(i));
	}

	// if there is nothing only on left, e.g. "b+c"
	// then generate only "!NL-b+c"
	//
	else if (phone_mono_map(i)(0) == (long)0) {// 0 == nothing on left
	  if (was_single_phone(phone_mono_map(i)(1)) &&
	      (phone_mono_map(i)(2) == sil_mono_ind)) {
	    context_maps.setLength(context_maps.length() - 1);
	    i_map--;
	  }
	  else {
	    context_maps(i_map).getContext()(0) =
	      SearchSymbol::NO_LEFT_CONTEXT;
	    context_maps(i_map).debug(L"context map:");
	  }
	}

	// if there is nothing only on right, e.g. "a-b"
	// then generate a) "a-b+!NR", b) "a-b+sp", c) "a-b+sil"
	//                             d) "b-sp+!NR", e) "b-sil+!NR"
	//
	else if (phone_mono_map(i)(2) == (long)0) { // 0 == nothing on right

	  was_first_two(phone_mono_map(i)(0))(phone_mono_map(i)(1)) = true;

	  // a)
	  //
	  context_maps(i_map).getContext()(2) =
	    SearchSymbol::NO_RIGHT_CONTEXT;
	  context_maps(i_map).debug(L"context map:");

	  context_maps.setLength(context_maps.length() + 2);
	  for (long k = 1; k <= 2; k++) {
	    context_maps(i_map + k).getContext().setLength(IMC_TRIPHONE_SIZE);
	  }

	  // b) +sp
	  ++i_map;
	  context_maps(i_map) = context_maps(i_map - 1);
	  context_maps(i_map).getContext()(2) = monophone_syms(sp_mono_ind);
	  context_maps(i_map).setContextIndex(phone_model_map(i));

	  // c) +sil
	  ++i_map;
	  context_maps(i_map) = context_maps(i_map - 1);
	  context_maps(i_map).getContext()(2) = monophone_syms(sil_mono_ind);
	  context_maps(i_map).setContextIndex(phone_model_map(i));

	  if (!was_end_phone(phone_mono_map(i)(1))) {

	    // mark that this phone was ending phone already
	    //
	    was_end_phone(phone_mono_map(i)(1)) = true;

	    context_maps.setLength(context_maps.length() + 2);
	    for (long k = 1; k <= 2; k++) {
	      context_maps(i_map + k).
		getContext().setLength(IMC_TRIPHONE_SIZE);
	    }

	    // d) c-sp+!NR
	    //
	    ++i_map;
	    context_maps(i_map).getContext()(2) =
	      SearchSymbol::NO_RIGHT_CONTEXT;
	    context_maps(i_map).getContext()(1) = monophone_syms(sp_mono_ind);
	    context_maps(i_map).getContext()(0) =
	      context_maps(i_map - 1).getContext()(1);
	    context_maps(i_map).setContextIndex(sp_model_ind); // sp model

	    // e) c-sil+!NR
	    //
	    ++i_map;
	    context_maps(i_map).getContext()(2) = SearchSymbol::NO_RIGHT_CONTEXT;
	    context_maps(i_map).getContext()(1) = monophone_syms(sil_mono_ind);
	    context_maps(i_map).getContext()(0) =
	      context_maps(i_map - 2).getContext()(1);
	    context_maps(i_map).setContextIndex(sil_model_ind); // sil model
	  }
	}
	
	// if there is a silence phone on the right, check whether the
	// same first two phones did not generated this context map
	// already
	//
	else if (phone_mono_map(i)(2) == sil_mono_ind) {
	  if (was_first_two(phone_mono_map(i)(0)) (phone_mono_map(i)(1))) {
	    context_maps.setLength(context_maps.length() - 1);
	    i_map--;
	  }
	}
      } // end of wint mode
    
      else if (context_mode == IMC_CROSS_WORD) {

	// if i is index of "sil" or "sp",
	// then generate only "!NL-sil+!NR" or "!NL-sp+!NR"
	//
	if ((i == sil_phone_ind) || (i == sp_phone_ind)) {
	  
	  context_maps.setLength(context_maps.length()
				 + (monophone_syms.length() + 1)
                                    * (monophone_syms.length() + 1)
				 - 1);

	  // generate !NL-sil+!NR
	  //
	  context_maps(i_map).getContext().setLength(IMC_TRIPHONE_SIZE);
	  context_maps(i_map).getContext()(0) = SearchSymbol::NO_LEFT_CONTEXT;
	  context_maps(i_map).getContext()(2) = SearchSymbol::NO_RIGHT_CONTEXT;
	  context_maps(i_map).setContextIndex(phone_model_map(i));
	  ++i_map;

	  // generate x-sil+!NR for all x
	  //
	  for (long l = 0; l < monophone_syms.length(); l++) {
	    context_maps(i_map).getContext().setLength(IMC_TRIPHONE_SIZE);
	    context_maps(i_map).getContext()(0) = monophone_syms(l);
	    context_maps(i_map).getContext()(1) = 
	      context_maps(i_map - 1).getContext()(1);
	    context_maps(i_map).getContext()(2) =
	      SearchSymbol::NO_RIGHT_CONTEXT;
	    context_maps(i_map).setContextIndex(phone_model_map(i));
	    ++i_map;
	  }
	  
	  // generate !NL-sil+y for all y
	  //
	  for (long r = 0; r < monophone_syms.length(); r++) {
	    context_maps(i_map).getContext().setLength(IMC_TRIPHONE_SIZE);
	    context_maps(i_map).getContext()(0) =
	      SearchSymbol::NO_LEFT_CONTEXT;
	    context_maps(i_map).getContext()(1) = 
	      context_maps(i_map - 1).getContext()(1);
	    context_maps(i_map).getContext()(2) = monophone_syms(r);
	    context_maps(i_map).setContextIndex(phone_model_map(i));
	    ++i_map;
	  }

	  // generate x-sil+y for all x and y
	  //
	  for (long l = 0; l < monophone_syms.length(); l++) {
	    for (long r = 0; r < monophone_syms.length(); r++) {
	      context_maps(i_map).getContext().setLength(IMC_TRIPHONE_SIZE);
	      context_maps(i_map).getContext()(0) = monophone_syms(l);
	      context_maps(i_map).getContext()(1) = 
		context_maps(i_map - 1).getContext()(1);
	      context_maps(i_map).getContext()(2) = monophone_syms(r);
	      context_maps(i_map).setContextIndex(phone_model_map(i));
	      ++i_map;
	    }
	  }
	  --i_map;
	}
	
	// if both left and right context symbol is nothing, e.g "ow" phone
	// then generate a) "!NL-ow+!NR"
	////             b) "!NL-ow+sp" and c) "!NL-ow+sil"
	//
	else if ((phone_mono_map(i)(0) == (long)0) &&
		 (phone_mono_map(i)(2) == (long)0)) {

	  was_single_phone(phone_mono_map(i)(1)) = true;
							   
	  // a)
	  context_maps(i_map).getContext()(0) = SearchSymbol::NO_LEFT_CONTEXT;
	  context_maps(i_map).getContext()(2) = SearchSymbol::NO_RIGHT_CONTEXT;
	    
	  context_maps.setLength(context_maps.length() + 2);
	  for (long k = 1; k <= 2; k++) {
	    context_maps(i_map + k).getContext().setLength(IMC_TRIPHONE_SIZE);
	  }

	  // b)
	  ++i_map;
	  context_maps(i_map).getContext()(0) = SearchSymbol::NO_LEFT_CONTEXT;
	  context_maps(i_map).getContext()(1) =
	    context_maps(i_map - 1).getContext()(1);
	  context_maps(i_map).getContext()(2) =
	    monophone_syms(sp_mono_ind);
	  context_maps(i_map).setContextIndex(phone_model_map(i));

	  // c)
	  ++i_map;
	  context_maps(i_map).getContext()(0) = SearchSymbol::NO_LEFT_CONTEXT;
	  context_maps(i_map).getContext()(1) =
	    context_maps(i_map - 1).getContext()(1);
	  context_maps(i_map).getContext()(2) =
	    monophone_syms(sil_mono_ind);
	  context_maps(i_map).setContextIndex(phone_model_map(i));
	}

	// if there is nothing only on left, e.g. "b+c"
	// then generate only "!NL-b+c"
	//
	else if (phone_mono_map(i)(0) == (long)0) {// 0 == nothing on left
	  if (was_single_phone(phone_mono_map(i)(1)) &&
	      (phone_mono_map(i)(2) == sil_mono_ind)) {
	    context_maps.setLength(context_maps.length() - 1);
	    i_map--;
	  }
	  else {
	    context_maps(i_map).getContext()(0) =
	      SearchSymbol::NO_LEFT_CONTEXT;
	    context_maps(i_map).debug(L"context map:");
	  }
	}

	// if there is nothing only on right, e.g. "a-b"
	// then generate a) "a-b+!NR", b) "a-b+sp", c) "a-b+sil"
	// ////                        d) "b-sp+!NR", e) "b-sil+!NR"
	//
	else if (phone_mono_map(i)(2) == (long)0) { // 0 == nothing on right

	  was_first_two(phone_mono_map(i)(0))(phone_mono_map(i)(1)) = true;

	  // a)
	  //
	  context_maps(i_map).getContext()(2) =
	    SearchSymbol::NO_RIGHT_CONTEXT;
	  context_maps(i_map).debug(L"context map:");

	  context_maps.setLength(context_maps.length() + 2);
	  for (long k = 1; k <= 2; k++) {
	    context_maps(i_map + k).getContext().setLength(IMC_TRIPHONE_SIZE);
	  }

	  // b) +sp
	  ++i_map;
	  context_maps(i_map) = context_maps(i_map - 1);
	  context_maps(i_map).getContext()(2) = monophone_syms(sp_mono_ind);
	  context_maps(i_map).setContextIndex(phone_model_map(i));

	  // c) +sil
	  ++i_map;
	  context_maps(i_map) = context_maps(i_map - 1);
	  context_maps(i_map).getContext()(2) = monophone_syms(sil_mono_ind);
	  context_maps(i_map).setContextIndex(phone_model_map(i));
	}
	
	// if there is a silence phone on the right, check whether the
	// same first two phones did not generated this context map
	// already
	//
	else if (phone_mono_map(i)(2) == sil_mono_ind) {
	  if (was_first_two(phone_mono_map(i)(0)) (phone_mono_map(i)(1))) {
	    context_maps.setLength(context_maps.length() - 1);
	    i_map--;
	  }
	}
      } // end of cross-word mode
      ++i_map;
      
    } // end of loop over phone->mono map
  } // end of not monophone mode
    
  // loop over all the models
  //
  for (long k = 0; k < num_models; k++) {
    
    String str = model_names(k);
    str.debug(L"Model phone is = ");
    long index = 0;
    long num_vertices = 0;
    long trans_index;
    GraphVertex<Ulong>** hmm_vert;
    
    if (context_mode == IMC_MONOPHONE) {
      
      // find the index of this phone in phone_syms vector
      //
      long j = 0;
      for (; j < monophone_syms.length(); j++) {

	// check if the phone symbols match
	//
	if (monophone_syms(j).eq((SearchSymbol)str, false)) {
	  index = j;
	  break;
	}
      }

      if (j == monophone_syms.length()) {
	String out(L"Error finding monophone for model: ");
	out.concat(str);
	out.concat(L" in monophone context mode.");
	Console::put(out);
	exit(Integral::exit());
      }

    }
    else {
      index = k;
    }

    // create a new hmm graph
    //
    hmm_graph(index).setAllocationMode(DstrBase::USER);

    // create the Graph vertex after knowing the vertices
    //
    num_vertices = model_state_map(k).length() + 2;
    hmm_vert = new GraphVertex<Ulong>*[num_vertices];


    // set the start and terminal vertices
    //
    hmm_vert[0] = hmm_graph(index).getStart();
    hmm_vert[num_vertices - 1] = hmm_graph(index).getTerm();

    // set the item for each vertex
    // in this graph
    //
    for (long j = 1; j < num_vertices - 1; j++) {

      item = new Ulong();

      // get the item from the model_state_map
      // model_state map tells the index of states that make this
      // model
      //
      long temp_item = (long)(model_state_map(k))(j - 1);
      item->assign(temp_item);
      hmm_vert[j] = hmm_graph(index).insertVertex(item);
    }

    // construct the arcs after reading the transition values
    //
    // get the transition matrix index for this model
    //
    trans_index = model_trans_map(k);
    float arc_value;

    // special case: dummy state. Case where sp
    // is not trained
    //
    if (num_vertices == 2) {
      hmm_graph(index).insertArc(hmm_vert[0], hmm_vert[1],
				 true,
				 (float)0.0);
      continue;
    }
    
    // loop over all the vertices(states) in this model
    //
    for (long j = 0; j < num_vertices; j++) {
      for (long i = 0; i < num_vertices; i++) {

	// get the arc value for transition between each state
	//
	arc_value = trans_matrices(trans_index).getValue(j,i);

	// if the transition value is non-zero connect these
	// two vertices with the transition value
	//
	if(arc_value != (float)0) {
	  hmm_graph(index).insertArc(hmm_vert[j], hmm_vert[i],
				     true,
				     (float)Integral::log((double)arc_value));
	}
      }
    }
  }

  // create state level symbols
  //
  Vector<SearchSymbol> state_syms(num_states - 1);
  SearchSymbol ssym(L"S_");
  SearchSymbol tmp_sym;
  SearchSymbol tmp_sym_1;
  String tmp_str;
  for (syms = 0; syms < (num_states - 1); syms++) {
    tmp_str.assign(syms+1);
    tmp_sym.assign(tmp_str);
    tmp_sym_1.concat(ssym, tmp_sym);
    state_syms(syms).assign(tmp_sym_1);
    tmp_sym_1.clear();
  }

  // create state hash table. It provides mapping of each state to
  // the index of it's statistical model
  //
  HashTable<SearchSymbol, Long> state_hashtable;
  for (long i = 0; i < state_syms.length(); i++) {
    Long l(i);
    state_hashtable.insert(state_syms(i), &l);
  }

  // write all the converted models to a Sof file
  //
  Sof out_lm_sof, out_am_sof, out_config_sof;
  String out_lm_file, out_am_file;
  String symbols(L"search_symbols");
  String models(L"search_models");
  String stat_mod(L"stat_models");
  String state_hasht(L"symbol_hashtable");

  out_lm_file.assign(output_lm_filename);
  out_am_file.assign(output_am_filename);

  File::TYPE file_mode;
  if (output_mode == (long) IMC_BINARY_MODE) {
    file_mode = File::BINARY;
  }
  else {
    file_mode = File::TEXT;
  }
  
  if (!out_lm_sof.open(out_lm_file, File::WRITE_ONLY, file_mode)) {
    String out(L"Cannot open Sof file: ");
    out.concat(out_lm_file);
    Console::put(out);
    exit(Integral::exit());
  }

  if (!out_am_sof.open(out_am_file, File::WRITE_ONLY, file_mode)) {
    String out(L"Cannot open Sof file: ");
    out.concat(out_am_file);
    Console::put(out);
    exit(Integral::exit());
  }

  if (context_mode == IMC_CROSS_WORD) {

    // search symbols that should be skipped in the context
    //
    Vector<String> search_skip_symbols(1);
    search_skip_symbols(0).assign(L"sp");

    if (!search_skip_symbols.write(out_lm_sof, 1,
				    L"search_skip_symbols")) {
      String out(L"Error writing search skip symbols to the file:");
      out.concat(out_lm_file);
      Console::put(out);
      exit(Integral::exit());
    }
  }

  // search symbols to not be traversed in the search
  //
  Vector<String> search_dummy_symbols(1);
  search_dummy_symbols(0).assign(L"!DUMMY");

  if (!search_dummy_symbols.write(out_lm_sof, 0,
				  L"search_dummy_symbols")) {
    String out(L"Error writing search dummy symbols to the file:");
    out.concat(out_lm_file);
    Console::put(out);
    exit(Integral::exit());
  }

  // search symbols to not be printed in output hypothesis
  //
  Vector<String> search_exclude_symbols(3);
  search_exclude_symbols(0).assign(L"!DUMMY");
  search_exclude_symbols(1).assign(L"!SENT_START");
  search_exclude_symbols(2).assign(L"!SENT_END");

  if (!search_exclude_symbols.write(out_lm_sof, 0,
				    L"search_exclude_symbols")) {
    String out(L"Error writing search exclude symbols to the file:");
    out.concat(out_lm_file);
    Console::put(out);
    exit(Integral::exit());
  }

  String search_tag(L"word");
  if (!search_tag.write(out_lm_sof, 0, L"search_tag")) {
    String out(L"Error writing search tag to file: ");
    out.concat(out_lm_file);
    Console::put(out);
    exit(Integral::exit());
  }

  if (!word_syms.write(out_lm_sof, 0, symbols)) {
    String out(L"Error writing symbol table to file: ");
    out.concat(out_lm_file);
    Console::put(out);
    exit(Integral::exit());
  }
  
  if (!wrd_graph.write(out_lm_sof, 0, models)) {
    String out(L"Error writing symbol table to file: ");
    out.concat(out_lm_file);
    Console::put(out);
    exit(Integral::exit());
  }

  search_tag.assign(L"phone");
  if (!search_tag.write(out_lm_sof, 1, L"search_tag")) {
    String out(L"Error writing search tag to file: ");
    out.concat(out_lm_file);
    Console::put(out);
    exit(Integral::exit());
  }

  if (!monophone_syms.write(out_lm_sof, 1, symbols)) {
    String out(L"Error writing symbol table to file: ");
    out.concat(out_lm_file);
    Console::put(out);
    exit(Integral::exit());
  }
  
  if (!pron_graph.write(out_lm_sof, 1, models)) {
    String out(L"Error writing symbol table to file: ");
    out.concat(out_lm_file);
    Console::put(out);
    exit(Integral::exit());
  }

  if (context_mode != IMC_MONOPHONE) {
    
    File output_config_file;
    output_config_file.open(output_config_filename, File::WRITE_ONLY);

    String header(L"@ Sof v1.0 @\n\n");
    
    header.concat(L"@ SearchLevel 0 @\n");
    header.concat(L"use_lexical_tree = true;\n\n");

    header.concat(L"@ SearchLevel 1 @\n");
    header.concat(L"# context dependency parameters\n");
    header.concat(L"use_symbol_context = true;\n");

    header.concat(L"left_context_length = ");
    header.concat(left_context_length);
    header.concat(L";\n");
    
    header.concat(L"right_context_length = ");
    header.concat(right_context_length);
    header.concat(L";\n\n");

    header.concat(L"@ SearchLevel 2 @\n");
    header.concat(L"use_beam_pruning = true;\n");
    header.concat(L"beam_threshold = 400.0;\n");

    output_config_file.put(header);

    output_config_file.close();

    String contexts_str(L"context_mapping");
    if (!context_maps.write(out_lm_sof, 1, contexts_str)) {
      String out(L"Error writing context mapping to file:");
      out.concat(out_lm_file);
      Console::put(out);
      exit(Integral::exit());
    };
  }
      
  search_tag.assign(L"state");
  if (!search_tag.write(out_lm_sof, 2, L"search_tag")) {
    String out(L"Error writing search tag to file: ");
    out.concat(out_lm_file);
    Console::put(out);
    exit(Integral::exit());
  }

  if (!state_syms.write(out_lm_sof, 2, symbols)) {
    String out(L"Error writing symbol table to file: ");
    out.concat(out_lm_file);
    Console::put(out);
    exit(Integral::exit());
  }
  
  if (!hmm_graph.write(out_lm_sof, 2, models)) {
    String out(L"Error writing hmm graphs to file: ");
    out.concat(out_lm_file);
    Console::put(out);
    exit(Integral::exit());
  }

  if (!state_hashtable.write(out_am_sof, 2, state_hasht)) {
    String out(L"Error writing state hashtable to file: ");
    out.concat(out_am_file);
    Console::put(out);
    exit(Integral::exit());
  }

  if (!stat_models.write(out_am_sof, 2, stat_mod)) {
    String out(L"Error writing statistical models to file: ");
    out.concat(out_am_file);
    Console::put(out);
    exit(Integral::exit());
  } 

  out_am_sof.close();
  out_lm_sof.close();
  
  // exit gracefully
  //
  Integral::exit();
}
