// file: $isip/class/search/SearchLevel/slev_08.cc
// version: $Id: slev_08.cc 10570 2006-05-13 23:51:06Z may $
//

// isip include files
//
#include "SearchLevel.h"

// method: convertSubgraphs
//
// arguments:
//
// return: a boolean indicating status
//
// this method converts the subgraphs used by the recognizer to the
// digraphs that are written to a file
//
Vector< DiGraph<Ulong> > SearchLevel::convertSubgraphs() const {

  // make sure the symbol table has been loaded
  //
  long index = 0;
  long dst_index = 0;
  boolean is_eps = false;  
  long num_graphs = sub_graphs_d.length();
  long num_symbols = symbol_table_d.length();
  
  if (num_symbols == 0) {

    // please load symbol table first
    //
    Error::handle(name(), L"writeSubGraphs", SearchLevel::ERR,
		  __FILE__, __LINE__);
  }
  
  // declare a Vector of Graph<Ulong> to be written to file
  //
  Ulong tmp_item;
  SearchNode* snode;  
  GraphArc<SearchNode>* dst_arc;
  GraphVertex<SearchNode>* dst_vertex;  
  Vector< DiGraph<Ulong> > tmp_graphs;
  Vector<DiGraph<SearchNode> >sub_graphs;

   // convert each Graph<SearchNode> > to Graph<Ulong>
   //

  sub_graphs.assign(sub_graphs_d);
  
  tmp_graphs.setLength(sub_graphs.length());

  for (long k = 0; k < num_graphs; k++) {

    // set the properties of graphs
    //
    long num_vertices = sub_graphs(k).length() + 2;
    tmp_graphs(k).setWeighted(sub_graphs(k).isWeighted());

    // insert all vertices corresponding the current graph index
    //
    index = 0;
    GraphVertex<SearchNode>* vertices[num_vertices];
    GraphVertex<Ulong>* tmp_vertices[num_vertices];

    // add the start vertices to the arrays
    //
    vertices[index] = sub_graphs(k).getStart();
    tmp_vertices[index++] = tmp_graphs(k).getStart();

    
    
    // add the rest of the vertices to the arrays
    //
    for (boolean more = sub_graphs(k).gotoFirst(); more;
	 more = sub_graphs(k).gotoNext()) {

      // get the current vertex
      //
      vertices[index] = const_cast<GraphVertex<SearchNode>* >(sub_graphs(k).getCurr());
      
      // get the search node corresponding to the vertex
      //
      snode = vertices[index]->getItem();

      // insert a vertex in the Graph<Ulong> for the current vertex
      // using the symbol ID of the vertex as the node item
      //
      tmp_item.assign(snode->getSymbolId());
      tmp_vertices[index++] = tmp_graphs(k).insertVertex(&tmp_item);
    }

    // add the term vertices to the arrays
    //
    vertices[num_vertices - 1] = sub_graphs(k).getTerm();
    tmp_vertices[num_vertices - 1] = tmp_graphs(k).getTerm();
    
    // connect all vertices corresponding to the current graph index
    //
    for (long i = 0; i < num_vertices; i++) {
      boolean arcs_remain = vertices[i]->gotoFirst();
      while (arcs_remain) {

	// get the destination vertex
	//
	dst_arc = vertices[i]->getCurr();
	dst_vertex = dst_arc->getVertex();
	is_eps = dst_arc->getEpsilon();
	
	// find the destination vertex
	//
	dst_index = -1;
	for (long j = 0; j < num_vertices; j++) {
	  if (dst_vertex == vertices[j]) {
	    dst_index = j;
	    break;
	  } 	  
	}

	// error - when we cannot find a destination index
	//
	if (dst_index == -1) {
	  Error::handle(name(), L"writeSubGraphs", Error::ARG, __FILE__, __LINE__);
	}

	// connect the vertices in the Graph<Ulong>
	//
	if (!tmp_graphs(k).insertArc(tmp_vertices[i], tmp_vertices[dst_index],
				     is_eps, (double)dst_arc->getWeight())) {
	  Error::handle(name(), L"writeSubGraphs", Error::ARG, __FILE__, __LINE__);
	}
	arcs_remain = vertices[i]->gotoNext();
      }
    }
  }

  // clear the pointers in the copy of sub_graphs_d so that data
  // of sub_graphs_d doesn't get deleted when method finishes
  //
  sub_graphs.setLength(0, true);
  
  // return the new digraphs
  //
  return tmp_graphs;
}

// method: convertContexts
//
// arguments:
//
//return: a boolean value indicating status
//
boolean SearchLevel::convertContexts() {

  // make sure the symbol table has been read
  //
  long num_symbols = symbol_table_d.length();  
  if (num_symbols < 1) {
    return Error::handle(name(), L"readContextMapping", Error::ARG, __FILE__, __LINE__);
  }
  
  // when there is a context mapping table at this level
  //
  if (context_map_d.length() > 0) {

    SearchSymbol start_symbol;
    SearchSymbol term_symbol;

    start_symbol.assign(SearchSymbol::NO_LEFT_CONTEXT);
    term_symbol.assign(SearchSymbol::NO_RIGHT_CONTEXT);
    // add start and terminal search symbol to the symbol table
    //
    if (!symbol_table_d.contains(&start_symbol)) {
      symbol_table_d.concat(start_symbol);
    }
    if (!symbol_table_d.contains(&term_symbol)) {
      symbol_table_d.concat(term_symbol);
    }
    
    // set up start and terminal search node for each subgraph
    //
    for (long i = 0; i < sub_graphs_d.length(); i++) {
      
      // start vertex
      //
      SearchNode* snode_p = new SearchNode();
      snode_p->setSearchLevel(this);
      snode_p->setSymbol(SearchSymbol::NO_LEFT_CONTEXT);
      sub_graphs_d(i).getStart()->setItem(snode_p);
      
      // terminal vertex
      //
      snode_p = new SearchNode();
      snode_p->setSearchLevel(this);
      snode_p->setSymbol(SearchSymbol::NO_RIGHT_CONTEXT);
      sub_graphs_d(i).getTerm()->setItem(snode_p);
    }
    
    // insert all context pairs into the context mapping hash table
    //
    long total_context_length = left_context_d + right_context_d + 1;

    // check the context length
    //
    if (context_map_d.length() > 0 ) {
      if (context_map_d(0).getContext().length() != total_context_length) {
	total_context_length = context_map_d(0).getContext().length();
      }
    }
    
    Context context(total_context_length);
    
    // loop over all context pairs
    //
    for (long i = 0; i < context_map_d.length(); i++) {
      
      // loop over the symbols in the context
      //
      for (long j = 0; j < total_context_length; j++) {
	
	SearchSymbol ss(context_map_d(i).getContext()(j));
	long symbol_id = getSymbolIndex(ss);
	
	// check whether the symbol is valid
	//
	if (symbol_id == -1) {
	  ss.debug(L"symbol");
	  return Error::handle(name(), L"readContextMapping", Error::ARG, __FILE__, __LINE__);
	}
	else {
	  context.assignAndAdvance(symbol_id);
	}
      }
      
      // insert a context specification into the hash table
      //
      Ulong index = context_map_d(i).getContextIndex();

      // check if the context is already in the table
      //
      Ulong* existing_index = context_hash_d.get(context);

      // if this context is not in the table yet, insert it
      //
      if (existing_index == NULL) {
	context_hash_d.insert(context, &index);
      }
      
      // if the context is already in the table, then check whether
      // the index of the model is the same as the one from table
      //
      else {

	// if indices are different, explain conflict and return error
	//
	if (!existing_index->eq(index)) {
	  context.debug(L"Context:");
	  String out;
	  out.concat(L" is already in context mapping table with the index: ");
	  out.concat(*existing_index);
	  out.concat(L"\n while new index is: ");
	  out.concat(index);
	  Console::put(out);
	  return Error::handle(name(), L"readContextMapping", Error::ARG,
			       __FILE__, __LINE__);
	}
	
	// otherwise indices are not conflicting, just print a warning
	//
	else {
	  context.debug(L"Warning: This context is already in the table:");
	}
      }
    }
    
    // output the debugging information
    //
    if (debug_level_d >= Integral::ALL) {     
      context_hash_d.debug(L"context hash table:");
    }
  }
  
  // when there is NO context mapping table at this level
  //
  else {
    
    // symbols will be mapped to the model with the same index at lower level
    //
    Context dummy(1);
    
    for (Ulong i = 0; i < (ulong)num_symbols; i++) {

      dummy.assignAndAdvance(i);
      // check if the context is already in the table
      //
      Ulong* existing_index = context_hash_d.get(dummy);

      // if this context is not in the table yet, insert it
      //
      if (existing_index == NULL) {
	context_hash_d.insert(dummy, &i);
      }

    }

    if (debug_level_d >= Integral::ALL) {
      context_hash_d.debug(L"dummy context hash table:");
    }
  }
  
  // exit gracefully
  //
  return true;
}


// method: convertDigraphs
//
// arguments:
//
//return: a boolean value indicating status
//
// this method converts the digraphs in tmp_graphs_d to the subgraphs
// used by the recognizer
//
boolean SearchLevel::convertDigraphs() {

  // define the initial symbol index
  //
  long symbol_index = -1;

  // clear the current subgraphs
  //
  sub_graphs_d.clear(Integral::RELEASE);

  // define the number of symbols in the symbol table
  //
  long num_symbols = symbol_table_d.length();

  // variable for graphs conversion
  //
  long num_vertices;
  GraphVertex<SearchNode>** snode_vert = (GraphVertex<SearchNode>**)NULL;
  SearchNode** snode = (SearchNode**)NULL;
  
  // set the number of sub graphs and allocation mode
  //
  long num_graphs = tmp_graphs_d.length();
  setNumSubGraphs(num_graphs);

  // convert each Graph<Ulong> to Graph<SearchNode> >
  //
  for (long k = 0; k < num_graphs; k++) {
    
    // get the number of vertices from the input graph
    //
    num_vertices  = tmp_graphs_d(k).length() + 2;
    
    // declare a graph of SearchNode and a local holder of those temporary
    // GraphVertex<Ulong>
    //
    snode_vert = new GraphVertex<SearchNode>*[num_vertices];
    snode = new SearchNode*[num_vertices];
    GraphVertex<Ulong>* tmp_vert[num_vertices];
    
    // set the properties of graphs
    //
    sub_graphs_d(k).setWeighted(tmp_graphs_d(k).isWeighted());

    // index all the vertices so that they can be referenced easily
    //
    tmp_vert[0] = tmp_graphs_d(k).getStart();
    
    // loop through the list of vertices and index them
    //
    tmp_graphs_d(k).gotoFirst();
    for (long i = 1; i < num_vertices - 1; i++) {
      tmp_vert[i] = const_cast<GraphVertex<Ulong>* >(tmp_graphs_d(k).getCurr());
      tmp_graphs_d(k).gotoNext();
    }
    
    // the last vertex is the terminal node
    //
    tmp_vert[num_vertices - 1] = tmp_graphs_d(k).getTerm();

    // create a list of SearchNode Vertices
    // set the items properly
    //
    snode_vert[0] = sub_graphs_d(k).getStart();
    snode_vert[num_vertices - 1] = sub_graphs_d(k).getTerm();
    
    for (long i = 1; i < num_vertices - 1; i++) {

      // set its item
      //
      Ulong* tmp_item = tmp_vert[i]->getItem();
      snode[i] = new SearchNode();
      snode[i]->setSearchLevel(this);

      symbol_index = (long)(*tmp_item);

      // check if this index is in the range of symbols
      //
      if (symbol_index < 0 || symbol_index >= num_symbols ) {
	return Error::handle(name(), L"readSubGraphs:: invalid symbol index",
			     SearchLevel::ERR, __FILE__, __LINE__);
      }
      snode[i]->setSymbolId(symbol_index);
    }
    
    // add the vertices to the graph
    //
    for (long i = 1; i < num_vertices - 1; i++) {
      snode_vert[i] = sub_graphs_d(k).insertVertex(snode[i]);
    }

    // connect the vertices with arcs to complete the graph
    //
    for (long i = 0; i < num_vertices; i++) {

      // get the vertex
      //
      GraphVertex<Ulong>* tmp_start_vert = tmp_vert[i];

      // get the list of arcs for this vertex
      //
      boolean arcs_remain = tmp_start_vert->gotoFirst();
      while (arcs_remain) {

	// declare temporary arcs and vertices
	//
	long dest_index = -1;
	GraphArc<Ulong>* tmp_arc = tmp_start_vert->getCurr();
	GraphVertex<Ulong>* dest_vertex = tmp_arc->getVertex();
	boolean is_eps = tmp_arc->getEpsilon();
	
	// find the index of the destination vertex
	//
	for (long j = 0; j < num_vertices; j++) {
	  if (dest_vertex == tmp_vert[j]) {
	    dest_index = j;
	    break;
	  } 
	}

	if (dest_index == -1) {
	  return Error::handle(name(), L"readSubGraphs -- Vertex not in list of Vertices", SearchLevel::ERR, __FILE__, __LINE__);
	}
	
	// connect the vertices in the searchnode graph
	//
	sub_graphs_d(k).insertArc(snode_vert[i], snode_vert[dest_index],
				  is_eps,
				  (double)tmp_arc->getWeight());
	
	// goto the next arc
	//
	arcs_remain = tmp_start_vert->gotoNext();
      }
    }
    
    //finally check if the number of vertices are same in the converted graph
    //
    if (sub_graphs_d(k).length() != tmp_graphs_d(k).length()) {

          return Error::handle(name(), L"readSubGraphs:: different number of vertices after conversion", SearchLevel::ERR, __FILE__, __LINE__);
    }
    
    // clean memory created by the tmp_graphs_d
    //
    //tmp_graphs_d(k).clear(Integral::FREE);
    delete [] snode_vert;
    delete [] snode;
    
  }  // end convert all the sub_graphs
  
  // exit gracefully
  //
  return true;
  
}

// method: loadNSymbolModel
//
// arguments:
//  Sof& sof: (input) sof file object
//  long tag: (input) sof object instance tag
//
// return: a boolean value indicating status
//
// this method reads context mapping information from an Sof file
//
boolean SearchLevel::loadNSymbolModel(Sof& sof_a, long tag_a) {

  // if no tag is specified, the tag should be this level number
  //
  if (tag_a == DEF_TAG) {
    tag_a = (long)level_index_d;
  }

  // make sure the symbol table has been loaded
  //
  long num_symbols = symbol_table_d.length();  
  if (num_symbols < 1) {
    return Error::handle(name(), L"loadNSymbolModel", Error::ARG, __FILE__, __LINE__);
  }

  // setup the symbol table
  //
  Vector<String> symbol_table(num_symbols);
  for (long i = 0; i < num_symbols; i++) {
    symbol_table(i).assign(symbol_table_d(i));
  }

  // set the language model order
  //
  nsymbol_model_d.setOrder(nsymbol_order_d);
  
  // load the nsymbol model
  //  
  if (!nsymbol_model_d.load(sof_a, tag_a, symbol_table)) {
    return Error::handle(name(), L"loadNSymbolModel", Error::ARG, __FILE__, __LINE__);
  }
  
  // exit gracefully
  //
  return true;
}

// method: storeContextMapping
//
// arguments:
//  Sof& sof: (input) sof file object
//  long tag: (input) sof object instance tag
//
// return: a boolean value indicating status
//
// this method reads context mapping information from an Sof file
//
boolean SearchLevel::storeContextMapping(Sof& sof_a, long tag_a) {

  // if no tag is specified, the tag should be this level number
  //
  if (tag_a == DEF_TAG) {
    tag_a = (long)level_index_d;
  }
  
  // write the context map at this level
  //
  context_map_d.write(sof_a, tag_a, PARAM_CONTEXT_MAPPING);
  
  // gracefully exit
  //
  return true;  
}

// method: loadContextMapping
//
// arguments:
//  Sof& sof: (input) sof file object
//  long tag: (input) sof object instance tag
//
// return: a boolean value indicating status
//
// this method reads context mapping information from an Sof file
//
boolean SearchLevel::loadContextMapping(Sof& sof_a, long tag_a) {

  // if no tag is specified, the tag should be this level number
  //
  if (tag_a == DEF_TAG) {
    tag_a = (long)level_index_d;
  }

  // make sure the symbol table has been loaded
  //
  long num_symbols = symbol_table_d.length();  
  if (num_symbols < 1) {
    return Error::handle(name(), L"loadContextMapping", Error::ARG, __FILE__, __LINE__);
  }
  
  // read the context map at this level
  //
  context_map_d.read(sof_a, tag_a, PARAM_CONTEXT_MAPPING);

  // when there is a context mapping table at this level
  //
  if (context_map_d.length() > 0) {
  
    // add start and terminal search symbol to the symbol table
    //
    symbol_table_d.concat(SearchSymbol::NO_LEFT_CONTEXT);
    symbol_table_d.concat(SearchSymbol::NO_RIGHT_CONTEXT);
    
    // set up start and terminal search node for each subgraph
    //
    for (long i = 0; i < sub_graphs_d.length(); i++) {
      
      // start vertex
      //
      SearchNode* snode_p = new SearchNode();
      snode_p->setSearchLevel(this);
      snode_p->setSymbol(SearchSymbol::NO_LEFT_CONTEXT);
      sub_graphs_d(i).getStart()->setItem(snode_p);
      
      // terminal vertex
      //
      snode_p = new SearchNode();
      snode_p->setSearchLevel(this);
      snode_p->setSymbol(SearchSymbol::NO_RIGHT_CONTEXT);
      sub_graphs_d(i).getTerm()->setItem(snode_p);
    }
    
    // insert all context pairs into the context mapping hash table
    //
    long total_context_length = left_context_d + right_context_d + 1;

    // check the context length
    //
    if (context_map_d.length() > 0 ) {
      if (context_map_d(0).getContext().length() != total_context_length) {
	total_context_length = context_map_d(0).getContext().length();
      }
    }
    
    Context context(total_context_length);
    
    // loop over all context pairs
    //
    for (long i = 0; i < context_map_d.length(); i++) {
      
      // loop over the symbols in the context
      //
      for (long j = 0; j < total_context_length; j++) {
	
	SearchSymbol ss(context_map_d(i).getContext()(j));
	long symbol_id = getSymbolIndex(ss);
	
	// check whether the symbol is valid
	//
	if (symbol_id == -1) {
	  ss.debug(L"symbol");
	  return Error::handle(name(), L"loadContextMapping", Error::ARG, __FILE__, __LINE__);
	}
	else {
	  context.assignAndAdvance(symbol_id);
	}
      }
      
      // insert a context specification into the hash table
      //
      Ulong index = context_map_d(i).getContextIndex();

      // check if the context is already in the table
      //
      Ulong* existing_index = context_hash_d.get(context);

      // if this context is not in the table yet, insert it
      //
      if (existing_index == NULL) {
	context_hash_d.insert(context, &index);
      }
      
      // if the context is already in the table, then check whether
      // the index of the model is the same as the one from table
      //
      else {

	// if indices are different, explain conflict and return error
	//
	if (!existing_index->eq(index)) {
	  context.debug(L"Context:");
	  String out;
	  out.concat(L" is already in context mapping table with the index: ");
	  out.concat(*existing_index);
	  out.concat(L"\n while new index is: ");
	  out.concat(index);
	  Console::put(out);
	  return Error::handle(name(), L"loadContextMapping", Error::ARG,
			       __FILE__, __LINE__);
	}
	
	// otherwise indices are not conflicting, just print a warning
	//
	else {
	  context.debug(L"Warning: This context is already in the table:");
	}
      }
    }
    
    // output the debugging information
    //
    if (debug_level_d >= Integral::ALL) {     
      context_hash_d.debug(L"context hash table:");
    }
  }
  
  // when there is NO context mapping table at this level
  //
  else {
    
    // symbols will be mapped to the model with the same index at lower level
    //
    Context dummy(1);
    
    for (Ulong i = 0; i < (ulong)num_symbols; i++) {
      dummy.assignAndAdvance(i);
      context_hash_d.insert(dummy, &i);
    }

    if (debug_level_d >= Integral::ALL) {
      context_hash_d.debug(L"dummy context hash table:");
    }
  }
  
  // exit gracefully
  //
  return true;
}

// method: getSubGraphIndex
//
// arguments:
//  const Context& context: (input) context containing symbol indices
//
// return: pointer to an index of the model of the input context
//
Ulong* SearchLevel::getSubGraphIndex(const Context& context_a) {
  return context_hash_d.get(context_a);
}
