/* FeatureTree.java
 *
 * created: Thu Jan 28 1999
 *
 * This file is part of Artemis
 * 
 * Copyright (C) 1998,1999,2000  Genome Research Limited
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header: /nfs/disk222/yeastpub/Repository/powmap/uk/ac/sanger/pathogens/embl/FeatureTree.java,v 1.2 2000/01/24 17:04:25 kmr Exp $
 **/

package uk.ac.sanger.pathogens.embl;

import collections.*;

import java.util.NoSuchElementException;

/**
 *  A tree that stores StreamFeature objects ordered with a StreamFeatureComparator
 *  object.
 *
 *  @author Kim Rutherford
 *  @version $Id: FeatureTree.java,v 1.2 2000/01/24 17:04:25 kmr Exp $
 **/

public class FeatureTree extends RBTree {
  /**
   *  Create a new (empty) FeatureTree.
   **/
  public FeatureTree (final Comparator comparator) {
    super (comparator);
  }

  /**
   *  Wrapper for RBTree.addIfAbsent () which sets
   *  size_of_largest_feature_seen.
   **/
  public synchronized void addIfAbsent (final Object element)
      throws IllegalElementException {

    final Feature this_feature = (Feature) element;

    final int this_feature_total_size =
      this_feature.getLocation ().getTotalRange ().getCount ();

    if (this_feature_total_size > size_of_largest_feature_seen) {
      size_of_largest_feature_seen = this_feature_total_size;
    }

    super.addIfAbsent (element);
  }

  /**
   *  Wrapper for RBTree.add () which sets size_of_largest_feature_seen.
   **/
  public synchronized void add (final Object element)
      throws IllegalElementException {

    final Feature this_feature = (Feature) element;

    final int this_feature_total_size =
      this_feature.getLocation ().getTotalRange ().getCount ();

    if (this_feature_total_size > size_of_largest_feature_seen) {
      size_of_largest_feature_seen = this_feature_total_size;
    }

    super.add (element);
  }

  /**
   *  Return a vector containing the references of the Feature objects within
   *  the given range.
   *  @param range Return features that overlap this range - ie the start of
   *    the feature is less than or equal to the end of the range and the end
   *    of the feature is greater than or equal to the start of the range.
   *  @return The features that are within the given range.  The returned
   *    object is a copy - changes will not effect the FeatureTree object
   *    itself.
   **/
  public synchronized FeatureVector getFeaturesInRange (final Range range) {
//      final Range real_range =
//        new Range (range.getStart (),
//                   range.getEnd () + size_of_largest_feature_seen);

    RBCell node = tree_;

    // this default size will cover many common cases
    final FeatureVector return_features = new FeatureVector (100);

    // find the leftmost node in the range

    RBCell suspect = tree_;

    while (node != null) {
      final Feature feature = (Feature) node.element();
//        System.out.println ("loop1 range: " + range);
//        System.out.println ("loop1 first base: " + feature.getFirstBase ());
//        System.out.println ("loop1 seen: " + size_of_largest_feature_seen);


//        System.out.println ("loop1: " + feature.getKey ());
//        System.out.println ("got suspect: " + ((StreamFeature) suspect.element()).getKey ());

      if (feature.getLocation ().getFirstBase () >=
          range.getStart () - size_of_largest_feature_seen) {
        suspect = node;
        node = node.left ();
      } else {
        node = node.right ();
      }
    }

//    System.out.println ("got: " + ((Feature) suspect.element()).getKey ());


    // suspect will now be the leftmost RBCell/Feature that could possibly in
    // the range

    // now loop over all the features that could possibly be in the range
    // (ie. those between range.getStart () - size_of_largest_feature_seen and
    // range.getEnd () + size_of_largest_feature_seen)

    node = suspect;

    while (node != null &&
           ((Feature) node.element ()).getFirstBase () <= range.getEnd ()) {
      final Feature current_feature = (Feature) node.element ();

//       System.out.println ("loop: " + current_feature.getKey () + "  " +
//                           current_feature.getFirstBase ());

      if (current_feature.getLocation ().getTotalRange ().overlaps (range)) {
        return_features.add (current_feature);
      }
      node = node.successor ();
    }

//    System.out.println ("getFeaturesInRange () - returning: " +
//                        return_features.size ());

    return return_features;
  }

  /**
   *  Returns an enumeration of the Feature objects in this FeatureTree.  The
   *  returned Enumeration object will generate all features in this object in
   *  turn.
   **/
  public FeatureEnumeration features () {
    return new FeatureEnumerator ();
  }

  /**
   *  Return the reference of the Feature that is immediately after the
   *  argument Feature in the tree or null if there is no next Feature.
   **/
  public Feature getNextFeature (final Feature this_feature) {
    final RBCell this_feature_cell = tree_.find (this_feature, cmp_);

    if (this_feature_cell == null) {
      return null;
    }

    final RBCell next_feature_cell = this_feature_cell.successor ();

    if (next_feature_cell == null) {
      return null;
    } else {
      return (Feature) next_feature_cell.element ();
    }
  }

  /**
   *  An Enumeration of Feature objects.
   **/
  public class FeatureEnumerator implements FeatureEnumeration {
    /**
     *  Create a new FeatureEnumeration that will enumerate the enclosing
     *  DocumentEntry object.  The DocumentEntry object must not be changed
     *  while the enumeration is active.
     **/
    public FeatureEnumerator () {
      enumerator = elements ();
    }

    /**
     *  See the FeatureEnumeration interface for details.
     **/
    public boolean hasMoreFeatures () {
      return enumerator.hasMoreElements ();
    }

    /**
     *  See the FeatureEnumeration interface for details.
     **/
    public Feature nextFeature ()
        throws NoSuchElementException {
//      System.out.println ("FeatureTable.nextFeature ()");
      return (Feature) enumerator.nextElement ();
    }

    /**
     *  This is the underlying enumeration that does all the work for us
     **/
    private CollectionEnumeration enumerator;
  }

  /**
   *  The size of the largest feature we have seen (actually the number may be
   *  smaller than that, but will always be at least as big as the current
   *  largest feature).  This is set by add () and used to improve the speed
   *  of getFeaturesInRange ().  The size of a feature is the number of bases
   *  between the first base of the feature and the last (inclusive).
   **/
  private int size_of_largest_feature_seen = 0;
}


