/* 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/pathsoft/repository/powmap/uk/ac/sanger/pathogens/embl/FeatureTree.java,v 1.3 2002/10/24 12:42:32 kmr Exp $
 **/

package uk.ac.sanger.pathogens.embl;

import collections.*;

import java.util.NoSuchElementException;
import java.util.Vector;

/**
 *  A tree that stores StreamFeature objects ordered with a StreamFeatureComparator
 *  object.
 *
 *  @author Kim Rutherford
 *  @version $Id: FeatureTree.java,v 1.3 2002/10/24 12:42:32 kmr Exp $
 **/

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

    this.comparator = 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;
    }

    getBucket (this_feature).addIfAbsent (this_feature);

    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;
    }

    getBucket (this_feature).add (this_feature);

    super.add (element);
  }

  /**
   *  Wrapper for RBTree.exclude() which removes the Feature from
   *  rbtree_buckets.
   **/
  public synchronized void exclude(Object element) {
    final Feature this_feature = (Feature) element;

    getBucket (this_feature).exclude (this_feature);

    super.exclude (element);
  }

  /**
   *  Wrapper for RBTree.removeOneOf() which removes the Feature from
   *  rbtree_buckets.
   **/
  public synchronized void removeOneOf(Object element) {
    final Feature this_feature = (Feature) element;

    getBucket (this_feature).removeOneOf (this_feature);

    super.removeOneOf (element);
  }

  /**
   *  Find the first node in the tree which has a first base that is >= the
   *  given base.
   **/
  private RBCell findByBase (final RBTree tree, final int base) {
    RBCell node = tree.getRootCell ();

    RBCell suspect = tree.getRootCell ();

    while (node != null) {
      final Feature feature = (Feature) node.element();
      if (feature.getLocation ().getFirstBase () >= base) {
        suspect = node;
        node = node.left ();
      } else {
        node = node.right ();
      }
    }

    return suspect;
  }

  /**
   *  Add to features_in_range all the features in the given RBTree which are
   *  within the given Range.
   *  @param max_feature_length the maximum length in bases of the features in
   *    the RBTree.
   **/
  private void getFeaturesInRange (final RBTree tree,
                                   final FeatureVector features_in_range,
                                   final Range range,
                                   final int max_feature_length) {
    // find the leftmost node in the range
    RBCell node = findByBase (tree,
                              range.getStart () -
                              max_feature_length);

    // node 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)

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

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

  /**
   *  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) {
    // this default size will cover many common cases
    final FeatureVector return_features = new FeatureVector (100);

    for (int i = 0 ;
         i < rbtree_buckets.size () && rbtree_buckets.elementAt (i) != null ;
         ++i) {
      getFeaturesInRange ((RBTree) rbtree_buckets.elementAt (i),
                          return_features, range,
                          (int) Math.pow (BUCKET_MULTIPLIER, i + 1));
    }
    
    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 {
      return (Feature) enumerator.nextElement ();
    }

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

  /**
   *
   **/
  private RBTree getBucket (final Feature feature) {
    final int feature_length =
      feature.getLocation ().getTotalRange ().getCount ();

    final int feature_bucket;

    if (feature_length <= BUCKET_MULTIPLIER) {
      feature_bucket = 0;
    } else {
      // subtract 0.5 to feature_length to make sure that rounding errors go
      // in out favour
      feature_bucket =
        (int) (Math.log (feature_length + 0.5) / Math.log (BUCKET_MULTIPLIER));
    }

    // make the bucket we need and all smaller buckets
    while (rbtree_buckets.size () <= feature_bucket) {
      rbtree_buckets.addElement (new RBTree (comparator));
    }

    return (RBTree) rbtree_buckets.elementAt (feature_bucket);
  }

  /**
   *  The maximum number of buckets.
   **/
  private final int BUCKET_COUNT = 10;

  /**
   *  Bucket 0 will hold a reference to all features that are less than
   *  BUCKET_MULTIPLIER in length.  Bucket 1 will hold those from
   *  BUCKET_MULTIPLIER to BUCKET_MULTIPLIER*BUCKET_MULTIPLIER-1 (inclusive)
   *  in length.  Bucket 2 will hold those from BUCKET_MULTIPLIER^2 to
   *  BUCKET_MULTIPLIER^3-1 (inclusive) in length.  etc.
   **/
  private final int BUCKET_MULTIPLIER = 4;

  /**
   *  See comment above.
   **/
  private final Vector rbtree_buckets = new Vector (BUCKET_COUNT);

  /**
   *  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;

  /**
   *  The Comparator that was passed to the constructor.
   **/
  private Comparator comparator;
}
