/*

    AKDtree.h: Adaptive K-D tree (Friedman) stored on disk

    6.6.2001

    Copyright (C) 2001 by Josef Pelikan, MFF UK Prague
        http://sun3.ms.mff.cuni.cz/~pepca/

    VUCAKO Bench is Open Source software (see http://www.opensource.org/
    for description what Open Source is); you may distributed it and/or
    modify it under the terms of GNU General Public License as published
    by the Free Software Foundation; either version 2, 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.

    A copy of the GNU General Public License is available as
    `/usr/doc/copyright/GPL' in the Debian GNU/Linux distribution or on
    the World Wide Web at http://www.gnu.org/copyleft/gpl.html.  You can
    also obtain it by writing to the Free Software Foundation, Inc., 59
    Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "pch.h"
#include "AKDtree.h"

//-----------------------------------------------------------
//  Virtual NBlock pool:

NBlockPool::NBlockPool ( const char *fileName, int poolSize )
{
    strncpy(fn,fileName,MaxFileName);
    fn[MaxFileName-1] = (char)0;
    f = fopen(fn,"r+b");
    if ( !f ) f = fopen(fn,"w+b");
    if ( !f ) return;
    size = (poolSize < MinPoolSize) ? MinPoolSize : poolSize;
    pool = (int32*)VIRTUAL_ALLOC(size*NBlockSize*sizeof(int32));
    fetched = new int32[size];
    memset(fetched,-1,size*sizeof(int32));
    changed = new bool[size];
    accessed = new unsigned[size];
    serial = 0;
    locked = new int[size];
    fseek(f,0L,SEEK_END);
    used = ftell(f) / ByteBlockSize;
    reads = writes = flushes = 0;
}

int32 *NBlockPool::getBlock ( int32 i )
{
    if ( i >= used ) return NULL;
    int b;
    for ( b = 0; b < size; b++ )
        if ( fetched[b] == i ) {
            accessed[b] = serial++;
            return( pool + b*NBlockSize );
            }
    return fetchBlock(i);
}

int NBlockPool::getFreeBuffer ()
{
    int b = 0;
    while ( b < size && fetched[b] >= 0 ) b++;
    if ( b >= size ) b = releaseBuffer();
    return b;
}

int32 *NBlockPool::fetchBlock ( int32 i )
{
        // assert: the block is not fetched in memory!
    int b = getFreeBuffer();
    fseek(f,i*ByteBlockSize,SEEK_SET);
    fread(pool+b*NBlockSize,sizeof(int32),NBlockSize,f);
    reads++;
    fetched[b] = i;
    changed[b] = false;
    accessed[b] = serial++;
    locked[b] = 0;
    return( pool + b * NBlockSize );
}

int NBlockPool::releaseBuffer ()
{
    unsigned age = serial;
    int b, oldest = -1;
    for ( b = 0; b < size; b++ )
        if ( fetched[b] < 0 ) return b;
        else
            if ( !locked[b] && accessed[b] < age ) {
                age = accessed[b];
                oldest = b;
                }
    if ( oldest < 0 ) return -1;
    syncBuffer(oldest);
    fetched[oldest] = -1;
    flushes++;
    return oldest;
}

int32 *NBlockPool::newBlock ( int32 &i )
{
    int b = getFreeBuffer();
    i = used++;
    memset(pool+b*NBlockSize,0,NBlockSize*sizeof(int32));
    fetched[b] = i;
    changed[b] = true;
    accessed[b] = serial++;
    locked[b] = 0;
    syncBuffer(b);
    return( pool + b * NBlockSize );
}

void NBlockPool::changeBlock ( int32 i )
{
    int b;
    for ( b = 0; b < size; b++ )
        if ( fetched[b] == i ) {
            changed[b] = true;
            accessed[b] = serial++;
            return;
            }
}

void NBlockPool::syncBuffer ( int b )
{
    if ( fetched[b] < 0 || !changed[b] ) return;
    fseek(f,fetched[b]*ByteBlockSize,SEEK_SET);
    fwrite(pool+b*NBlockSize,sizeof(int32),NBlockSize,f);
    writes++;
    changed[b] = false;
}

void NBlockPool::sync ()
{
    int b;
    for ( b = 0; b < size; b++ ) syncBuffer(b);
}

void NBlockPool::close ()
{
    if ( f ) {
        sync();
        fclose(f);
        f = NULL;
        }
    if ( pool ) {
        VIRTUAL_FREE(pool,size*NBlockSize*sizeof(int32));
        pool = NULL;
        }
    delete []fetched;
    fetched = NULL;
    delete []changed;
    changed = NULL;
    delete []accessed;
    accessed = NULL;
    delete []locked;
    locked = NULL;
}

NBlockPool::~NBlockPool ()
{
    close();
}

//-----------------------------------------------------------
//  Adaptive K-D tree:

#define GetNode(node)       (pool.getBlock((node)/NodesInBlock)+((node)%NodesInBlock)*NodeSize)
#define ChangeNode(node)    pool.changeBlock((node)/NodesInBlock)

AKDtree::AKDtree ( const char *fileName, int32 divX, int poolSize ) : pool(fileName,poolSize)
{
    inner = leaf = hits = 0;
        // find lastNode:
    if ( !pool.getBlock(RootNode) ) {       // create an empty AKD tree
        lastNode = RootNode-1;
        int32 *n;
        newNode(NodeFlagX,divX,NodeNull,NodeNull,&n);
        n[NodeLeft] = newBucket();
        n[NodeRight] = newBucket();
        }
    else {
        lastNode = RootNode;
        inner++;
        int32 *n = GetNode(RootNode);
        findLastNode(n[NodeLeft]);
        findLastNode(n[NodeRight]);
        }
}

int32 AKDtree::newNode ( int32 flags, int32 div, int32 left, int32 right, int32 **n )
{
    int32 *nn;
    if ( ++lastNode % NodesInBlock == 0 ) { // allocate a new node-block
        int32 block;
        nn = pool.newBlock(block);
        lastNode = block * NodesInBlock;
        }
    else
        nn = GetNode(lastNode);
    nn[NodeFlags] = flags;
    nn[NodeDiv]   = div;
    nn[NodeLeft]  = left;
    nn[NodeRight] = right;
    if ( n ) *n = nn;
    inner++;
    return lastNode;
}

void AKDtree::findLastNode ( int32 node )
{
    if ( node == NodeNull ) return;
    if ( node < 0 ) {
        leaf++;
        return;
        }
    if ( node > lastNode ) lastNode = node;
    int32 *n = GetNode(node);
    if ( n ) {
        inner++;
        findLastNode(n[NodeLeft]);
        findLastNode(n[NodeRight]);
        }
}

int32 AKDtree::newBucket ( int32 **ptr )
{
    int32 block;
    int32 *b = pool.newBlock(block);
    if ( !b ) return NodeNull;
    memset(b,0,ByteBlockSize);
    pool.changeBlock(block);
    if ( ptr ) *ptr = b;
    leaf++;
    return -block;
}

bool AKDtree::search ( int32 x, int32 y )
{
    bool result = searchNode(RootNode,x,y);
    if ( result ) hits++;
    return result;
}

bool AKDtree::searchNode ( int32 node, int32 x, int32 y )
{
    int32 *n = GetNode(node);
    if ( n[NodeFlags] & NodeFlagX )         // horizontal division
        node = (x < n[NodeDiv]) ? n[NodeLeft] : n[NodeRight];
    else                                    // vertical division
        node = (y < n[NodeDiv]) ? n[NodeLeft] : n[NodeRight];
    if ( node == NodeNull ) return false;
    if ( node > 0 ) return searchNode(node,x,y);
    return searchBlock(-node,x,y);          // leaf node
}

bool AKDtree::searchBlock ( int32 block, int32 x, int32 y )
{
    int32 *b = pool.getBlock(block);
    if ( !b || !b[BlockUsed] ) return false;
    int i;
    int32 *ptr = b + BlockRecord0;
    for ( i = b[BlockUsed]; i-- ; ptr += RecordSize )
        if ( ptr[RecordX] == x && ptr[RecordY] == y ) return true;
    return false;
}

bool AKDtree::insert ( int32 x, int32 y )
{
    bool result = insertNode(RootNode,x,y);
    if ( !result ) hits++;
    return result;
}

bool AKDtree::insertNode ( int32 node, int32 x, int32 y )
{
    int32 *n = GetNode(node);
    int where;
    if ( n[NodeFlags] & NodeFlagX )         // horizontal division
        where = (x < n[NodeDiv]) ? NodeLeft : NodeRight;
    else                                    // vertical division
        where = (y < n[NodeDiv]) ? NodeLeft : NodeRight;
    int32 old = n[where];
    if ( old == NodeNull ) {                // insert a new leaf node
        old = n[where] = newBucket();
        ChangeNode(node);
        }
    if ( old > 0 ) return insertNode(old,x,y);
        // leaf node:
    bool result = insertIntoBucket(n[where],x,y);
    if ( old != n[where] ) ChangeNode(node);
    return result;
}

bool AKDtree::insertIntoBucket ( int32 &ptr, int32 x, int32 y )
{
    int32 block = -ptr;                     // positive
    int32 *b = pool.getBlock(block);
    if ( !b )                               // block does not exist yet
        block = -(ptr = newBucket(&b));
    int i;
    int32 *p = b + BlockRecord0;            // try to find the record
    for ( i = b[BlockUsed]; i-- ; p += RecordSize )
        if ( p[RecordX] == x && p[RecordY] == y ) return false;
        // not found => insert it!
    if ( b[BlockUsed] == RecordsInBlock ) { // the block is full => split it!
        int32 flags, div;
        int32 block2 = splitBucket(b,flags,div); // negative
        pool.changeBlock(block);
        ptr = newNode(flags,div,-block,block2);
        ChangeNode(ptr);
        return insertNode(ptr,x,y);
        }
    i = b[BlockUsed]++;
    b[BlockRecord0 + i * RecordSize + RecordX] = x;
    b[BlockRecord0 + i * RecordSize + RecordY] = y;
    if ( i ) {
        if ( x < b[BlockXmin] ) b[BlockXmin] = x;
        if ( x > b[BlockXmax] ) b[BlockXmax] = x;
        if ( y < b[BlockYmin] ) b[BlockYmin] = y;
        if ( y > b[BlockYmax] ) b[BlockYmax] = y;
        }
    else {
        b[BlockXmin] = b[BlockXmax] = x;
        b[BlockYmin] = b[BlockYmax] = y;
        }
    b[BlockSumX]  += x;
    b[BlockSumXX] += x * x;
    b[BlockSumY]  += y;
    b[BlockSumYY] += y * y;
    pool.changeBlock(block);
    return true;
}

int32 AKDtree::splitBucket ( int32 *b, int32 &flags, int32 &div )
{
    int32 *nb;
    int32 bucket = newBucket(&nb);
        // determine the splitting direction:
    int32 n = b[BlockUsed];
    if ( n * b[BlockSumXX] - b[BlockSumX] * b[BlockSumX] >
         n * b[BlockSumYY] - b[BlockSumY] * b[BlockSumY] ) {    // divide horizontally
        flags = NodeFlagX;
        div = (b[BlockSumX] + (n>>1)) / n;
        }
    else {                                                      // divide vertically
        flags = 0;
        div = (b[BlockSumY] + (n>>1)) / n;
        }
    nb[BlockXmin] = b[BlockXmax];
    nb[BlockXmax] = b[BlockXmin];
    nb[BlockYmin] = b[BlockYmax];
    nb[BlockYmax] = b[BlockYmin];
    b[BlockXmin] = nb[BlockXmin];
    b[BlockXmax] = nb[BlockXmax];
    b[BlockYmin] = nb[BlockYmin];
    b[BlockYmax] = nb[BlockYmax];
    int i, j;
    int32 x, y;
    int32 *p = b + BlockRecord0;
    for ( i = j = 0; i < n; i++, p += RecordSize ) {
        if ( j < i )
            memcpy(b+BlockRecord0+j*RecordSize,p,RecordSize*sizeof(int32));
        x = p[RecordX];
        y = p[RecordY];
        if ( (flags && x >= div) ||
             (!flags && y >= div) ) {       // move this record to "nb"
            b[BlockSumX]   -= x;
            b[BlockSumXX]  -= x * x;
            b[BlockSumY]   -= y;
            b[BlockSumYY]  -= y * y;
            nb[BlockSumX]  += x;
            nb[BlockSumXX] += x * x;
            nb[BlockSumY]  += y;
            nb[BlockSumYY] += y * y;
            if ( x < nb[BlockXmin] ) nb[BlockXmin] = x;
            if ( x > nb[BlockXmax] ) nb[BlockXmax] = x;
            if ( y < nb[BlockYmin] ) nb[BlockYmin] = y;
            if ( y > nb[BlockYmax] ) nb[BlockYmax] = y;
            b[BlockUsed]--;
            memcpy(nb+BlockRecord0+nb[BlockUsed]++*RecordSize,p,RecordSize*sizeof(int32));
            }
        else {
            j++;
            if ( x < b[BlockXmin] ) b[BlockXmin] = x;
            if ( x > b[BlockXmax] ) b[BlockXmax] = x;
            if ( y < b[BlockYmin] ) b[BlockYmin] = y;
            if ( y > b[BlockYmax] ) b[BlockYmax] = y;
            }
        }
    pool.changeBlock(-bucket);
    return bucket;
}

bool AKDtree::remove ( int32 x, int32 y )
{
    return false;
}

int AKDtree::findNearest ( int32 x, int32 y, int n, int32 *result )
{
    return 0;
}

void AKDtree::getStat ( unsigned *stat )
    // reads, writes, flushes, inner nodes, leaf nodes, number of hits
{
    if ( !stat ) return;
    stat[0] = pool.reads;
    stat[1] = pool.writes;
    stat[2] = pool.flushes;
    stat[3] = inner;
    stat[4] = leaf;
    stat[5] = hits;
}

void AKDtree::close ()
{
    pool.close();
}

AKDtree::~AKDtree ()
{
    close();
}
