// file: $isip/class/asr/AnnotationIndex/anno_5.cc
// version: $Id: andx_05.cc 8785 2002-11-13 00:21:46Z alphonso $
//

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

// method: clear
//
// arguments:
//   Integral::CMODE cmode: (input) clear mode
//
// return: logical error status
//
// this method clears the content of the current object
//
boolean AnnotationIndex::clear(Integral::CMODE cmode_a) {

  // declare local variables
  //
  boolean status = false;

  status = ancrset_d.clear(cmode_a);
  status &= ancr_by_id_d.clear(cmode_a);  
  status &= ancrset_by_offset_d.clear(cmode_a);
  status &= anno_by_id_d.clear(cmode_a);
  status &= anno_by_type_d.clear(cmode_a);
  status &= start_by_offset_d.clear(cmode_a);
  status &= end_by_offset_d.clear(cmode_a);
  status &= incoming_d.clear(cmode_a);
  status &= outgoing_d.clear(cmode_a);
  status &= by_feature_d.clear(cmode_a);
  
  // return the ;ogical status
  //
  return status;  
}

// method: eq
//
// arguments:
//   const AnnotationIndex& arg: (input) annotation Index
//
// return: logical error status
//
// method determines if the input and the current graphs are equal
//
boolean AnnotationIndex::eq(const AnnotationIndex& arg_a) const {

  // determine if the member data are equal
  //
  if (!ancrset_d.eq(arg_a.ancrset_d)) {
    return false;
  }
  if (!ancr_by_id_d.eq(arg_a.ancr_by_id_d)) {
    return false;
  }
  if (!ancrset_by_offset_d.eq(arg_a.ancrset_by_offset_d)) {
    return false;
  }
  if (!anno_by_id_d.eq(arg_a.anno_by_id_d)) {
    return false;
  }
  if (!anno_by_type_d.eq(arg_a.anno_by_type_d)) {
    return false;
  }
  if (!start_by_offset_d.eq(arg_a.start_by_offset_d)) {
    return false;
  }
  if (!end_by_offset_d.eq(arg_a.end_by_offset_d)) {
    return false;
  }
  if (!incoming_d.eq(arg_a.incoming_d)) {
    return false;
  }
  if (!outgoing_d.eq(arg_a.outgoing_d)) {
    return false;
  }
  if (!by_feature_d.eq(by_feature_d)) {
    return false;
  }
  
  // return the logical status
  //
  return true;
}

// method: assign
//
// arguments:
//   const AnnotationIndex& arg: (input) annotation Index
//
// return: logical error status
//
// method assigns the input annotation Index to the current one
//
boolean AnnotationIndex::assign(const AnnotationIndex& arg_a) {

  // declare local variables
  //
  boolean status = false;

  // assign the member data
  //
  status = ancrset_d.assign(arg_a.ancrset_d);
  status &= ancr_by_id_d.assign(arg_a.ancr_by_id_d);  
  status &= ancrset_by_offset_d.assign(arg_a.ancrset_by_offset_d);
  status &= anno_by_id_d.assign(arg_a.anno_by_id_d);
  status &= anno_by_type_d.assign(arg_a.anno_by_type_d);
  status &= start_by_offset_d.assign(arg_a.start_by_offset_d);
  status &= end_by_offset_d.assign(arg_a.end_by_offset_d);
  status &= incoming_d.assign(arg_a.incoming_d);
  status &= outgoing_d.assign(arg_a.outgoing_d);
  status &= by_feature_d.assign(by_feature_d);
  
  // return the logical status
  //
  return status;   
}
  
// method: add
//
// arguments:
//   Annotation* anno: (input) annotation to be added
//
// return: logical error status
//
// method to add an annotation to the indexes
//
boolean AnnotationIndex::add(Annotation* anno_a) {

  // declare local variables
  //
  Vector<String> keys;  
  HashKey<Anchor> hashkey;
  HashTable<String, String> features;  
  DoubleLinkedList<Annotation> tmplist(DstrBase::USER);    
  tmplist.insert(anno_a);

  // get the start and end anchors of the annotation
  //
  Anchor* start_anchor = anno_a->getStartAnchor();
  Anchor* end_anchor = anno_a->getEndAnchor();

  // hash the annotation via the identifier
  //
  anno_by_id_d.insert(anno_a->getId(), anno_a);

  // hash the annotation via the type
  //
  if (anno_by_type_d.containsKey(anno_a->getType())) {
    if (!anno_by_type_d.get(anno_a->getType())->contains(anno_a)) {
      anno_by_type_d.get(anno_a->getType())->insert(anno_a);
    }
  }
  else {
    anno_by_type_d.insert(anno_a->getType(), &tmplist);
  }

  // hash the features for the annotation
  //
  features = anno_a->getFeatureMap();
  features.keys(keys);
  for (int i=0; i < keys.length(); i++) {
    addFeature(anno_a, keys(i), *features.get(keys(i)));
  }
  
  // hash the annotation by the start offset if it is anchored
  //
  if (start_anchor->getAnchored()) {
    if (start_by_offset_d.containsKey(start_anchor->getOffset())) {
      if (!start_by_offset_d.get(start_anchor->getOffset())->contains(anno_a)) {
	start_by_offset_d.get(start_anchor->getOffset())->insert(anno_a);
      }
    }
    else {
      start_by_offset_d.insert(start_anchor->getOffset(), &tmplist);
    }
  }

  // hash the annotation by the end offset if it is anchored
  //
  if (end_anchor->getAnchored()) {
    if (end_by_offset_d.containsKey(end_anchor->getOffset())) {
      if (!end_by_offset_d.get(end_anchor->getOffset())->contains(anno_a)) {
	end_by_offset_d.get(end_anchor->getOffset())->insert(anno_a);
      }
    }
    else {
      end_by_offset_d.insert(end_anchor->getOffset(), &tmplist);
    }
  }  
  
  // the annotation leaves the start anchor - add it to the outgoing list
  //
  hashkey.assign(start_anchor);
  if (outgoing_d.containsKey(hashkey)) {
    if (!outgoing_d.get(hashkey)->contains(anno_a)) {
      outgoing_d.get(hashkey)->insert(anno_a);
    }
  }
  else {
    outgoing_d.insert(hashkey, &tmplist);
  }
  
  // the annotation enters the end anchor - add it to the incoming list
  //
  hashkey.assign(end_anchor);
  if (incoming_d.containsKey(hashkey)) {
    if (!incoming_d.get(hashkey)->contains(anno_a)) {
      incoming_d.get(hashkey)->insert(anno_a);
    }
  }
  else {
    incoming_d.insert(hashkey, &tmplist);
  }  

  // add the start anchor to the anchor set
  //
  addAnchor(start_anchor);
  
  // add the end anchor to the anchor set
  //  
  addAnchor(end_anchor);
  
  // exit gracefully
  //
  return true;
}

// method: deleteAnnotation
//
// arguments:
//   Annotation* anno: (input) annotation to be deleted
//
// return: logical error status
//
// method to delete an annotation from the indexes
//
boolean AnnotationIndex::deleteAnnotation(Annotation* anno_a) {

  // declare local variables
  //
  Vector<String> keys;
  HashKey<Anchor> hashkey;
  HashTable<String, String> features;
  
  // get the start and end anchors of the annotation
  //
  Anchor* start_anchor = anno_a->getStartAnchor();
  Anchor* end_anchor = anno_a->getEndAnchor();

  // delete the annotation via the identifier
  //
  if (anno_by_id_d.containsKey(anno_a->getId())) {
    anno_by_id_d.remove(anno_a->getId());
  }
  
  // delete the annotation via the type
  //
  if (anno_by_type_d.containsKey(anno_a->getType())) {
    if (anno_by_type_d.get(anno_a->getType())->find(anno_a)) {
      anno_by_type_d.get(anno_a->getType())->remove();
    }
    if (anno_by_type_d.get(anno_a->getType())->isEmpty()) {
      anno_by_type_d.remove(anno_a->getType());
    }
  }

  // delete the features for the annotation
  //
  features = anno_a->getFeatureMap();
  features.keys(keys);
  for (int i=0; i < keys.length(); i++) {
    deleteFeature(anno_a, keys(i));
  }
  
  // delete the annotation by the start offset if it is anchored
  //
  if (start_anchor->getAnchored()) {
    if (start_by_offset_d.containsKey(start_anchor->getOffset())) {
      if (start_by_offset_d.get(start_anchor->getOffset())->find(anno_a)) {
	start_by_offset_d.get(start_anchor->getOffset())->remove();
      }
      if (start_by_offset_d.get(start_anchor->getOffset())->isEmpty()) {
	start_by_offset_d.remove(start_anchor->getOffset());
      }
    }
  }

  // delete the annotation by the end offset if it is anchored
  //
  if (end_anchor->getAnchored()) {
    if (end_by_offset_d.containsKey(end_anchor->getOffset())) {
      if (end_by_offset_d.get(end_anchor->getOffset())->find(anno_a)) {
	end_by_offset_d.get(end_anchor->getOffset())->remove();
      }
      if (end_by_offset_d.get(end_anchor->getOffset())->isEmpty()) {
	end_by_offset_d.remove(end_anchor->getOffset());
      }
    }
  }

  // the annotation leaves the start anchor - remove it to the outgoing list
  //
  hashkey.assign(start_anchor);
  if (outgoing_d.containsKey(hashkey)) {
    if (outgoing_d.get(hashkey)->find(anno_a)) {
      outgoing_d.get(hashkey)->remove();
    }
    if (outgoing_d.get(hashkey)->isEmpty()) {
      outgoing_d.remove(hashkey);
    }
  }

  // the annotation enters the end anchor - remove it to the incoming list
  //
  hashkey.assign(end_anchor);
  if (incoming_d.containsKey(hashkey)) {
    if (incoming_d.get(hashkey)->find(anno_a)) {
      incoming_d.get(hashkey)->remove();
    }
    if (incoming_d.get(hashkey)->isEmpty()) {
      incoming_d.remove(hashkey);
    }
  }
  
  // exit gracefully
  //
  return true;  
}

// method: existsFeature
//
// arguments:
//   const String& feature: (input) annotation feature
//   const String& value: (input) annotation feature value
//
// return: logical error status
//
// method to check if a feature-value pair exists
//
boolean AnnotationIndex::existsFeature(const String& feature_a,
				       const String& value_a) {

  // declare local variables
  //
  String index;

  // generate the hash index
  //
  index.assign(feature_a);
  index.concat(value_a);
  
  // determine if the key exists
  //
  return by_feature_d.containsKey(index);
}
  
// method: addFeature
//
// arguments:
//   Annotation* anno: (input) annotation
//   const String& feature: (input) annotation feature
//   const String& value: (input) annotation feature value
//
// return: logical error status
//
// method to insert the feature-value pair into the feature map
//
boolean AnnotationIndex::addFeature(Annotation* anno_a,
				    const String& feature_a,
				    const String& value_a) {

  // declare local variables
  //
  String index;
  DoubleLinkedList<Annotation> tmplist(DstrBase::USER);
  tmplist.insert(anno_a);
  
  // generate the hash key
  //
  index.assign(feature_a);
  index.concat(value_a);

  // hash the annotation via the feature-value pair
  //
  if (by_feature_d.containsKey(index)) {
    if (!by_feature_d.get(index)->contains(anno_a)) {
      by_feature_d.get(index)->insert(anno_a);
    }
  }
  else {
    by_feature_d.insert(index, &tmplist);
  }
    
  // exit gracefully
  //
  return true;      
}

// method: deleteFeature
//
// arguments:
//   Annotation* anno: (input) annotation
//   const String& feature: (input) annotation feature
//
// return: logical error status
//
// method that deletes the feature-value pair from the feature map
//
boolean AnnotationIndex::deleteFeature(Annotation* anno_a,
				       const String& feature_a) {

  // declare local variables
  //
  String value;
  String index;
  
  // remove the key value pait from the annotation
  //
  if (anno_a->existsFeature(feature_a)) {

    // generate the hash index
    //
    value = anno_a->getFeature(feature_a);
    index.assign(feature_a);
    index.concat(value);

    if (by_feature_d.containsKey(index)) {    
      if (by_feature_d.get(index)->find(anno_a)) {
	by_feature_d.get(index)->remove();
      }
      if (by_feature_d.get(index)->isEmpty()) {
	by_feature_d.remove(index);
      }
    }
  }
  
  // exit gracefully
  //
  return true;        
}

// method: getNearestOffset
//
// arguments:
//   float offset: (input) anchor offset
//
// return: logical error status
//
// method to get the nearest used offset to the specified offset
//
float AnnotationIndex::getNearestOffset(float offset_a) {

  // declare local variables
  //
  float min_offset = 0.0;
  float min_delta = 1.0e10;  
  Vector<Float> keys;

  // check if the hash table of anchor sets is empty
  //
  if (ancrset_by_offset_d.isEmpty()) {
    return min_offset;
  }

  // get the vector of keys from the hash table
  //
  ancrset_by_offset_d.keys(keys);

  // loop through the set of keys and find the nearest offset
  //
  if (keys.length() == 1) {
    return (float)keys(0);
  }
  for (int i=0; i < keys.length(); i++) {
    if (Integral::abs(keys(i) - offset_a) < min_delta) {
      min_offset = keys(i);
      min_delta = Integral::abs(keys(i) - offset_a);
    }
  }

  // return the nearest offset
  //
  return min_offset;
}

// method: getByOffset
//
// arguments:
//   float offset: (input) anchor offset
//   DoubleLinkedList<Annotation>& list: (input) list of annotations
//
// return: logical error status
//
// method that gets the annotations that overlap a particular time offset.
// gets all annotations whose start anchor offset is smaller than or
// equal to the given offset AND end anchor offset is greater than 
// or equal to the given offet
//
boolean AnnotationIndex::getByOffset(float offset_a,
				     DoubleLinkedList<Annotation>& list_a) {

  // declare local variables
  //
  Vector<Float> end_keys;  
  Vector<Float> start_keys;
  Anchor* anchor = (Anchor*)NULL;
  Annotation* annotation = (Annotation*)NULL;
  DoubleLinkedList<Annotation>* alist;
  
  // clear the input list to begin with
  //
  list_a.clear();
  
  // check to make sure that the offset is greater than zero
  //
  if (offset_a < 0.0) {
    return false;
  }

  // get the vector of keys from the start and end offset hash tables
  //
  start_by_offset_d.keys(start_keys);
  end_by_offset_d.keys(end_keys);

  // loop over the start keys and gets all annotations whose start anchor
  // offset is smaller than or equal to the given offset
  //
  for (int i=0; i < start_keys.length(); i++) {
    if (start_keys(i) <= offset_a) {

      // check if the end offset is greater than or equal to the given offet
      //
      alist = start_by_offset_d.get(start_keys(i));
      for(boolean more = alist->gotoFirst(); more; more = alist->gotoNext()) {
	annotation = alist->getCurr();
	anchor = annotation->getEndAnchor();
	if ((anchor != (Anchor*)NULL) && (anchor->getOffset() >= offset_a)) {
	  if (!list_a.contains(annotation)) {
	    list_a.insert(annotation);
	  }
	}
      }
    }
  }
  
  // loop over all end keys and gets all annotations whose end anchor
  // offset is greater than or equal to the given offet
  //
  for (int i=0; i < end_keys.length(); i++) {
    if (end_keys(i) >= offset_a) {
      
      // check if the start offset is smaller than or equal to the given offset
      //
      alist = end_by_offset_d.get(end_keys(i));
      for(boolean more = alist->gotoFirst(); more; more = alist->gotoNext()) {
	annotation = alist->getCurr();
	anchor = annotation->getStartAnchor();
	if ((anchor != (Anchor*)NULL) && (anchor->getOffset() <= offset_a)) {
	  if (!list_a.contains(annotation)) {
	    list_a.insert(annotation);
	  }
	}
      }
    }
  }
  
  // exit gracefully
  //
  return true;
}

// method: getAnnotationByOffset
//
// arguments:
//   float offset: (input) anchor offset
//
// return: logical error status
//
// method get one of the annotations which overlap a particular time offset.
// same as getByOffset except that getAnnotationByOffset returns only
// one qualified annotation while getByOffset returns all of them
//
Annotation* AnnotationIndex::getAnnotationByOffset(float offset_a) {

  // declare local variables
  //
  Vector<Float> end_keys;  
  Vector<Float> start_keys;
  Anchor* anchor = (Anchor*)NULL;
  Annotation* annotation = (Annotation*)NULL;
  DoubleLinkedList<Annotation>* alist;
  
  // check to make sure that the offset is greater than zero
  //
  if (offset_a < 0.0) {
    return (Annotation*)NULL;
  }

  // get the vector of keys from the start offset hash tables
  //
  start_by_offset_d.keys(start_keys);

  // loop over the start keys and gets all annotations whose start anchor
  // offset is smaller than or equal to the given offset
  //
  for (int i=0; i < start_keys.length(); i++) {
    if (start_keys(i) <= offset_a) {

      // check if the end offset is greater than or equal to the given offet
      //
      alist = start_by_offset_d.get(start_keys(i));
      for(boolean more = alist->gotoFirst(); more; more = alist->gotoNext()) {
	annotation = alist->getCurr();
	anchor = annotation->getEndAnchor();
	if ((anchor != (Anchor*)NULL) && (anchor->getOffset() >= offset_a)) {
	  return annotation;
	}
      }
    }
  }

  // get the vector of keys from the end offset hash tables
  //
  end_by_offset_d.keys(end_keys);
  
  // loop over all end keys and gets all annotations whose end anchor
  // offset is greater than or equal to the given offet
  //
  for (int i=0; i < end_keys.length(); i++) {
    if (end_keys(i) >= offset_a) {

      // check if the start offset is smaller than or equal to the given offset
      //
      alist = end_by_offset_d.get(end_keys(i));
      for(boolean more = alist->gotoFirst(); more; more = alist->gotoNext()) {
	annotation = alist->getCurr();
	anchor = annotation->getStartAnchor();
	if ((anchor != (Anchor*)NULL) && (anchor->getOffset() <= offset_a)) {
	  return annotation; 
	}
      }
    }
  }
    
  // return the annotation
  //
  return (Annotation*)NULL;  
}

// method: existsAnnotation
//
// arguments:
//   const String& id: (input) annotation id
//
// return: logical error status
//
// method to check if the specified annotation exists
//
boolean AnnotationIndex::existsAnnotation(const String& id_a) {

  // determine if the annotation exists
  //
  return anno_by_id_d.containsKey(id_a);
}

// method: existsAnchor
//
// arguments:
//   const String& id: (input) anchor id
//
// return: logical error status
//
// method to check if the specified anchor exists
//
boolean AnnotationIndex::existsAnchor(const String& id_a) {

  // determine if the annotation exists
  //
  return ancr_by_id_d.containsKey(id_a);
}

// method: addAnchor
//
// arguments:
//   Anchor* ancr: (input) anchor to be added
//
// return: logical error status
//
// method to add an anchor to the indexes
//
boolean AnnotationIndex::addAnchor(Anchor* ancr_a) {

  // declare local variables
  //
  HashKey<Anchor> hashkey;
  Float offset(ancr_a->getOffset());
  DoubleLinkedList<Annotation>* incoming;
  DoubleLinkedList<Annotation>* outgoing;  

  DoubleLinkedList<Annotation> annlist(DstrBase::USER);
  DoubleLinkedList<Anchor> tmplist(DstrBase::USER);
  tmplist.insert(ancr_a);
  
  // determine if the offset is present - if not then delete it
  //
  if (!ancrset_d.contains(ancr_a)) {
    ancrset_d.insert(ancr_a);
    ancr_by_id_d.insert(ancr_a->getId(), ancr_a);
  }

  // hash the anchor by the offset
  //
  if (ancr_a->getAnchored()) {
    if (ancrset_by_offset_d.containsKey(offset)) {
      if (!ancrset_by_offset_d.get(offset)->contains(ancr_a)) {
	ancrset_by_offset_d.get(offset)->insert(ancr_a);
      }
    }
    else {
      ancrset_by_offset_d.insert(offset, &tmplist);
    }

    // get the incoming and outgoing annotations to/from the anchor
    //
    hashkey.assign(ancr_a);
    incoming = incoming_d.get(hashkey);
    outgoing = outgoing_d.get(hashkey);
    
    // loop over all incoming annotations to the anchor and hash them
    // by the end anchor offset
    //
    if (incoming != (DoubleLinkedList<Annotation>*)NULL) {
      for (boolean more = incoming->gotoFirst(); more;
	   more = incoming->gotoNext()) {
	if (end_by_offset_d.containsKey(offset)) {
	  if (!end_by_offset_d.get(offset)->contains(incoming->getCurr())) {
	    end_by_offset_d.get(offset)->insert(incoming->getCurr());
	  }
	}
	else {
	  annlist.clear();
	  annlist.insert(incoming->getCurr());
	  end_by_offset_d.insert(offset, &annlist);
	}
      }
    }
    
    // loop over all outgoing annotations from the anchor and hash them
    // by the start anchor offset
    //    
    if (outgoing != (DoubleLinkedList<Annotation>*)NULL) {
      for (boolean more = outgoing->gotoFirst(); more;
	   more = outgoing->gotoNext()) {
	if (start_by_offset_d.containsKey(offset)) {
	  if (!start_by_offset_d.get(offset)->contains(outgoing->getCurr())) {
	    start_by_offset_d.get(offset)->insert(outgoing->getCurr());
	  }
	}
	else {
	  annlist.clear();
	  annlist.insert(outgoing->getCurr());
	  start_by_offset_d.insert(offset, &annlist);
	}
	}
    }
  }
  
  // exit gracefully
  //
  return true;
}

// method: deleteAnchor
//
// arguments:
//   Anchor* ancr: (input) anchor to be deleted
//
// return: logical error status
//
// method to delete an anchor to the indexes
//
boolean AnnotationIndex::deleteAnchor(Anchor* ancr_a) {

  // declare local variables
  //
  Annotation* anno;
  HashKey<Anchor> hashkey;
  Float offset(ancr_a->getOffset());
  DoubleLinkedList<Annotation>* incoming;
  DoubleLinkedList<Annotation>* outgoing;
  
  // determine if the offset is present - if not then delete it
  //
  if (ancrset_d.find(ancr_a)) {
    ancrset_d.remove();
    ancr_by_id_d.remove(ancr_a->getId());
  }

  // delete the anchor by the offset
  //
  if (ancr_a->getAnchored()) {
    ancrset_by_offset_d.remove(offset);

    // get the incoming and outgoing annotations to/from the anchor
    //
    hashkey.assign(ancr_a);
    incoming = incoming_d.get(hashkey);
    outgoing = outgoing_d.get(hashkey);

    // loop over all incoming annotations to the anchor and remove them
    // by the end anchor offset
    //
    if (incoming != (DoubleLinkedList<Annotation>*)NULL) {
      for (boolean more = incoming->gotoFirst(); more;
	   more = incoming->gotoNext()) {
	if (end_by_offset_d.containsKey(offset)) {
	  anno = incoming->getCurr();
	  if (end_by_offset_d.get(offset)->find(anno)) {
	    end_by_offset_d.get(offset)->remove();
	  }
	  if (end_by_offset_d.get(offset)->isEmpty()) {
	    end_by_offset_d.remove(offset);
	  }
	}
      }
    }

    // loop over all outgoing annotations from the anchor and remove them
    // by the start anchor offset
    //    
    if (outgoing != (DoubleLinkedList<Annotation>*)NULL) {
      for (boolean more = outgoing->gotoFirst(); more;
	   more = outgoing->gotoNext()) {
	if (start_by_offset_d.containsKey(offset)) {
	  anno = outgoing->getCurr();
	  if (start_by_offset_d.get(offset)->find(anno)) {
	    start_by_offset_d.get(offset)->remove();
	  }
	  if (start_by_offset_d.get(offset)->isEmpty()) {
	    start_by_offset_d.remove(offset);
	  }
	}
      }    
    }    
  }
  
  // exit gracefully
  //
  return true;
}
