// file: $isip/class/search/SearchNode/snod_06.cc
// version: $Id: snod_06.cc 9347 2003-11-26 20:46:15Z alphonso $
//

// isip include files
//
#include "SearchNode.h"
#include <Trace.h>
#include <Instance.h>

// method: addTrace
//
// arguments:
//  Trace* trace: (input) the trace to add to the trace list. we do Viterbi
//                pruning at this point
//  long timestamp: (input) the timestamp for the incoming trace
//  Trace* ptr: (input) pointer to existing trace with the same history
//
// return: a boolean indicating status
//
// this method adds the trace to the trace list if it is the highest scoring
// trace at that point
//
boolean SearchNode::addTrace(Trace* trace_a, long timestamp_a,
			     Trace*& ptr_a) {

  // declare local variables
  //
  boolean status = true;
  
  if (this == &DiGraph<SearchNode>::TERM_OBJ ||
      this == &DiGraph<SearchNode>::START_OBJ ||
      isDummyNode()) {    
    return status;
  }

  // output the debugging information
  //
  if (debug_level_d == Integral::ALL) {
    String output(L"attempting to adding trace: ");
    output.concat(trace_a);
    output.concat(L" to snode [");
    SearchSymbol symbol;
    getSymbol(symbol);
    output.concat(symbol);
    output.concat(L"] old timestamp:");
    output.concat(timestamp_d);
    output.concat(L" new timestamp:");
    output.concat(timestamp_a);
    output.concat(L" num traces:");
    output.concat(traces_d.length());
    Console::put(output);
  }
  
  // check the timestamp
  //
  if (timestamp_a != timestamp_d) {
    traces_d.clear();
    score_d = Trace::INACTIVE_SCORE;
    timestamp_d = timestamp_a;
  }

  // do Viterbi pruning - at this time, we only handle one-best Viterbi
  //
  if (trace_a->getTraceMode() == Trace::TRAIN) {
    status = addWithBaumWelch(trace_a, ptr_a);
  }
  else {
    status = addWithViterbi(trace_a, ptr_a);
  }
  return status;
}

// method: mergeTrace
//
// arguments:
//  Trace* trace: (input) the trace to add to the trace list. we do Viterbi
//                pruning at this point
//  long timestamp: (input) the timestamp for the incoming trace
//  Trace* ptr: (input) pointer to existing trace with the same history
//  long nsymbol_index: (input) nsymbol index
//  long nsymbol_order: (input) nsymbol order
//
// return: a boolean indicating status
//
// this method adds the trace to the trace list if it is the highest scoring
// trace at that point
//
boolean SearchNode::mergeTrace(Trace* trace_a, long timestamp_a,
			       Trace*& ptr_a, long nsymbol_index_a,
			       long nsymbol_order_a) {

  if (this == &DiGraph<SearchNode>::TERM_OBJ ||
      this == &DiGraph<SearchNode>::START_OBJ ||
      isDummyNode()) {    
    return true;
  }

  // output the debugging information
  //
  if (debug_level_d == Integral::ALL) {
    String output(L"attempting to adding trace: ");
    output.concat(trace_a);
    output.concat(L" to snode [");
    SearchSymbol symbol;
    getSymbol(symbol);
    output.concat(symbol);
    output.concat(L"] old timestamp:");
    output.concat(timestamp_d);
    output.concat(L" new timestamp:");
    output.concat(timestamp_a);
    output.concat(L" num traces:");
    output.concat(traces_d.length());
    Console::put(output);
  }
  
  // check the timestamp
  //
  if (timestamp_a != timestamp_d) {
    traces_d.clear();
    score_d = Trace::INACTIVE_SCORE;
    timestamp_d = timestamp_a;
  }


  // if there are no traces here, then just add this one
  //
  if (traces_d.isEmpty()) {
    traces_d.insertFirst(trace_a);
    return true;
  }

  // a flag showing that if need to add a trace
  //
  boolean add_flag = false;
  
  // see if any other trace with the same history exists at this point
  // right now we loop over all traces at this point and compare histories
  //
  for (boolean more_traces = traces_d.gotoFirst(); more_traces;
       more_traces = traces_d.gotoNext()) {
    
    Trace* curr_trace = traces_d.getCurr();
    
    if (trace_a == curr_trace) {
      return Error::handle(name(), L"mergeTrace", Error::ARG, __FILE__, __LINE__);
    }
    
    // make sure the timestamps match even before we compare them
    //
    boolean are_same = true;

    // compare the traces
    //
    if ((curr_trace->getFrame() != trace_a->getFrame()) ||
	(curr_trace->getSymbol() != trace_a->getSymbol()) ||
	(curr_trace->getHistory() != trace_a->getHistory())) {
      are_same = false;
    }

    // compare the nsymbols
    //
    Context** context1 = trace_a->getNSymbol();
    Context** context2 = curr_trace->getNSymbol();	      

    for (long index=1; index < nsymbol_order_a; index++) {
      if ((*context1[nsymbol_index_a])(-index) !=
	  (*context2[nsymbol_index_a])(-index)) {
	are_same = false;
	break;
      }  
    }
    
    if (are_same) {
      
      add_flag = true;
      
      // set the pointer of the trace that already exists
      //
      ptr_a = curr_trace;
      
      if (trace_a->getScore() > curr_trace->getScore()) {
	
	traces_d.remove(curr_trace);
	
	// delete the bad trace if possible
	//
	if (curr_trace->getRefCount() < 1) {
	  curr_trace->setActive(false);
	}
	
	// add the new trace
	//
	traces_d.insertFirst(trace_a);
	return true;
      }
      
      else {
	
	trace_a->setActive(false);
	return false;
      }
    }
  }
  
  // no trace is the same as the one to be added, add this one
  //
  if (!add_flag) {
    traces_d.insert(trace_a);
  }
  
  // exit gracefully
  //
  return true;
}

// method: mergeInstance
//
// arguments:
//  Instance* instance: (input) the instance to add to the instance list
//  long timestamp: (input) the timestamp for the incoming instance
//  Instance* ptr: (input) pointer to existing instance with the same history
//  long nsymbol_index: (input) nsymbol index
//  long nsymbol_order: (input) nsymbol order
//
// return: a boolean indicating status
//
// this method adds the instance to the instance list if it is the highest
// scoring instance at that point
//
boolean SearchNode::mergeInstance(Instance* instance_a, long timestamp_a,
				  Instance*& ptr_a, long nsymbol_index_a,
				  long nsymbol_order_a) {

  if (this == &DiGraph<SearchNode>::TERM_OBJ ||
      this == &DiGraph<SearchNode>::START_OBJ ||
      isDummyNode()) {    
    return true;
  }

  // output the debugging information
  //
  if (debug_level_d == Integral::ALL) {
    String output(L"attempting to adding instance: ");
    output.concat(instance_a);
    output.concat(L" to snode [");
    SearchSymbol symbol;
    getSymbol(symbol);
    output.concat(symbol);
    output.concat(L"] old timestamp:");
    output.concat(timestamp_d);
    output.concat(L" new timestamp:");
    output.concat(timestamp_a);
    output.concat(L" num instances:");
    output.concat(instances_d.length());
    Console::put(output);
  }
  
  // check the timestamp
  //
  if (timestamp_a != timestamp_d) {
    instances_d.clear();
    score_d = Instance::INACTIVE_SCORE;
    timestamp_d = timestamp_a;
  }

  // if there are no instances here, then just add this one
  //
  if (instances_d.isEmpty()) {
    instances_d.insertFirst(instance_a);
    return true;
  }

  // a flag showing that if need to add a instance
  //
  boolean add_flag = false;
  
  // see if any other instance with the same history exists at this point
  // right now we loop over all instances at this point and compare histories
  //
  for (boolean more_instances = instances_d.gotoFirst(); more_instances;
       more_instances = instances_d.gotoNext()) {
    
    Instance* curr_instance = instances_d.getCurr();
    
    if (instance_a == curr_instance) {
      return Error::handle(name(), L"mergeInstance", Error::ARG, __FILE__, __LINE__);
    }
    
    // make sure the timestamps match even before we compare them
    //
    boolean are_same = true;

    // compare the instances
    //
    if ((curr_instance->getFrame() != instance_a->getFrame()) ||
	(curr_instance->getSymbol() != instance_a->getSymbol()) ||
	(curr_instance->getHistory() != instance_a->getHistory()) ||
	(curr_instance->getSymbolStack() != instance_a->getSymbolStack())) {
      are_same = false;
    }

    // compare the nsymbols
    //
    Context** context1 = instance_a->getNSymbol();
    Context** context2 = curr_instance->getNSymbol();	      

    for (long index=1; index < nsymbol_order_a; index++) {
      if ((*context1[nsymbol_index_a])(-index) !=
	  (*context2[nsymbol_index_a])(-index)) {
	are_same = false;
	break;
      }  
    }
    
    if (are_same) {
      
      add_flag = true;
      
      // set the pointer of the instance that already exists
      //
      ptr_a = curr_instance;
      
      if (instance_a->getScore() > curr_instance->getScore()) {
	
	instances_d.remove(curr_instance);
	
	// delete the bad instance if possible
	//
	if (curr_instance->getRefCount() < 1) {
	  curr_instance->setActive(false);
	}
	
	// add the new instance
	//
	instances_d.insertFirst(instance_a);
	return true;
      }
      
      else {
	
	instance_a->setActive(false);
	return false;
      }
    }
  }
  
  // no instance is the same as the one to be added, add this one
  //
  if (!add_flag) {
    instances_d.insert(instance_a);
  }
  
  // exit gracefully
  //
  return true;
}

// method: addWithViterbi
//
// arguments:
//  Trace* trace: (input) the trace to add to the trace list. we do Viterbi
//                pruning at this point
//  Trace* ptr: (input) pointer to the existing trace with the same history
//
// return: a boolean indicating status
//
// this method adds the trace to the trace list if it is the highest scoring
// trace at that point
//
boolean SearchNode::addWithViterbi(Trace* trace_a, Trace*& ptr_a) {
  
  // if there are no traces here, then just add this one
  //
  if (traces_d.isEmpty()) {
    traces_d.insertFirst(trace_a);
    return true;
  }
  
  // a flag showing that if need to add a trace
  //
  boolean add_flag = false;
  ulong input_trace_frame_index = trace_a->getFrame();
  
  // see if any other trace with the same history exists at this point
  // right now we loop over all traces at this point and compare histories
  //
  for (boolean more_traces = traces_d.gotoFirst(); more_traces;
       more_traces = traces_d.gotoNext()) {
    
    Trace* tmp = traces_d.getCurr();
    
    if (trace_a == tmp) {      
      return Error::handle(name(), L"addWithViterbi", Error::ARG, __FILE__, __LINE__);
    }
    
    // make sure the timestamps match even before we compare them
    //      
    if (input_trace_frame_index == tmp->getFrame()) {
      if (trace_a->eq(*tmp)) {
	
	add_flag = true;
	
	// set the pointer of the trace that already exists
	//
	ptr_a = tmp;
	
	if (trace_a->getScore() > tmp->getScore()) {
	  
	  traces_d.remove(tmp);
	  
	  // delete the bad trace if possible
	  //
	  if (tmp->getRefCount() < 1) {
	    tmp->setActive(false);
	  }
	  
	  // add the new trace
	  //
	  traces_d.insertFirst(trace_a);
	  return true;
	}
	
	else {
	  
	  trace_a->setActive(false);
	  return false;
	}
      }
    }
    
    // there should not be a trace with an old time stamp - time synchronous
    //
    else {
      return Error::handle(name(), L"addWithViterbi", Error::ARG, __FILE__, __LINE__);
    }      
  }
  
  // no trace is the same as the one to be added, add this one
  //
  if (!add_flag) {
    traces_d.insert(trace_a);
  }
  
  // exit gracefully
  //
  return true;
}

// method: addWithBaumWelch
//
// arguments:
//  Trace* trace: (input) the trace to add to the trace list.
//  Trace* ptr: (input) pointer to the existing trace with the same history
//
// return: a boolean indicating status
//
// this method adds the trace to the trace list if it is the highest scoring
// trace at that point
//
boolean SearchNode::addWithBaumWelch(Trace* trace_a, Trace*& ptr_a) {

  // if there are no traces here, then just add this one
  //
  if (traces_d.isEmpty()) {
    traces_d.insertFirst(trace_a);
    return true;
  }

  // a flag showing that if need to add a trace
  //
  boolean add_flag = false;
  ulong input_trace_frame_index = trace_a->getFrame();
  
  // see if any other trace with the same history exists at this point
  // right now we loop over all traces at this point and compare histories
  //
  for (boolean more_traces = traces_d.gotoFirst(); more_traces;
       more_traces = traces_d.gotoNext()) {

    Trace* tmp = traces_d.getCurr();
   
    if (trace_a == tmp) {
      return Error::handle(name(), L"addWithBaumWelch", Error::ARG, __FILE__, __LINE__);
    }
    
    // make sure the timestamps match even before we compare them
    //
    if (input_trace_frame_index == tmp->getFrame()) {
      if (trace_a->eq(*tmp)) {
	
	add_flag = true;
	
	// set the pointer of the trace that already exists
	//
	ptr_a = tmp;
	
	// signal to delete the new incoming trace
	//
	return false;
      }
    }
    
    else {
      return Error::handle(name(), L"addWithBaumWelch", Error::ARG, __FILE__, __LINE__);
    }
  }

  // no trace is the same as the one to be added, add this one
  //
  if (!add_flag) {
    traces_d.insert(trace_a);
  }
  
  // exit gracefully
  //
  return true;  
}

// method: clearTraceList
//
// arguments: none
//
// return: a boolean indicating status
//
// this method clears the trace list (typically happens before every frame)
//
boolean SearchNode::clearTraceList() {

  timestamp_d = -1;
  return traces_d.clear();
}

// method: removeTrace
//
// arguments:
//  Trace* trace: (input) trace to remove from trace list of this node
//
// return: a boolean indicating status
//
// this method removes trace from the trace list of the search node
//
boolean SearchNode::removeTrace(Trace* trace_a) {

  // find the trace and remove it
  //
  if (traces_d.find(trace_a)) {
    traces_d.remove();
    return true;
  }  
  return false;
}

// method: addInstance
//
// arguments:
//  Instance* instance: (input) the instance to add to the instance list.
//                      we do Viterbi pruning at this point
//  long timestamp: (input) the timestamp for the incoming instance
//  Instance* ptr: (input) pointer to existing instance with the same history
//
// return: a boolean indicating status
//
// this method adds the instance to the instance list if it is the highest
// scoring instance at that point
//
boolean SearchNode::addInstance(Instance* instance_a,
				long timestamp_a, Instance*& ptr_a) {

  // declare local variables
  //
  boolean status = true;
  
  if (this == &DiGraph<SearchNode>::TERM_OBJ ||
      this == &DiGraph<SearchNode>::START_OBJ ||
      isDummyNode()) {    
    return status;
  }

  // output the debugging information
  //
  if (debug_level_d == Integral::ALL) {
    String output(L"attempting to adding instance: ");
    output.concat(instance_a);
    output.concat(L" to snode [");
    SearchSymbol symbol;
    getSymbol(symbol);
    output.concat(symbol);
    output.concat(L"] old timestamp:");
    output.concat(timestamp_d);
    output.concat(L" new timestamp:");
    output.concat(timestamp_a);
    output.concat(L" num instances:");
    output.concat(instances_d.length());
    Console::put(output);
  }
  
  // check the timestamp
  //
  if (timestamp_a != timestamp_d) {
    instances_d.clear();
    score_d = Instance::INACTIVE_SCORE;
    timestamp_d = timestamp_a;
  }

  // do Viterbi pruning - at this time, we only handle one-best Viterbi
  //
  if (instance_a->getInstanceMode() == Instance::TRAIN) {
    status = addWithBaumWelch(instance_a, ptr_a);
  }
  else {
    status = addWithViterbi(instance_a, ptr_a);
  }
  return status;
}

// method: addWithViterbi
//
// arguments:
//  Instance* instance: (input) the instance to add to the instance list.
//                      we do Viterbi pruning at this point
//  Instance* ptr: (input) pointer to existing instance with the same history
//
// return: a boolean indicating status
//
// this method adds the instance to the instance list if it is the highest
// scoring instance at that point
//
boolean SearchNode::addWithViterbi(Instance* instance_a, Instance*& ptr_a) {

  // if there are no instances here, then just add this one
  //
  if (instances_d.isEmpty()) {
    instances_d.insertFirst(instance_a);
    return true;
  }

  // a flag showing that if need to add a instance
  //
  boolean add_flag = false;
  ulong input_instance_frame_index = instance_a->getFrame();
  
  // see if any other instance with the same history exists at this point
  // right now we loop over all instances at this point and compare histories
  //
  for (boolean more_instances = instances_d.gotoFirst(); more_instances;
       more_instances = instances_d.gotoNext()) {

    Instance* tmp = instances_d.getCurr();
   
    if (instance_a == tmp) {
      
      return Error::handle(name(), L"addWithViterbi-- address same", SearchNode::ERR, __FILE__, __LINE__);
    }
    
    // make sure the timestamps match even before we compare them
    //
    if (input_instance_frame_index == tmp->getFrame()) {
      if (instance_a->eq(*tmp)) {
	
	add_flag = true;

	// set the pointer of the trace that already exists
	//
	ptr_a = tmp;
	
	if (instance_a->getScore() > tmp->getScore()) {
	  instances_d.remove(tmp);
	  
	  // delete the bad instance if possible
	  //
	  if (tmp->getRefCount() < 1) {
	    tmp->setActive(false);
	  }
	  
	  // add the new instance
	  //
	  instances_d.insertFirst(instance_a);
	  return true;
	}
	else {
	  instance_a->setActive(false);
	  return false;
	}
      }
    }
    else {
      return Error::handle(name(), L"addWithViterbi-- oldtime instance in node", Error::ARG, __FILE__, __LINE__, Error::WARNING);
    }
  }

  // no instance is the same as the one to be added, add this one
  //
  if (!add_flag) {
    instances_d.insert(instance_a);
  }
  
  // exit gracefully
  //
  return true;  
}

// method: addWithBaumWelch
//
// arguments:
//  Instance* instance: (input) the instance to add to the instance list.
//  Instance* ptr: (input) pointer to existing instance with the same history
//
// return: a boolean indicating status
//
// this method adds the instance to the instance list without prunning
//
boolean SearchNode::addWithBaumWelch(Instance* instance_a, Instance*& ptr_a) {

  // if there are no instances here, then just add this one
  //
  if (instances_d.isEmpty()) {
    instances_d.insertFirst(instance_a);
    return true;
  }

  // a flag showing that if need to add a instance
  //
  boolean add_flag = false;
  ulong input_instance_frame_index = instance_a->getFrame();
  
  // see if any other instance with the same history exists at this point
  // right now we loop over all instances at this point and compare histories
  //
  for (boolean more_instances = instances_d.gotoFirst(); more_instances;
       more_instances = instances_d.gotoNext()) {

    Instance* tmp = instances_d.getCurr();
   
    if (instance_a == tmp) {
      return Error::handle(name(), L"addWithBaumWelch", Error::ARG, __FILE__, __LINE__);
    }
    
    // make sure the timestamps match even before we compare them
    //
    if (input_instance_frame_index == tmp->getFrame()) {
      if (instance_a->eq(*tmp)) {
	
	add_flag = true;

	// set the pointer of the instance that already exists
	//
	ptr_a = tmp;
	
	// signal to delete the new incoming instance
	//
	return false;
      }
    }
    else {
      return Error::handle(name(), L"addWithBaumWelch", Error::ARG, __FILE__, __LINE__);
    }
  }

  // no instance is the same as the one to be added, add this one
  //
  if (!add_flag) {
    instances_d.insert(instance_a);
  }
  
  // exit gracefully
  //
  return true;  
}

// method: clearInstanceList
//
// arguments: none
//
// return: a boolean indicating status
//
// this method clears the instance list (typically happens before every frame)
//
boolean SearchNode::clearInstanceList() {

  timestamp_d = -1;
  return instances_d.clear();
}

// method: removeInstance
//
// arguments:
//  Instance* instance: (input) instance to remove from instance list
//
// return: a boolean indicating status
//
// this method removes instance from the instance list of the search node
//
boolean SearchNode::removeInstance(Instance* instance_a) {

  // find the instance and remove it
  //
  for (boolean more_instances = instances_d.gotoFirst(); more_instances;
       more_instances = instances_d.gotoNext()) {
    Instance* tmp = instances_d.getCurr();
    if ( tmp == instance_a ){
      instances_d.remove();
      return true;
    }
  }

  return false;
}
