// file: $isip/class/search/NBestPath/nbstp_05.cc
// version: $Id: nbstp_05.cc 9332 2003-11-03 14:27:14Z alphonso $
//

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

// method: clear
//
// arguments:
//  Integral::CMODE cmode: (input) clear mode
//
// return: a boolean value indicating status
//
// this method clears the model. in RETAIN mode, the virtual model pointer
// is maintained; all other modes reset the virtual model pointer to the
// default value.
//
boolean NBestPath::clear(Integral::CMODE ctype_a) {

  // clear the data members
  //
  frame_index_d = DEF_FRAME_INDEX;
  max_score_d = DEF_MAX_SCORE;

  // clear the contents of the list
  //
  paths_d.clear(Integral::FREE);
  
  // exit gracefully
  //
  return true;
}

// method: insertPath
//
// arguments:
//  NBestPath* node: (input) partial path
//
// return: a boolean indicating status
//
// this method adds a partial paths to the list
//
boolean NBestPath::insertPath(NBestNode* path_a) {
  
  // define local variables
  //
  float score = 0.0;
  boolean is_inserted = false;
  NBestNode* nnode = (NBestNode*)NULL;

  // when the path to add is null return ungracefully
  //
  if (path_a == (NBestNode*)NULL) {
    return false;
  }
  
  // retrieve the new path score
  //  
  float new_score = path_a->getPathScore();
  
  // insert the new path at the right place
  //
  for (boolean more = paths_d.gotoFirst(); more; more = paths_d.gotoNext()) {

    // retrieve the current node and the corresponding score
    //
    nnode = paths_d.getCurr();
    score = nnode->getPathScore();

    // insert the new path if it has a better score
    //
    if (new_score > score) {
      is_inserted = true;
      paths_d.insert(path_a, true);
      break;
    }
  }    

  // make sure the node was inserted
  //
  if (!is_inserted) {
    paths_d.insertLast(path_a);
  }
  
  // exit gracefully
  //
  return true;    
}

// method: growPaths
//
// arguments:
//  NBestPath**& path_list: (input) partial paths
//  float scale: (input) language model scale
//  float penalty: (input) symbol insertion penalty
//  long max_paths: (input) max number of hypothesis
//  float beam: (input) beam pruning threshold
//
// return: a boolean indicating status
//
// this method extend partial paths
//
boolean NBestPath::growPaths(NBestPath**& path_list_a, float scale_a,
			     float penalty_a,
			     long max_paths_a, float beam_a) {
  
  // define local variables
  //
  long frame_index = 0;
  float score = 0.0;
  float lm_score = 0.0;
  float ac_score = 0.0;
  
  NBestNode* nnode = (NBestNode*)NULL;
  NBestNode* new_nbest_node = (NBestNode*)NULL;
  
  SymbolGraphNode* curr_node = (SymbolGraphNode*)NULL;
  SymbolGraphNode* next_node = (SymbolGraphNode*)NULL;  
  
  // loop through the partial path list
  //
  for (boolean more = paths_d.gotoFirst(); more; more = paths_d.gotoNext()) {

    // retrieve the path and the symbol graph node
    //
    nnode = paths_d.getCurr();
    curr_node = nnode->getSymbolNode();

    // retrieve the list of next nodes from the symbol graph node
    //
    SingleLinkedList<SymbolGraphNode>& next_list =
      curr_node->getNextNodesList();

    // get the corresponding transition scores
    //
    SingleLinkedList<Float>& lm_scores = curr_node->getLMScoresList();
    SingleLinkedList<Float>& ac_scores = curr_node->getScoresList();
  
    // loop over all next nodes corresponding to the symbol graph node
    //
    lm_scores.gotoFirst();
    ac_scores.gotoFirst();
  
    for (boolean more1 = next_list.gotoFirst(); more1;
	 more1 = next_list.gotoNext()) {

      // retrieve the next node
      //
      next_node = next_list.getCurr();
      
      // retrieve the corresponding language and acoustic model scores
      //
      Float* val = (Float*)NULL;
      
      if ((val = lm_scores.getCurr()) == (Float*)NULL) {
	return Error::handle(name(), L"insertNextNode", Error::ARG, __FILE__, __LINE__);
      }
      lm_score = (float)(*val);
      
      val = (Float*)NULL;
      if ((val = ac_scores.getCurr()) == (Float*)NULL) {
	return Error::handle(name(), L"insertNextNode", Error::ARG, __FILE__, __LINE__);
      }      
      ac_score = (float)(*val);

      // retrieve the corresponding frame index and score
      //
      frame_index = next_node->getFrameIndex();

      score = nnode->getPathScore() + ac_score +
	(scale_a * lm_score) + penalty_a;

      // add this path to the path list only if it falls within the beam
      //
      if (score > (path_list_a[frame_index]->max_score_d - beam_a)) {

	// create the partial path
	//
	new_nbest_node = new NBestNode(next_node, nnode, score);
	
	// and add it to the appropriate path list
	//
	path_list_a[frame_index]->insertPath(new_nbest_node);
	
	// set the max score
	//
	if (score > path_list_a[frame_index]->max_score_d) {
	  path_list_a[frame_index]->max_score_d = score;
	}	
      }
      
      // move to the next transition
      //
      lm_scores.gotoNext();
      ac_scores.gotoNext();        
    }
  }
  
  // exit gracefully
  //
  return true;    
}

// method: prune
//
// arguments:
//  float beam: (input) beam pruning threshold
//  long max_paths: (input) max number of hypothesis
//
// return: a boolean indicating status
//
// this method prunes partial paths
//
boolean NBestPath::prune(float beam_a, long max_paths_a) {

  // define local variables
  //
  NBestNode* node = (NBestNode*)NULL;  
  NBestNode* nnode = (NBestNode*)NULL;

  float beam_threshold = 0.0;
  
  // when the list of partial paths is empty we are done
  //
  if (paths_d.isEmpty()) {
    return true;
  }

  // determine the beam threshold
  //
  beam_threshold = max_score_d - beam_a;

  // loop over all paths in the partial list
  //
  boolean more_paths = paths_d.gotoFirst();
  
  while (more_paths) {

    // get current path from the list
    //
    nnode = paths_d.getCurr();
    
    if (nnode == (NBestNode*)NULL) {
      return Error::handle(name(), L"prune", Error::ARG, __FILE__, __LINE__);
    }
    
    // if this node falls below threshold, prune it
    //
    if (nnode->getPathScore() < beam_threshold) {

      // is this the last path in the list?
      //
      if (paths_d.isLast()) {
	more_paths = false;
      }
      
      // remove the node from the list
      //
      paths_d.remove(node);

      // free allocated memory
      //
      delete node;
      node = (NBestNode*)NULL;
    }

    else {

      // move to the next path in the list
      //
      more_paths = paths_d.gotoNext();
    }
  }

  // prune to limit the maximum number of paths in the list
  //
  while (paths_d.length() > max_paths_a) {

    // remove the node from the list
    //
    paths_d.removeLast(node);

    // free allocated memory
    //
    delete node;
    node = (NBestNode*)NULL;    
  }
  
  // exit gracefully
  //
  return true;    
}
