// file: lat_subp_0.cc
//

// isip include files
//
#include "lattice.h"
#include "lattice_constants.h"

// method: find_subpaths_cc
//
// arguments:
//  char_1*& search_words: (input) sequence of words to search for
//  Hash_table* lexicon: (input) used to make sure phones are valid
//  int_4& num_results: (output) number of results found
//  Link_list*& results: (output) linked list of Subpath_match objects representing
//                       matching subpaths.
//
// return: a logical_1 indicating status
//
// this method searches for all subpath matches corresponding to the 
// word sequence in search_words. the start and end times of each match
// are returned, as well as the scores of each match
//
logical_1 Lattice::find_subpaths_cc(char_1* search_words_a,
				    Hash_table* lexicon_a,
				    int_4& num_results_a,
				    Link_list*& results_a) {


  // memory manager
  //
  Memory_manager* manager = Link_list::get_manager_cc();

  if ((lexicon_a->hash_lookup_cc(WRD_NULL)) != (Hash_cell*)NULL) {
    null_word_d =
      (Word*)((lexicon_a->hash_lookup_cc(WRD_NULL))->get_item_cc());
  }

  // parse search words and create a list of Word* pointers
  //

  // if no search word string is found, exit with error
  //
  if (search_words_a == (char_1*)NULL) {
    error_handler_cc((char_1*)"lattice_find_subpaths_cc",
		     (char_1*)"No search words provided");
  }

  // copy the utterance string before tokenizing and work on the copy
  //
  char_1* str = (char_1*)NULL;
  char_1* utt = new char_1[strlen((char*)search_words_a) + 1];
  strcpy((char*)utt, (char*)search_words_a);

  // store the word transcription strings in a linked list for starters
  //
  Link_list* str_list = new Link_list();

  // tokenize the utterance string and place each token in the list
  //
  int_4 idx = (int_4)0;
  str = (char_1*)strtok((char*)utt, ISIP_STRING_SPACE);

  // loop over the tokens and place them in a list
  //
  while (str != (char_1*)NULL) {
    str_list->insert_cc(str);
    idx++;
    str = (char_1*)strtok(NULL, ISIP_STRING_SPACE);
  }
  
  // create the word list
  //
  Word** search_words = new Word*[idx];
  
  // now generate the corresponding word models for each word
  //
  int_4 num_words = idx;
  idx = (int_4)0;
  Hash_cell* hcell = (Hash_cell*)NULL;
  Link_node* nd;
  for (nd = str_list->get_head_cc(); nd != (Link_node*)NULL;
       nd = nd->get_next_cc()) {

    // get the string
    //
    str = (char_1*)(nd->get_item_cc());

    // get the corresponding word
    //
    hcell = lexicon_a->hash_lookup_cc(str);
    if (hcell == (Hash_cell*)NULL) {
      error_handler_cc((char_1*)"lattice_find_subpaths_cc",
		       (char_1*)"word not found in lexicon");
    }
    search_words[idx++] = (Word*)(hcell->get_item_cc());

  } // end for loop

  // finished parsing words and verifying that they exist in the lexicon.
  // now we construct a list of paths which match this word sequence.
  //

  // Linked list of matching paths
  //
  Link_list* match_list = new Link_list();

  Hash_cell** node_cells = lnode_d->get_cells_cc();
  int_4 num_cells = lnode_d->get_size_cc();
  int_4 count = 0;
  int_4 num_matches = 0;
  
  // loop over all nodes in the hash table and search for a path originating
  // from each node that matches the word sequence
  //
  for (int_4 i = 0; i < num_cells; i++) {
    Hash_cell* cell = node_cells[i];

    while (cell != (Hash_cell*)NULL) {
      count++;
      // create search path starting at the current node
      //
      Lattice_path* search_path = manager->new_lpath_cc();
      //Lattice_path* search_path = new Lattice_path();
      Lattice_node* temp_node = (Lattice_node*)cell->get_item_cc();
      search_path->set_curr_cc(temp_node);

      Lattice_path* match_path = (Lattice_path*)NULL;

      // make sure this isn't an end node
      //
      if (temp_node->get_next_nodes_cc() != (Link_list*)NULL) {

	// check for a matching subpath at this node
	//
	correct_subpath_cc(search_path, search_words, match_path, num_words);
      }

      // if a match is found, add it to the match list
      //
      if (match_path != (Lattice_path*)NULL) {
	match_list->insert_cc(match_path);
	num_matches++;
      }
      else {
	//delete search_path;
	manager->delete_cc(search_path);
      }
      cell = cell->get_next_cc();
    }

  }

  // keep up with index
  // 
  idx = 0;

  // we need to go to the node at the beginning of the path and get the node(s)
  // before it in the lattice. this is necessary to get the left time boundary
  // of the first node since the frame_index object in the Lattice_node class
  // defines when the node ends. if there are more than one nodes before the
  // first node, we choose the node with the highest frame index.
  //
  for (Link_node* lnd = match_list->get_head_cc();
       lnd != (Link_node*)NULL; lnd = lnd->get_next_cc()) {  
    
    // match properties
    //
    int_4 start_time = 0;
    int_4 end_time = 0;
    float_4 score = 0;

    // get this path
    //
    Lattice_path* lpath = (Lattice_path*)lnd->get_item_cc();

    // get the frame index for the current (last) node and set it in the
    // end_times_a array
    //
    end_time = lpath->get_curr_cc()->get_frame_index_cc();

    // backtrack to the beginning
    //
    while (lpath->get_prev_cc() != (Lattice_path*)NULL) {
      lpath = lpath->get_prev_cc();
    }

    // get the list of previous nodes at the beginning of this path
    //
    Link_list* prev_list = lpath->get_curr_cc()->get_prev_nodes_cc();

    // find the node with the highest acoustic score
    // 
    float_4 max_score = 0;
    int_4 max_frame_idx = 0;
    Lattice_node* max_idx_node = (Lattice_node*)NULL;

    for (Link_node* lnd2 = prev_list->get_head_cc();
	 lnd2 != (Link_node*)NULL; lnd2 = lnd2->get_next_cc()) {  

      Lattice_node* prev_node = (Lattice_node*)lnd2->get_item_cc();

      // gets a little complicated here. a from this node, we need to find the index
      // of the emenating node which leads to the current node (lpath->get_curr_cc). 
      // once we have this index, we can get the corresponding score
      //
      Link_list* temp_next_nodes = prev_node->get_next_nodes_cc();
      Link_node* lnd3 = temp_next_nodes->get_head_cc();
      Lattice_node* temp_next_node = (Lattice_node*)lnd3->get_item_cc();
      int_4 temp_next_idx = 0;

      while (temp_next_node != (Lattice_node*)lpath->get_curr_cc()) {
	temp_next_idx++;
	lnd3 = lnd3->get_next_cc();
	temp_next_node = (Lattice_node*)lnd3->get_item_cc();
      }

      float_4* these_scores = prev_node->get_ac_scores_cc();

      if (these_scores[temp_next_idx] >= max_score || max_score == 0) {
	max_frame_idx = prev_node->get_frame_index_cc();
	max_score = these_scores[temp_next_idx];
	max_idx_node = prev_node;
      }
    }

    // set start time for path
    //
    start_time = max_frame_idx;

    // create new Lattice_path object, set its current node to the
    // node with the highest frame index, and set the prev pointer
    // of the last node of the path to this new path
    //
    Lattice_path* prev_path = manager->new_lpath_cc();
    //Lattice_path* prev_path = new Lattice_path();
    prev_path->set_curr_cc(max_idx_node);
    lpath->set_prev_cc(prev_path);
      
    // get the original path (end of the path) 
    //
    lpath = (Lattice_path*)lnd->get_item_cc();

    // compute score of path
    //
    float_4 ac_score = 0;
    float_4 lm_score = 0;

    lpath->path_likelihood_cc( lm_scale_d, word_penalty_d, ac_score, lm_score);

    // normalize by number of frames. NOTE: a better method may be required
    //
    score = ac_score/(end_time - start_time);


    // is the likelihood score valid?
    //
    if (score <= 0) {
      // create Subpath_match object and initialize
      //
      Subpath_match* path_match = new Subpath_match(start_time, end_time, score);
      
      // add this to the results link list if the score is valid
      //
      results_a->insert_cc(path_match);

      // increment index
      //
      idx++;

    }

    // remove lattice path from memory
    //
    while (lpath != (Lattice_path*) NULL) {
      Lattice_path* temp_path = lpath;
      lpath = lpath->get_prev_cc();
      //delete temp_path;
      manager->delete_cc(temp_path);

    }


  }

  // set number of results
  //
  num_results_a = idx;

  // free memory
  //
  delete [] utt;
  utt = (char_1*)NULL;
  delete str_list;
  str_list = (Link_list*)NULL;
  delete match_list;
  match_list = (Link_list*)NULL;  
  delete [] search_words;
  search_words = NULL;
  
  // exit gracefully
  //
  return ISIP_TRUE;
}
