// file: $isip/class/search/HierarchicalSearch/hsrch_05.cc
// version: $Id: hsrch_05.cc 9742 2004-08-20 20:43:15Z may $
//

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

// method: forcedAlignment
//
// arguments:
//  String& alignment: (output) forced allignment
//  Long level: (input) the level to print hypotheses
//  DoubleLinkedList<Trace>& trace_path: (input) best hypothesis trace path
//  boolean cumulative: (input) whether to output a cumulative score
//
// return: logical error status
//
// method to output the forced allignment of the current decoded transcription
//
boolean HierarchicalSearch::forcedAlignment(String& alignment_a, Long level_a,
					 DoubleLinkedList<Trace>& trace_path_a,
					 boolean cumulative_a) {

  // declare local variables
  //
  Trace* tmp_trace = (Trace*)NULL;
  SearchNode*  prev_node = (SearchNode*)NULL;
  SearchNode*  tmp_node = (SearchNode*)NULL;

  long counter = 0;
  long frame_ind = 0;
  long prev_frame_ind = -1;
  float score = 0.0;
  float prev_score = 0.0;
  long curr_level = 0;
  
  String out_str;
  SearchSymbol sym;  
  
  // clear the output
  //
  alignment_a.clear();

  // check the level specification
  //
  if ((long)level_a < 0) {
    return Error::handle(name(), L"forcedAlignment - invalid level specified", Error::ARG, __FILE__, __LINE__);
  }
  
  // use the best hypothesis trace path to generate the forced allignment
  //
  for (boolean more_traces = trace_path_a.gotoFirst(); more_traces;
       more_traces = trace_path_a.gotoNext()) {

    counter++;
    tmp_trace = trace_path_a.getCurr();
    frame_ind = tmp_trace->getFrame();
    
    // get the central vertex from the top of the history stack
    //
    GraphVertex<SearchNode>* tmp_vertex = (GraphVertex<SearchNode>*)NULL;
    tmp_vertex = tmp_trace->getSymbol()->getCentralVertex();

    // check for a NULL vertex
    //
    if (tmp_vertex == (GraphVertex<SearchNode>*)NULL) {
      return Error::handle(name(), L"forcedAlignment", Error::ARG, __FILE__, __LINE__);
    }
     
    // make sure the vertex is neither the dummy
    // start nor terminating node
    //
    if ((!tmp_vertex->isStart()) &&
	(!tmp_vertex->isTerm())) {
	
      tmp_node = tmp_vertex->getItem();
      curr_level = tmp_node->getSearchLevel()->getLevelIndex();

      if (curr_level == level_a) {
	
	tmp_node->getSymbol(sym);
	score = tmp_trace->getScore();

	// generate the output hypothesis
	//
	if (counter > 1) {
	  out_str.assign(L"\n");
	} else {
	  out_str.assign(L"");
	}
	
	// append previous frame index
	//
	if (level_a != (getNumLevels() - 1)) {
	  out_str.concat(prev_frame_ind);
	}
	else {
	  out_str.concat(prev_frame_ind + 1);
	}
	out_str.concat(L"\t");
	
	// append current frame index
	//
	if (level_a != (getNumLevels() - 1)) {
	  out_str.concat(frame_ind);
	}
	else {
	  out_str.concat(frame_ind + 1);
	}
	out_str.concat(L"\t");
	
	// append search symbol
	//
	out_str.concat(sym);
	out_str.concat(L"\t");
	
	// append score
	//
	out_str.concat(score - prev_score);
	
	// append cumulative score if required
	//
	if (cumulative_a) {
	  out_str.concat(L"\t");
	  out_str.concat(score);
	}

	// append partial string to the complete hypothesis report
	//
	if (level_a == (getNumLevels() - 1)) {
	  alignment_a.concat(out_str);
	}
	else {
	  if ((frame_ind != prev_frame_ind) && (frame_ind != 0)) {
	    alignment_a.concat(out_str);
	  }
	}
	
	prev_node = tmp_node;
	prev_score = score;
	prev_frame_ind = frame_ind;
      }
    }
  }

  // exit gracefully
  //  
  return true;
}

// method: generateSymbolGraph
//
// arguments:
//  SymbolGraphNode* prev_node: (input) previous node
//  SymbolGraphNode* curr_node: (input) current node
//  float score: (input) transition score
//  float lm_score: (input) language model score
//
// return: logical error status
//
// this method recursively sets up the symbol graph transitions and computes
// acoustic and language model scores for each transition
//
boolean HierarchicalSearch::generateSymbolGraph(SymbolGraphNode* prev_node_a,
						SymbolGraphNode* curr_node_a,
						float score_a,
						float lm_score_a) {

  // declare local variables
  //
  Float prev_score;
  Float prevlm_score;
  
  SymbolGraphNode* prev_node = (SymbolGraphNode*)NULL;
  
  // error checking
  //
  if ((prev_node_a == (SymbolGraphNode*)NULL) ||
      (curr_node_a == (SymbolGraphNode*)NULL)) {
    return Error::handle(name(), L"generateSymbolGraph", Error::ARG, __FILE__, __LINE__);
  }

  // determine the language model score for the forward transition
  //
  float lm_scale = curr_node_a->getParentGraph()->getScale();
  float symbol_penalty = curr_node_a->getParentGraph()->getPenalty();  
  float lm_score = lm_score_a / lm_scale;

  // determine the acoustic model score for the forward transition
  //
  float ac_score = 0.0;
  float score = curr_node_a->getScore();
  
  if (prev_node_a != symbol_graph_d.getTerm()) {
    ac_score = score_a - score  - (lm_score * lm_scale + symbol_penalty);
  }

  if (debug_level_d >= Integral::DETAILED) {
    String src_str;
    String dst_str;    
    curr_node_a->getSymbol(src_str);
    prev_node_a->getSymbol(dst_str);
    
    String output(L"\n-> src: ");
    output.concat(curr_node_a);
    output.concat(L" [");  
    output.concat(src_str);
    output.concat(L"], frame: ");
    output.concat(curr_node_a->getFrameIndex());

    output.concat(L", dst: ");
    output.concat(prev_node_a);
    output.concat(L" [");  
    output.concat(dst_str);
    output.concat(L"], frame: ");
    output.concat(prev_node_a->getFrameIndex());
    output.concat(L", ac score: ");
    output.concat(ac_score);
    output.concat(L", lm score: ");
    output.concat(lm_score);    
    Console::put(output);
  }
  
  // insert the forward transition in the symbol graph
  //
  curr_node_a->insertNextNode(prev_node_a, lm_score, ac_score);
    
  // get the list of all previous nodes and scores
  //
  SingleLinkedList<SymbolGraphNode>& prev_list = curr_node_a->getPrevNodesList();
  SingleLinkedList<Float>& prev_scores = curr_node_a->getPrevScoresList();
  SingleLinkedList<Float>& prevlm_scores = curr_node_a->getPrevLMScoresList();

  // loop over all nodes in the list
  //
  boolean has_more_nodes = prev_list.gotoFirst() & prev_scores.gotoFirst() &
    prevlm_scores.gotoFirst();
  
  while (has_more_nodes) {

    // determine the previous node and the path transition score
    //
    prev_list.removeFirst(prev_node);    

    prev_score.assign(*(prev_scores.getFirst()));    
    prev_scores.removeFirst();

    prevlm_score.assign(*(prevlm_scores.getFirst()));    
    prevlm_scores.removeFirst();
    
    // recursively visit all preceding nodes in the graph
    //    
    if (!generateSymbolGraph(curr_node_a, prev_node,
			     (float)prev_score, (float)prevlm_score)) {
      return Error::handle(name(), L"generateSymbolGraph", Error::ARG, __FILE__, __LINE__);
    }

    // move to the next node in the list 
    //    
    has_more_nodes = prev_list.gotoFirst() & prev_scores.gotoFirst() &
      prevlm_scores.gotoFirst();    
  }
  
  // exit gracefully
  //  
  return true;  
}

// method: generateSymbolGraph
//
// arguments: none
//
// return: logical error status
//
// this method sets up the symbol graph transitions and computes acoustic and
// language model scores for each transition
//
boolean HierarchicalSearch::generateSymbolGraph() {

  // declare local variables
  //
  Float prev_score;
  Float prevlm_score;
  
  SymbolGraphNode* prev_node = (SymbolGraphNode*)NULL;
  SymbolGraphNode* curr_node = symbol_graph_d.getTerm();
  
  // get the list of all previous nodes and scores
  //
  SingleLinkedList<SymbolGraphNode>& prev_list = curr_node->getPrevNodesList();
  SingleLinkedList<Float>& prev_scores = curr_node->getPrevScoresList();
  SingleLinkedList<Float>& prevlm_scores = curr_node->getPrevLMScoresList();  
  
  // loop over all nodes in the list
  //
  boolean has_more_nodes = prev_list.gotoFirst() & prev_scores.gotoFirst() &
    prevlm_scores.gotoFirst();
  
  while (has_more_nodes) {

    // determine the previous node and the path transition score
    //
    prev_list.removeFirst(prev_node);

    prev_score.assign(*(prev_scores.getFirst()));    
    prev_scores.removeFirst();

    prevlm_score.assign(*(prevlm_scores.getFirst()));    
    prevlm_scores.removeFirst();

    // recursively visit all preceding nodes in the graph
    //    
    if (!generateSymbolGraph(curr_node, prev_node,
			     (float)prev_score, (float)prevlm_score)) {
      return Error::handle(name(), L"generateSymbolGraph", Error::ARG, __FILE__, __LINE__);
    }

    // move to the next node in the list 
    //
    has_more_nodes = prev_list.gotoFirst() & prev_scores.gotoFirst() &
      prevlm_scores.gotoFirst();
  }

  // exit gracefully
  //  
  return true;  
}

// method: initializeSymbolGraphTrace
//
// arguments:
//  double& score: (output) utterance score
//  long& num_frames: (output) total number of frames
//
// return: logical error status
//
// this method completes the symbol graph generation for the utterance
//
boolean HierarchicalSearch::initializeSymbolGraphTrace(double& score_a,
						  long& num_frames_a) {

  // declare local variables
  //
  Trace* tmp_trace = (Trace*)NULL;
  
  // move all traces forward in the search space
  //
  if (!propagateTraces()) {
    return false;
  }

  // make sure we have at least one valid hypothesis
  //  
  if (trace_valid_hyps_d.length() < 1) {
    return false;
  }

  // loop over all valid traces in the hypothesis list
  //
  trace_valid_hyps_d.gotoFirst();
  Trace* best_end_hyp = trace_valid_hyps_d.getCurr();

  // find the trace with the best score, i.e., best hypothesis trace
  //
  while (true) {
  
    if (!trace_valid_hyps_d.gotoNext()) {
      break;
    }

    tmp_trace = trace_valid_hyps_d.getCurr();
    if (tmp_trace->getScore() > best_end_hyp->getScore()) {
      best_end_hyp = tmp_trace;
    }
  }

  // when we are generating a symbol graph
  //
  if (best_end_hyp->getSymbolNode() == (SymbolGraphNode*)NULL) {
    return false;
  }

  // set the utterance score
  //
  score_a = best_end_hyp->getScore();

  // set the total number of frames
  //
  num_frames_a = best_end_hyp->getFrame();
  
  // create a transition from the best hypothesis to the term of the
  // symbol graph
  //
  SymbolGraphNode* new_symbol_node = symbol_graph_d.getTerm();
  new_symbol_node->setParentGraph(&symbol_graph_d);
  new_symbol_node->setFrameIndex(num_frames_a);

  // loop over all valid instances in the hypothesis list
  //
  trace_valid_hyps_d.gotoFirst();

  while (true) {

    // retrieve the current trace
    //
    tmp_trace = trace_valid_hyps_d.getCurr();
  
    // create a transition from the best hypothesis to the term of the
    // symbol graph
    //    
    SymbolGraphNode* old_symbol_node = tmp_trace->getSymbolNode();
    
    float path_score = tmp_trace->getScore();
    long history_paths =
      getSearchLevel((long)initial_level_d).getHistoryPaths();
    
    new_symbol_node->insertPrevNode(old_symbol_node,
				    path_score, history_paths, 0.0);

    if (!trace_valid_hyps_d.gotoNext()) {
      break;
    }    
  }

  // prune the symbol graph - remove all unreferenced nodes
  //
  if (!symbol_graph_d.prune()) {
    return Error::handle(name(), L"finalizeSymbolGraph", Error::ARG, __FILE__, __LINE__);
  }

  // determine the symbol graph generation level
  //
  long level = -1;
  for (long i=0; i < h_digraph_d->length(); i++) {
    if ((*h_digraph_d)(i).useSymbolGraph()) {
      level = i;
      break;
    }
  }

  if (level < 0) {
    return Error::handle(name(), L"initializeSymbolGraph", Error::ARG, __FILE__, __LINE__);
  }
  
  // set the language model order
  //
  symbol_graph_d.setOrder(getSearchLevel(level).getNSymbolOrder());
  
  // set the language model scale
  //
  symbol_graph_d.setScale(getSearchLevel(level).getLmScale());
    
  // set the symbol insertion penalty
  //
  symbol_graph_d.setPenalty(getSearchLevel(level).getSymbolPenalty());
  
  // exit gracefully
  //  
  return true;  
}

// method: initializeSymbolGraphInstance
//
// arguments:
//  double& score: (output) utterance score
//  long& num_frames: (output) total number of frames
//
// return: logical error status
//
// this method completes the symbol graph generation for the utterance
//
boolean HierarchicalSearch::initializeSymbolGraphInstance(double& score_a,
						  long& num_frames_a) {

  // declare local variables
  //
  Instance* tmp_instance = (Instance*)NULL;
  
  // move all traces forward in the search space
  //
  if (!propagateLexInstances()) {
    return false;
  }

  // make sure we have at least one valid hypothesis
  //  
  if (instance_valid_hyps_d.length() < 1) {
    return false;
  }
  
  // loop over all valid instances in the hypothesis list
  //
  instance_valid_hyps_d.gotoFirst();
  Instance* best_end_hyp = instance_valid_hyps_d.getCurr();

  // find the instance with the best score, i.e., best hypothesis trace
  //
  while (true) {
  
    if (!instance_valid_hyps_d.gotoNext()) {
      break;
    }

    tmp_instance = instance_valid_hyps_d.getCurr();
    if (tmp_instance->getScore() > best_end_hyp->getScore()) {
      best_end_hyp = tmp_instance;
    }
  }

  // when we are generating a symbol graph
  //
  if (best_end_hyp->getSymbolNode() == (SymbolGraphNode*)NULL) {
    return false;
  }

  // set the utterance score
  //
  score_a = best_end_hyp->getScore();

  // set the total number of frames
  //
  num_frames_a = best_end_hyp->getFrame();
  
  // loop over all valid instances in the hypothesis list
  //
  instance_valid_hyps_d.gotoFirst();

  SymbolGraphNode* new_symbol_node = symbol_graph_d.getTerm();
  new_symbol_node->setParentGraph(&symbol_graph_d);
  new_symbol_node->setFrameIndex(num_frames_a);
  
  while (true) {

    // retrieve the current instance
    //
    tmp_instance = instance_valid_hyps_d.getCurr();
  
    // create a transition from the best hypothesis to the term of the
    // symbol graph
    //    
    SymbolGraphNode* old_symbol_node = tmp_instance->getSymbolNode();
    
    float path_score = tmp_instance->getScore();
    long history_paths =
      getSearchLevel((long)initial_level_d).getHistoryPaths();
    
    new_symbol_node->insertPrevNode(old_symbol_node,
				    path_score, history_paths, 0.0);

    if (!instance_valid_hyps_d.gotoNext()) {
      break;
    }    
  }

  // prune the symbol graph - remove all unreferenced nodes
  //
  if (!symbol_graph_d.prune()) {
    return Error::handle(name(), L"finalizeSymbolGraph", Error::ARG, __FILE__, __LINE__);
  }

  // determine the symbol graph generation level
  //
  long level = -1;
  for (long i=0; i < h_digraph_d->length(); i++) {
    if ((*h_digraph_d)(i).useSymbolGraph()) {
      level = i;
      break;
    }
  }

  if (level < 0) {
    return Error::handle(name(), L"initializeSymbolGraph", Error::ARG, __FILE__, __LINE__);
  }
  
  // set the language model order
  //
  symbol_graph_d.setOrder(getSearchLevel(level).getNSymbolOrder());
  
  // set the language model scale
  //
  symbol_graph_d.setScale(getSearchLevel(level).getLmScale());
    
  // set the symbol insertion penalty
  //
  symbol_graph_d.setPenalty(getSearchLevel(level).getSymbolPenalty());
  
  // exit gracefully
  //  
  return true;  
}

// method: getHypotheses
//
// arguments:
//  String& output_hyp: (output) the current search hypotheses
//  long level: (input) the level to print hypotheses from
//  double& total_score: (output) the hypothesis total score
//  long& num_frames: (output) frame index of the last trace
//  DoubleLinkedList<Trace>& trace_path: (output) best hypothesis trace path
//
// return: logical error status
//
// build a graph representing the hypotheses and return it
//
boolean HierarchicalSearch::getHypotheses(String& output_hyp_a,
					  long level_a,
					  double& total_score_a,
					  long& num_frames_a,
					  DoubleLinkedList<Trace>& trace_path_a) {

  // declare local variables
  //
  Trace* tmp_trace = (Trace*)NULL;
  SearchNode*  prev_node = (SearchNode*)NULL;
  SearchNode*  tmp_node = (SearchNode*)NULL;
  BiGraphVertex<TrainNode>* vertex = (BiGraphVertex<TrainNode>*)NULL;
  
  long counter = 0;
  long curr_level = 0;
  long frame_ind = 0;
  long prev_frame_ind = -1;
  long back_count = 0;
  float score = 0.0;
  float prev_score = 0.0;
  long symbol_id = 0;
  
  String out_str;
  SearchSymbol sym;
    
  // clear the output and set the allocation mode for the trace path
  //
  output_hyp_a.clear();
  trace_path_a.clear();
  
  trace_path_a.setAllocationMode(DstrBase::USER);
  
  // move all traces forward in the search space
  //
  if (!propagateTraces()) {
    return false;
  }

  // make sure we have at least one valid hypothesis
  //  
  if (trace_valid_hyps_d.length() < 1) {
    return false;
  }

  if (search_mode_d == TRAIN) {

    // loop over all valid hypothesis
    //    
    for (boolean more = trace_valid_hyps_d.gotoFirst(); more;
	 more = trace_valid_hyps_d.gotoNext()) {

      // retrieve the vertex corresponding to the trace
      //          
      vertex = trace_valid_hyps_d.getCurr()->getReference();

      if (vertex == (BiGraphVertex<TrainNode>*)NULL) {
	return Error::handle(name(), L"getHypotheses - cannot find the valid hypothesis in the trellis", Error::ARG, __FILE__, __LINE__);
      }

      // connect the current vertex to the terminal node
      //
      trellis_d.insertArc(vertex, trellis_d.getTerm(), false, 0);      
    }
  }

  // loop over all valid traces in the hypothesis list
  //
  trace_valid_hyps_d.gotoFirst();
  Trace* best_end_hyp = trace_valid_hyps_d.getCurr();

  // find the trace with the best score, i.e., best hypothesis trace
  //
  while (true) {
  
    if (!trace_valid_hyps_d.gotoNext()) {
      break;
    }

    tmp_trace = trace_valid_hyps_d.getCurr();
    if (tmp_trace->getScore() > best_end_hyp->getScore()) {
      best_end_hyp = tmp_trace;
    }
  }
  
  // backtrack from the best hypothesis trace and generate the trace path
  //
  for (tmp_trace = best_end_hyp; tmp_trace != (Trace*)NULL;
       tmp_trace = tmp_trace->getBackPointer()) {
    trace_path_a.insertFirst(tmp_trace);
    back_count++;
  }
  
  // use the best hypothesis trace path to generate the hypothesis string
  //
  for (boolean more_traces = trace_path_a.gotoFirst(); more_traces;
       more_traces = trace_path_a.gotoNext()) {

    counter++;
    tmp_trace = trace_path_a.getCurr();
    frame_ind = tmp_trace->getFrame();
    
    // get the central vertex from the top of the history stack
    //    
    GraphVertex<SearchNode>* tmp_vertex = (GraphVertex<SearchNode>*)NULL;
    tmp_vertex = tmp_trace->getSymbol()->getCentralVertex();    

    // check for a NULL vertex
    //
    if (tmp_vertex == (GraphVertex<SearchNode>*)NULL) {
      return Error::handle(name(), L"getHypotheses - NULL VERTEX", Error::ARG, __FILE__, __LINE__);
    }
    
    // make sure the vertex is neither the dummy
    // start nor terminating node
    //
    if ((!tmp_vertex->isStart()) &&
	(!tmp_vertex->isTerm())) {

      tmp_node = tmp_vertex->getItem();
      curr_level = tmp_node->getSearchLevel()->getLevelIndex();

      if ((level_a < 0) && (curr_level == (long)initial_level_d)) {

	tmp_node->getSymbol(sym);
	score = tmp_trace->getScore();
	symbol_id = tmp_node->getSymbolId();
	
	// generate the output hypothesis containing only search symbols
	//
	if (!getSearchLevel(curr_level).isExcludeSymbol(symbol_id)) {
	  out_str.assign(sym);
	  out_str.concat(L" ");
	}
	else {
	  out_str.assign(L"");
	}
	
	// append partial string to the complete hypothesis report
	//
	if ((frame_ind != prev_frame_ind) && (frame_ind != 0)) {
	  output_hyp_a.concat(out_str);
	}
	
	prev_node = tmp_node;
	prev_score = score;
	prev_frame_ind = frame_ind;
      }
      
      if ((level_a >= 0) && (curr_level == level_a)) {

	String context;
	tmp_trace->getSymbol()->print(context);
	score = tmp_trace->getScore();
	sym.assign(context);
	
	// generate the output hypothesis
	//
	if (counter > 1) {
	  out_str.assign(L"\n");
	} else {
	  out_str.assign(L"");
	}

	// append previous frame index
	//
	if (level_a != (getNumLevels() - 1)) {
	  out_str.concat(prev_frame_ind);
	}
	else {
	  out_str.concat(prev_frame_ind + 1);
	}
	out_str.concat(L"\t");

	// append current frame index
	//
	if (level_a != (getNumLevels() - 1)) {
	  out_str.concat(frame_ind);
	}
	else {
	  out_str.concat(frame_ind + 1);
	}
	out_str.concat(L"\t");
	
	// append search symbol
	//
	out_str.concat(sym);
	out_str.concat(L"\t\t");
	
	// append score
	//
	out_str.concat(score - prev_score);
	
	// append partial string to the complete hypothesis report
	//
	if (level_a == (getNumLevels() - 1)) {
	  output_hyp_a.concat(out_str);
	}
	else {
	  if ((frame_ind != prev_frame_ind) && (frame_ind != 0)) {
	    output_hyp_a.concat(out_str);
	  }
	}	
	
	prev_node = tmp_node;
	prev_score = score;
	prev_frame_ind = frame_ind;
      }      
    }    
  }

  total_score_a = score;
  num_frames_a = frame_ind;
  
  // exit gracefully
  //
  return true;
}

// method: printNewPath
//
// arguments:
//  Trace* new_trace: (input) the endpoint of the new arc
//  Trace* old_trace: (input) the start point of the new arc
//
// return: logical error status
//
// print an arc created in the hypothesis space
//
boolean HierarchicalSearch::printNewPath(Trace* new_trace_a,
					 Trace* old_trace_a) {

  SearchSymbol start_sym;
  SearchSymbol end_sym;
  
  float old_score = 0.0;
  float new_score = 0.0;

  long old_frame = -1;
  long new_frame = -1;
  
  if (old_trace_a != (Trace*)NULL) {
    
    GraphVertex<SearchNode>* old_vertex = old_trace_a->getSymbol()->getCentralVertex();
    old_frame = old_trace_a->getFrame();   
    if (old_vertex->isStart()) {
      start_sym.assign(L"_START_");
    }
    else if (old_vertex->isTerm()) {
      start_sym.assign(L"_TERM_");
    }
    else {
      old_trace_a->getSymbol()->print(start_sym);
    }
    old_score = old_trace_a->getScore();
  }

  if (new_trace_a != (Trace*)NULL) {

    GraphVertex<SearchNode>* new_vertex = new_trace_a->getSymbol()->getCentralVertex();    
    
    new_frame = new_trace_a->getFrame();
    if (new_vertex->isStart()) {
      end_sym.assign(L"_START_");
    }
    else if (new_vertex->isTerm()) {
      end_sym.assign(L"_TERM_");
    }
    else {
      new_trace_a->getSymbol()->print(end_sym);      
    }
    new_score = new_trace_a->getScore();
  }

  String val;  
  String out(L"\n-> trace: ");
  out.concat(new_trace_a);
  out.concat(L", backpointer: ");
  out.concat(old_trace_a);
  
  out.concat(L"\n   [");
  out.concat(start_sym);
  out.concat(L"], frame: ");
  val.assign((long)old_frame);
  out.concat(val);
  out.concat(L", score: ");
  out.concat(old_score);  
  out.concat(L" -> [");
  out.concat(end_sym);
  out.concat(L"], frame: ");
  val.assign((long)new_frame);  
  out.concat(val); 
  out.concat(L", score: ");
  out.concat(new_score);
  Console::put(out);
  
  return true;
}

// method: printDeletedPath
//
// arguments:
//  Trace* new_trace: (input) the endpoint of the new arc
//  Trace* old_trace: (input) the start point of the new arc
//
// return: logical error status
//
// print an arc created in the hypothesis space
//
boolean HierarchicalSearch::printDeletedPath(Trace* new_trace_a,
					     Trace* old_trace_a) {

  SearchSymbol start_sym;
  SearchSymbol end_sym;

  long old_frame = -1;
  long new_frame = -1;
  
  if (old_trace_a != (Trace*)NULL) {

    GraphVertex<SearchNode>* old_vertex = old_trace_a->getSymbol()->getCentralVertex();    

    old_frame = old_trace_a->getFrame();    
    if (old_vertex->isStart()) {
      start_sym.assign(L"_START_");
    }
    else if (old_vertex->isTerm()) {
      start_sym.assign(L"_TERM_");
    }
    else {
      old_trace_a->getSymbol()->print(start_sym);      
    }
  }

  if (new_trace_a != (Trace*)NULL) {

    GraphVertex<SearchNode>* new_vertex = new_trace_a->getSymbol()->getCentralVertex();    

    new_frame = new_trace_a->getFrame();
    if (new_vertex->isStart()) {
      end_sym.assign(L"_START_");
    }
    else if (new_vertex->isTerm()) {
      end_sym.assign(L"_TERM_");
    }
    else {
      new_trace_a->getSymbol()->print(end_sym);      
    }
  }

  String val;  
  String out(L"-> deleted trace: ");
  out.concat(new_trace_a);
  out.concat(L", backpointer: ");
  out.concat(old_trace_a);
  
  out.concat(L"\n   [");
  out.concat(start_sym);
  out.concat(L"], frame: ");
  val.assign((long)old_frame);  
  out.concat(val);
  out.concat(L" -> ");
  out.concat(end_sym);
  out.concat(L"], frame: ");
  val.assign((long)new_frame);  
  out.concat(val);
  Console::put(out);
  
  return true;
}

// method: getHypotheses
//
// arguments:
//  String& output_hyp: (output) the current search hypotheses
//  long level: (input) the level to print hypotheses from
//  double& total_score: (output) the hypothesis total score
//  long& num_frames: (output) frame index of the last instance
//  DoubleLinkedList<Instance>& instance_path: (output) best hypothesis instance path
//
// return: logical error status
//
// build a graph representing the hypotheses and return it
//
boolean HierarchicalSearch::getHypotheses(String& output_hyp_a,
					  long level_a,
					  double& total_score_a,
					  long& num_frames_a,
					  DoubleLinkedList<Instance>& instance_path_a) {

  // declare local variables
  //
  Instance* tmp_instance = (Instance*)NULL;
  SearchNode*  prev_node = (SearchNode*)NULL;
  SearchNode*  tmp_node = (SearchNode*)NULL;
  BiGraphVertex<TrainNode>* vertex = (BiGraphVertex<TrainNode>*)NULL;
  
  long counter = 0;
  long curr_level = 0;
  long frame_ind = 0;
  long prev_frame_ind = -1;
  long back_count = 0;
  float score = 0.0;
  float prev_score = 0.0;
  long symbol_id = 0;
  
  String out_str;
  SearchSymbol sym;    
   
  // clear the output and set the allocation mode for the instance path
  //
  output_hyp_a.clear();
  instance_path_a.clear();
  
  instance_path_a.setAllocationMode(DstrBase::USER);
  
  // move all instances forward in the search space
  //
  if (!propagateInstances()) {
    return false;
  }

  // make sure we have at least one valid hypothesis
  //  
  if (instance_valid_hyps_d.length() < 1) {
    return false;
  }

  if (search_mode_d == TRAIN) {

    // loop over all valid hypothesis and connect them to the trellis term
    //    
    for (boolean more = instance_valid_hyps_d.gotoFirst(); more;
	 more = instance_valid_hyps_d.gotoNext()) {
      
      vertex = instance_valid_hyps_d.getCurr()->getReference();
      
      if (vertex == (BiGraphVertex<TrainNode>*)NULL) {
	return Error::handle(name(), L"getHypotheses - cannot find the valid hypothesis in the trellis", Error::ARG, __FILE__, __LINE__);
      }
      
      trellis_d.insertArc(vertex, trellis_d.getTerm(), false, 0);
    }
  }
  
  // loop over all valid instances in the hypothesis list
  //
  instance_valid_hyps_d.gotoFirst();
  Instance* best_end_hyp = instance_valid_hyps_d.getCurr();
  
  // find the instance with the best score, i.e., best hypothesis instance
  //
  while (true) {
  
    if (!instance_valid_hyps_d.gotoNext()) {
      break;
    }

    tmp_instance = instance_valid_hyps_d.getCurr();
    if (tmp_instance->getScore() > best_end_hyp->getScore()) {
      best_end_hyp = tmp_instance;
    }
  }
  
  // backtrack from the best hypothesis instance and generate the instance path
  //
  for (tmp_instance = best_end_hyp; tmp_instance != (Instance*)NULL;
       tmp_instance = tmp_instance->getBackPointer()) {
    instance_path_a.insertFirst(tmp_instance);
    back_count++;
  }

  // use the best hypothesis instance path to generate the hypothesis string
  //
  for (boolean more_instances = instance_path_a.gotoFirst(); more_instances;
       more_instances = instance_path_a.gotoNext()) {

    counter++;
    tmp_instance = instance_path_a.getCurr();
    frame_ind = tmp_instance->getFrame();
    
    // get the central vertex from the top of the history stack
    //
    GraphVertex<SearchNode>* tmp_vertex = (GraphVertex<SearchNode>*)NULL;
    tmp_vertex = tmp_instance->getSymbol()->getCentralVertex();    

    // check for a NULL vertex
    //
    if (tmp_vertex == (GraphVertex<SearchNode>*)NULL) {
      return Error::handle(name(), L"getHypotheses - NULL VERTEX", Error::ARG, __FILE__, __LINE__);
    }
    
    // make sure the vertex is neither the dummy
    // start nor terminating node
    //
    if ((!tmp_vertex->isStart()) &&
	(!tmp_vertex->isTerm())) {

      tmp_node = tmp_vertex->getItem();
      curr_level = tmp_node->getSearchLevel()->getLevelIndex();

      if ((level_a < 0) && (curr_level == (long)initial_level_d)) {

	tmp_node->getSymbol(sym);
	score = tmp_instance->getScore();
	symbol_id = tmp_node->getSymbolId();
	
	// generate the output hypothesis containing only search symbols
	//
	if (!getSearchLevel(curr_level).isExcludeSymbol(symbol_id)) {
	  out_str.assign(sym);
	  out_str.concat(L" ");
	}
	else {
	  out_str.assign(L"");
	}
	
	// append partial string to the complete hypothesis report
	//
	if ((frame_ind != prev_frame_ind) && (frame_ind != 0)) {
	  output_hyp_a.concat(out_str);
	}
	
	prev_node = tmp_node;
	prev_score = score;
	prev_frame_ind = frame_ind;
      }
      
      if ((level_a >= 0) && (curr_level == level_a)) {

	String context;
	tmp_instance->getSymbol()->print(context);	
	score = tmp_instance->getScore();
	sym.assign(context);
	
	// generate the output hypothesis
	//
	if (counter > 1) {
	  out_str.assign(L"\n");
	} else {
	  out_str.assign(L"");
	}

	// append previous frame index
	//
	if (level_a != (getNumLevels() - 1)) {
	  out_str.concat(prev_frame_ind);
	}
	else {
	  out_str.concat(prev_frame_ind + 1);
	}
	out_str.concat(L"\t");

	// append current frame index
	//
	if (level_a != (getNumLevels() - 1)) {
	  out_str.concat(frame_ind);
	}
	else {
	  out_str.concat(frame_ind + 1);
	}
	out_str.concat(L"\t");
	
	// append search symbol
	//
	out_str.concat(sym);
	out_str.concat(L"\t\t");
	
	// append score
	//
	out_str.concat(score - prev_score);
	
	// append partial string to the complete hypothesis report
	//
	if (level_a == (getNumLevels() - 1)) {
	  output_hyp_a.concat(out_str);
	}
	else {
	  if ((frame_ind != prev_frame_ind) && (frame_ind != 0)) {
	    output_hyp_a.concat(out_str);
	  }
	}	
	
	prev_node = tmp_node;
	prev_score = score;
	prev_frame_ind = frame_ind;
      }      
    }    
  }

  total_score_a = score;
  num_frames_a = frame_ind;
  
  // exit gracefully
  //
  return true;
}

// method: printNewPath
//
// arguments:
//  Instance* new_instance: (input) newly created instance
//  Instance* old_instance: (input) previous instance (backpointer)
//
// return: logical error status
//
// prints a transition generated in the hypothesis space
//
boolean HierarchicalSearch::printNewPath(Instance* new_instance_a,
					 Instance* old_instance_a) {
  
  SearchSymbol start_sym;
  SearchSymbol end_sym;
  
  float old_score = 0.0;
  float new_score = 0.0;

  long old_frame = -1;
  long new_frame = -1;
      
  if (old_instance_a != (Instance*)NULL) {
    
    GraphVertex<SearchNode>* old_vertex = old_instance_a->getSymbol()->getCentralVertex();    

    old_frame = old_instance_a->getFrame();   
    if (old_vertex->isStart()) {
      start_sym.assign(L"_START_");
    }
    else if (old_vertex->isTerm()) {
      start_sym.assign(L"_TERM_");
    }
    else {
      old_instance_a->getSymbol()->print(start_sym);      
    }
    old_score = old_instance_a->getScore();
  }

  if (new_instance_a != (Instance*)NULL) {

    GraphVertex<SearchNode>* new_vertex = new_instance_a->getSymbol()->getCentralVertex();    
    
    new_frame = new_instance_a->getFrame();
    if (new_vertex->isStart()) {
      end_sym.assign(L"_START_");
    }
    else if (new_vertex->isTerm()) {
      end_sym.assign(L"_TERM_");
    }
    else {
      new_instance_a->getSymbol()->print(end_sym);      
    }
    new_score = new_instance_a->getScore();
  }

  String val;  
  String output(L"\n-> instance: ");
  output.concat(new_instance_a);
  output.concat(L", backpointer: ");
  output.concat(old_instance_a);    

  output.concat(L"\n   [");
  output.concat(start_sym);
  output.concat(L"], frame: ");
  val.assign((long)old_frame);  
  output.concat(val);
  output.concat(L", score: ");
  output.concat(old_score);  
  output.concat(L" -> [");
  output.concat(end_sym);
  output.concat(L"], frame: ");
  val.assign((long)new_frame);  
  output.concat(val);
  output.concat(L", score: ");
  output.concat(new_score);
  Console::put(output);
  
  return true;
}

// method: printDeletedPath
//
// arguments:
//  Instance* new_instance: (input) the endpoint of the new arc
//  Instance* old_instance: (input) the start point of the new arc
//
// return: logical error status
//
// print an arc created in the hypothesis space
//
boolean HierarchicalSearch::printDeletedPath(Instance* new_instance_a,
					     Instance* old_instance_a) {

  SearchSymbol start_sym;
  SearchSymbol end_sym;

  long old_frame = -1;
  long new_frame = -1;

  if (old_instance_a != (Instance*)NULL) {

    GraphVertex<SearchNode>* old_vertex = old_instance_a->getSymbol()->getCentralVertex();    

    old_frame = old_instance_a->getFrame();    
    if (old_vertex->isStart()) {
      start_sym.assign(L"_START_");
    }
    else if (old_vertex->isTerm()) {
      start_sym.assign(L"_TERM_");
    }
    else {
      old_instance_a->getSymbol()->print(start_sym);      
    }
  }

  if (new_instance_a != (Instance*)NULL) {

    GraphVertex<SearchNode>* new_vertex = new_instance_a->getSymbol()->getCentralVertex();    

    new_frame = new_instance_a->getFrame();
    if (new_vertex->isStart()) {
      end_sym.assign(L"_START_");
    }
    else if (new_vertex->isTerm()) {
      end_sym.assign(L"_TERM_");
    }
    else {
      new_instance_a->getSymbol()->print(end_sym);      
    }
  }

  String val;  
  String output(L"\n-> deleted instance: ");
  output.concat(new_instance_a);
  output.concat(L", backpointer: ");
  output.concat(old_instance_a);  

  output.concat(L"\n   [");
  output.concat(start_sym);
  output.concat(L"], frame: ");
  val.assign((long)old_frame);  
  output.concat(val);
  output.concat(L" -> [");
  output.concat(end_sym);
  output.concat(L"], frame: ");
  val.assign((long)new_frame);  
  output.concat(val);
  Console::put(output);
  
  return true;
}
