// file: $isip/class/sp/Recipe/rcp_05.cc
// version: $Id: rcp_05.cc 9061 2003-04-01 02:06:52Z duncan $
//

// isip include files
//
#include "Recipe.h"
#include <FrontEndBase.h>
#include <FtrBuffer.h>
#include <Sdb.h>
#include <CoefficientLabel.h>
#include <AlgorithmContainer.h>

// method: findComponent
//
// arguments:
//  SingleLinkedList<Component>& comp: (output) the component we find
//  const FrontEndBase& fend: (input) the FrontEndBase we use
//  const String& coef_name: (input) coefficient name
//  
// return: a boolean value indicating status
//
// determine the sequence of processes to apply from the available inputs,
// desired output, and graph of recipes.
//
boolean Recipe::findComponent(SingleLinkedList<Component>& comp_a,
			      const FrontEndBase& fend_a,
			      const String& coef_name_a) {

  // check the arguments
  //
  if (coef_name_a.length() < 1) {
    return Error::handle(name(), L"findComponent", ERR, __FILE__, __LINE__);
  }

  // create an input vector
  //
  Vector<String> inputs(1);
  inputs(0).assign(coef_name_a);

  // find the component
  //
  return findComponent(comp_a, fend_a, inputs);
}

// method: findComponent
//
// arguments:
//  SingleLinkedList<Component>& comp: (output) the recipe we find
//  const FrontEndBase& fend: (input) the FrontEndBase we use
//  Vector<String>& inputs: (input) coefficients we need
//  
// return: a boolean value indicating status
//
// determine from the available inputs, desired output, and graph of
// recipes a sequence of processes to apply
//
boolean Recipe::findComponent(SingleLinkedList<Component>& comp_a,
			      const FrontEndBase& fend_a,
			      Vector<String>& inputs_a) {

  // check the debug level
  //
  if (debug_level_d >= Integral::ALL) {
    recipe_d.debug(L"recipe");
  }

  // set all nodes to white
  //
  recipe_d.setColor(Integral::WHITE);

  // visit the name of the output
  //
  for (long i = inputs_a.length() - 1; i >= 0; i--) {
    if (!reverseVisitDfs(fend_a, inputs_a(i))) {
      comp_a.debug(L"comp_a");
      fend_a.debug(L"fend_a");
      inputs_a.debug(L"inputs_a");
      return Error::handle(name(), L"findComponent",
			   ERR_NOPATH, __FILE__, __LINE__);
    }
  }

  // invert the color of all nodes. right now only black nodes are
  // needed to produce the output for the given inputs.
  //
  for (boolean more_nodes = recipe_d.gotoFirst();
       more_nodes;
       more_nodes = recipe_d.gotoNext()) {

    GraphVertex<Component>* v = (GraphVertex<Component>*)recipe_d.getCurr();
    if (recipe_d.getColor(v) == Integral::BLACK) {
      recipe_d.setColor(v, Integral::WHITE);
    }
    else {
      recipe_d.setColor(v, Integral::BLACK);
    }
  }
  
  // perform a partial topological sort of the Components
  //
  return recipe_d.topologicalSort(comp_a, true);
}

// method: reverseVisitDfs
//
// arguments:
//  const FrontEndBase& fend: (input) the FrontEndBase we use
//  const String& name: (input) needed coefficient name
//  
// return: a boolean value indicating status: can we produce the named feature?
//
// determine a path back to all input names from the specified
// name. mark all nodes along this path as black, mark intermeadiate
// nodes as gray. this allows for a depth-first-search, though it is
// backwards through the graph so less efficient.
//
boolean Recipe::reverseVisitDfs(const FrontEndBase& fend_a,
				const String& name_a) {

  // if the name is an available input, of course it can be found
  //
  if (fend_a.isNameInput(name_a)) {
    return true;
  }

  // local variables
  //
  boolean found = false;
  boolean unavailable = false;

  // loop over all vertices
  //
  for (boolean more_nodes = recipe_d.gotoFirst();
       more_nodes;
       more_nodes = recipe_d.gotoNext()) {

    GraphVertex<Component>* v = (GraphVertex<Component>*)recipe_d.getCurr();

    // dfs lets us skip over gray vertices
    //
    if (recipe_d.getColor(v) == Integral::GREY) {
      continue;
    }

    // is the output of this vertex our desired node?
    //
    if (v->getItem()->getOutputName().ne(name_a)) {
      continue;
    }

    // if this is a black node then it already has a path back, good news
    //
    if (recipe_d.getColor(v) == Integral::BLACK) {

      // make sure we don't have multiple paths
      //
      if (found) {
	return Error::handle(name(), L"reverseVisitDfs",
			     ERR_MPATH, __FILE__, __LINE__);
      }
      found = true;
    }

    // if this is a white node then we should evaluate it
    //
    else {

      // mark the node as seen but not complete
      //
      recipe_d.setColor(v, Integral::GREY);

      // get the needed input names
      //
      //      Vector<String> input_names;
      //      v->getItem()->getPreName(input_names);
      unavailable = false;

      // loop through each input
      //
      for (long i = 0; i < v->getItem()->getNumInputs(); i++) {
	if (!reverseVisitDfs(fend_a, v->getItem()->getInputName(i))) {
	  unavailable = true;
	  break;
	}
      }

      // if all paths back were available, this is a good node
      //
      if (!unavailable) {
	recipe_d.setColor(v, Integral::BLACK);
	
	// make sure we don't have multiple paths
	//
	if (found) {
	  return Error::handle(name(), L"reverseVisitDfs", ERR_MPATH,
			       __FILE__, __LINE__);
	}
	found = true;
      }
    }
  }

  // return if the path back to input was found
  //
  return found;
}

// method: delayComponent
//
// arguments:
//  Vector< SingleLinkedList<Component> >& dcomp: (output) delayed list
//  FtrBuffer& buf: (output) feature buffer
//  FrontEndBase& fend: (input) the FrontEndBase we use
//  SingleLinkedList<Component>& temp_list: (input) list to delay
//  
// return: a boolean value indicating status
//
// determine from the available inputs, desired output, and graph of
// recipes a sequence of processes to apply
//
boolean Recipe::delayComponent(Vector< SingleLinkedList<Component> >& dcomp_a,
			       FtrBuffer& buf_a,
			       FrontEndBase& fend_a,
			       SingleLinkedList<Component>& temp_list_a) {

  // loop over the input list
  //
  long lag = 0;

  while (!temp_list_a.isEmpty()) {

    // set the length
    //
    if (lag >= dcomp_a.length()) {
      dcomp_a.setLength(lag + 1);
    }
    
    // loop over all recipes in the list:
    //  the incremement is at the bottom of the code
    //
    for (boolean more_nodes = temp_list_a.gotoFirst();
	 more_nodes; ) {
      
      // declare some temporary variables
      //
      Component* algo = temp_list_a.getCurr();
      
      boolean found_algo = true;
      boolean found_input;
      Long needed_lag = 0;
      
      // get the inputs for this algorithm
      //
      long num_inputs = algo->getNumInputs();
      
      // loop over all inputs for this Component
      //
      for (long i = 0; found_algo && (i < num_inputs); i++) {
	
	found_input = false;
	long algo_lead_pad = (long)Integral::max(0, algo->getLeadingPad());

	// if the coefficient is an input, it has no lag itself
	//
	if (fend_a.isNameInput(algo->getInputName(i))
	    && ((algo->getInputOffset(i) + algo_lead_pad) <= lag)) {

	  if (debug_level_d >= Integral::DETAILED) {
	    String str;
	    str.assign(algo->getOutputName());
	    str.concat(L":");
	    str.concat(L"this is an input");
	    algo->getInputName(i).debug((unichar*)str);
	  }
	  found_input = true;
	  needed_lag.max(algo->getInputOffset(i) + algo_lead_pad);
	  continue;
	}

	// if the coefficient is not an input, determine what lag it
	// has. we are interested in finding the input coefficient
	// with the largest lag. to find the lag of the coefficient we
	// will loop through the vector and determine at what point
	// the coefficient is output (ie, a postName for a Component).
	//
	for (long j = 0; (!found_input) && (j <= lag); j++) {

	  // sanity check
	  //
	  if (j >= dcomp_a.length()) {
	    return Error::handle(name(), L"delayComponent",
				 ERR, __FILE__, __LINE__);
	  }


	  // loop through all Components at this delay point to find if
	  // our target coefficient is the output
	  //
	  for (boolean m2 = dcomp_a(j).gotoFirst(); m2;
	       m2 = dcomp_a(j).gotoNext()) {
	    
	    // create a pointer to a Component
	    //
	    Component* j_algo = dcomp_a(j).getCurr();
	    
	    // get the output name
	    //
	    if (algo->getInputName(i).eq(j_algo->getOutputName()) 
		&& ((algo->getInputOffset(i) + algo_lead_pad) <= (lag - j))) {

	      found_input = true;
	      needed_lag.max(lag);
	    }
	  }
	}
	if (!found_input) {
	  found_algo = false;
	}
      }
      
      // move algorithm from temp_list to proper position
      //
      if (found_algo) {

	// see if there are more nodes
	//
	if ((algo = temp_list_a.getNext()) != (Component*)NULL) {
	  more_nodes = true;
	}
	else {
	  more_nodes = false;
	}
	
	// remove the component
	//
	temp_list_a.remove(algo);

	if (debug_level_d >= Integral::DETAILED) {
	  String num;
	  num.assign(needed_lag);
	  num.concat(L": about to add an algorithm");
	  num.insert(L"delayComponent", 0);
	  algo->debug((unichar*)num);
	}

	// insert the component
	//
	dcomp_a(needed_lag).gotoLast();
	algo->init();
	dcomp_a(needed_lag).insert(algo);

	if (debug_level_d >= Integral::DETAILED) {
	  String num;
	  num.assign(needed_lag);
	  num.concat(L": added algorithm");
	  dcomp_a(needed_lag).debug((unichar*)num);
	}
      }
      else {
	more_nodes = temp_list_a.gotoNext();
      }
    }
    lag++;
  }

  // loop over all remaining components
  //
  long needed_size = dcomp_a.length();

  for (long i = dcomp_a.length() - 1; i >= 0; i--) {

    if (dcomp_a(i).length() == 0) {
      needed_size--;
    }
    else {
      break;
    }
  }

  dcomp_a.setLength(needed_size);

  // set the output to use the full file-size
  //
  Long len = fend_a.getNumFrames();

  buf_a.addOrInitLength(buf_a.getCoefName(), len + needed_size);

  if (debug_level_d >= Integral::ALL) {
    buf_a.getLength().debug(L"coef_length, step A");
  }

  // loop over all components
  //
  for (long i = 0; i < needed_size; i++) {
    for (boolean more = dcomp_a(i).gotoFirst();
	 more; more = dcomp_a(i).gotoNext()) {
      
      Component* algo = dcomp_a(i).getCurr();
	    
      long trailing_pad = (long)Integral::max(algo->getTrailingPad(), 0);
      
      long num_inputs = algo->getNumInputs();
      for (long j = 0; j < num_inputs; j++) {
	
	buf_a.setLengthToMax(algo->getInputName(j),
			     needed_size + trailing_pad + 1);
      }
    }
  }

  if (debug_level_d >= Integral::ALL) {
    buf_a.getLength().debug(L"coef_length, step B");
  }
  
  // process delays
  //
  Vector<String> keys;
  buf_a.getLength().keys(keys);
  
  for (long i = keys.length() - 1; i >= 0; i--) {
    long delay = buf_a.getLength(keys(i));
    
    if (debug_level_d >= Integral::ALL) {
      Long d(delay);
      d.debug((unichar*)keys(i));
    }

    if ((delay < 0) || (delay > 999999)) {
      keys(i).debug(L"this has negative delay");
      return Error::handle(name(), L"delayComponent",
			   Error::ARG, __FILE__, __LINE__);
    }

    buf_a.getBuffer(keys(i))(0).setCapacity(2 * delay + 1);
  }
  
  if (debug_level_d >= Integral::DETAILED) {
    buf_a.getLength().debug(L"buf_d.length_d");
  }
  
  // exit gracefully
  //
  return true;
}

// method: readGraph
//
// arguments:
//  Sof& sof: (input) sof file object
//  long graph_tag: (input) tag of graph
//
// return: a boolean value indicating status
//
// this method reads in the Component graph from the input file. the
// input file should contain a DiGraph<Long> for all the graph
// connections, each long is an index to an Algorithm object in the
// file.
//
boolean Recipe::readGraph(Sof& sof_a, long graph_tag_a) {

  // declare local variables
  //
  DiGraph<Long> index_graph;
  SingleLinkedList<Component> algos(DstrBase::USER);

  // first read the graph that defines the connections
  //
  if (!index_graph.read(sof_a, graph_tag_a)) {
    return Error::handle(name(), L"readGraph",
			 Error::READ, __FILE__, __LINE__, Error::WARNING);
  }

  // declare space to hold the graph
  //
  SingleLinkedList<Long> node_indices(DstrBase::USER);
  SingleLinkedList< Triple< Pair<Long, Long>, Float, Boolean> > topo;
  index_graph.get(node_indices, topo);

  // sanity check -- the nodes should be consecutive integers
  //
  long count = 0;
  for (boolean m = node_indices.gotoFirst(); m; m = node_indices.gotoNext()) {
    if (node_indices.getCurr()->ne(count++)) {
      return Error::handle(name(), L"readGraph", ERR, __FILE__, __LINE__);
    }
  }

  // boolean values to represent if the node has parents
  //
  boolean no_parents[count + 1];  

  for (long i = 0; i < count + 1; i++) {
    no_parents[i] = true; // initialization to no parents
  }
  
  // iterate over all vertices in the list to test if a node has parents
  //
  for (boolean more = topo.gotoFirst(); more;  more = topo.gotoNext()) {

    // retrieve the destination vertex position of the current arc
    // the node located in second place always has parents
    //
    Long dst_pos = topo.getCurr()->first().second();
    no_parents[(long)dst_pos] = false;
  }

  // provide some debug information 
  //
  if (debug_level_d > Integral::BRIEF) {

    // declare local variables
    //
    String value;

    for (long i = 0; i < count; i++) {
      if (no_parents[i]) {
	value.assign(i);
	value.assign(L"node #");
	value.concat(i);
	value.concat(L": (**no** parent)");
	Console::put(value);
      }
      else {
	value.assign(i);
	value.assign(L"node #");
	value.concat(i);
	value.concat(L": (parents exist)");
	Console::put(value);  
      }
    }
    
    Console::put(L"\n");
  }

  // boolean values to represent if the node has a child
  //
  boolean no_child[count + 1];  

  for (long i = 0; i < count + 1; i++)
    no_child[i] = true; // initialization to no child
   
  // iterate over all vertices in the list to test if a node has a child
  //
  for (boolean more = topo.gotoFirst(); more;  more = topo.gotoNext()) {
    
    // retrieve the destination vertex position of the current arc
    // the node located in first place always has a child
    //
    Long dst_pos = topo.getCurr()->first().first();
    no_child[(long)dst_pos] = false;
  }
  
  // print out debug information 
  //
  if (debug_level_d > Integral::BRIEF) {
    // declare local variables
    //
    String value;

    for (long i = 0; i < count; i++) {
      if (no_child[i]) {
	value.assign(i);
	value.assign(L"node #");
	value.concat(i);
	value.concat(L": (**no** child)");
	Console::put(value);
      }
      else {
	value.assign(i);
	value.assign(L"node #");
	value.concat(i);
	value.concat(L": (children exist)");
	Console::put(value);  
      }
    }
    
    Console::put(L"\n");
  }

  // count how many end points in the graph
  //
  long number_no_child = 0;

  for (long i = 0; i < count; i++) {
    if (no_child[i]) {
      number_no_child++;
    }
  }
  
  // now read in the Algorithms themselves -- put them into a
  // hashtable based on the tag number.
  //
  for (boolean m = node_indices.gotoFirst(); m; m = node_indices.gotoNext()) {

    // declare a pointer to the current algorithm
    //
    long tag = *(node_indices.getCurr());
    Algorithm algo;

    if (!algo.read(sof_a, tag)) {
      return Error::handle(name(), L"readGraph", Error::READ, __FILE__,
			   __LINE__, Error::WARNING);
    }

    Component* recipe = new Component();
    recipe->setAlgorithm(algo);
    algos.insertLast(recipe);
  }
  
  // add one node as dummy output point
  //
  Component* recipeA = new Component();
  Algorithm algo;
  if (number_no_child == 1) {
    algo.setType(Algorithm::COEFFICIENT_LABEL);
  }
  else {
    algo.setType(Algorithm::CONNECTION);
  }
  
  recipeA->setAlgorithm(algo);
  String str(DUMMY_OUTPUT_NAME);
  recipeA->setOutputName(str);
  
  algos.insertLast(recipeA);
  
  // link all the end points to dummy output point
  //
  float delay = 0.0;
  
  for (long i = 0; i < count; i++) {
    if (no_child[i]) {
      
      Float float_00(delay);
      Boolean bool_00(false);
      Pair<Long, Long> pair_00(i, count);

      Triple< Pair<Long, Long>, Float, Boolean>
	triple(pair_00, float_00, bool_00);
      topo.insert(&triple);
      delay += 1;
    }
  }  
  
  no_parents[count] = false;
  
  // now create the Component graph
  //
  recipe_d.clear();
  
  recipe_d.setAllocationMode(DstrBase::USER);
  recipe_d.assign(algos, topo);
  
  // now expand the sub-graphs
  //
  while (expandSubGraphs(sof_a));

  recipe_d.setAllocationMode(DstrBase::SYSTEM);
  
  algos.clear();
  topo.clear();
  
  recipe_d.get(algos, topo);
  
  // reorganize the node status after read the sub graph
  //
  count = 0;
  for (boolean m = algos.gotoFirst(); m; m = algos.gotoNext()) {
    count++;
  }
  
  for (long i = 0; i < count; i++) {
    no_parents[i] = true; // initialization to no parents
  }
  
  // iterate over all vertices in the list to test if a node has parents
  //
  for (boolean more = topo.gotoFirst(); more;  more = topo.gotoNext()) {
    
    // retrieve the destination vertex position of the current arc
    // the node located in second place always has parents
    //
    Long dst_pos = topo.getCurr()->first().second();
    no_parents[(long)dst_pos] = false;
  }
  
  // provide some debug information 
  //
  if (debug_level_d > Integral::BRIEF) {
    // declare local variables
    //
    String value;

    for (long i = 0; i < count; i++) {
      if (no_parents[i]) {
	value.assign(i);
	value.assign(L"node #");
	value.concat(i);
	value.concat(L": (**no** parent)");
	Console::put(value);
      }
      else {
	value.assign(i);
	value.assign(L"node #");
	value.concat(i);
	value.concat(L": (parents exist)");
	Console::put(value);  
      }
    }
    
    Console::put(L"\n");
  }

  // to check if the number of input for components is correct
  //
  if (!checkInputs()) {
    return Error::handle(name(), L"readGraph", ERR, __FILE__, __LINE__);
  }

  // set the color
  //
  recipe_d.makeColorHash(Integral::WHITE);

  // search for input file type 
  //
  long node_count = 0;
  String dummy_input_point;
  
  for (boolean m = recipe_d.gotoFirst(); m;
       m = recipe_d.gotoNext()) {

    Component* rcp = (Component*)(recipe_d.getCurr()->getItem());
    
    if (no_parents[node_count] &&
	rcp->getAlgorithm().getType() == Algorithm::COEFFICIENT_LABEL) {
      
      CoefficientLabel::TYPE ctype = rcp->getAlgorithm().getCLabelType();
      if (ctype == CoefficientLabel::INPUT) {
	dummy_input_point.assign(rcp->getAlgorithm().getCLabelVariable());
	if (dummy_input_point.length() > 0) {
	  dummy_input_point.insert(INPUT_PREFIX, 0);
	}
      }
    }
    node_count++;
  }

  node_count = 0;
  
  // now mark all data buffers as black and set the input and output names
  //
  for (boolean m = recipe_d.gotoFirst(); m;
       m = recipe_d.gotoNext()) {

    Component* rcp = (Component*)(recipe_d.getCurr()->getItem());
    
    if (no_parents[node_count] &&
	rcp->getAlgorithm().getType() != Algorithm::COEFFICIENT_LABEL) {
      recipe_d.setColor(recipe_d.getCurr(), Integral::BLACK);
      if (dummy_input_point.length() > 0) {
	rcp->setInputName(0, dummy_input_point);
      }
      else {
	String str(SAMPLED_DATA_NAME);
	rcp->setInputName(0, str);
      }
    }
    
    if (rcp->getAlgorithm().getType() == Algorithm::COEFFICIENT_LABEL) {
      recipe_d.setColor(recipe_d.getCurr(), Integral::BLACK);

      CoefficientLabel::TYPE ctype = rcp->getAlgorithm().getCLabelType();

      if (ctype != CoefficientLabel::OUTPUT) {
	String str(rcp->getAlgorithm().getCLabelVariable());
	if (str.length() > 0) {
	  str.insert(INPUT_PREFIX, 0);
	  rcp->setInputName(0, str);
	}
      }
      
      if (ctype != CoefficientLabel::INPUT) {
	String str(rcp->getAlgorithm().getCLabelVariable());
	if (str.length() > 0) {
	  str.insert(OUTPUT_PREFIX, 0);
	  rcp->setOutputName(str);
	}
      }
    }
    node_count++;
  }

  // loop through and move input & output names from the data buffers
  //
  for (boolean m = recipe_d.gotoFirst(); m; m = recipe_d.gotoNext()) {

    // if this node is black, propagate the input name forward
    //
    GraphVertex<Component>* gv = (GraphVertex<Component>*)recipe_d.getCurr();

    if ((recipe_d.getColor(gv) == Integral::BLACK) &&
	(gv->getItem()->getOutputName().length() > 0)) {

      String vname(gv->getItem()->getOutputName());

      for (boolean a = gv->gotoFirst(); a; a = gv->gotoNext()) {
	
	GraphArc<Component>* ga = gv->getCurr();
	GraphVertex<Component>* dst = ga->getVertex();

	long weight = (long)ga->getWeight();

	dst->getItem()->setInputName(weight, vname);
		
      }

      continue;
    }
    
    for (boolean a = gv->gotoFirst(); a; a = gv->gotoNext()) {

      GraphArc<Component>* ga = gv->getCurr();
      GraphVertex<Component>* dst = ga->getVertex();

      // if the destination node is black, propagate the output name
      // backwards
      //
      if (recipe_d.getColor(dst) == Integral::BLACK) {

	long weight = (long)ga->getWeight();

	// make sure this arc has an input name
	//
	if (dst->getItem()->getNumInputs() > weight) {
	  
	  String vname(dst->getItem()->getInputName(weight));

	  // make sure the name was not empty
	  //
	  if (vname.length() > 0) {
	    gv->getItem()->setOutputName(vname);
	  }
	}
      }
    }
  }

  // now place dummy names on all other connections
  //
  long dummy_nc = 0;

  for (boolean m = recipe_d.gotoFirst(); m;
       m = recipe_d.gotoNext()) {

    GraphVertex<Component>* gv = (GraphVertex<Component>*)recipe_d.getCurr();

    // declare local dummy name for output data
    //
    String dummy_name;
    dummy_name.assign(DUMMY_DATA_NAME);

    boolean has_name = false;

    // give output name to each component
    // there are two cases:
    //   if output name exists, directly using it
    //   else assign a name to it
    //
    if (gv->getItem()->getOutputName().length() == 0) {
      dummy_name.concat(dummy_nc++);
      gv->getItem()->setOutputName(dummy_name);
    }
    else {
      dummy_name.assign(gv->getItem()->getOutputName());
      has_name = true;
    }

    // propagate the output name as input name to components which
    // just follow the current component
    //
    for (boolean a = gv->gotoFirst(); a; a = gv->gotoNext()) {
      
      GraphArc<Component>* ga = gv->getCurr();
      GraphVertex<Component>* dst = ga->getVertex();

      // put a dummy name on both the output of the current node and
      // the input of the next node
      //
      long weight = (long)ga->getWeight();

      if ((dst->getItem()->getNumInputs() > weight) &&
	  (dst->getItem()->getInputName(weight).length() != 0) &&
	  (dst->getItem()->getInputName(weight).ne(dummy_name))) {
	dst->getItem()->debug(L"dst recipe");
	gv->getItem()->debug(L"cur recipe");
	dummy_name.debug(L"dummy_name");
	String out_value;
	out_value.concat(L"weight = %ld\n");
	out_value.concat(weight);
	Console::put(out_value);
	return Error::handle(name(), L"readGraph", ERR,
			     __FILE__, __LINE__);
      }

      if (!has_name) {
	dst->getItem()->setInputName(weight, dummy_name);
      }

      else {
	if ((dst->getItem()->getNumInputs() <= weight) ||
	    (dst->getItem()->getInputName(weight).ne(dummy_name))) {
	  dst->getItem()->debug(L"recipe");
	  dummy_name.debug(L"dummy_name");
	  return Error::handle(name(), L"readGraph",
			       ERR, __FILE__, __LINE__);
	}
      }
    }
  }

  // exit gracefully
  //
  return true;
}

// method: expandSubGraphs
//
// arguments:
//  Sof& sof: (input) open sof file
//
// return: whether or not any sub-graphs are expanded this pass
//
// loop through the vertices and expand sub-graphs
//
boolean Recipe::expandSubGraphs(Sof& sof_a) {

  // declare a status flag
  //
  boolean res = false;

  // loop over all components
  //
  for (boolean m = recipe_d.gotoFirst(); m; m = recipe_d.gotoNext()) {
    
    GraphVertex<Component>* gv = (GraphVertex<Component>*)recipe_d.getCurr();
    
    if (gv->getItem()->getAlgorithm().getType() ==
	Algorithm::ALGORITHM_CONTAINER) {

      // set the flag so we know that a sub-graph was expanded this
      // pass
      //
      res = true;
      
      // record the tag of the sub graph
      //
      long gtag = gv->getItem()->getAlgorithm().getContainerGraphTag();

      // now we need to replace the container object with two
      // coefficient label algorithms. all of the inputs to the
      // container will go to the first coefficient label, so we can
      // quickly do this by just changing the type of the container
      // object to a coefficient label (this means all incomming arcs
      // will not need to be changed). We will then make a new
      // coefficient label and move all of the arcs currently
      // emminating from the container to the new coefficient label.
      //
      Component* rcp = new Component();
      Algorithm algo;
      algo.setType(Algorithm::COEFFICIENT_LABEL);
      rcp->setAlgorithm(algo);

      // insert our new coefficient label into the graph
      //
      GraphVertex<Component>* src = recipe_d.insertVertex(rcp);
      
      for (boolean a = gv->gotoFirst(); a; a = gv->gotoNext()) {
	
	GraphArc<Component>* ga = gv->getCurr();

	// first add a new arc
	//
	recipe_d.insertArc(src, ga->getVertex(), ga->getEpsilon(),
				ga->getWeight());

	// now delete the current arc
	//
	recipe_d.removeArc(gv, ga->getVertex());
      }

      // change the container object to a coefficient label
      //
      gv->getItem()->setAlgorithm(algo);

      // now read in the sub-graph
      //
      GraphVertex<Component>* sub_input = (GraphVertex<Component>*)NULL;
      GraphVertex<Component>* sub_output = (GraphVertex<Component>*)NULL;
      readSubGraph(sub_input, sub_output, sof_a, gtag);

      // now add arcs from the first coefficient label to the sub-graph input
      //
      recipe_d.insertArc(gv, sub_input);
      recipe_d.insertArc(sub_output, src);
      
    }
  }
  return res;
}
  
// method: readSubGraph
//
// arguments:
//  GraphVertex<Component>*& sub_input: (output) container's input vertex
//  GraphVertex<Component>*& sub_output: (output) container's output vertex
//  Sof& sof: (input) Sof file
//  long graph_tag: (input) tag for graph to read
//
// return: a boolean value indicating status
//
// this method reads a sub-graph from the file. it is very similar to
// the readGraph method, except it does not delete the current
// entries. upon read, it expands the sub-graph into the current
// graph.
//
boolean Recipe::readSubGraph(GraphVertex<Component>*& sub_input_a,
			     GraphVertex<Component>*& sub_output_a,
			     Sof& sof_a, long graph_tag_a) {

  // initialize output parameters
  //
  sub_input_a = (GraphVertex<Component>*)NULL;
  sub_output_a = (GraphVertex<Component>*)NULL;

  // local variables
  //
  DiGraph<Long> index_graph;
  SingleLinkedList<Component> algos(DstrBase::USER);
  
  // first read the graph that defines the connections
  //
  if (!index_graph.read(sof_a, graph_tag_a)) {
    return Error::handle(name(), L"readSubGraph", Error::READ, __FILE__,
			 __LINE__, Error::WARNING);
  }

  SingleLinkedList<Long> node_indices(DstrBase::USER);
  SingleLinkedList< Triple< Pair<Long, Long>, Float, Boolean> > topo;
  index_graph.get(node_indices, topo);

  // sanity check -- the nodes should be consecutive integers
  //
  long count = graph_tag_a;
  for (boolean m = node_indices.gotoFirst(); m; m = node_indices.gotoNext()) {
    if (node_indices.getCurr()->ne(count++)) {
      return Error::handle(name(), L"readSubGraph", ERR, __FILE__, __LINE__);
    }
  }
  
  // now read in the Algorithms themselves -- put them into a
  // hashtable based on the tag number.
  //
  for (boolean m = node_indices.gotoFirst(); m; m = node_indices.gotoNext()) {
    long tag = *(node_indices.getCurr());
  
    Algorithm algo;
    if (!algo.read(sof_a, tag)) {
      return Error::handle(name(), L"readSubGraph", Error::READ, __FILE__,
			   __LINE__, Error::WARNING);
    }

    // subtract the graph tag from the index so that the indices will
    // range from 0 to n (the same range as the list)
    //
    node_indices.getCurr()->assign(tag - graph_tag_a);
    
    Component* recipe = new Component;
    recipe->setAlgorithm(algo);
    algos.insertLast(recipe);
  }

  recipe_d.setAllocationMode(DstrBase::USER);

  // declare local variables for the graph copy
  //
  long src = 0;
  long dst = 0;        
  float weight = 0.0;
  boolean epsilon = false;
  
  // create vertices for the input data. we will also catch the input
  // and output coefficient buffer at this time.
  //
  GraphVertex<Component>* vertices[algos.length()];
  for (int i = 0; i < algos.length(); i++) {
    algos.gotoPosition(i);
    vertices[i] = recipe_d.insertVertex(algos.getCurr());

    if (algos.getCurr()->isCoefficientLabel(CoefficientLabel::INPUT)) {
      if (sub_input_a != (GraphVertex<Component>*)NULL) {
	return Error::handle(name(), L"readSubGraph", ERR,
			     __FILE__, __LINE__);
      }
      sub_input_a = vertices[i];
    }
    if (algos.getCurr()->isCoefficientLabel(CoefficientLabel::OUTPUT)) {
      if (sub_output_a != (GraphVertex<Component>*)NULL) {
	return Error::handle(name(), L"readSubGraph", ERR,
			     __FILE__, __LINE__);
      }
      sub_output_a = vertices[i];
    }
  }

  // loop over all elements in the arc list
  //
  for (boolean more = topo.gotoFirst(); more; more = topo.gotoNext()) {

    // define the source and destination objects
    //
    GraphVertex<Component>* src_vertex;
    GraphVertex<Component>* dst_vertex;
    
    // determine the parameters of the arc
    //
    weight = (float)topo.getCurr()->second();
    epsilon = (boolean)topo.getCurr()->third();
    src = (long)topo.getCurr()->first().first();
    dst = (long)topo.getCurr()->first().second();
    
    // check if the source is the start vertex
    //
    if ((src == DiGraph<Component>::START_INDEX) ||
	(src == DiGraph<Component>::TERM_INDEX) ||
	(dst == DiGraph<Component>::START_INDEX) ||
	(dst == DiGraph<Component>::TERM_INDEX) ||
	(src >= algos.length()) || (dst >= algos.length())) {
      return Error::handle(name(), L"readSubGraph", ERR, __FILE__, __LINE__);
    }

    src_vertex = vertices[src];
    dst_vertex = vertices[dst];

    // insert an arc from the source to the destination vertices
    //
    recipe_d.insertArc(src_vertex, dst_vertex, epsilon, weight);
  }

  recipe_d.setAllocationMode(DstrBase::SYSTEM);

  // exit gracefully
  //
  return true;
}

// method: checkInputs
//
// arguments: none
//
// return: a boolean value indicating status
//
// this method tests the graph to make sure only Connection objects
// have multiple inputs
//
boolean Recipe::checkInputs() {
  
  // loop over all components
  //
  for (boolean m = recipe_d.gotoFirst(); m;
       m = recipe_d.gotoNext()) {
    
    GraphVertex<Component>* gv = (GraphVertex<Component>*)recipe_d.getCurr();
    
    for (boolean a = gv->gotoFirst(); a; a = gv->gotoNext()) {

      GraphArc<Component>* ga = gv->getCurr();
      Algorithm::TYPES type =
	ga->getVertex()->getItem()->getAlgorithm().getType();
      if ((ga->getWeight() != 0.0) &&
	  (type != Algorithm::CONNECTION) &&
	  (type != Algorithm::MATH) &&
	  (type != Algorithm::CORRELATION)) {
	ga->getVertex()->getItem()->getAlgorithm().debug(L"algo");
	return Error::handle(name(), L"checkInputs", ERR, __FILE__, __LINE__);
      }
    }
  }

  // exit gracefully
  //
  return true;
}
