import java.io.*;

/**
 *  Virtual NBlock pool (cache of blocks fetched from disk file).
 *   @author  Josef.Pelikan@mff.cuni.cz
 *   @version 0.007 (2001/06/25)
 *   @since   0.007
 */
class NBlockPool {

    /** Size of one NBlock (in 32-bit integer items). */
    protected static final int N_BLOCK_SIZE = 1024;

    /** Size of one NBlock (in bytes). */
    protected static final int BYTE_BLOCK_SIZE = N_BLOCK_SIZE << 2;

    /** Important prefix of each NBlock (number of items). */
    protected static final int IMPORTANT_PREFIX = N_BLOCK_SIZE;

    /** Minimum size of NBlock pool. */
    protected static final int MIN_POOL_SIZE = 16;

    /** Working raw file. */
    protected RandomAccessFile f;

    /** Array of fetched NBlocks. */
    protected int[] pool;

    /** Raw file access buffer. */
    protected byte[] fBuffer;

    /** Pool size (number of NBlocks). */
    protected int size;

    /** NBlocks fetched in memory (<code>-1</code> for not used item). */
    protected int[] fetched;

    /** Change mask. */
    protected boolean[] changed;

    /** Serial number of the last access. */
    protected int[] accessed;

    /** Actual serial number. */
    protected int serial;

    /** Lock count for fetched NBlocks. */
    protected int[] locked;

    /** Number of used NBlocks (disk file size). */
    protected int used;

    /** Statistics: number of NBlock reads. */
    public int reads;

    /** Statistics: number of NBlock writes. */
    public int writes;

    /** Statistics: number of NBlock flushes. */
    public int flushes;

    /**
     *  Synchronizes the given NBlock.
     *   @param  b Cache number of the NBlock.
     */
    protected void syncBuffer ( int b ) {
        if ( fetched[b] < 0 || !changed[b] ) return;
        int i, j;
        int bound = b * N_BLOCK_SIZE + IMPORTANT_PREFIX;
        for ( i = bound - IMPORTANT_PREFIX, j = 0; i < bound; ) {
            fBuffer[j++] = (byte)(pool[i  ]      );
            fBuffer[j++] = (byte)(pool[i  ] >>  8);
            fBuffer[j++] = (byte)(pool[i  ] >> 16);
            fBuffer[j++] = (byte)(pool[i++] >> 24);
            }
        try {
            f.seek(fetched[b]*BYTE_BLOCK_SIZE);
            f.write(fBuffer);
            changed[b] = false;
            }
        catch ( IOException e ) {}
        writes++;
    }

    /**
     *  Reads the given NBlock from disk.
     *   @param  i Order number of the NBlock.
     *   @return Cache index of the cached NBlock.
     */
    protected int fetchBlock ( int i ) {
            // assert: the block is not fetched in memory!
        int b = getFreeBuffer();
        int bi = b * N_BLOCK_SIZE;          // index into the cache (pool[])
        try {
            f.seek(i*BYTE_BLOCK_SIZE);
            f.read(fBuffer);
            }
        catch ( IOException e ) {
            return -1;
            }
        int k, l;
        int bound = bi + IMPORTANT_PREFIX;
        for ( k = bi, l = 0; k < bound; l += 4 )
            pool[k++] = (((int)fBuffer[l  ])       & 0x000000FF) +
                        (((int)fBuffer[l+1] <<  8) & 0x0000FF00) +
                        (((int)fBuffer[l+2] << 16) & 0x00FF0000) +
                         ((int)fBuffer[l+3] << 24);
        reads++;
        fetched[b] = i;
        changed[b] = false;
        accessed[b] = serial++;
        locked[b] = 0;
        return bi;
    }

    /**
     *  Releases one cache buffer.
     *   @return Cache order of the free buffer.
     */
    protected int releaseBuffer () {
        int age = serial;
        int b, oldest = -1;
        for ( b = 0; b < size; b++ )
            if ( fetched[b] < 0 ) return b;
            else
                if ( locked[b] == 0 && accessed[b] < age ) {
                    age = accessed[b];
                    oldest = b;
                    }
        if ( oldest < 0 ) return -1;
        syncBuffer(oldest);
        fetched[oldest] = -1;
        flushes++;
        return oldest;
    }

    /**
     *  Finds one free block slot.
     *   @return Cache number of the free NBlock.
     */
    protected int getFreeBuffer () {
        int b = 0;
        while ( b < size && fetched[b] >= 0 ) b++;
        if ( b >= size ) b = releaseBuffer();
        return b;
    }

    /**
     *  Initializing constructor.
     *   @param  fileName File-name to open/create.
     *   @param  poolSize Poole size in NBlocks.
     */
    public NBlockPool ( String fileName, int poolSize ) {
        try {
            f = new RandomAccessFile(fileName,"rw");
            }
        catch ( FileNotFoundException e ) {
            f = null;
            return;
            }
        size = (poolSize < MIN_POOL_SIZE) ? MIN_POOL_SIZE : poolSize;
        pool = new int[size*N_BLOCK_SIZE];
        fetched = new int[size];
        int i;
        for ( i = 0; i < size; )
            fetched[i++] = -1;
        changed = new boolean[size];
        accessed = new int[size];
        serial = 0;
        locked = new int[size];
        try {
            used = (int)(f.length() / BYTE_BLOCK_SIZE);
            }
        catch ( IOException e ) {}
        fBuffer = new byte[BYTE_BLOCK_SIZE];
        reads = writes = flushes = 0;
    }

    /**
     *  Gets array of cached NBlocks.
     *   @return <code>int[]</code> array to which all cache indices will point.
     */
    public int[] getCache () {
        return pool;
    }

    /**
     *  Fetch the given NBlock from disk to memory cache.
     *   @param  i Order number of the NBlock.
     *   @return Pool cache index (index into the <code>getCache()</code> array).
     */
    public int getBlock ( int i ) {
        if ( i >= used ) return -1;
        int b;
        for ( b = 0; b < size; b++ )
            if ( fetched[b] == i ) {
                accessed[b] = serial++;
                return( b * N_BLOCK_SIZE );
                }
        return fetchBlock(i);
    }

    /**
     *  Marks the given NBlock as changed.
     *   @param  i Order number of the NBlock.
     */
    public void changeBlock ( int i ) {
        int b;
        for ( b = 0; b < size; b++ )
            if ( fetched[b] == i ) {
                changed[b] = true;
                accessed[b] = serial++;
                return;
                }
    }

    /**
     *  Adds new block to the AKD-file.
     *   @return Order number of the new NBlock.
     */
    public int newBlock () {
        int b = getFreeBuffer();
        int i = b * N_BLOCK_SIZE;
        int bound = i + N_BLOCK_SIZE;
        while ( i < bound ) pool[i++] = 0;
        fetched[b] = used;
        changed[b] = true;
        accessed[b] = serial++;
        locked[b] = 0;
        syncBuffer(b);
        return used++;
    }

    /**
     *  Synchronizes the whole cache.
     */
    public void sync () {
        int b;
        for ( b = 0; b < size; b++ ) syncBuffer(b);
    }

    /**
     *  Closes the NBlock pool.
     */
    public void close () {
        if ( f != null ) {
            sync();
            try {
                f.close();
                }
            catch ( IOException e ) {}
            f = null;
            }
    }

}

/**
 *  Adaptive K-D-tree stored on disk.
 *   @author  Josef.Pelikan@mff.cuni.cz
 *   @version 0.007 (2001/06/25)
 *   @since   0.007
 */
public class AKDTree {

    /** Default size of <code>NBlockPool</code>. */
    protected static final int DEFAULT_POOL_SIZE = 64;

    /** Number of point records in the leaf block (bucket size). */
    protected static final int RECORDS_IN_BLOCK = 8;

    /** Offset of &quot;used&quot; in the leaf block. */
    protected static final int BLOCK_USED = 0;

    /** Offset of &quot;X-min&quot; in the leaf block. */
    protected static final int BLOCK_XMIN = 1;

    /** Offset of &quot;X-max&quot; in the leaf block. */
    protected static final int BLOCK_XMAX = 2;

    /** Offset of &quot;Y-min&quot; in the leaf block. */
    protected static final int BLOCK_YMIN = 3;

    /** Offset of &quot;Y-max&quot; in the leaf block. */
    protected static final int BLOCK_YMAX = 4;

    /** Offset of &quot;Sum(X<sub>i</sub>)&quot; in the leaf block. */
    protected static final int BLOCK_SUM_X = 5;

    /** Offset of &quot;Sum(X<sub>i</sub><sup>2</sup>)&quot; in the leaf block. */
    protected static final int BLOCK_SUM_XX = 6;

    /** Offset of &quot;Sum(Y<sub>i</sub>)&quot; in the leaf block. */
    protected static final int BLOCK_SUM_Y = 7;

    /** Offset of &quot;Sum(Y<sub>i</sub><sup>2</sup>)&quot; in the leaf block. */
    protected static final int BLOCK_SUM_YY = 8;

    /** Offset of the first point record in the leaf block. */
    protected static final int BLOCK_RECORD_0 = 10;

    /** Size of one point record in 32-bit integer items. */
    protected static final int RECORD_SIZE = 2;

    /** Offset of &quot;X-coordinate&quot; in the point record. */
    protected static final int RECORD_X = 0;

    /** Offset of &quot;Y-coordinate&quot; in the point record. */
    protected static final int RECORD_Y = 1;

    /** Number of inner nodes in the NBlock record. */
    protected static final int NODES_IN_BLOCK = 256;

    /** Size of the inner node in 32-bit integer items. */
    protected static final int NODE_SIZE = 4;

    /** Offset of &quot;flags&quot; item in the inner node. */
    protected static final int NODE_FLAGS = 0;

    /** &quot;Flag X&quot;: division in x-axis. */
    protected static final int NODE_FLAG_X = 0x0001;

    /** Offset of &quot;division value&quot; in the inner node. */
    protected static final int NODE_DIV = 1;

    /** Offset of &quot;left son&quot; in the inner node. */
    protected static final int NODE_LEFT = 2;

    /** Offset of &quot;right son&quot; in the inner node. */
    protected static final int NODE_RIGHT = 3;

    /** Handle of the root node. */
    protected static final int ROOT_NODE = 0;

    /** Null-handle. */
    protected static final int NODE_NULL = 0;

    /** Working NBlock pool. */
    protected NBlockPool pool;

    /** NBlock-pool cache array (accelerator). */
    protected int[] cache;

    /** Handle to the last allocated KD-tree node. */
    protected int lastNode;

    /** Output value (flags) for <code>splitBucket()</code>. */
    protected int splitFlags;

    /** Output value (div) for <code>splitBucket()</code>. */
    protected int splitDiv;

    /** Output value (bucket handle) for <code>insertIntoBucket()</code>. */
    protected int insertBucket;

    /**
     *  Gets pointer to the given node.
     *   @param  node Node handle.
     *   @return Index to the pool cache array.
     */
    protected final int getNode ( int node ) {
        return( pool.getBlock( node / NODES_IN_BLOCK ) +
                (node % NODES_IN_BLOCK) * NODE_SIZE );
    }

    /**
     *  Marks the given node as changed.
     *   @param  node Node handle.
     */
    protected final void changeNode ( int node ) {
        pool.changeBlock( node / NODES_IN_BLOCK );
    }

    /**
     *  Gets pointer to the given bucket.
     *   @param  bucket Handle to the bucket-record (&lt; 0).
     *   @return Index to the pool cache array.
     */
    protected final int getBucket ( int bucket ) {
        return pool.getBlock(-bucket);
    }

    /**
     *  Allocates a new bucket-record.
     *   @return Handle to the new bucket-record (&lt; 0).
     */
    protected int newBucket () {
        int block = pool.newBlock();
        pool.changeBlock(block);
        leafNodes++;
        return -block;
    }

    /**
     *  Allocates a new inner KD-tree node.
     *   @param  flags Node flags.
     *   @param  div Division flag.
     *   @param  left Handle to the left son.
     *   @param  right Handle to the right son.
     *   @return Handle to the new inner node.
     */
    protected int newNode ( int flags, int div, int left, int right ) {
        if ( (++lastNode % NODES_IN_BLOCK) == 0 ) // allocate a new node-block
            lastNode = pool.newBlock() * NODES_IN_BLOCK;
        int n = getNode(lastNode);
        cache[n+NODE_FLAGS] = flags;
        cache[n+NODE_DIV]   = div;
        cache[n+NODE_LEFT]  = left;
        cache[n+NODE_RIGHT] = right;
        innerNodes++;
        return lastNode;
    }

    /**
     *  Reinitializes the given sub-tree: finds handle to the last inner-node,
     *  initializes statistic counters.
     *   @param  node Handle to the sub-tree root.
     */
    protected void findLastNode ( int node ) {
        if ( node == NODE_NULL ) return;
        if ( node < 0 ) {                   // leaf node (bucket)
            leafNodes++;
            return;
            }
        if ( node > lastNode ) lastNode = node;
        int n = getNode(node);
        innerNodes++;
        findLastNode(cache[n+NODE_LEFT]);
        findLastNode(cache[n+NODE_RIGHT]);
    }

    /**
     *  Looks for the given point in the sub-tree.
     *   @param  node Handle to the sub-tree root.
     *   @param  x X-coordinate of the point.
     *   @param  y Y-coordinate of the point.
     *   @return <code>true</code> if the given point is present.
     */
    protected boolean searchNode ( int node, int x, int y ) {
        int n = getNode(node);
        if ( (cache[n+NODE_FLAGS] & NODE_FLAG_X) != 0 )
                // horizontal division
            node = (x < cache[n+NODE_DIV]) ? cache[n+NODE_LEFT] : cache[n+NODE_RIGHT];
        else
                // vertical division
            node = (y < cache[n+NODE_DIV]) ? cache[n+NODE_LEFT] : cache[n+NODE_RIGHT];
        if ( node == NODE_NULL ) return false;
        if ( node > 0 ) return searchNode(node,x,y);
        return searchBlock(-node,x,y);          // leaf node
    }

    /**
     *  Looks for the given point in the leaf node (bucket).
     *   @param  block Handle to the leaf node (bucket) .. must be &gt; 0.
     *   @param  x X-coordinate of the point.
     *   @param  y Y-coordinate of the point.
     *   @return <code>true</code> if the given point is present.
     */
    protected boolean searchBlock ( int block, int x, int y ) {
        int b = pool.getBlock(block);
        int i;
        if ( b < 0 || (i = cache[b+BLOCK_USED]) <= 0 ) return false;
        int ptr = b + BLOCK_RECORD_0;
        for ( ; i-- > 0 ; ptr += RECORD_SIZE )
            if ( cache[ptr+RECORD_X] == x && cache[ptr+RECORD_Y] == y ) return true;
        return false;
    }

    /**
     *  Inserts the point in the given sub-tree.
     *   @param  node Handle to the sub-tree root.
     *   @param  x X-coordinate of the point.
     *   @param  y Y-coordinate of the point.
     *   @return <code>true</code> if the given point was inserted.
     */
    protected boolean insertNode ( int node, int x, int y ) {
        int n = getNode(node);
        int where;
        if ( (cache[n+NODE_FLAGS] & NODE_FLAG_X) != 0 )
                // horizontal division
            where = n + ((x < cache[n+NODE_DIV]) ? NODE_LEFT : NODE_RIGHT);
        else
                // vertical division
            where = n + ((y < cache[n+NODE_DIV]) ? NODE_LEFT : NODE_RIGHT);
        int old = cache[where];
        if ( old == NODE_NULL ) {           // insert a new leaf node
            old = cache[where] = newBucket();
            changeNode(node);
            }
        if ( old > 0 ) return insertNode(old,x,y);
            // leaf node:
        insertBucket = old;
        boolean result = insertIntoBucket(x,y);
        if ( old != (cache[where] = insertBucket) ) changeNode(node);
        return result;
    }

    /**
     *  Inserts the point into the given leaf node (bucket).
     *   @param  x X-coordinate of the point.
     *   @param  y Y-coordinate of the point.
     *   @return <code>true</code> if the given point was inserted.
     */
    protected boolean insertIntoBucket ( int x, int y ) {
        int b = getBucket(insertBucket);
        if ( b < 0 ) {                      // block does not exist yet
            insertBucket = newBucket();
            b = getBucket(insertBucket);
            }
        int block = -insertBucket;          // positive
        int i;
        int p = b + BLOCK_RECORD_0;         // try to find the record
        for ( i = cache[b+BLOCK_USED]; i-- > 0 ; p += RECORD_SIZE )
            if ( cache[p+RECORD_X] == x && cache[p+RECORD_Y] == y ) return false;
            // not found => insert it!
        if ( cache[b+BLOCK_USED] == RECORDS_IN_BLOCK ) { // the block is full => split it!
            int block2 = splitBucket(b);    // negative
            pool.changeBlock(block);
            insertBucket = newNode(splitFlags,splitDiv,-block,block2);
            changeNode(insertBucket);
            int oldBucket = insertBucket;
            boolean result = insertNode(insertBucket,x,y);
            insertBucket = oldBucket;
            return result;
            }
        i = cache[b+BLOCK_USED]++;
        cache[b + BLOCK_RECORD_0 + i * RECORD_SIZE + RECORD_X] = x;
        cache[b + BLOCK_RECORD_0 + i * RECORD_SIZE + RECORD_Y] = y;
        if ( i > 0 ) {
            if ( x < cache[b+BLOCK_XMIN] ) cache[b+BLOCK_XMIN] = x;
            if ( x > cache[b+BLOCK_XMAX] ) cache[b+BLOCK_XMAX] = x;
            if ( y < cache[b+BLOCK_YMIN] ) cache[b+BLOCK_YMIN] = y;
            if ( y > cache[b+BLOCK_YMAX] ) cache[b+BLOCK_YMAX] = y;
            }
        else {
            cache[b+BLOCK_XMIN] = cache[b+BLOCK_XMAX] = x;
            cache[b+BLOCK_YMIN] = cache[b+BLOCK_YMAX] = y;
            }
        cache[b+BLOCK_SUM_X]  += x;
        cache[b+BLOCK_SUM_XX] += x * x;
        cache[b+BLOCK_SUM_Y]  += y;
        cache[b+BLOCK_SUM_YY] += y * y;
        pool.changeBlock(block);
        return true;
    }

    /**
     *  Splits the given bucket into two ones.
     *   @param  b Pool cache index of the splitted bucket.
     *   @return Handle to the new bucket.
     */
    protected int splitBucket ( int b ) {
        int bucket = newBucket();
        int nb = getBucket(bucket);
            // determine the splitting direction:
        int n = cache[b+BLOCK_USED];
        if ( n * cache[b+BLOCK_SUM_XX] - cache[b+BLOCK_SUM_X] * cache[b+BLOCK_SUM_X] >
             n * cache[b+BLOCK_SUM_YY] - cache[b+BLOCK_SUM_Y] * cache[b+BLOCK_SUM_Y] ) {
            splitFlags = NODE_FLAG_X;       // divide horizontally
            splitDiv = (cache[b+BLOCK_SUM_X] + (n>>1)) / n;
            }
        else {
            splitFlags = 0;                 // divide vertically
            splitDiv = (cache[b+BLOCK_SUM_Y] + (n>>1)) / n;
            }
        cache[nb+BLOCK_XMIN] = cache[b+BLOCK_XMAX];
        cache[nb+BLOCK_XMAX] = cache[b+BLOCK_XMIN];
        cache[nb+BLOCK_YMIN] = cache[b+BLOCK_YMAX];
        cache[nb+BLOCK_YMAX] = cache[b+BLOCK_YMIN];
        cache[b+BLOCK_XMIN]  = cache[nb+BLOCK_XMIN];
        cache[b+BLOCK_XMAX]  = cache[nb+BLOCK_XMAX];
        cache[b+BLOCK_YMIN]  = cache[nb+BLOCK_YMIN];
        cache[b+BLOCK_YMAX]  = cache[nb+BLOCK_YMAX];
        int i, j;
        int x, y;
        int p = b + BLOCK_RECORD_0;
        int jPtr = p;
        int nbPtr = nb + BLOCK_RECORD_0;
        for ( i = j = 0; i < n; i++, p += RECORD_SIZE ) {
            x = cache[p+RECORD_X];
            y = cache[p+RECORD_Y];
            if ( (splitFlags != 0 && x >= splitDiv) ||
                 (splitFlags == 0 && y >= splitDiv) ) { // move this record to "nb"
                cache[b+BLOCK_SUM_X]   -= x;
                int xx = x * x;
                cache[b+BLOCK_SUM_XX]  -= xx;
                cache[b+BLOCK_SUM_Y]   -= y;
                int yy = y * y;
                cache[b+BLOCK_SUM_YY]  -= yy;
                cache[nb+BLOCK_SUM_X]  += x;
                cache[nb+BLOCK_SUM_XX] += xx;
                cache[nb+BLOCK_SUM_Y]  += y;
                cache[nb+BLOCK_SUM_YY] += yy;
                if ( x < cache[nb+BLOCK_XMIN] ) cache[nb+BLOCK_XMIN] = x;
                if ( x > cache[nb+BLOCK_XMAX] ) cache[nb+BLOCK_XMAX] = x;
                if ( y < cache[nb+BLOCK_YMIN] ) cache[nb+BLOCK_YMIN] = y;
                if ( y > cache[nb+BLOCK_YMAX] ) cache[nb+BLOCK_YMAX] = y;
                cache[b+BLOCK_USED]--;
                cache[nb+BLOCK_USED]++;
                System.arraycopy(cache,p,cache,nbPtr,RECORD_SIZE);
                nbPtr += RECORD_SIZE;
                }
            else {                          // keep this record in "b"
                if ( j++ < i )
                    System.arraycopy(cache,p,cache,jPtr,RECORD_SIZE);
                jPtr += RECORD_SIZE;
                if ( x < cache[b+BLOCK_XMIN] ) cache[b+BLOCK_XMIN] = x;
                if ( x > cache[b+BLOCK_XMAX] ) cache[b+BLOCK_XMAX] = x;
                if ( y < cache[b+BLOCK_YMIN] ) cache[b+BLOCK_YMIN] = y;
                if ( y > cache[b+BLOCK_YMAX] ) cache[b+BLOCK_YMAX] = y;
                }
            }
        pool.changeBlock(-bucket);
        return bucket;
    }

    /** Statistics: total number of inner nodes. */
    protected int innerNodes;

    /** Statistics: total number of leaf nodes. */
    protected int leafNodes;

    /** Statistics: total number of point-hits (in inserts and lookups). */
    protected int hits;

    /**
     *  Initializing constructor.
     *   @param  fileName File-name to store AKD-tree data.
     *   @param  divX Division direction of the root node.
     *   @param  poolSize Size of the NBlock-pool.
     */
    public AKDTree ( String fileName, int divX, int poolSize ) {
        if ( poolSize <= 0 ) poolSize = DEFAULT_POOL_SIZE;
        pool = new NBlockPool(fileName,poolSize);
        cache = pool.getCache();
        innerNodes = leafNodes = hits = 0;
            // find lastNode:
        if ( pool.getBlock(ROOT_NODE) < 0 ) { // create an empty AKD tree
            lastNode = ROOT_NODE - 1;
            newNode(NODE_FLAG_X,divX,NODE_NULL,NODE_NULL);
            int n = getNode(ROOT_NODE);
            cache[n+NODE_LEFT]  = newBucket();
            cache[n+NODE_RIGHT] = newBucket();
            }
        else {
            lastNode = ROOT_NODE;
            innerNodes++;
            int n = getNode(ROOT_NODE);
            findLastNode(cache[n+NODE_LEFT]);
            findLastNode(cache[n+NODE_RIGHT]);
            }
    }

    /**
     *  Looks for the given point.
     *   @param  x X-coordinate of the point.
     *   @param  y Y-coordinate of the point.
     *   @return <code>true</code> if the given point is present.
     */
    public boolean search ( int x, int y ) {
        boolean result = searchNode(ROOT_NODE,x,y);
        if ( result ) hits++;
        return result;
    }

    /**
     *  Inserts the given point. Keeps the tree state if the point is already present.
     *   @param  x X-coordinate of the point.
     *   @param  y Y-coordinate of the point.
     *   @return <code>true</code> if the given point was inserted.
     */
    public boolean insert ( int x, int y ) {
        boolean result = insertNode(ROOT_NODE,x,y);
        if ( !result ) hits++;
        return result;
    }

    /**
     *  Remove the given point.
     *   @param  x X-coordinate of the point.
     *   @param  y Y-coordinate of the point.
     *   @return <code>true</code> if the given point was removed.
     *   @todo   Not implemented yet.
     */
    public boolean remove ( int x, int y ) {
        return false;
    }

    /**
     *  Looks for n nearest points from the given center.
     *   @param  x X-coordinate of the center.
     *   @param  y Y-coordinate of the center.
     *   @param  n Number of points to find.
     *   @param  result Pre-allocated array to hold results (X<sub>0</sub>, Y<sub>0</sub>,
     *                  X<sub>1</sub>, Y<sub>1</sub>, ...). Array size has to be at least
     *                  <code>2*n</code>.
     *   @return Number of points found.
     *   @todo   Not implemented yet.
     */
    public int findNearest ( int x, int y, int n, int[] result ) {
        return 0;
    }

    /**
     *  Retrieves AKD-tree (and NBlockPool) statistics.
     *   @param  stat Pre-allocated array to hold results: # of block reads, # of block writes,
     *                # of block flushes, # of inner nodes, # of leaf nodes, # of hits (in both
     *                searching and inserting).
     */
    public void getStat ( int[] stat ) {
        if ( stat == null || stat.length < 6 ) return;
        stat[0] = pool.reads;
        stat[1] = pool.writes;
        stat[2] = pool.flushes;
        stat[3] = innerNodes;
        stat[4] = leafNodes;
        stat[5] = hits;
    }

    /**
     *  Closes the AKD-tree data structure.
     */
    public void close () {
        pool.close();
    }

}
