//RTree.java
//
//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU Lesser General Public
//License as published by the Free Software Foundation; either
//version 2.1 of the License, or (at your option) any later version.
//
//This library 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
//Lesser General Public License for more details.
package rtree;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import rtree.join.IntersectPred;
import rtree.join.PairElmt;
import rtree.join.SweepLine;
/******************************************************************************************************
 * <p><b>1:</b>Call <code>flush</code> after single or multiple inserts. In affect before the application
 * shuts down, the flush method flushes all the rtrees.
 * <p><b>2:</b>Do not run more than one instance of this application. The package
 * cannot handle more than one instance of the application as all the locking
 * is done in the code itself(Monitors) and not on the hard-disk file.
 * <p><b>3:</b>The file name (of the rtree) is case insensitive.
 * <p><b>4:</b>The package is thread proof, i.e you can run as many threads as
 * you want, not worrying about concurrent updations. The package will handle the
 * situation of concurrent reads and writes. Again, it can take care of threads
 * of only one process(Application) and not for more than one process of the  program.
 * <p><b>5:</b>For immediate effects of tree writes, give the thread that needs
 * to write to the file a high priority. This will further speed up tree writes.
 * <p><b>6:</b>Give messages like "Updating, please wait..." to the user when
 * calling any of the methods that modifies the tree as the thread may have to
 * wait on a lock variable.
 * <p><b>7:</b>The package maintains a queue of all threads that need to wait on some lock.
 * The sequence of excecution of the threads is dependent on that queue. All the actions are fired in
 * the order in which they were received. <br><b>In short the tree is perfectly concurrent.</b>
 * <p><b>8:</b>For developers: Always obtain a lock from the <code>lockRead</code> or
 * <code>lockWrite</code> method before going into a <code>public</code> method of the
 * <code>RTree</code> class. Unlock by calling the <code>unlock</code> method.
 * See any existing method to understand the mechanism.
 * <p><b>9:</b>To adjust the cache buffer size, see the <code>Node</code> class documentation.
 * @author Prachuryya Barua
 ******************************************************************************************************/
public class RTree //the tree that would be made
{
  /**<b>Caution:</b> The file name (of the rtree) is case insensitive in spite
     of the fact that this package was developed on a Linux(RH7.0) platform.
  */
  protected String fileName;
  static Map fileList;//the no. of files open
  // static for the other way
  protected FileHdr fileHdr;
  public static CachedNodes chdNodes;
  /**Inner class for the fileList vector - A List of files*/
  class Header
  {
    FileHdr flHdr;
    String fileName;
    Header(FileHdr flH,String name)
    {
      fileName = name;
      flHdr = flH;
    }
  }
  public static void clearCache(){
	  chdNodes = new CachedNodes();
	  fileList = new HashMap();
	  CachedNodes.clearFileNamesMap();
  }
  
  public RTree(String fileName)
    throws  RTreeException
  {
    try{
      this.fileName = fileName;
      if(fileList == null)
        fileList = new HashMap();
      synchronized(fileList){//this may give problem
        if(fileList.get(fileName) != null){
          fileHdr = ((Header)fileList.get(fileName)).flHdr;
          return;
        }
        //a new file
        fileList.put(fileName, new Header(new FileHdr(Node.FREE_LIST_LIMIT, fileName),fileName));
        fileHdr = ((Header)fileList.get(fileName)).flHdr;
        //the cache of nodes - one cache for all the tree files.
        if(chdNodes == null)
          chdNodes = new CachedNodes();
      }
    }
    catch(Exception e){
      throw new  RTreeException("RTree.RTree: " +e.getMessage());
    }
  }
  /**
     This method is used to ask the fileHdr to update itself. This method is package parivate used by
     <code>Pack</code> class only.
  */
  void updateHdr()
    throws RTreeException, IOException, FileNotFoundException, NodeWriteException
  {
    Header tmp = (Header)fileList.get(fileName);
    if(tmp != null){
      //chdNodes.removeAll();//XXX check this out
      fileHdr.update(fileName);
    }
  }

  public Node getReadNode(long index) throws RTreeException
  {
    try{
      return chdNodes.getReadNode(fileHdr.getFile(), fileName, index, fileHdr);
    }catch(Exception e){
      throw new RTreeException ("RTree.getSortedNode : " + e.getMessage());
    }
  }

  public String getFileName()
  {
    return fileName;
  }
  /**
     Another package private method for getting the file header
  */
  public FileHdr getFileHdr()
  {
    return fileHdr;
  }
  public void flush()
    throws RTreeException
  {
    fileHdr.lockWrite();
    try{
      fileHdr.flush();
      chdNodes.flush();
    }catch(Exception e){
      throw new RTreeException(e.getMessage());
    }finally{
      fileHdr.unlock();
    }
  }
  /**
   * Adjust Tree from <b>Guttman the Great</b>.
   * @param Node[] The nodes that was has the new element and also the element
   * that resulted from the splt(if any).i.e N-node[0], NN-nodes[1]
   * Note:- This method is functionally very much coupled with Node.spliNode()
   * @param node The two new nodes caused by split.
   * @param slotIndex The index of the slot of this tree if any, else give NOT_DEFINED.
   * @return The new root (if it was created). If no new root was created then returns null.
   */
  protected Node adjustTree(Node[] nodes, long slotIndex)
    throws  RTreeException
  {
    try{
      //if(nodes[0].getParent() == Node.NOT_DEFINED){//original
      if(nodes[0].getParent() == slotIndex){
        if(nodes[1] != null){//if root is split
          Node newRoot;
          if(fileHdr.isWriteThr())
            newRoot = new Node(fileHdr.getFile(),fileName, slotIndex, Node.NONLEAF_NODE,
                               ((Header)fileList.get(fileName)).flHdr);
          else
            newRoot = chdNodes.getNode(fileHdr.getFile(),fileName, slotIndex, Node.NONLEAF_NODE,
                                       ((Header)fileList.get(fileName)).flHdr, nodes[0]);
          NonLeafElement branchA = new NonLeafElement(nodes[0].getNodeMBR(),nodes[0].getNodeIndex());
          NonLeafElement branchB = new NonLeafElement(nodes[1].getNodeMBR(),nodes[1].getNodeIndex());
          newRoot.insertElement(branchB);
          newRoot.insertElement(branchA);
          return newRoot;
        }
        return null;
      }else{
        //where the new element is inserted
        Node[] insertedNode = new Node[2];
        /*
          set the parent element's MBR equal to the nodeA's MBR.
        */
        //the parent of this node
        Node parentN;
        if(fileHdr.isWriteThr())
          parentN = new Node(fileHdr.getFile(),fileName,nodes[0].getParent(),fileHdr);
        else
          parentN = chdNodes.getNode(fileHdr.getFile(),fileName,nodes[0].getParent(),fileHdr);
        //get the parent element of nodes[0]
        //Integer intValue = new Integer(nodes[0].getNodeIndex());
        int parentElmtIndex = parentN.getElementIndex(nodes[0].getNodeIndex()/*intValue*/);
        //adjust the parent element's MBR
        parentN.modifyElement(parentElmtIndex,nodes[0].getNodeMBR());
        insertedNode[0] = parentN;
        insertedNode[1] = null;
        //if it is an split node add its entry in the parent
        if(nodes[1] != null){
          NonLeafElement elmtNN = new NonLeafElement(nodes[1].getNodeMBR(),nodes[1].getNodeIndex());
          try{//if another insert is possible
            parentN.insertElement(elmtNN);
            insertedNode[0] = parentN;
            insertedNode[1] = null;
          }catch(NodeFullException e){
            insertedNode = parentN.splitNode(elmtNN, Node.NOT_DEFINED);
          }
        }
        return adjustTree(insertedNode, slotIndex);
      }
    }
    catch(Exception e){
      e.printStackTrace();
      throw new  RTreeException("RTree.adjustTree: "+e.getMessage());
    }
  }
  /**
     Pass a <code>LeafElement</code>object.
  */
  public void insert(Element elmt)//Leaf
    throws  RTreeInsertException
  {
    fileHdr.lockWrite();
    Node node = null;
    try{
      //the node into which to insert
      node = chooseLeaf(elmt);
      //System.out.println("Returned:"+node.getNodeIndex());
      Node[] newNodes = new Node[2];
      try{
        node.insertElement(elmt);//if another insert is possible
        newNodes[0] = node;
        newNodes[1] = null;
      }
      //if another insert is not possible
      catch(NodeFullException e){
        newNodes = node.splitNode(elmt, Node.NOT_DEFINED);
      }
      adjustTree(newNodes, Node.NOT_DEFINED);
    }
    catch(Exception e){
      //e.printStackTrace();
      throw new RTreeInsertException("RTree.insert: " + e.getMessage() + " for " +
                                     Thread.currentThread());
    }
    finally{
      fileHdr.unlock();
    }
  }
  /**
     See <b>Guttman the Great</b>.
  */
  private Node chooseLeaf(Element elmt)
    throws  IllegalValueException, RTreeException
  {
    try{
      //get the root node
      long root = fileHdr.getRootIndex();
      Node node = null;
      if(fileHdr.isWriteThr())
        node = new Node(fileHdr.getFile(), fileName,root,fileHdr);
      else
        node = chdNodes.getNode(fileHdr.getFile(), fileName,root,fileHdr);
      switch (node.getElementType()){
      case Node.LEAF_NODE :
        break;
      case Node.NONLEAF_NODE :
        //repeat till you reach a leaf node
        while(true){
          //get the best fitting rect from the node
          Element nextElmt = node.getLeastEnlargement(elmt);
          if(nextElmt.getElementType() == Node.LEAF_NODE)
            break;
          if(fileHdr.isWriteThr())
            node = new Node(fileHdr.getFile(), fileName, nextElmt.getPtr(), fileHdr);
          else
            node = chdNodes.getNode(fileHdr.getFile(), fileName, nextElmt.getPtr(), fileHdr);
        }
        break;
      default :
        throw new IllegalValueException("RTree.chooseLeaf: Node corrupt, Illegal element type in "
                                        +"node");
      }
      return node;
    }
    catch( IllegalValueException e){
      throw new  IllegalValueException("RTree.chooseLeaf: "+e.getMessage());
    }
    catch(Exception e){
      e.printStackTrace();
      throw new  RTreeException("RTree.chooseLeaf: " + e.getMessage());
    }
  }
  /**
     given the leaf element, this method deletes the element from the tree
     compairing its Rect and pointer with the similar entries in the tree.
     @throws ElementNotException when the given element is not found.
  */
  public void delete(LeafElement elmt)
    throws  RTreeException,ElementNotFoundException
  {
    fileHdr.lockWrite();
    long root;
    if(elmt ==  null)
      throw new  RTreeException("RTree.delete: Rect is null");
    try{
      if(fileHdr.isWriteThr())
        chdNodes.removeAll();
      root = fileHdr.getRootIndex();//FileHdr.getRootIndex(fileName);
      //find the leaf that contains the element
      Node delNode;
      if(fileHdr.isWriteThr())
        delNode = findLeaf(new Node(fileHdr.getFile(),fileName,root,fileHdr),elmt);
      else
        delNode = findLeaf(chdNodes.getNode(fileHdr.getFile(),fileName,root,fileHdr),elmt);
      //if found...
      if(delNode != null){
        Element[] elmts = delNode.getAllElements();//all the elements
        int totElmts = delNode.getTotalElements();//total elements
        //index of the desired element
        int childIndex = Node.NOT_DEFINED;
        //find the index of the element that has to be deleted
        for(int i=0;i<totElmts;i++){
          if(elmts[i].getRect().encloses(elmt.getRect()) && (elmts[i].getPtr() == elmt.getPtr())){
            childIndex = i;
            break;
          }
        }
        //strange, but the element is not found....impossible!
        if(childIndex == Node.NOT_DEFINED)
          throw new ElementNotFoundException("RTree.delete: Element not in tree");
        delNode.deleteElement(childIndex, false);
        Stack stack = new Stack();
        condenseTree(delNode,stack);
        //find the root
        Node rootNode;
        if(fileHdr.isWriteThr())
          rootNode = new Node(fileHdr.getFile(),fileName,fileHdr.getRootIndex(),
                              fileHdr);
        else
          rootNode = chdNodes.getNode(fileHdr.getFile(),fileName,fileHdr.getRootIndex(),
                                      fileHdr);
        //if root has only one element then make the node pointed by the
        //element as the new root
        if((rootNode.getTotalElements() == 1) && (rootNode.getElementType() == Node.NONLEAF_NODE)){
          long childPtr= rootNode.getElement(0).getPtr();
          Node child;
          if(fileHdr.isWriteThr())
            child = new Node(fileHdr.getFile(),fileName, childPtr,fileHdr);
          else
            child = chdNodes.getNode(fileHdr.getFile(),fileName, childPtr,fileHdr);
          //set as the new root
          child.setParent(Node.NOT_DEFINED);
          //delete the original parent
          rootNode.deleteNode();
        }
      }
      else
        throw new ElementNotFoundException("RTree.delete: Element not in tree");
    }
    catch(Exception e){
      if(e instanceof ElementNotFoundException)
        throw (ElementNotFoundException)e;
      throw new  RTreeException("RTree.delete: "+e.getMessage());
    }
    finally{
      fileHdr.unlock();
    }
  }

  private void condenseTree(Node node,Stack stack)
    throws Exception
  {
    //is the parent node
    if(node.getParent() == Node.NOT_DEFINED){
      while(!stack.empty()){
        Node nd = (Node)stack.pop();
        List v = trvsRPost(nd,true);
        for(int i=0;i<v.size();i++)
          insert((LeafElement)v.get(i));
      }
      return;
    }
    //get the parent
    Node parentN;
    if(fileHdr.isWriteThr())
      parentN = new Node(fileHdr.getFile(),fileName,node.getParent(),fileHdr);
    else
      parentN = chdNodes.getNode(fileHdr.getFile(),fileName,node.getParent(),fileHdr);
    //get the parent element of 'node'.
    //Integer intValue = new Integer(node.getNodeIndex());
    int parentElmtIdx = parentN.getElementIndex(node.getNodeIndex()/*intValue*/);
    //delete parent element if node has less than 'm' elements
    if(node.getTotalElements() < Node.MIN){
      parentN.deleteElement(parentElmtIdx, false);
      stack.push(node);
    }
    else{//if ok
      parentN.modifyElement(parentElmtIdx,node.getNodeMBR());
    }
    condenseTree(parentN,stack);
  }

  /**Basically it returns the node that <b>may</b> contain the required element.
   */
  private Node findLeaf(Node node,LeafElement elmt)
    throws  IllegalValueException,FileNotFoundException,IOException, NodeReadException, NodeWriteException
  {
    if((node == null) || (elmt == null))
      throw new  IllegalValueException("RTree.findLeaf: Node is null");
    Node nd = null;
    Element[] allElmt = node.getAllElements();
    int totElements = node.getTotalElements();
    //Integer elmtPtr = (Integer)elmt.getPtr();
    for(int i=0; i<totElements; i++){//for every element
      //select elements that overlap
      if(!allElmt[i].getRect().disjoint(elmt.getRect())){//intersect
        //Integer nxtIndex = (Integer)allElmt[i].getPtr();
        if(allElmt[i].getElementType() == Node.NONLEAF_NODE){//non leaf
          if(fileHdr.isWriteThr())
            nd = findLeaf(new Node(fileHdr.getFile(),fileName, allElmt[i].getPtr(),fileHdr),elmt);
          else
            nd = findLeaf(chdNodes.getNode(fileHdr.getFile(),fileName, allElmt[i].getPtr(),fileHdr),elmt);
          if(nd != null)
            return nd;
        }
        else{
          //if any of the leaf element matches then check for exact
          //match with passed element, also check for the pointer
          //value so that elements are excactly match.
          if(allElmt[i].getRect().equals(elmt.getRect()) && (allElmt[i].getPtr() == elmt.getPtr()))
            return node;
        }
      }
    }
    return null;
  }
  //-----------------------------------------------------------------------
  /**
   * Find all index records whose MBR overlap a search MBR 'rect'. - <b>Guttman the Great</b>.
   * An vector is returned. The tree is traversed recusively in post order.
   * @return If the file is empty or search rect. is out of scope then the method returns a zero size vector.
   */
  public List overlaps(Rect rect)
    throws  RTreeException,FileNotFoundException
  {
    fileHdr.lockRead();
    //System.out.println("RTree.overlaps : in for thread " + Thread.currentThread().toString());
    long root;
    if(rect ==  null)
      throw new  RTreeException("RTree.overlaps: Rect is null");
    root = fileHdr.getRootIndex();
    try{
      Node node = chdNodes.getReadNode(fileHdr.getFile(),fileName,root, fileHdr);
      List ret = getRPostOvrlap(node, rect);
      return ret;
      //return(getRPostOvrlap(chdNodes.getNode(fileHdr.getFile(),fileName,root, fileHdr), rect));
    }
    catch(Exception e){
      throw new RTreeException("RTree.overlaps: "+e.getMessage());
    }
    finally{
      //System.out.println("RTree.overlaps : out for thread " + Thread.currentThread().toString());
      fileHdr.unlock();
    }
  }
  /**
     Given any node it traverses a tree in a recursive post order manner
     fetching all overlaping elements in the leaves.
  */
  private List getRPostOvrlap(Node node, Rect rect)
    throws  IllegalValueException,FileNotFoundException,NodeReadException,IOException, NodeWriteException
  {
    if((node == null) || (rect == null))
      throw new  IllegalValueException("RTree.getRPostOvrlap: Node is null");
    List list = new ArrayList();
    //System.out.println("Nodes visited:"+node.getNodeIndex());
    Element[] elmts = node.getAllElements();
    int totElements = node.getTotalElements();
    for(int i=0; i<totElements; i++){//for every element; we can use sweepline algorithm here
      if(elmts[i].getRect().overlaps(rect)){ //select elements that overlap
        if(elmts[i].getElementType() == Node.NONLEAF_NODE){//non leaf
          //Integer ndIndex = (Integer)elmts[i].getPtr();
          list.addAll(getRPostOvrlap(chdNodes.getReadNode(fileHdr.getFile(),fileName,elmts[i].getPtr(),
                                                          fileHdr),rect));
        }else{//if leaf element
          list.add(elmts[i]);
        }
      }
    }
    return list;
  }
  public List overlapsSweep(Rect rect)
    throws  RTreeException,FileNotFoundException
  {
    fileHdr.lockRead();
    long root;
    if(rect ==  null)
      throw new  RTreeException("RTree.overlaps: Rect is null");
    root = fileHdr.getRootIndex();
    try{
      return getRPostOvrlapSweep(chdNodes.getReadNode(fileHdr.getFile(),fileName,root, fileHdr), rect);
    }
    catch(Exception e){
      e.printStackTrace();
      throw new  RTreeException("RTree.overlaps: "+e.getMessage());
    }
    finally{
      fileHdr.unlock();
    }
  }
  private List getRPostOvrlapSweep(Node node, Rect rect)
    throws  IllegalValueException,FileNotFoundException,NodeReadException,IOException, NodeWriteException
  {
    if((node == null) || (rect == null))
      throw new  IllegalValueException("RTree.getRPostOvrlap: Node is null");
    List list = new ArrayList();
    //System.out.println("Nodes visited:"+node.getNodeIndex());
    Element[] elmts = node.getAllElements();
    SweepLine spLine = new SweepLine(new IntersectPred());
    //System.out.println("RTree.getRPostOvrlapSweep : calling sweep");
    List pairs = spLine.intersects(rect, elmts);
    //System.out.println("RTree.getRPostOvrlapSweep : called..and now for every pair");

    for(int i=0; i<pairs.size(); i++){//for every element; we can use sweepline algorithm here
      Element intElmt = ((PairElmt)pairs.get(i)).getRtElmt();
      if(intElmt.getElementType() == Node.NONLEAF_NODE){//non leaf
        list.addAll(getRPostOvrlapSweep(chdNodes.getReadNode(fileHdr.getFile(),fileName,intElmt.getPtr(),
                                                             fileHdr),rect));
        //System.out.println("RTree.getRPostOvrlapSweep : total objects NONLEAF " + list.size());
      }
      else{//if leaf element
        list.add(intElmt);
        //System.out.println("RTree.getRPostOvrlapSweep : total objects LEAF " + list.size());
      }
    }
    //System.out.println("RTree.getRPostOvrlapSweep : checked pairs as well");
    return list;
  }
  //-----------------------------------------------------------------------
  /**
     Find all index records whose MBR intsersect a search MBR 'rect'.
     The diff. betn. this method and 'overlap' method is that this method returns
     all rectangle that is in any way in touch with the test rectangle 'rect'.
     In method 'overlap' only the rects that have common area with 'rect'
     are returned but unlike this method it does not return rects that have
     common side with 'rect'.
     An vector is returned. The tree is traversed recusively in post order.
     @return If the file is empty or search rect. is out of scope then the method returns a zero size vector.
  */
  public List nonDisjoint(Rect rect)
    throws  RTreeException,FileNotFoundException
  {
    fileHdr.lockRead();
    long root;
    if(rect ==  null)
      throw new  RTreeException("RTree.nonDisjoint: Rect is null");
    root = fileHdr.getRootIndex();
    try{
      return(getRPostIntsect(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr), rect));
    }
    catch(Exception e){
      throw new  RTreeException("RTree.nonDisjoint: "+e.getMessage());
    }
    finally{
      fileHdr.unlock();
    }
  }
  /**
     Given any node it traverses a tree in a recursive post order manner
     fetching all intersecting elements in the leaves.
  */
  private List getRPostIntsect(Node node, Rect rect)
    throws  IllegalValueException,FileNotFoundException,NodeReadException,IOException, NodeWriteException
  {
    if((node == null) || (rect == null))
      throw new  IllegalValueException("RTree.getRPostIntsect: Node is null");
    List list = new ArrayList();
    //System.out.println("Nodes visited:"+node.getNodeIndex());
    Element[] elmts = node.getAllElements();
    int totElements = node.getTotalElements();
    for(int i=0; i<totElements; i++){//for every element
      if(!elmts[i].getRect().disjoint(rect)){//select elements that overlap
        if(elmts[i].getElementType() == Node.NONLEAF_NODE){//non leaf
          //Integer ndIndex = (Integer)elmts[i].getPtr();
          list.addAll(getRPostIntsect(chdNodes.getReadNode(fileHdr.getFile(),fileName,elmts[i].getPtr(),
                                                           fileHdr),rect));
        }
        else{//if leaf element
          list.add(elmts[i]);
        }
      }
    }
    return list;
  }

  //-----------------------------------------------------------------------
  /**Find all index records whose MBR are enclosed by the MBR 'rect'.
     An Vector is returned. The tree is traversed recusively in post order.
     @return If the file is empty or search rect. is out of scope then the
     method returns a zero size vector.
  */
  public List containedBy(Rect rect)
    throws  RTreeException,FileNotFoundException
  {
    fileHdr.lockRead();
    long root;
    if(rect ==  null)
      throw new  RTreeException("RTree.containedBy: Rect is null");
    root = fileHdr.getRootIndex();
    try{
      return(getRPostContBy(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr), rect));
    }
    catch(Exception e){
      throw new  RTreeException("RTree.containedBy: "+e.getMessage());
    }
    finally{
      fileHdr.unlock();
    }
  }
  /**
     Given any node it traverses a tree in a recursive post order manner
     fetching all the enclosed(inside 'rect') elements in the leaves.
  */
  private List getRPostContBy(Node node, Rect rect)
    throws  IllegalValueException,FileNotFoundException,NodeReadException,IOException, NodeWriteException
  {
    if((node == null) || (rect == null))
      throw new  IllegalValueException("RTree.getRPostContBy: Node is null");
    List list = new ArrayList();
    Element[] elmts = node.getAllElements();
    int totElements = node.getTotalElements();
    //System.out.println("Nodes visited:"+node.getNodeIndex());
    for(int i=0; i<totElements; i++){//for every element  //encloses
      if(rect.overlaps(elmts[i].getRect())){//select elements that overlap
        if(elmts[i].getElementType() == Node.NONLEAF_NODE){//non leaf
          //Integer ndIndex = (Integer)elmts[i].getPtr();
          list.addAll(getRPostContBy(chdNodes.getReadNode(fileHdr.getFile(),fileName,elmts[i].getPtr(),
                                                          fileHdr),rect));
        }
        else{//if leaf element
          if(elmts[i].getRect().containedBy(rect))
            list.add(elmts[i]);
        }
      }
    }
    return list;
  }
  //-----------------------------------------------------------------------
  /**Find all index records whose MBR are geometrically equal to MBR 'rect'
     An Vector is returned. The tree is traversed recusively in post order.
     @return If the file is empty or search rect. is out of scope then the
     method returns a zero size vector.
  */
  public List equal(Rect rect)
    throws  RTreeException,
    FileNotFoundException
  {
    fileHdr.lockRead();
    long root;
    if(rect ==  null)
      throw new  RTreeException("RTree.equal: Rect is null");
    root = fileHdr.getRootIndex();
    try{
      return(getRPostEqual(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr), rect));
    }
    catch(Exception e){
      throw new  RTreeException("RTree.equal: "+e.getMessage());
    }
    finally{
      fileHdr.unlock();
    }
  }
  /**
     Given any node it traverses a tree in a recursive post order manner
     fetching all the enclosed(inside 'rect') elements in the leaves.
  */
  private List getRPostEqual(Node node, Rect rect)
    throws  IllegalValueException,FileNotFoundException,NodeReadException,IOException, NodeWriteException
  {
    if((node == null) || (rect == null))
      throw new  IllegalValueException("RTree.getRPostEqual: Node is null");
    List list = new ArrayList();
    Element[] elmts = node.getAllElements();
    int totElements = node.getTotalElements();
    //System.out.println("Nodes visited:"+node.getNodeIndex());
    for(int i=0; i<totElements; i++){//for every element  //encloses
      if(rect.overlaps(elmts[i].getRect())){//select elements that overlap
        if(elmts[i].getElementType() == Node.NONLEAF_NODE){//non leaf
          //Integer ndIndex = (Integer)elmts[i].getPtr();
          list.addAll(getRPostEqual(chdNodes.getReadNode(fileHdr.getFile(),fileName,elmts[i].getPtr(),
                                                         fileHdr),rect));

        }
        else{//if leaf element
          if(elmts[i].getRect().equals(rect))
            list.add(elmts[i]);
        }
      }
    }
    return list;
  }
  //-----------------------------------------------------------------------
  /**Find all index records whose MBR meet on the sides of MBR 'rect'.
     An Vector is returned. The tree is traversed recusively in post order.
     @return If the file is empty or search rect. is out of scope then the
     method returns a zero size vector.
  */
  public List meet(Rect rect)
    throws  RTreeException,FileNotFoundException
  {
    fileHdr.lockRead();
    long root;
    if(rect ==  null)
      throw new  RTreeException("RTree.meet: Rect is null");
    root = fileHdr.getRootIndex();
    try{
      return(getRPostMeet(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr),  rect));
    }
    catch(Exception e){
      throw new  RTreeException("RTree.meet: "+e.getMessage());
    }
    finally{
      fileHdr.unlock();
    }
  }
  /**
     Given any node it traverses a tree in a recursive post order manner
     fetching all the enclosed(inside 'rect') elements in the leaves.
  */
  private List getRPostMeet(Node node,Rect rect)
    throws  IllegalValueException, FileNotFoundException,NodeReadException,IOException, NodeWriteException
  {
    if((node == null) || (rect == null))
      throw new  IllegalValueException("RTree.meet: Node is null");
    List list = new ArrayList();
    Element[] elmts = node.getAllElements();
    int totElements = node.getTotalElements();
    //System.out.println("Nodes visited:"+node.getNodeIndex());
    for(int i=0; i<totElements; i++){//for every element  //encloses
      if(!rect.disjoint(elmts[i].getRect())){//select elements that overlap
        if(elmts[i].getElementType() == Node.NONLEAF_NODE){//non leaf
          //Integer ndIndex = (Integer)elmts[i].getPtr();
          list.addAll(getRPostMeet(chdNodes.getReadNode(fileHdr.getFile(),fileName, elmts[i].getPtr() ,
                                                        fileHdr),rect));
        }
        else{//if leaf element
          if(elmts[i].getRect().meet(rect))
            list.add(elmts[i]);
        }
      }
    }
    return list;
  }
  //----------------------------------------------------------------------
  /**Find all index records whose MBR enclose/contain the MBR 'rect'.
     An Vector is returned. The tree is traversed recusively in post order.
     @return If the file is empty or search rect. is out of scope then the
     method returns a zero size vector.
  */
  public List contains(Rect rect)
    throws  RTreeException,FileNotFoundException
  {
    fileHdr.lockRead();
    long root;
    if(rect ==  null)
      throw new  RTreeException("RTree.contains: Rect is null");
    root = fileHdr.getRootIndex();
    try{
      return(getRPostContains(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr),rect));
    }
    catch(Exception e){
      throw new  RTreeException("RTree.contains: " +e.getMessage());
    }
    finally{
      fileHdr.unlock();
    }
  }
  /**
     Given any node it traverses a tree in a recursive post order manner
     fetching all the enclosed(inside 'rect') elements in the leaves.
  */
  private List getRPostContains(Node node, Rect rect)
    throws  IllegalValueException,FileNotFoundException,NodeReadException,IOException, NodeWriteException
  {
    if((node == null) || (rect == null))
      throw new  IllegalValueException("RTree.getRPostContains: Node is null");
    List list = new ArrayList();
    Element[] elmts = node.getAllElements();
    int totElements = node.getTotalElements();
    //System.out.println("Nodes visited:"+node.getNodeIndex());
    for(int i=0; i<totElements; i++){//for every element  //encloses
      if(rect.overlaps(elmts[i].getRect())){//select elements that overlap
        if(elmts[i].getElementType() == Node.NONLEAF_NODE){//non leaf
          //Integer ndIndex = (Integer)elmts[i].getPtr();
          list.addAll(getRPostContains(chdNodes.getReadNode(fileHdr.getFile(),fileName,elmts[i].getPtr(),
                                                            fileHdr),rect));
        }
        else{//if leaf element
          if(elmts[i].getRect().contains(rect))
            list.add(elmts[i]);
        }
      }
    }
    return list;
  }
  //-----------------------------------------------------------------------
  /**
     Returns all the elements traversing the tree recursively in <b>postorder</b>
  */
  public List getAllElements()
    throws  RTreeException, FileNotFoundException
  {
    //fileHdr.enter(Node.READ);
    fileHdr.lockRead();
    //System.out.println("RTree.getAllElements : in for thread " + Thread.currentThread().toString());
    long root;
    root = fileHdr.getRootIndex();
    try{
      return(trvsRPost(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr),false));
    }
    catch(Exception e){
      //e.printStackTrace();
      throw new RTreeException("RTree.getAllElements: " +e.getMessage());
    }
    finally{
      //fileHdr.leave();
      //System.out.println("RTree.getAllElements : out for thread " + Thread.currentThread().toString());
      fileHdr.unlock();
    }
  }
  /**
     Traverses the tree recursively in post order.
     The second parameter is for the delete method. As it goes through the nodes
     returning the leafelements it also deletes the nodes.
     This feature is not required for any other methods hence set <code>del</code> as
     <code>false</code> for all other cases.
  */
  private List trvsRPost(Node node,boolean del)
    throws  IllegalValueException,FileNotFoundException,NodeReadException,IOException, NodeWriteException
  {
    if((node == null))
      throw new  IllegalValueException("RTree.getRPostOvrlap: Node is null");
    List list = new ArrayList();
    Element[] elmts = node.getAllElements();
    int totElements = node.getTotalElements();
    //System.out.println("Nodes visited:"+node.getNodeIndex())
    for(int i=0; i<totElements; i++){//for every element
      //non leaf
      if(elmts[i].getElementType()==Node.NONLEAF_NODE){
        //Integer ndIndex = (Integer)elmts[i].getPtr();
        list.addAll(trvsRPost(chdNodes.getReadNode(fileHdr.getFile(),fileName,elmts[i].getPtr(), fileHdr),
                              del));
      }
      else{//if leaf element
        list.add(elmts[i]);
      }
    }
    if(del)
      node.deleteNode();
    return list;
  }
  //-------------------------------------------------------------------
  /**Prints the tree in recursively <b>preorder</b> manner.
   */
  public void printTree()
    throws  RTreeException,FileNotFoundException
  {
    fileHdr.lockRead();
    long root;
    root = fileHdr.getRootIndex();
    try{
      trvsRPrePrint(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr));
    }
    catch(Exception e){
      throw new  RTreeException("RTree.printTree: "+e.getMessage());
    }
    finally{
      fileHdr.unlock();
    }
  }
  /**
     This method will return MBR of the whole tree.
  */
  public Rect getTreeMBR()
  {
    fileHdr.lockRead();
    long root;
    try{
      root = fileHdr.getRootIndex();
      Node node = chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr);
      return node.getNodeMBR();
    }
    catch(Exception e){
      return null;
    }
    finally{
      fileHdr.unlock();
    }
  }
  public void deleteAllElements()
    throws RTreeException
  {
    fileHdr.lockWrite();
    try{
      chdNodes.removeAll();
      fileHdr.resetHeader();
    }catch(Exception e){
      throw new RTreeException("RTree.deleteAllElements : " + e.getMessage());
    }
    finally{
      fileHdr.unlock();
    }
  }

  /**
     Traverses the tree recursively in post order.
  */
  private void trvsRPrePrint(Node node)
    throws  IllegalValueException,
    FileNotFoundException,NodeReadException,
    IOException,NodeWriteException
  {
    if((node == null))
      throw new  IllegalValueException("RTree.trvsRPrePrint: Node is null");
    Element[] elmts = node.getAllElements();
    int totElements = node.getTotalElements();
    System.out.println(node.toString());
    for(int i=0; i<totElements; i++){//for every element
      if(elmts[i].getElementType() == Node.NONLEAF_NODE){//non leaf
        //Integer ndIndex = (Integer)elmts[i].getPtr();
        trvsRPrePrint(chdNodes.getReadNode(fileHdr.getFile(),fileName,elmts[i].getPtr(),fileHdr));
      }
    }
    return;
  }
  //-----------------------------------------------------------------------
  /**
     <b>Read Me Well.</b><br>
     The Nearest Neighbour(NN) search from Roussopoulos and Cheung.
     <br>The returned leaf elements are sorted in ascending order of their
     differences from the query point. <br><b>If the no. of leaf elements found
     are less then <code>n</code>, then the rest of the values in the returend array
     are null.<br></b>Give the limit(distance) within which you want to search.
     The limit is in the same unit as the coordinates of the MBRs. <p>There may
     be some objects in the tree which come within the region but are so big
     that their size makes them extend much beyond the given region.
     Such objects would also be considered.
     If you do not want any limit then give <code>Long.MAX_VALUE</code> in <code>range</code>.
     You can also search for presence of any object at the given point by giving
     <code>range</code> as <code>0</code>.
     Also required are the no. of objects that need to be fetched(N-Nearest objects).
     <br><b>The value of <code>range</code> is actually square of the distance you want.
     Therefor if you want to search in an area of 10cm, give the <code>range</code> as 100.</b>
     @param pt the query point.
     @param range the region within which you want to search.
     @param n the number of objects required.
     @return the leaf elements found near the point.The length of the returned
     array would be equal to <code>n</code>.
  */
  public ABL[] nearestSearch(Point pt,long range,int n)
    throws  RTreeException, IllegalValueException
  {
    fileHdr.lockRead();
    if((pt == null) || (range < 0) || (n <= 0))
      throw new  IllegalValueException("RTree.nearestSearch: Illegal arguments");
    try{
      long root = fileHdr.getRootIndex();
      ABL[] elmts = new ABL[n];
      Nearest nrstDist = new Nearest();
      nrstDist.value = range;
      return INNSearch(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr),pt, elmts,nrstDist);
    }
    catch( IllegalValueException e){
      throw new IllegalValueException(e.getMessage());
    }
    catch(Exception e){
      throw new RTreeException("RTree.nearestSearch: " +e.getMessage());
    }
    finally{
      fileHdr.unlock();
    }
  }
  /**
     Improved Nearest Neighbour Search - Cheung, theory Roussopoulos
  */
  private ABL[] INNSearch(Node node, Point pt,ABL[] nrstElements,Nearest nrstDist)
    throws  IllegalValueException,FileNotFoundException,IOException,NodeReadException,
    RTreeException,NodeWriteException
  {
    if((node == null))
      throw new  IllegalValueException("RTree.INNSearch: Node is null");
    Element[] elmts = node.getAllElements();
    int totElements = node.getTotalElements();

    if(totElements == 0)//no elements
      return null;
    //System.out.println("In Node:"+node.getNodeIndex());
    //if leaf
    if(node.getElementType() == Node.LEAF_NODE){
      //at leaf level compute distance to actual objects
      for(int i=0; i<totElements; i++){
        long lfDist = Rect.minDist(pt,elmts[i].
                                   getRect());
        if(lfDist <= nrstDist.value){//assign the new nearest value
          //Integer index = (Integer)elmts[i].getPtr();
          ABL newElmt =  new ABL(new LeafElement(elmts[i].getRect(),elmts[i].getPtr()), lfDist);
          insertArray(nrstElements,newElmt,nrstDist);
        }
      }
      return nrstElements;
    }
    //if non-leaf
    else{
      //the Active Branch List
      ABL[] abl = new ABL[totElements];
      //calculate MINDIST and assign to the ABL array
      for(int i=0; i<abl.length; i++){
        //Integer ndIndex = (Integer)elmts[i].getPtr();
        abl[i] = new ABL(new NonLeafElement(elmts[i].getRect(),elmts[i].getPtr()),
                         Rect.minDist(pt,elmts[i].getRect()));
      }
      //sort the ABL
      abl[0].mergeSort(abl);
      //upward prun the branch
      for(int i=0; i<abl.length; i++){
        if(abl[i].minDist <= nrstDist.value){
          //Integer ndIndex = (Integer)abl[i].element.getPtr();
          nrstElements = INNSearch(chdNodes.getReadNode(fileHdr.getFile(),fileName,elmts[i].getPtr(),
                                                        fileHdr),pt,nrstElements,nrstDist);
        }
      }
      return nrstElements;
    }//else
  }//INNSearch
  /**
     A utility method for the search algorithm that inserts an element into the
     the correct position and adjusts the array accordingly.
     Assumes that the new 'element' is lesser than the last element of 'arr'.
  */
  protected void insertArray(ABL[] arr,ABL elmt,Nearest nrstDist)
    throws  RTreeException
  {
    try{
      ABL temp=null,temp1=null;
      //int nNearest = 0;
      int i=arr.length-1;
      //if the first or the only element to enter
      if((arr[0] == null) || (arr.length == 1))
        arr[0] = (ABL)elmt.clone();
      else{
        while((i < arr.length) && (i >= 0)){
          if(arr[--i] != null){
            if((arr[i].minDist <= elmt.minDist) ||
               ((i == 0)&&(elmt.minDist<arr[i].minDist))){//special case
              if((i==0)&&(elmt.minDist<arr[i].minDist))//special case
                i--;
              temp = elmt;//the element to insert
              //shift all elements one place right
              while(++i < (arr.length)){
                if(arr[i] != null){//if next is not null
                  temp1 = arr[i];//backup
                  arr[i] = (ABL)temp.clone();//replace
                  temp = temp1;//for the next loop
                }
                else{
                  arr[i] = (ABL)temp.clone();
                  break;
                }
              }
              break;
            }
          }
        }
      }

      //if last element is null(arr is not full) then that means that more
      //elements can be accepted in the given region. Thus do not change
      //the min. distance. Else the last element will be the min. - every
      //element that comes after must have a value less than that.
      if(arr[arr.length-1] != null)
        nrstDist.value = arr[arr.length-1].minDist;
    }
    catch(Exception e){
      throw new  RTreeException("RTree.insertArray: "+e.getMessage());
    }
  }
  //-------------------------------------------------------------------------
  /**Another version of the <code>nearestSearch</code> method(not overloaded). This
     method finds all the objects within the given range.<b> To understand
     this method please refer to the other <code>nearestSearch</code> method.</b>
     <br>When the no. of objects required is less then a few thousands, this
     method would be many <b>times</b> slower than the above method.
     If possible use the other method.
     @return vector of ABL objects within the given range.
  */
  public List nearestSearch(Point pt,long range)
    throws  RTreeException, IllegalValueException
  {
    fileHdr.lockRead();
    if((pt == null) || (range < 0))
      throw new  IllegalValueException("RTree.nearestSearch: "
                                       +"Point null or int less than one");
    try{
      long root = fileHdr.getRootIndex();
      List elmts = new ArrayList();
      elmts = INNSearch(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr),pt,elmts,range);
      return elmts;
    }
    catch( IllegalValueException e){
      throw new  IllegalValueException(e.getMessage());
    }
    catch(Exception e){
      throw new  RTreeException("RTree.nearestSearch: " + e.getMessage());
    }
    finally{
      fileHdr.unlock();
    }
  }
  private List INNSearch(Node node, Point pt, List  nrstElements,long nrstDist)
    throws  IllegalValueException, FileNotFoundException,IOException,NodeReadException, Exception
  {
    if((node == null))
      throw new  IllegalValueException("RTree.INNSearch: Node is null");
    Element[] elmts = node.getAllElements();
    int totElements = node.getTotalElements();

    if(totElements == 0)//no elements
      return null;
    //System.out.println("In Node:"+node.getNodeIndex());
    //if leaf
    if(node.getElementType() == Node.LEAF_NODE){
      //at leaf level compute distance to actual objects
      for(int i=0; i<totElements; i++){
        long lfDist = Rect.minDist(pt,elmts[i].
                                   getRect());
        if(lfDist <= nrstDist){//assign the new nearest value
          //Integer index = (Integer)elmts[i].getPtr();
          ABL newElmt =  new ABL(new LeafElement(elmts[i].getRect(),elmts[i].getPtr()),lfDist);
          //insert into vector
          int j=0;
          //the following line is doubtful
          for(j = 0;
              (j < nrstElements.size()) &&
                (((ABL)(nrstElements.get(j))).minDist<=newElmt.minDist);
              j++);
          nrstElements.add(j, newElmt);
        }
      }
      return nrstElements;
    }
    //if non-leaf
    else{
      //the Active Branch List
      ABL[] abl = new ABL[totElements];
      //calculate MINDIST and assign to the ABL array
      for(int i=0; i<abl.length; i++){
        //Integer ndIndex = (Integer)elmts[i].getPtr();
        abl[i] = new ABL(new NonLeafElement(elmts[i].getRect(),elmts[i].getPtr()),
                         Rect.minDist(pt,elmts[i].getRect()));
      }
      //sort the ABL
      abl[0].mergeSort(abl);
      //upward prun the branch
      for(int i=0; i<abl.length; i++){
        if(abl[i].minDist <= nrstDist){
          //Integer ndIndex = (Integer)abl[i].element.getPtr();
          nrstElements = INNSearch(chdNodes.getReadNode(fileHdr.getFile(),fileName,elmts[i].getPtr(),
                                                        fileHdr), pt,nrstElements,nrstDist);
        }
      }
      return nrstElements;
    }//else
  }//NNSearch

  /**
     this class is a wrapper class for a <code>long</code> value. There is no way to overwrite
     the value contained in the <code>java.lang.Long</code> wrapper class.
  */
  protected class Nearest//a wrapper class for long
  {
    public long value;
  }
  //--------------------------------get height------------------------------
  /**Upto 5 lakh objectes we can have a maximum height of 3
     TODO : Calculate other levels as well. One may need to know whether the tree is packed or unpacked for
     this.
     `*/
  public synchronized int getHeight()
  {
    int totNodes = fileHdr.getTotalNodes();
    if(totNodes <= 1)
      return 1;
    else if(totNodes <= 170)
      return 2;
    else //if((totNodes > (84*84)) && (totNodes < (169*169+1)))
      return 3;
    //    else
    //return 4;
  }
}
/* TODO:
   Immediate Improvements
   1)Take the common code from each query methods and put them in a single method.
   2)Make a way of retuning a Integer objects other than LeafElement Objects.
*/
