/*
 * Copyright (c) 2016 Vivid Solutions.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package org.locationtech.jts.index.bintree;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;


/**
 * An <code>BinTree</code> (or "Binary Interval Tree")
 * is a 1-dimensional version of a quadtree.
 * It indexes 1-dimensional intervals (which may
 * be the projection of 2-D objects on an axis).
 * It supports range searching
 * (where the range may be a single point).
 * This structure is dynamic - 
 * new items can be added at any time,   
 * and it will support deletion of items 
 * (although this is not currently implemented).
 * <p>
 * This implementation does not require specifying the extent of the inserted
 * items beforehand.  It will automatically expand to accommodate any extent
 * of dataset.
 * <p>
 * The bintree structure is used to provide a primary filter
 * for interval queries.  The query() method returns a list of
 * all objects which <i>may</i> intersect the query interval.
 * Note that it may return objects which do not in fact intersect.
 * A secondary filter is required to test for exact intersection.
 * Of course, this secondary filter may consist of other tests besides
 * intersection, such as testing other kinds of spatial relationships.
 * <p>
 * This index is different to the Interval Tree of Edelsbrunner
 * or the Segment Tree of Bentley.
 *
 * @version 1.7
 */
public class Bintree
{
  /**
   * Ensure that the Interval for the inserted item has non-zero extents.
   * Use the current minExtent to pad it, if necessary
   */
  public static Interval ensureExtent(Interval itemInterval, double minExtent)
  {
    double min = itemInterval.getMin();
    double max = itemInterval.getMax();
    // has a non-zero extent
    if (min != max) return itemInterval;

    // pad extent
    if (min == max) {
      min = min - minExtent / 2.0;
      max = min + minExtent / 2.0;
    }
    return new Interval(min, max);
  }

  private Root root;
  /**
  *  Statistics
  *
  * minExtent is the minimum extent of all items
  * inserted into the tree so far. It is used as a heuristic value
  * to construct non-zero extents for features with zero extent.
  * Start with a non-zero extent, in case the first feature inserted has
  * a zero extent in both directions.  This value may be non-optimal, but
  * only one feature will be inserted with this value.
  **/
  private double minExtent = 1.0;

  public Bintree()
  {
    root = new Root();
  }

  public int depth()
  {
    if (root != null) return root.depth();
    return 0;
  }
  public int size()
  {
    if (root != null) return root.size();
    return 0;
  }
  /**
   * Compute the total number of nodes in the tree
   *
   * @return the number of nodes in the tree
   */
  public int nodeSize()
  {
    if (root != null) return root.nodeSize();
    return 0;
  }

  public void insert(Interval itemInterval, Object item)
  {
    collectStats(itemInterval);
    Interval insertInterval = ensureExtent(itemInterval, minExtent);
//int oldSize = size();
    root.insert(insertInterval, item);
    /* DEBUG
int newSize = size();
System.out.println("BinTree: size = " + newSize + "   node size = " + nodeSize());
if (newSize <= oldSize) {
      System.out.println("Lost item!");
      root.insert(insertInterval, item);
      System.out.println("reinsertion size = " + size());
}
    */
  }

  /**
   * Removes a single item from the tree.
   *
   * @param itemInterval the interval of the item to be removed
   * @param item the item to remove
   * @return <code>true</code> if the item was found (and thus removed)
   */
  public boolean remove(Interval itemInterval, Object item)
  {
    Interval insertInterval = ensureExtent(itemInterval, minExtent);
    return root.remove(insertInterval, item);
  }
  
  public Iterator iterator()
  {
    List foundItems = new ArrayList();
    root.addAllItems(foundItems);
    return foundItems.iterator();
  }

  public List query(double x)
  {
    return query(new Interval(x, x));
  }

  /**
   * Queries the tree to find all candidate items which 
   * may overlap the query interval.
   * If the query interval is <tt>null</tt>, all items in the tree are found.
   * 
   * min and max may be the same value
   */
  public List query(Interval interval)
  {
    /**
     * the items that are matched are all items in intervals
     * which overlap the query interval
     */
    List foundItems = new ArrayList();
    query(interval, foundItems);
    return foundItems;
  }

  /**
   * Adds items in the tree which potentially overlap the query interval
   * to the given collection.
   * If the query interval is <tt>null</tt>, add all items in the tree.
   * 
   * @param interval a query interval, or null
   * @param foundItems the candidate items found
   */
  public void query(Interval interval, Collection foundItems)
  {
    root.addAllItemsFromOverlapping(interval, foundItems);
  }

  private void collectStats(Interval interval)
  {
    double del = interval.getWidth();
    if (del < minExtent && del > 0.0)
      minExtent = del;
  }

}
