Newer
Older
ubixfs-2 / lists_backup.pas
@flameshadow flameshadow on 10 Jun 2005 67 KB UbixFS
{$TPO-}  // treat all pointer inc/dec's as byte adjusts
unit lists;

interface

type
  int8          = shortInt;
  int16         = smallInt;
  int32         = longInt;

  uInt8         = byte;
  uInt16        = word;
  uInt32        = dWord;

type
  treeTypes = (BT_CUSTOM, BT_PCHAR, BT_STRING, BT_SINGLE, BT_DOUBLE, BT_INT32, BT_INT64);
  PbNode = ^TbNode;
  PbNodeData = ^TbNodeData;

  TBlockRun = packed record
    AG     : int32;
    start  : uInt16;
    len    : uInt16;
  end;

  inodeAddr = TblockRun;

  uPtr = packed record
   case integer of
     0: (i      : int32);
     1: (u      : uInt32);
     2: (s      : single);
     3: (d      : double);
     4: (p      : pointer);
     5: (bPtr   : PbNode);
     6: (offset : int64);
     7: (iAddr  : inodeAddr);
  end;

  PQueue = ^TQueue;
  TQueue = object
   private
     queue:pointer;
     elements:uInt32;
     maxElements:uInt32;
     size:uInt32;
     head, tail:uInt32;
   public
     constructor init(elementCount, elementSize:uInt32);
     function dequeue(var item):boolean;
     function enqueue(var item):boolean;
     function getElementCount:uInt32;
     destructor done;
  end; // TQueue

  PBTreeVFS = ^TBTreeVFS;
  TBTreeVFS = object
   public
    constructor init;
    function fClose:boolean; virtual;
    function fCreate(const filename:string):boolean; virtual;
    function fExist(const filename:string):boolean; virtual;
    function fOpen(const filename:string):boolean; virtual;
    function fRead(var buf; size:uInt32):boolean; virtual;
    function fSeek(offset:uInt32):boolean; virtual;
    function fWrite(var buf; size:uInt32):boolean; virtual;
    destructor done; virtual;
  end; // TBTreeVFS
  
  TbNode = packed record
    magic    : uInt32;
    tag      : uInt32;
    numKeys  : uInt32;
    size     : uInt32;
    left     : uPtr;
    right    : uPtr;
    parent   : uPtr;
    reserved : uPtr;
    data     : array[0..0] of byte;
  end;
  
  TbNodeData = packed record
    link     : uPtr;
    key      : record
      case treeTypes of
        BT_PCHAR,
        BT_STRING: (str:string[1]);
        BT_SINGLE: (s:single);
        BT_DOUBLE: (d:double);
        BT_INT32 : (i:int32);
        BT_INT64 : (x:int64);
    end; {record}
  end; {TbNode}

type 
  BTreeSearchRec = record
    value : uPtr;
    key : pointer;
    keySize:integer;
  end; {BTreeSearchRec}
 
type
  PBTreeInfo = ^BTreeInfo;
  BTreeInfo = packed record
    magic	 : int64;
    root         : uPtr;
    firstDeleted : uPtr;
    nodes        : uInt32;
    height       : uInt32;
    keys         : uInt32;
    bNodeSize    : uInt32;
    treeType     : treeTypes;
  end; {BTreeHeader}

type compareKeyFunc = function(key1, key2:pointer):integer;
type copyKeyProc = procedure(srcKey, destKey:pointer);
type keySizeFunc = function(key:pointer):integer;

type
  PbTree = ^bTree;
  bTree = object
   private
    treeName     : string;
    compareKeys  : compareKeyFunc;
    copyKey      : copyKeyProc;
    keySize      : keySizeFunc;
    info         : PBTreeInfo;
    vfs          : PBTreeVFS;
    //dataFile     : file;
    memoryTree   : boolean;
    treeChanged  : boolean; 
    function    align(keyLength:integer):integer;
    function    allocEmptyNode:uPtr;
    function    calcSize(node:PbNode):integer;
    procedure   deAllocNode(node:PbNode);
    function    deleteEntry(u:uPtr; key:pointer; value:uPtr):uPtr;
    procedure   discardPage(node:PbNode);
    function    fetchFirstKey(u:uPtr; var searchRec:BTreeSearchRec):boolean;
    function    fetchLastKey(u:uPtr; var searchRec:BTreeSearchRec):boolean;
    function    findLeafNode(u:uPtr; key:pointer):uPtr;
    procedure   freeAll;
    procedure   freePage(u:uPtr);
    function    getAddress(node:PbNode):uPtr;
    function    getRoot:uPtr;
    function    insertEntry(u:uPtr; key:pointer; value:uPtr):boolean;
    function    isNull(u:uPtr):boolean;
    function    loadPage(u:uPtr):PbNode;
    procedure   loadSuperBlock;
    procedure   Print(var f:text; u:uPtr);
    procedure   PrintInfo(var f:text; u:uPtr);    
    function    replaceEntry(u:uPtr; key:pointer; value:uPtr):boolean;
    procedure   savePage(node:PbNode);
    procedure   saveSuperBlock;
    procedure   setLeft(u:uPtr; newPtr:uPtr);
    procedure   setNull(var u:uPtr);
    procedure   setRight(u:uPtr; newPtr:uPtr);
    procedure   setParent(u:uPtr; newPtr:uPtr);

   public
    constructor init(const filename:string; nodeSize:uInt32; tt:treeTypes; virtualFS:PBTreeVFS);
    constructor open(const filename:string; virtualFS:PBTreeVFS);
    function    Delete(key:pointer; value:uPtr):uPtr;
    function    Insert(key:pointer; value:uPtr):boolean;
    procedure   InstallUserFunctions(cmpFunc:compareKeyFunc; copyProc:copyKeyProc; ksFunc:keySizeFunc);
    function    Find(key:pointer; var u:uPtr):boolean;
    procedure   FindClose(var searchRec:BTreeSearchRec);
    function    FindFirst(key:pointer; var searchRec:BTreeSearchRec):boolean;
    function    FindNext(var searchRec:BTreeSearchRec):boolean;
    function    FindPrev(var searchRec:BTreeSearchRec):boolean;
    function    GetFirstKey(var searchRec:BTreeSearchRec):boolean;
    function    GetKeyCount:uInt32;
    function    GetLastKey(var searchRec:BTreeSearchRec):boolean;
    procedure   PrintList(const filename:string);
    procedure   PrintWholeTree(const filename:string);
    destructor  done;
  end; // bTree

implementation

uses strings, dos;

const B_NODE_LEAF  = $4641454C; // LEAF
const B_NODE_INDEX = $58444E49; // INDX
const B_NODE_OPEN  = $4E45504F; // OPEN
const B_NODE_TREEINFO  = $4F464E4945455254;  // TREEINFO

type
  PStandardVFS = ^TStandardVFS;
  TStandardVFS = object(TBTreeVFS)
   private
    dataFile:file;
    openFile:boolean;
   public
    constructor init;
    function fClose:boolean; virtual;
    function fCreate(const filename:string):boolean; virtual;
    function fExist(const filename:string):boolean; virtual;
    function fOpen(const filename:string):boolean; virtual;
    function fRead(var buf; size:uInt32):boolean; virtual;
    function fSeek(offset:uInt32):boolean; virtual;
    function fWrite(var buf; size:uInt32):boolean; virtual;
    destructor done; virtual;
  end; // TStandardVFS

// TVFS virtual abstract functions

constructor TBTreeVFS.init;
begin
  writeln('Don''t call the parent constructor');
end; // TBTreeVFS.init

function TBTreeVFS.fClose;  result := FALSE;  
function TBTreeVFS.fCreate; result := FALSE;  
function TBTreeVFS.fExist;  result := FALSE;  
function TBTreeVFS.fOpen;   result := FALSE;  
function TBTreeVFS.fRead;   result := FALSE;  
function TBTreeVFS.fSeek;   result := FALSE;  
function TBTreeVFS.fWrite;  result := FALSE;  

destructor TBTreeVFS.done; 
begin
  writeln('Don''t call the parent destructor');
end; // TBTreeVFS.done

// TStandardVFS methods

constructor TStandardVFS.init;
begin
  openFile := FALSE;
end; // TStandardVFS.init

function TStandardVFS.fClose;
begin
  if (openFile) then {$I-} close(dataFile); {$I+}
  openFile := (IOResult() <> 0);
  result := not openFile
end; // TStandardVFS.fClose

function TStandardVFS.fCreate;
begin
  assign(dataFile, filename);
  {$I-} rewrite(dataFile, 1); {$I+}
  openFile := (IOResult() = 0);
  result := openFile
end; // TStandardVFS.fCreate

function TStandardVFS.fExist;
begin
  result := (fSearch(filename, '') <> '') 
end; // TStandardVFS.fExist

function TStandardVFS.fOpen;
begin
  assign(dataFile, filename);
  {$I-} reset(dataFile, 1); {$I+}
  openFile := (IOResult() = 0);
  result := openFile
end; // TStandardVFS.fOpen

function TStandardVFS.fRead;
var lResult:uInt32;
begin
  blockread(dataFile, buf, size, lResult);
  result := ((IOResult() = 0) and (lResult = size));
end; // TStandardVFS.fRead

function TStandardVFS.fSeek;
begin
  {$I-} seek(dataFile, offset); {$I+}
  result := (IOResult() = 0)  
end; // TStandardVFS.fSeek

function TStandardVFS.fWrite;
var lResult:uInt32;
begin
  // writeln('fWrite(dataFile, buf, ', count, ') @ position: ', filePos(dataFile));
  blockwrite(dataFile, buf, size, lResult);
  result := ((IOResult() = 0) and (lResult = size));
end; // TStandardVFS.fWrite

destructor TStandardVFS.done;
begin
  if (openFile) then fClose();
end; // TStandardVFS.done

function compareKeyNull(key1, key2:pointer):integer; result := 0; // compareKeyNull

procedure copyKeyNull(srcKey, destKey:pointer); begin end; // copyKeyNull

function keySizeNull(key:pointer):integer; result := 0; // keySizeNull

(*
 * CompareKey functions
 *
 *)

function compareKeyString(key1, key2:pointer):integer;
begin
  result := 0;
  if ((key1 = NIL) or (key2 = NIL)) then exit;
  if string(key1^) < string(key2^) then 
    result := -1
  else 
    if string(key1^) > string(key2^) then result := 1;
end; {comapreKeyString}

function compareKeyPChar(key1, key2:pointer):integer;
begin
  result := 0;
  if ((key1 = NIL) or (key2 = NIL)) then exit;
  result := strcomp(pchar(key1), pchar(key2));
end; {compareKeyPChar}

function compareKeyDouble(key1, key2:pointer):integer;
begin
  result := 0;
  if ((key1 = NIL) or (key2 = NIL)) then exit;
  if (double(key1^) < double(key2^)) then 
    result := -1 
  else 
    if (double(key1^) > double(key2^)) then result := 1;
end; {compareKeyDouble}

function compareKeySingle(key1, key2:pointer):integer;
begin
  result := 0;
  if ((key1 = NIL) or (key2 = NIL)) then exit;
  if (single(key1^) < single(key2^)) then 
    result := -1 
  else 
    if (single(key1^) > single(key2^)) then result := 1;
end; {compareKeySingle}

function compareKeyInt32(key1, key2:pointer):integer;
begin
  result := 0;
  if ((key1 = NIL) or (key2 = NIL)) then exit;
  if (int32(key1^) < int32(key2^)) then
    result := -1
  else
    if (int32(key1^) > int32(key2^)) then result := 1;
end; {compareKeyInt32}

function compareKeyInt64(key1, key2:pointer):integer;
begin
  result := 0;
  if ((key1 = NIL) or (key2 = NIL)) then exit;

  if (int64(key1^) < int64(key2^)) then
    result := -1
  else
    if (int64(key1^) > int64(key2^)) then result := 1;

end; // compareKeyInt64

(*
 * CopyKey functions
 *
 *)

procedure copyKeyPChar(srcKey, destKey:pointer);
begin
  if ((srcKey = NIL) or (destKey = NIL)) then exit;
  strcopy(pchar(destKey), pchar(srcKey));
end; {copyeKeyPChar}

procedure copyKeyString(srcKey, destKey:pointer);
begin
  if ((srcKey = NIL) or (destKey = NIL)) then exit;
  string(destKey^) := string(srcKey^);
end; {copyKeyString}

procedure copyKeySingle(srcKey, destKey:pointer);
begin
  if ((srcKey = NIL) or (destKey = NIL)) then exit;
  single(destKey^) := single(srcKey^);
end; {copyKeySingle}

procedure copyKeyDouble(srcKey, destKey:pointer);
begin
  if ((srcKey = NIL) or (destKey = NIL)) then exit;
  double(destKey^) := double(srcKey^);
end; {copyKeyDouble}

procedure copyKeyInt32(srcKey, destKey:pointer);
begin
  if ((srcKey = NIL) or (destKey = NIL)) then exit;
  int32(destKey^) := int32(srcKey^);
end; {copyKeyInt32}

procedure copyKeyInt64(srcKey, destKey:pointer);
begin
  if ((srcKey = NIL) or (destKey = NIL)) then exit;
  int64(destKey^) := int64(srcKey^);
end; {copyKeyInt64}

(*
 * KeySize functions
 *
 *)
 
function keySizePChar(key:pointer):integer;
begin
  result := 0;
  if (key = NIL) then exit;
  result := strlen(pchar(key))+1;
end; {keySizePChar}

function keySizeString(key:pointer):integer;
begin
  result := 0;
  if (key = NIL) then exit;
  result := length(string(key^))+1;
end; {keySizeString}

function keySizeSingle(key:pointer):integer;
begin
  if (key = NIL) then result := 0 else result := sizeof(single);
end; {keySizeSingle}

function keySizeDouble(key:pointer):integer;
begin
  if (key = NIL) then result := 0 else result := sizeof(double);
end; {keySizeDouble}

function keySizeInt32(key:pointer):integer;
begin
  if (key = NIL) then result := 0 else result := sizeof(int32);
end; {keySizeInt32}

function keySizeInt64(key:pointer):integer;
begin
  if (key = NIL) then result := 0 else result := sizeof(int64);
end; {keySizeInt64}

(********************************)

function compareUPtrs(u1, u2:uPtr):boolean;
begin
  result := (u1.offset = u2.offset)
end; // compareUPtrs

overload = = compareUPtrs;

(*
 *
 *  B^Tree object methods
 *
 *)

constructor bTree.init;
var lResult:longint;
begin
  memoryTree := fileName = '';
  treeName := filename;
  // allocate the info section
  // getmem(info, sizeof(BTreeInfo));
  new(info);
  if (info = NIL) then runError;
  fillchar(info^, sizeof(BTreeInfo), 0);
  vfs := virtualFS;
  with info^ do
    begin
      magic := B_NODE_TREEINFO;
      setNull(root);
      setNull(firstDeleted);
      nodes := 0;
      height := 0;
      keys := 0;
      bNodeSize := nodeSize;
      treeType := tt;
    end; {with}

  if not (memoryTree) then
    begin

      if (vfs = NIL) then vfs := new(PStandardVFS, init);
      if (vfs^.fExist(filename)) then
        begin
          if not (vfs^.fOpen(filename)) then runError;
          if not vfs^.fRead(info^, sizeof(BTreeInfo)) then writeln('Error reading in bTreeInfo');
        end
      else
        begin
          if not vfs^.fCreate(filename) then runError;
          if not vfs^.fWrite(info^, sizeof(BTreeInfo)) then writeln('Error writing bTreeInfo');
        end;
//      if not vfs^.fClose() then runError;
//      if not (vfs^.fOpen(filename, info^.bNodeSize)) then runError;
    end;

  case info^.treeType of
    BT_CUSTOM : InstallUserFunctions(compareKeyNull,   copyKeyNull,   keySizeNull);
    BT_PCHAR  : InstallUserFunctions(compareKeyPChar,  copyKeyPChar,  keySizePChar);
    BT_STRING : InstallUserFunctions(compareKeyString, copyKeyString, keySizeString);
    BT_SINGLE : InstallUserFunctions(compareKeySingle, copyKeySingle, keySizeSingle);
    BT_DOUBLE : InstallUserFunctions(compareKeyDouble, copyKeyDouble, keySizeDouble);
    BT_INT32  : InstallUserFunctions(compareKeyInt32,  copyKeyInt32,  keySizeInt32);
    BT_INT64  : InstalluserFunctions(compareKeyInt64,  copyKeyInt64,  keySizeInt64);
   else
    InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull);
  end; // case
  treeChanged := FALSE;  // The superblock of the tree hasn't been modified
end; // bTree.init

constructor bTree.open;
var 
  lResult:longint;
label safeExit;
begin
  info := NIL;
  memoryTree := FALSE;
  treeChanged := FALSE;  // The superblock of the tree hasn't been modified
  treeName := filename;
  InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull);
  //if (filename = '') or (fsearch(filename, '') = '') then begin writeln(filename, ' does not exist'); exit; end;

  // allocate the info section
  vfs := virtualFS;
 
  if (vfs = NIL) then vfs := new(PStandardVFS, init);
  if not vfs^.fExist(filename) then 
    begin
      writeln('in bTree.open(): file does not exist');
      goto safeExit;
    end;
  new(info);  
  fillchar(info^, sizeof(info^), 0);
  if not vfs^.fOpen(filename) then runError;
  if not vfs^.fRead(info^, sizeof(BTreeInfo)) then runError;
  if (info^.magic <> B_NODE_TREEINFO) then runError;
//  if not vfs^.fClose() then runError;
//  vfs^.fOpen(filename, info^.bNodeSize); 

  case info^.treeType of
    BT_CUSTOM : InstallUserFunctions(compareKeyNull,   copyKeyNull,   keySizeNull);
    BT_PCHAR  : InstallUserFunctions(compareKeyPChar,  copyKeyPChar,  keySizePChar);
    BT_STRING : InstallUserFunctions(compareKeyString, copyKeyString, keySizeString);
    BT_SINGLE : InstallUserFunctions(compareKeySingle, copyKeySingle, keySizeSingle);
    BT_DOUBLE : InstallUserFunctions(compareKeyDouble, copyKeyDouble, keySizeDouble);
    BT_INT32  : InstallUserFunctions(compareKeyInt32,  copyKeyInt32,  keySizeInt32);
    BT_INT64  : InstalluserFunctions(compareKeyInt64,  copyKeyInt64,  keySizeInt64);
  end; // case
safeExit:
end; // bTree.open

function bTree.align;
begin
  result := ((sizeof(uptr) + keyLength + 7) shr 3) shl 3; 
end; {bTree.align}

function bTree.allocEmptyNode;
var newNode:PbNode;
begin
  saveSuperBlock();
  setNull(result);

  if (not isNull(info^.firstDeleted)) then
    begin
      newNode := loadPage(info^.firstDeleted);
      if (newNode = NIL) then exit;
      info^.firstDeleted := newNode^.parent;
    end
  else
    begin
      getmem(newNode, info^.bNodeSize);
      fillchar(newNode^, info^.bNodeSize, 0);
      inc(info^.nodes);
      newNode^.tag := info^.nodes;
    end;

  with newNode^ do
    begin
      magic          := B_NODE_LEAF;
      numKeys        := 1;
      size           := dword(@newNode^.data) - dword(newNode);
      setNull(left);
      setNull(right);
      setNull(parent);
      setNull(reserved);
    end; {with}
  result := getAddress(newNode);
  savePage(newNode);
  discardPage(newNode);
//  loadSuperBlock();
end; {bTree.allocEmptyNode}

function bTree.calcSize;
var 
  size:integer;
  curNode:integer;
  dataNode:PbNodeData;
begin
  if (node = NIL) then exit;
  dataNode := @node^.data;
  size := dword(dataNode) - dword(node);
  
  for curNode := 1 to node^.numKeys do
    begin
      inc(size, align(keySize(@dataNode^.key)));
      inc(dataNode, align(keySize(@dataNode^.key)));
    end;
  if (node^.magic = B_NODE_INDEX) then inc(size, sizeof(uPtr));
  result := size;
end;

procedure bTree.deAllocNode;
begin
  if (node = NIL) then exit;

  with node^ do
    begin
      magic := B_NODE_OPEN;
      setNull(left);
      setNull(right);
      setNull(reserved);
      if not memoryTree then parent := info^.firstDeleted;
      
      size := dword(@node^.data) - dword(node);
      fillchar(data, info^.bNodeSize - size, 0);
    end;
  if not memoryTree then info^.firstDeleted := getAddress(node);
  savePage(node);
  freemem(node, info^.bNodeSize);
end; {btree.deAllocNode}

function bTree.deleteEntry;
var
  bnode, tmpNode, neighbor, parent : PbNode;
  neighborSize:integer;

  dataNode, nextDataNode, tmpDataNode:PbNodeData;
  src, dst : pointer;
  tmpLink:uPtr;
  nodeSize:uInt32;
  curNode, res, size: integer;
  tmpTag:uInt32;
  found : boolean;

  procedure getNeighbor;
  var 
    l, r:PbNode;
  begin
    neighbor := NIL;
    neighborSize := 0;
    l := loadPage(bnode^.left);
    r := loadPage(bnode^.right);

    if (l = NIL) then 
      begin
        // this next check may be unecessary
        if (r^.parent = bnode^.parent) then
          begin
            neighbor := r;
            neighborSize := r^.size;
          end;
        exit
      end;

    if (r = NIL) then 
      begin
        // this next check may be unecessary
        if (l^.parent = bnode^.parent) then
          begin
            neighbor := l;
            neighborSize := l^.size;
          end;
        exit
      end;

    if (l^.parent = r^.parent) then   // left/right neighbors are children of the same parent
      begin
        if (l^.size < r^.size) then 
          begin
            neighbor := l;
            neighborSize := l^.size;
            discardPage(r);
            exit
          end
        else
          begin
            neighbor := r;
            neighborSize := r^.size;
            discardPage(l);
            exit
          end;
      end
    else    // one of the neighbors has a different parent
      begin
        if (l^.parent = bnode^.parent) then
          begin
            neighbor := l;
            neighborSize := l^.size;
            discardPage(r);
            exit
          end
        else
          begin
            neighbor := r;
            neighborSize := r^.size;
            discardPage(l);
            exit
          end;
      end;
    discardPage(l);
    discardPage(r);    
  end;   {getNeighbor local function}

begin

  setNull(result);

  if (isNull(u)) then exit;
  bnode := loadPage(u);

  if (bnode^.numKeys = 0) then runError;  {this should never happen}

  if (bnode^.magic = B_NODE_LEAF) then
    begin

   {   case info^.treeType of
        BT_STRING:writeln(f, 'Deleting ', string(key^), ' size: ', align(keySize(key)));
        BT_PCHAR:writeln(f, 'Deleting ', pchar(key));
      end;}

      dataNode := @bnode^.data;
      curNode := 0;
      repeat
        res := compareKeys(@dataNode^.key, key);
        if (res >= 0) then break;
        inc(dataNode, align(keySize(@dataNode^.key)));
        inc(curNode);
      until (curNode = bnode^.numKeys);

      if (res <> 0) then 
        begin
//          writeln(f, 'Could not find in leaf node');
          discardPage(bnode);
          exit
        end;

      // Since we found the entry, we will have to save the
      // superblock at the end. Set treeChanged to TRUE
      treeChanged := TRUE; 
      result := dataNode^.link;

      nodeSize := align(keySize(@dataNode^.key));

      nextDataNode := dataNode;
      inc(nextDataNode, nodeSize);

      move(nextDataNode^, dataNode^, bnode^.size - (dword(nextDataNode) - dword(bnode)));
      dec(bnode^.numKeys);
      dec(bnode^.size, nodeSize);
      dataNode := pointer(dword(bnode) + bnode^.size);
      fillchar(dataNode^, nodeSize, 0);
      dec(info^.keys);

      // check for merges and other stuff
      if (getAddress(bnode) = info^.root) then
        begin
          if (bnode^.numKeys = 0) then
            begin
              setNull(info^.root);
              dec(info^.height);
              deAllocNode(bnode);
              exit;
            end;
        end
      else    // not root
        begin
          if (bnode^.size < (info^.bNodeSize / 2)) then
            begin
              getNeighbor;
              if (neighbor <> NIL) and (neighborSize <> 0) then
                begin
                  if (bnode^.size + neighborSize <= info^.bNodeSize) then
                    begin
//writeln(f, 'Merging leaf pages');
                      // if neighbor is a predecessor of bnode then swap them
                      if (getAddress(neighbor) = bnode^.left) then
                        begin
                          tmpNode := bnode;
                          bnode := neighbor;
                          neighbor := tmpNode; 
                        end;
                      // merge the two nodes together
                      // bnode is the left node, neighbor is the right node
                      src := @neighbor^.data;
                      dst := pointer(dword(bnode) + bnode^.size);

                      size := neighbor^.size - (dword(src) - dword(neighbor));

                      move(src^, dst^, size);
                      inc(bnode^.size, size);              
                      inc(bnode^.numKeys, neighbor^.numKeys);
                      setRight(bnode^.left, getAddress(neighbor));
                      bnode^.right := neighbor^.right; 
                      tmpTag := neighbor^.tag;  // save tag info
                      move(bnode^, neighbor^, info^.bNodeSize); 
                      neighbor^.tag := tmpTag;  // restore tag info
                      savePage(neighbor);
                      deleteEntry(bnode^.parent, NIL, getAddress(bnode));
                      discardPage(neighbor);
                      deAllocNode(bnode);
                      exit;
                    end   //if (bnode^.size + neighborSize <= info^.bNodeSize) 
                  else  // "acquire" an entry from a neighbor
                    begin

                      if (getAddress(neighbor) = bnode^.right) then
                        begin
//writeln(f, 'acquiring from leaf bnode/neighbor');
                          // neighbor node is to the right. Append an entry from it to bnode
                          dataNode := @neighbor^.data;
                          src := dataNode;
                          dst := pointer(dword(bnode) + bnode^.size);
                          nodeSize := align(keySize(@dataNode^.key));
                          move(src^, dst^, nodeSize);
                          inc(bnode^.size, nodeSize);
                          inc(bnode^.numKeys);
                          nextDataNode := dataNode;
                          inc(nextDataNode, nodeSize);
                          move(nextDataNode^, 
                               dataNode^, 
                               neighbor^.size - (dword(dataNode) - dword(neighbor)) - nodeSize);
                          dec(neighbor^.size, nodeSize);
                          dec(neighbor^.numKeys);

                          // clear off the end of the neighbor node
                          dataNode := pointer(dword(neighbor) + neighbor^.size);
                          fillchar(dataNode^, nodeSize, 0);
                          savePage(bnode);
                          savePage(neighbor);
                          dataNode := dst;
                          replaceEntry(bnode^.parent, @dataNode^.key, getAddress(bnode));
                        end
                      else
                        begin
//writeln(f, 'acquiring from leaf neighbor/bnode');
                          // neighbor node is to the left. Take the last entry and prepend to bnode
                          dataNode := @neighbor^.data;
                          for curNode := 1 to neighbor^.numKeys-1 do
                            inc(dataNode, align(keySize(@dataNode^.key)));
                          // dataNode (hopefully) now points to the last key/value pair in neighbor
                          nodeSize := align(keySize(@dataNode^.key));
                          src := @bnode^.data;
                          dst := src;
                          inc(dst, nodeSize);
                          move(src^, dst^, bnode^.size - (dword(src) - dword(bnode)));
                          dst := src;
                          src := dataNode;
                          move(src^, dst^, nodeSize);
                          fillchar(src^, nodeSize, 0);
                          dec(neighbor^.size, nodeSize);
                          dec(neighbor^.numKeys);
                          inc(bnode^.size, nodeSize);
                          inc(bnode^.numKeys);
                          savePage(bnode);
                          savePage(neighbor);
                          // this can be optimized by storing the previous 
                          // node from the for loop above
                          dataNode := @neighbor^.data;
                          for curNode := 1 to neighbor^.numKeys-1 do
                            inc(dataNode, align(keySize(@dataNode^.key)));
                          replaceEntry(neighbor^.parent, @dataNode^.key, getAddress(neighbor));
                        end;  // else
                      discardPage(bnode);
                      discardPage(neighbor);
                      exit;

                    end; 

                end;  //if (neighbor <> NIL) and (neighborSize <> 0)
              discardPage(neighbor);
            end; // if (bnode^.size < (info^.bNodeSize / 2)) 
        end; {else not root}

    end
  else     {not a leaf}
    begin
      dataNode := @bnode^.data;
      nextDataNode := dataNode;
      nodeSize := align(keySize(@dataNode^.key));
      inc(nextDataNode, nodeSize);
      found := FALSE;
      for curNode := 1 to bnode^.numKeys do
        begin
          found := (dataNode^.link = value);
          if (found) then break;
          if (curNode <> bnode^.numKeys) then
            begin
              dataNode := nextDataNode;
              nodeSize := align(keySize(@nextDataNode^.key));
              inc(nextDataNode, nodeSize);
            end;
        end;

      if (not found) then
        begin
          if (nextDataNode^.link = value) then
            nextDataNode^.link := dataNode^.link
          else
            begin
  //            writeln(f, 'Could not find in index node');
              discardPage(bnode);
              exit;
            end;
        end;
      {if (dataNode = nextDataNode) then inc(nextDataNode, align(keySize(@dataNode^.key)));}
      // Copy this link into the next link so we can just delete this node
      {nextDataNode^.link := dataNode^.link;}

      fillchar(dataNode^, nodeSize, 0);
      move(nextDataNode^, dataNode^, bnode^.size - (dword(nextDataNode) - dword(bnode)));
      
      dec(bnode^.numKeys);
      dec(bnode^.size, nodeSize);
      dst := pointer(dword(bnode) + bnode^.size);
      fillchar(dst^, nodeSize, 0);
      // check for merges and other stuff
      if (getAddress(bnode) = getRoot()) then
        begin
          if (bnode^.numKeys = 0) then
            begin
              dataNode := @bnode^.data;
              info^.root := dataNode^.link;
              dec(info^.height);
              deAllocNode(bnode);
              bnode := loadPage(info^.root);
              setNull(bnode^.parent);
              savePage(bnode);
              discardPage(bnode);
              exit
            end;
        end
      else {not root}
        begin
          if (bnode^.size < (info^.bNodeSize / 2)) then
            begin
              getNeighbor; 
              if (neighbor<>NIL) and (neighborSize<>0) then
                begin
                  if (bnode^.size + neighborSize <= info^.bNodeSize) then
                    begin
//writeln(f, 'merging index pages');
                      // if neighbor is a predecessor of bnode then swap them
                      if (getAddress(neighbor) = bnode^.left) then
                        begin
                          tmpNode := bnode;
                          bnode := neighbor;
                          neighbor := tmpNode; 
                        end;
                      // We need to get the key that currently resides between
                      // the bnode (left) link and the neighbor (right) link in the parent
                      parent := loadPage(bnode^.parent);
                      if (parent = NIL) then runError;   // this would be bad

                      dataNode := @parent^.data;    
                      for curNode := 0 to parent^.numKeys-1 do
                        begin
                          if (dataNode^.link = getAddress(bnode)) then break;
                          inc(dataNode, align(keySize(@dataNode^.key)));
                        end; {for}
                      // theoretically dataNode^.key is the key between the two links
                 
                      size := align(keySize(@dataNode^.key));  

                      if ((bnode^.size + neighbor^.size + size - (dword(@bnode^.data) - dword(bnode))) > info^.bNodeSize) then
                        begin
                          // The key wouldn't fit. bail
                          discardPage(parent);  
                          savePage(neighbor);
                          savePage(bnode);
                          discardPage(neighbor);
                          discardPage(bnode);
                          exit;
                        end;

                       // copy the key to the end of bnode
                      src := @dataNode^.key;
                      dst := pointer(dword(bnode) + bnode^.size);
                      move(src^, dst^, keySize(@dataNode^.key));
                      inc(bnode^.numKeys);      // we just added a key

                      dec(size, sizeof(uPtr));
                      inc(bnode^.size, size);   // increment by aligned size
                      inc(dst, size);           // also increment the dst by aligned size
        
                      discardPage(parent);      // don't need the parent any more
                      // merge the two nodes together
                      // bnode is the left node, neighbor is the right node
                      src := @neighbor^.data;
                      // dst := pointer(dword(bnode) + bnode^.size); // set above
                      size := neighbor^.size - (dword(src) - dword(neighbor));
                      move(src^, dst^, size);
                      inc(bnode^.size, size);              
                      inc(bnode^.numKeys, neighbor^.numKeys);

                      dataNode := @bnode^.data;
                      for curNode := 0 to bnode^.numKeys do
                        begin
                          setParent(dataNode^.link, getAddress(neighbor));
                          inc(dataNode, align(keySize(@dataNode^.key)));
                        end; {for}

                      setRight(bnode^.left, getAddress(neighbor));
                      bnode^.right := neighbor^.right;
                      tmpTag := neighbor^.tag;  // save tag info
                      move(bnode^, neighbor^, info^.bNodeSize);
                      neighbor^.tag := tmpTag;  // restore tag info
                      savePage(neighbor);
                      deleteEntry(bnode^.parent, NIL, getAddress(bnode));
                      deAllocNode(bnode);
                      discardPage(neighbor);

                      exit;
                        
                    end
                  else  // "acquire" an entry from a neighbor
                    begin
                      // we need to do one extra check
                      if (getAddress(neighbor) = bnode^.right) then
                        begin
                          tmpLink := getAddress(bnode);
                          tmpDataNode := @neighbor^.data;
                        end
                      else
                        begin
                          tmpLink := getAddress(neighbor);
                          tmpDataNode := @neighbor^.data;
                          for curNode := 1 to neighbor^.numKeys-1 do
                            inc(dataNode, align(keySize(@tmpDataNode^.key)));
                        end;

                      if (neighbor^.size - align(keySize(@tmpDataNode^.key)) < info^.bNodeSize / 2) then
                        begin
                          savePage(bnode);
                          discardPage(bnode);
                          discardPage(neighbor);
                          exit
                        end;

                      // get the key between the neighbor/bnode pair

                      parent := loadPage(bnode^.parent);
                      if (parent = NIL) then runError;   // this would be bad

                      dataNode := @parent^.data;    
                      for curNode := 1 to parent^.numKeys do
                        begin
                          if (dataNode^.link = tmpLink) then break;
                          inc(dataNode, align(keySize(@dataNode^.key)));
                        end; {for}
                      // theoretically dataNode^.key is the key between the two links
                      
                      nodeSize := align(keySize(@dataNode^.key));
                      
                      if (getAddress(neighbor) = bnode^.right) then
                        begin
//writeln(f, 'acquiring from index bnode/neighbor');
                          savePage(neighbor);   // hmm.. did we change neighbor?

                          src := @dataNode^.key;
                          dataNode := @neighbor^.data;
                          savePage(bnode);
                          insertEntry(getAddress(bnode), src, dataNode^.link);
                          replaceEntry(getAddress(parent), @dataNode^.key, getAddress(bnode));
                          deleteEntry(getAddress(neighbor), NIL, dataNode^.link);
                          
                        end
                      else  // neighbor is to the left, bnode is to the right
                        begin
//writeln(f, 'acquiring from index neighbor/bnode');
          
                          // We need to take the key from the parent node, coupled with the
                          // last value in the neighbor, and insert them into bnode
                          // To do this we have to move everything in bnode over
                          src := @dataNode^.key;
                          nodeSize := align(keySize(@dataNode^.key));
                          dataNode := @bnode^.data;
                          nextDataNode := dataNode;
                          inc(nextDataNode, nodeSize);
                          move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bnode)));
                          
                          // copy in the key
                          fillchar(dataNode^, nodeSize, 0);
                          move(src^, dataNode^.key, keySize(src));
                          // now we update the size and keys
                          inc(bnode^.size, nodeSize);
                          inc(bnode^.numKeys);
                          // we still have to insert the value and update the child's parent ptr   
                          tmpDataNode := dataNode;
                          
                          dataNode := @neighbor^.data;
                          for curNode := 1 to neighbor^.numKeys-1 do
                            inc(dataNode, align(keySize(@dataNode^.key)));
                          nextDataNode := dataNode;
                          inc(nextDataNode, align(keySize(@dataNode^.key)));
                          // insert the value
                          tmpDataNode^.link := nextDataNode^.link;
                          setParent(tmpDataNode^.link, getAddress(bnode));
                          // need to change the insert (don't forget to update the child's parent ptr)
                          // insertEntry(getAddress(bnode), src, nextDataNode^.link);
                          savePage(bnode);
                          savePage(neighbor);
                          replaceEntry(getAddress(parent), @dataNode^.key, getAddress(neighbor));
                          deleteEntry(getAddress(neighbor), NIL, nextDataNode^.link);
                         end; 
                      discardPage(bnode);
                      discardPage(neighbor);
                      discardPage(parent);

                      exit;
                    end; 
                  discardPage(neighbor);
                end;  // if (bnode^.size + neighborSize <= info^.bNodeSize) then

            end;  // if (bnode^.size < (info^.bNodeSize / 2)) then
        
        end;  // else not root

    end;  // not a leaf

  savePage(bnode);
  discardPage(bnode);
end; {bTree.deleteEntry} 

procedure bTree.discardPage;
begin
  // disk based trees allocate temporary memory to hold a page. So, if we're not
  // a memory tree, dispose of the temp page
  if not (memoryTree) then if (node <> NIL) then freemem(node, info^.bNodeSize);
end; {bTree.discardPage}

function bTree.getAddress;
var u:uPtr;
begin
  setNull(u);

  if (memoryTree) then
    u.bPtr := node
  else
    u.offset := node^.tag; { * bNodeSize;}

  result := u;
end; {bTree.getAddress}

function bTree.getRoot;
begin
  if (info = NIL) then 
    setNull(result)
  else 
    result := info^.root;
end;  // bTree.getRoot

function bTree.insertEntry;
var
  tmpUPtr, tmpRoot:uPtr;
  dataNode, nextDataNode, prevDataNode:PbNodeData;
  midKey:BTreeSearchRec;
  keyLength, curNode, dataSize, res:integer;
  bnode, newNode, rootNode:PbNode;
  nodeSize:uInt32;
begin
  result := FALSE;
  if (key = NIL) then exit;

{  case info^.treeType of
    BT_STRING:writeln(f, 'Inserting ', string(key^));
    BT_PCHAR:writeln(f, 'Inserting ', pchar(key));
  end;}

  midKey.key := NIL;
  midKey.keySize := 0;

  keyLength := keySize(key); 

  if (keyLength = 0) or (keyLength > info^.bNodeSize) then exit;

  // The superblock will have to be updated when the tree is destructed
  // set treeChanged to TRUE
  treeChanged := TRUE;

  nodeSize := align(keyLength);

  if (isNull(info^.root)) then
    begin

      (*
       * This case should only occur once when a directory is first created.
       *)
      tmpRoot := allocEmptyNode();
//      info^.root := allocEmptyNode();
      if (isNull(tmpRoot)) then runError;

      rootNode := loadPage(tmpRoot);
      info^.root := tmpRoot;
      inc(info^.keys);
      inc(info^.height);
      rootNode^.magic := B_NODE_LEAF;
      dataNode := @rootNode^.data;

      fillchar(dataNode^, nodeSize, 0);

      dataNode^.link := value;
      copyKey(key, @dataNode^.key);

      inc(rootNode^.size, nodeSize); {hm.. should I add the sizeof the link?}

      savePage(rootNode);
      discardPage(rootNode);
      result := TRUE;
      exit
    end;

  bnode := loadPage(u);

  if (bnode^.size + nodeSize <= info^.bNodeSize) then
    begin

      dataNode := @bnode^.data;
      curNode := 0;
      repeat
        res := compareKeys(key, @dataNode^.key);
        if (res <= 0) then break;
        inc(dataNode, align(keySize(@dataNode^.key)));
        inc(curNode);
      until curNode = bnode^.numKeys;

      if (res = 0) then 
        begin
          dataNode^.link := value;
          savePage(bnode);
          discardPage(bnode);
          result := TRUE;
          exit; 
        end;

      nextDataNode := dataNode;
      inc(nextDataNode, nodeSize); {cast to byte size records}

      move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bNode)));

      fillchar(dataNode^, nodeSize, 0);
      copyKey(key, @dataNode^.key);

      dataNode^.link := value;

      if (bnode^.magic = B_NODE_INDEX) then
        begin
          if (not isNull(dataNode^.link)) then setParent(dataNode^.link, getAddress(bnode));
         { if (curNode = bnode^.numKeys) then
            begin}
          tmpUPtr := dataNode^.link;
          dataNode^.link := nextDataNode^.link;
          nextDataNode^.link := tmpUPtr;
         {   end;}
        end;
      inc(bnode^.numKeys);

      if (bnode^.magic = B_NODE_LEAF) then inc(info^.keys);
      inc(bNode^.size, nodeSize);
      savePage(bnode);
    end
  else
    begin

      newNode := loadPage(allocEmptyNode);
      if (newNode = NIL) then runError;

      newNode^.magic := bnode^.magic;

      newNode^.left := getAddress(bnode);
      newNode^.right := bnode^.right;
      newNode^.parent := bnode^.parent;

      if not(isNull(newNode^.right)) then setLeft(newNode^.right, getAddress(newNode));
      bnode^.right := getAddress(newNode);

      dataNode := @bnode^.data;
 
      {for curNode:=0 to (bnode^.numKeys div 2)-1 do
        inc(dataNode, align(keySize(@dataNode^.key)));}
      curNode := 1;
      repeat
        prevDataNode := dataNode;
        inc(dataNode, align(keySize(@dataNode^.key)));
        inc(curNode);
      until (dword(dataNode) - dword(bnode) >= bnode^.size shr 1);

      {midKey := dataNode;}
      dataNode := prevDataNode;
      midKey.keySize := keySize(@dataNode^.key);
      GetMem(midKey.key, midKey.KeySize);
      {if (midKey.key = NIL) then halt;}
      copyKey(@dataNode^.key, midKey.key);

      dec(curNode);

      if (bnode^.magic = B_NODE_LEAF) then
        begin

          inc(dataNode, align(keySize(@dataNode^.key)));

          dataSize := bnode^.size - (dword(dataNode) - dword(bnode));
          move(dataNode^, newNode^.data, dataSize);
          fillchar(dataNode^, dataSize, 0);

          dec(bnode^.size, dataSize);
          newNode^.size := dataSize + (dword(@newNode^.data) - dword(newNode));

          newNode^.numKeys := bnode^.numKeys - curNode;

          dec(bnode^.numKeys, newNode^.numKeys);

          savePage(bnode);
          savePage(newNode);

          if (compareKeys(key, midKey.key) <= 0) then
            insertEntry(getAddress(bnode), key, value)
          else
            insertEntry(getAddress(newNode), key, value);

        end
      else     // not a leaf
        begin

          inc(dataNode, align(keySize(@dataNode^.key)));

          dataSize := bnode^.size - (dword(dataNode) - dword(bnode));
          move(dataNode^, newNode^.data, dataSize);

          fillchar(dataNode^, dataSize, 0);

          dec(bnode^.size, dataSize);
          // we need to adjust the size because we're taking out the midkey
          // and we're also adding that extra pointer
          dec(bnode^.size, align(keySize(midkey.key)));
          inc(bnode^.size, sizeof(uPtr));

          newNode^.size := dataSize + (dword(@newNode^.data) - dword(newNode));
          
          newNode^.numKeys := bnode^.numKeys - curNode;
          dec(bnode^.numKeys, newNode^.numKeys);
          dec(bnode^.numKeys);

          savePage(bnode);
          savePage(newNode);

          dataNode := @newNode^.data;
          for curNode := 0 to newNode^.numKeys do
            begin
              setParent(dataNode^.link, getAddress(newNode));
              inc(dataNode, align(keySize(@dataNode^.key)));
            end;
          //savePage(bnode);

          if (compareKeys(key, midKey.key) <= 0) then
            insertEntry(getAddress(bnode), key, value)
          else
            insertEntry(getAddress(newNode), key, value);

        end;

      (*
       * If we were the root node, then create a new one
       *)

      if (getAddress(bnode) = info^.root) then
        begin

          tmpRoot := allocEmptyNode();
//          info^.root := allocEmptyNode;
          rootNode := loadPage(tmpRoot);
          info^.root := tmpRoot;
          setParent(getAddress(bnode), info^.root);
          setParent(getAddress(newNode), info^.root);

          inc(info^.height);
          {dataNode := @newNode^.data;     }
          dataNode := @rootNode^.data;
          with rootNode^ do
            begin
              magic := B_NODE_INDEX;
              {numKeys := 1;}
              dataNode^.link := getAddress(bnode);

              keyLength := midKey.keySize;
              copyKey(midKey.key, @dataNode^.key);              
              inc(size, align(keyLength));
              inc(size, sizeof(uPtr));
              inc(dataNode, align(keyLength));
              dataNode^.link := getAddress(newNode);

            end;  {with}
          savePage(rootNode);
          discardPage(rootNode);
        end {if oldNode = root}
      else
        begin
          insertEntry(bnode^.parent, midKey.key, getAddress(newNode));
        end;
      if (midKey.key <> NIL) and (midKey.keySize <> 0) then FreeMem(midKey.key, midKey.keySize);
      discardPage(newNode);
    end; {if out of space in this node}
  discardPage(bnode);
  result := TRUE
end; {bTree.insertEntry}

function bTree.isNull;
begin
  {if (memoryTree) then
    result := (u.bPtr = NIL)
  else}
    result := (u.offset = 0)
end; {bTree.isNull}

function bTree.loadPage;
var 
  node:PbNode;
begin
  result := NIL;
  if isNull(u) then exit;

  if (memoryTree) then
    node := u.bPtr
  else
    begin
      if not vfs^.fSeek(dword(u.offset) * info^.bNodeSize) then runError; {exit}
      getmem(node, info^.bNodeSize);
      if (node = NIL) then exit;
      if not vfs^.fRead(node^, info^.bNodeSize) then runError; {exit;}
    end; // !memoryTree

  result := node
end; // bTree.loadPage

procedure bTree.loadSuperBlock;
begin
  if memoryTree then exit;
  if not vfs^.fSeek(0) then writeln('Error seeking in bTree.loadSuperBlock');
  if not vfs^.fRead(info^, sizeof(BTreeInfo)) then writeln('Error reading in bTree Info in bTree.loadSuperBlock');
end;

function bTree.replaceEntry;
var 
  dataNode:PbNodeData;
  node:PbNode;
  curNode, res, srcKeySize, dstKeySize, deltaSize:integer;
  src, dst:pointer;
begin
  result := FALSE;
  if ((key = NIL) or (isNull(u))) then exit;
  node := loadPage(u);
  if (node = NIL) then exit;

  if (node^.magic <> B_NODE_INDEX) then 
    begin
      writeln('Error! Not in index nodes.');
      discardPage(node);
      exit
    end;

  dataNode := @node^.data;
  // possible bug on count
  for curNode := 0 to node^.numKeys do
    begin
      if (dataNode^.link = value) then
        begin
          srcKeySize := keySize(key);
          dstKeySize := keySize(@dataNode^.key);
          deltaSize := align(srcKeySize) - align(dstKeySize);
          if (deltaSize = 0) then
            begin
              // source and dest keys are equal length. Replace old key with new key
              fillchar(dataNode^.key, dstKeySize, 0);
              move(key^, dataNode^.key, srcKeySize);
              savePage(node);
            end
          else 
            if (deltaSize < 0) then
              begin
                // source key is smaller than dest key.
                fillchar(dataNode^.key, dstKeySize, 0);
                move(key^, dataNode^.key, srcKeySize);
                src := dataNode;
                dst := dataNode;
                inc(src, align(dstKeySize));   // no, this isn't backwards
                inc(dst, align(srcKeySize));   // we're copying from the old size
                move(src^, dst^, node^.size - (dword(src) - dword(node)));
                inc(node^.size, deltaSize);    // deltaSize is negative
                savePage(node);
              end
            else
              begin
                // source key is bigger than dest key
                if ((deltaSize + node^.size) > info^.bNodeSize) then
                  begin
//                    writeln(f, 'Source key would cause split');
                  end
                else  // source key will fit
                  begin
                    // dataNode points to the value/key pair
                    src := dataNode;
                    dst := dataNode;
                    inc(src, align(dstKeySize));
                    inc(dst, align(srcKeySize));
                    move(src^, dst^, node^.size - (dword(src) - dword(node)));
                    fillchar(dataNode^.key, dstKeySize, 0);
                    move(key^, dataNode^.key, srcKeySize);
                    inc(node^.size, deltaSize);
                    savePage(node);
                  end;
        
              end;
         
          break;
        end;
      inc(dataNode, align(keySize(@dataNode^.key)));
    end;
  discardPage(node);
  result := TRUE;
end; {bTree.replaceEntry}

procedure bTree.savePage;
begin
  if (memoryTree) then exit;

  if (node = NIL) then exit;
  
  if not vfs^.fSeek(dword(node^.tag) * info^.bNodeSize) then  writeln('Error seeking in bTree.savePage()');
  if not vfs^.fWrite(node^, info^.bNodeSize) then writeln('Error writing page in bTree.savePage()');
end; // bTree.savePage

procedure bTree.saveSuperBlock;
begin
  if (not(memoryTree)) then 
    begin
//      if not vfs^.fClose() then writeln('Error calling fClose (for the first time) file in bTree.done()');
//      if not vfs^.fOpen(treeName, 1) then writeln('Error calling fOpen in bTree.done()');
      if not vfs^.fSeek(0) then writeln('Error seeking in bTree.saveSuperBlock()'); // reset back to the beginning
      if not vfs^.fWrite(info^, sizeof(BTreeInfo)) then writeln('Error saving superblock in bTree.saveSuperBlock()');
      // disposing the vfs pointer will close the file as necessary
    end;

end; // bTree.saveSuperBlock

procedure bTree.setLeft;
var node:PbNode;
begin
  node := loadPage(u);
  if (node = NIL) then exit;
  node^.left := newPtr;
  savePage(node);
  discardPage(node);
end; {bTree.setLeft}

procedure bTree.setParent;
var node:PbNode;
begin
  node := loadPage(u);
  if (node = NIL) then exit;
  node^.parent := newPtr;
  savePage(node);
  discardPage(node);
end; {bTree.setParent}

procedure bTree.setNull;
begin
{  if (memoryTree) then}
    u.offset := 0
  {else
    u.offset := -1;}
end; {bTree.setNull}

procedure bTree.setRight;
var node:PbNode;
begin
  node := loadPage(u);
  if (node = NIL) then exit;
  node^.right := newPtr;
  savePage(node);
  discardPage(node);
end; {bTree.setRight}

function bTree.fetchFirstKey;
var 
  node:PbNode;
  dataNode:PbNodeData;
begin
  result := FALSE;
  if isNull(u) then exit;
  node := loadPage(u);
  if (node = NIL) then exit;

  dataNode := @node^.data;
  if (node^.magic = B_NODE_LEAF) then
    begin
      searchRec.value := dataNode^.link;
      searchRec.keySize := keySize(@dataNode^.key);
      GetMem(searchRec.key, align(searchRec.keySize));
      if (searchRec.key = NIL) then exit;

      fillchar(searchRec.key^, align(searchRec.keySize), 0);
      copyKey(@dataNode^.key, searchRec.key);
     
      result := TRUE;
    end
  else
    begin
      result := fetchFirstKey(dataNode^.link, searchRec);
    end;
  discardPage(node);
end; {bTree.fetchFirstKey}

function bTree.fetchLastKey;
var 
  node:PbNode;
  dataNode:PbNodeData;
  curNode:integer;
begin
  result := FALSE;
  if isNull(u) then exit;
  node := loadPage(u);
  if (node = NIL) then exit;

  dataNode := @node^.data;

  if (node^.magic = B_NODE_LEAF) then
    begin
      for curNode := 1 to node^.numKeys-1 do
        inc(dataNode, align(keySize(@dataNode^.key)));

      searchRec.value := dataNode^.link;
      searchRec.keySize := keySize(@dataNode^.key);
      GetMem(searchRec.key, align(searchRec.keySize));
      if (searchRec.key = NIL) then exit;

      fillchar(searchRec.key^, align(searchRec.keySize), 0);
      copyKey(@dataNode^.key, searchRec.key);
      result := TRUE;
    end
  else
    begin
      for curNode := 1 to node^.numKeys do
        inc(dataNode, align(keySize(@dataNode^.key)));
      result := fetchLastKey(dataNode^.link, searchRec);
    end;
  discardPage(node);
end; {bTree.fetchLastKey}

procedure bTree.findLeafNode;
var
  bNode:PbNode;
  dataNode:PbNodeData;
  i:integer;
  link:uPtr;
begin
  setNull(result);
  if (key = NIL) then exit;

  if (isNull(u)) then exit;

  bnode := loadPage(u);

  if (bnode^.magic = B_NODE_LEAF) then
    begin
      result := u;
      discardPage(bnode);
      exit;
    end
  else
    begin
      dataNode := @bnode^.data;
      for i:=0 to bnode^.numKeys -1 do
        begin
          if (compareKeys(key, @dataNode^.key) <= 0) then
            begin
              link := dataNode^.link;
              discardPage(bnode);
              result := findLeafNode(link, key);
              exit;
            end;
          inc(dataNode, align(keySize(@dataNode^.key)));
        end; {for}
      link := dataNode^.link;
      discardPage(bnode);
      result := findLeafNode(link, key);
    end;
end; {bTree.findLeafNode}

procedure bTree.freeAll;
var node:PbNode;
begin
  if not(memoryTree) then exit;
  freePage(getRoot());
  setNull(info^.root);
end; {bTree.freeAll}

procedure bTree.freePage;
var
  curNode:integer;
  dataNode:PbNodeData;
  node : PbNode;
begin

  if isNull(u) then exit;

  node := u.bPtr;
  curNode := 0;
  if (node^.magic = B_NODE_INDEX) then
    begin
      dataNode := @node^.data;
      for curNode := 0 to node^.numKeys-1 do
        begin
          freePage(dataNode^.link);
          inc(dataNode, align(keySize(@dataNode^.key)));
        end;
      freePage(dataNode^.link);
    end;
  freemem(node, info^.bNodeSize);
end; // bTree.freePage

function bTree.Delete;
begin
  result := deleteEntry(findLeafNode(getRoot(), key), key, value);
end; {bTree.Delete}

function bTree.Find;
var
  bnode:PbNode;
  dataNode:PbNodeData;
  i, res:integer;

begin
  result := FALSE;
  setNull(u);

  bnode := loadPage(findLeafNode(getRoot(), key));
  if (bnode = NIL) then exit; {oops! serious problem}

  if (bnode^.magic <> B_NODE_LEAF) then writeln('Error! not in leaf nodes');

  dataNode := @bnode^.data;

  for i:=0 to bnode^.numKeys-1 do
    begin
      res := compareKeys(@dataNode^.key, key);  // strcomp(@dataNode^.key.str[1], pchar(key));
      if (res >= 0) then break;
      inc(dataNode, align(keySize(@dataNode^.key)));
    end; {for i}

   // did we find it?
   if (res = 0) then 
     begin
       u := dataNode^.link;
       result := TRUE;
     end; {if res = 0}

  discardPage(bnode);
end; {bTree.Find}

procedure bTree.FindClose;
begin
  with searchRec do
    begin
      setNull(value);
      if (key<>NIL) then 
        FreeMem(key, align(keySize));
      key := NIL;
      keySize := 0;
    end; {with F}
end; {bTree.FindClose}

function bTree.FindFirst;
var
  node:PbNode;  
  dataNode:PbNodeData;
  curNode, res:integer;
begin
  result := FALSE;

  with searchRec do
    begin
      setNull(value);
      key := NIL;
      keySize := 0
    end; {with searchRec}
  if (key = NIL) then exit;
  node := loadPage(findLeafNode(getRoot(), key));
  if (node = NIL) then exit;

  dataNode := @node^.data;
  curNode := 0;
  repeat
    res := compareKeys(key, @dataNode^.key);
    if (res <= 0) then break;
    inc(dataNode, align(keySize(@dataNode^.key)));
    inc(curNode);
  until curNode = node^.numKeys;

  if (res = 0) then   // we found it
    begin
      searchRec.value := dataNode^.link;
      searchRec.keySize := keySize(@dataNode^.key);
      GetMem(searchRec.key, align(searchRec.keySize));
      fillchar(searchRec.key^, align(searchRec.keySize), 0);
      if (searchRec.key = NIL) then exit;
      copyKey(@dataNode^.key, searchRec.key);
      result := TRUE;
    end; {if res = 0}

  discardPage(node);
end; {bTree.FindFirst}

function bTree.FindNext;
var
  nextNode:uPtr;
  node:PbNode;  
  dataNode:PbNodeData;
  curNode, res:integer;

begin
  result := FALSE;

  if (searchRec.key = NIL) then exit;

  node := loadPage(findLeafNode(getRoot(), searchRec.key));
  if (node = NIL) then 
    begin
      findClose(searchRec);
      exit
    end;

  dataNode := @node^.data;
  curNode := 0;
  repeat
    res := compareKeys(@dataNode^.key, searchRec.key);
    if (res > 0) then break;
    inc(dataNode, align(keySize(@dataNode^.key)));
    inc(curNode);
  until curNode = node^.numKeys;

  if (res <= 0) and (curNode = node^.numKeys) then
    begin
      nextNode := node^.right;
      discardPage(node);
      node := loadPage(nextNode);
      if (node = NIL) then 
        begin
          findClose(searchRec);
          exit
        end; {node = NIL}
      dataNode := @node^.data;
    end;
  
  FreeMem(searchRec.key, align(searchRec.keySize));
  searchRec.value := dataNode^.link;
  searchRec.keySize := keySize(@dataNode^.key);
  searchRec.key := NIL;
  GetMem(searchRec.key, align(searchRec.keySize));
  // sanity check
  if (searchRec.key <> NIL) then
    begin
      fillchar(searchRec.key^, align(searchRec.keySize), 0);
      copyKey(@dataNode^.key, searchRec.key);
      result := TRUE
    end;

  discardPage(node);
end; {bTree.FindNext}

function bTree.FindPrev;
var
  prevNode:uPtr;
  node:PbNode;  
  lastDataNode, dataNode:PbNodeData;
  curNode, res:integer;

begin
  result := FALSE;
  if (searchRec.key = NIL) then exit;

  node := loadPage(findLeafNode(getRoot(), searchRec.key));
  if (node = NIL) then 
    begin
      findClose(searchRec);
      exit
    end;

  dataNode := @node^.data;
  lastDataNode := dataNode;
  curNode := 0;
  res := compareKeys(searchRec.key, @dataNode^.key);

  if (res > 0) then
    repeat
      lastDataNode := dataNode;
      inc(curNode);
      inc(dataNode, align(keySize(@dataNode^.key)));
      res := compareKeys(searchRec.key, @dataNode^.key);
    until (res <= 0) or (curNode = node^.numKeys);

  dataNode := lastDataNode;

  if ((res <= 0) and (curNode = 0)) then
    begin
      prevNode := node^.left;
      discardPage(node);
      node := loadPage(prevNode);
      if (node = NIL) then
        begin
          FindClose(searchRec);
          exit
        end;
      dataNode := @node^.data;
      // scan to the end of the previous node. 
      // If there's only 1 key, this will do nothing
      for curNode := 1 to node^.numKeys-1 do    
        inc(dataNode, align(keySize(@dataNode^.key)));
    end;

  FreeMem(searchRec.key, align(searchRec.keySize));
  searchRec.value := dataNode^.link;
  searchRec.keySize := keySize(@dataNode^.key);
  searchRec.key := NIL;
  GetMem(searchRec.key, align(searchRec.keySize));
  // sanity check
  if (searchRec.key <> NIL) then
    begin
      fillchar(searchRec.key^, align(searchRec.keySize), 0);
      copyKey(@dataNode^.key, searchRec.key);
      result := TRUE
    end;

  discardPage(node)
end; {bTree.FindPrev}

function bTree.GetFirstKey;
begin
  with searchRec do
    begin
      setNull(value);
      key := NIL;
      keySize := 0;
    end; {with searchRec}
  result := fetchFirstkey(getRoot(), searchRec);

end; {bTree.GetFirstKey}

function bTree.GetKeyCount;
begin
  if (info <> NIL) then
    result := info^.keys
  else
    result := 0	
end;  // bTree.GetKeyCount

function bTree.GetLastKey;
begin
  with searchRec do
    begin
      setNull(value);
      key := NIL;
      keySize := 0;
    end; {with searchRec}
  result := fetchLastKey(getRoot(), searchRec);
end; {bTree.GetLastKey}

procedure bTree.Print;
var
  curNode:integer;
  dataNode:PbNodeData;
  node : PbNode;
  link : uPtr;
begin
  if isNull(u) then exit;
  PrintInfo(f, u);
  node := loadPage(u);
  curNode := 0;
  if (node^.magic = B_NODE_INDEX) then
    begin
      dataNode := @node^.data;
      for curNode := 0 to node^.numKeys-1 do
        begin
          Print(f, dataNode^.link);
          inc(dataNode, align(keySize(@dataNode^.key)));
        end;
      Print(f, dataNode^.link);
    end;
  discardPage(node);
end; {bTree.Print}

procedure bTree.PrintInfo;
const leafStr:array[boolean] of string[5] = ('INDEX', 'LEAF');
var
  dataNode:PbNodeData;
  curNode:integer;
  node : PbNode;
  i:integer;
begin	
  node := loadPage(u);
  if (node = NIL) then exit;
  write(f, '             ', hex(dword(node^.parent.offset)));
  writeln(f, '              ', leafStr[node^.magic = B_NODE_LEAF], ' (', hex(node^.magic), ')');
  write(f, hex(dword(node^.left.bPtr)), ' <-> ',  hex(getAddress(node).u), ' <-> ', hex(dword(node^.right.bPtr)));
  writeln(f, '   |   capacity ',      node^.size, '[', calcSize(node), '](', info^.bNodeSize, ')   |   keys:', node^.numKeys);
  if (node^.numKeys = 0) then exit;
  dataNode := @node^.data;

  curNode := 0;
  for curNode := 0 to node^.numKeys-(ord(node^.magic = B_NODE_LEAF)) do
    begin

      write(f, '| ', curNode:2, '| ');

      {if (node^.magic = B_NODE_INDEX) then}
        write(f, '[', hex(dword(dataNode^.link.offset)), ']');

      if (curNode <> node^.numKeys) then
        case info^.treeType of
          BT_custom:with TBlockRun(dataNode^.key) do writeln(f, ' {', AG, ':', start, ':', len, '} ');
          BT_INT32:writeln(f, ' {', hex(dataNode^.key.i), '} ');
          BT_INT64:writeln(f, ' {', hex(dataNode^.key.x), '} ');
          BT_SINGLE:writeln(f, ' {', dataNode^.key.s, '} ');
          BT_DOUBLE:writeln(f, ' {', dataNode^.key.d, '} ');
          BT_STRING:writeln(f, ' {', dataNode^.key.str, '} ');
          BT_PCHAR:begin
               (*writeln(f, ' {', pchar(@dataNode^.key)^, '} '); *)
               write(f, ' {');
               i:=0;
               while (dataNode^.key.str[i]<>#0) do 
                 begin
                   write(f, dataNode^.key.str[i]);
                   inc(i);
                 end;
               writeln(f, '} '); 
              end; 
        end;

      inc(dataNode, align(keySize(@dataNode^.key)));
    end;
  discardPage(node);
  writeln(f);
end; {bTree.PrintInfo}

function bTree.Insert;
begin
  result := insertEntry(findLeafNode(getRoot(), key), key, value);
end; {bTree.Insert}

procedure bTree.InstallUserFunctions;
begin
  compareKeys := cmpFunc;
  copyKey := copyProc;
  keySize := ksFunc;
end; {bTree.InstallUserFunc}

procedure bTree.PrintList;
var
  dataNode:PbNodeData;
  node:PbNode;
  link:uPtr;
  i, j : integer;
  f:text;
begin

  if (isNull(getRoot())) then exit;
  assign(f, filename);
  {$I-} rewrite(f); {$I+}
  if (IOResult() <> 0) then exit;
  
  node := loadPage(getRoot());
  if (node = NIL) then exit;
  while (node^.magic = B_NODE_INDEX) do
    begin
      dataNode := @node^.data;
      link := dataNode^.link;
      discardPage(node);
      node := loadPage(link);
    end;

  while (not isNull(link)) do
    begin
      dataNode := @node^.data;
      for i := 0 to node^.numKeys -1 do
        begin
          case info^.treeType of
            BT_CUSTOM:with TBlockRun(dataNode^.key) do writeln(f, AG, ':', start, ':', len);
            BT_INT32:writeln(f, hex(dataNode^.key.i));
            // BT_INT64:writeln(f, dataNode^.key.x);
            BT_SINGLE:writeln(f, dataNode^.key.s);
            BT_DOUBLE:writeln(f, dataNode^.key.d);
            BT_STRING:writeln(f, dataNode^.key.str);
            BT_PCHAR:begin
                 j:=0;
                 while (dataNode^.key.str[j]<>#0) do 
                   begin
                     write(f, dataNode^.key.str[j]);
                     inc(j);
                   end;
                 writeln(f); 
                end; 
          end;  // case
          inc(dataNode, align(keySize(@dataNode^.key)));
        end; {for i}
      link := node^.right;
      discardPage(node);
      node := loadPage(link);
    end; {while}
  {$I-} close(f); {$I+}
  if (IOResult() <> 0) then writeln('Error calling close() in bTree.PrintList');
end; {bTree.PrintList}

procedure bTree.PrintWholeTree;
var f:text;
begin
  assign(f, filename);
  {$I-} rewrite(f); {$I+}  
  if (IOResult() <> 0) then exit;
  Print(f, getRoot());
  {$I-} close(f);  {$I+}
  if (IOResult() <> 0) then writeln('Error calling close() in bTree.PrintWholeTree');
end; {bTree.PrintWholeTree}

destructor bTree.done;
begin
  if (not(memoryTree) and treeChanged) then 
    begin
//      if not vfs^.fClose() then writeln('Error calling fClose (for the first time) file in bTree.done()');
//      if not vfs^.fOpen(treeName, 1) then writeln('Error calling fOpen in bTree.done()');
      if not vfs^.fSeek(0) then writeln('Error seeking in bTree.done()'); // reset back to the beginning
      if not vfs^.fWrite(info^, sizeof(BTreeInfo)) then writeln('Error saving superblock in bTree.done()');
      if not vfs^.fClose() then writeln('Error calling fClose file in bTree.done()');
      // disposing the vfs pointer will close the file as necessary
    end;
  if (not isNull(getRoot())) then freeAll;

  {  with info^ do
      begin
        writeln('tree height: ', height);
        writeln('tree leaf key count: ', keys);
        writeln('Total number of nodes: ', nodes);
 //       freeAll;
      end;
  }
     
  dispose(info);
  if (vfs <> NIL) then dispose(vfs, done);
  info := NIL;
end; // bTree.done

constructor TQueue.init;
begin
  queue := NIL;
  elements := 0;
  maxElements := elementCount;
  size := elementSize;
  head := 0;
  tail := 0;
  if (size <> 0) and (maxElements <> 0) then 
    begin
      GetMem(queue, size*maxElements);
      fillchar(queue^, size*maxElements, 0);
    end;
end; // TQueue.init

function TQueue.dequeue;
var p:pointer;
begin
  result := FALSE;
  if (queue = NIL) or (elements = 0) then exit;
  p := queue;
  inc(p, head*size);
  head := (head+1) mod maxElements;
  move(p^, item, size);
  dec(elements);
  result := TRUE;
end; // TQueue.dequeue

function TQueue.enqueue;
var p:pointer;
begin
  result := FALSE;
  if (queue = NIL) or (elements = maxElements) then exit;
  p := queue;
  inc(p, tail*size);
  move(item, p^, size);
  tail := (tail+1) mod maxElements;
  inc(elements);
  result := TRUE;
end; // TQueue.enqueue

function TQueue.getElementCount; result := elements;

destructor TQueue.done;
begin
  if (size <> 0) and (maxElements <> 0) then FreeMem(queue, size*maxElements);
  queue := NIL;
  elements := 0;
  maxElements := 0;
  size := 0;
  head := 0;
  tail := 0;
end; // TQueue.done

end.