//
//  File:   hzIsamT.cpp
//
//  Legal Notice:   This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com)
//
//  The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free
//  Software Foundation, either version 3 of the License, or any later version.
//
//  The HadronZoo C++ Class Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
//  A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses.
//
#include <iostream>
#include "hzString.h"
#include "hzLock.h"
#include "hzProcess.h"
#include "hzIsamT.h"
using namespace std ;
/*
**  Definitions
*/
void    _hz_vn_Idx::SetCumulatives  (void)
{
    //  Set the cumulative count in an index node
    _hz_vn_Any* pChild ;    //  Child node to examine
    int32_t     n ;         //  Child iterator
    m_nCumulative = 0 ;
    for (n = 0 ; n < usage ; n++)
    {
        //pChild = (_hz_vn_Any*) pIdx->m_Ptrs[n] ;
        pChild = m_Ptrs[n] ;
        m_nCumulative += pChild->Cumulative() ;
    }
}
void    _hz_tmpl_ISAM::_moveElement (_hz_vn_Dat* pNodeTgt, _hz_vn_Dat* pNodeSrc, uint32_t tgtSlot, uint32_t srcSlot)
{
    //  Move object from one bucket to another
    _hzfunc("_hz_tmpl_ISAM::_moveElement") ;
    uchar*  pSrc ;
    uchar*  pTgt ;
    if (!pNodeTgt || !pNodeSrc)
        Fatal("Src node %p tgt node %p\n", pNodeTgt, pNodeSrc) ;
    pSrc = (uchar*) pNodeSrc->m_pElements ;
    pTgt = (uchar*) pNodeTgt->m_pElements ;
    if (!pSrc || !pTgt)
        Fatal("Src bucket %p tgt bucket %p\n", pSrc, pTgt) ;
    if (m_nSizeKey)
    {
        pSrc += m_nSizeKey * srcSlot ;
        pTgt += m_nSizeKey * tgtSlot ;
        memcpy(pTgt, pSrc, m_nSizeKey) ;
        memset(pSrc, 0, m_nSizeKey) ;
    }
    if (m_nSizeObj)
    {
        pSrc = (uchar*) pNodeSrc->m_pElements ;
        pTgt = (uchar*) pNodeTgt->m_pElements ;
        pSrc += (m_nSizeKey * HZ_T_ISAMNODE_FULL) + (m_nSizeObj * srcSlot) ;
        pTgt += (m_nSizeKey * HZ_T_ISAMNODE_FULL) + (m_nSizeObj * tgtSlot) ;
        memcpy(pTgt, pSrc, m_nSizeObj) ;
        memset(pSrc, 0, m_nSizeObj) ;
    }
}
_hz_vn_Dat* _hz_tmpl_ISAM::_allocDataSlot   (_hz_vn_Dat* pDN, int32_t& nSlot)
{
    //  Support function to make space available as part of an INSERT operation. A target data node and slot are supplied and these will have been identified earlier by one of two
    //  means: Either _findDnodeByKey() within InsertKey() or _findDnodeByPos() within InsertPos().
    //
    //  This function has the following outcomes:-
    //
    //      1)  If the target is not full the new entry will be placed in the target at the designated slot, shifting entries above the slot up by one place.
    //
    //      2)  If the target node is full but the target's infra node has space then one of two things will happen:-
    //
    //          a)  If the designated slot is 0 then the new entry will be placed directly in the top slot of the infra node.
    //
    //          b)  If the designated slot is above 0 then slot 0 of the target is moved to the top slot of the infra node, the designated slot is reduced by 1 and all
    //              slots lower than the new designated slot is shifted down one place.
    //
    //      3)  If the target node is full and there is no room on an infra, there will always be room on an ultra node. This is because if the ultra does not exist or
    //          is full, a new ultra will be created. Then one of two things will happen:-
    //
    //          a)  If the designated slot is HZ_T_ISAMNODE_FULL (which is possible if _findDnodeByPos() is called with bExpand = true), then the new entry will be placed in slot 0 of
    //              the ultra node.
    //
    //          b)  If the designated slot is < HZ_T_ISAMNODE_FULL then the top slot of the target node is moved to slot 0 of the ultra and then the insert will proceed as in
    //              scenario (1)
    //
    //  Arguments   1)  The target data node
    //              2)  The slot
    //
    //  Returns     Pointer to the actual node the new entry resides in
    //
    //  NOTE that neither this function or its caller, actually writes the element being inserted as this can only be done within the Insert of a fully qualified template.
    _hzfunc("_hz_tmpl_ISAM::_allocDataSlot") ;
    _hz_vn_Dat* pInfra = 0 ;    //  New data node
    _hz_vn_Dat* pUltra = 0 ;    //  New data node
    _hz_vn_Idx* pIdx ;          //  Index node (for updating cimulatives)
    int32_t     n ;             //  Loop controller
    //  Test inputs
    if (!pDN)
        Fatal("isam=%d (%s): No taget data node supplied\n", m_isamId, *m_Name) ;
    if (pDN->Usage() > HZ_T_ISAMNODE_FULL)  Fatal("Isam=%d (%s): Garbage usage value (%d)\n", m_isamId, *m_Name, n) ;
    if (nSlot > HZ_T_ISAMNODE_FULL)         Fatal("Isam=%d (%s): Illegal slot value (%d)\n", m_isamId, *m_Name, nSlot) ;
    if (nSlot > pDN->Usage())               Fatal("Isam=%d (%s): Excessive slot value (%d)\n", m_isamId, *m_Name, nSlot) ;
    m_nElements++ ;
    if (nSlot < HZ_T_ISAMNODE_FULL)
    {
        //  Target slot within target node (only way the slot can be HZ_T_ISAMNODE_FULL is if the target data node is the highest one)
        if (pDN->Usage() < HZ_T_ISAMNODE_FULL)
        {
            //  New element can be accomodated on this node so add it and return
            for (n = pDN->Usage() ; n > nSlot ; n--)
                _moveElement(pDN, pDN, n, n-1) ;
            pDN->IncUsage() ;
            for (pIdx = pDN->parent ; pIdx ; pIdx = pIdx->parent)
                pIdx->m_nCumulative++ ;
            return pDN ;
        }
        //  Node is full so check if first element can be moved to the infra node, if it exists.
        pInfra = (_hz_vn_Dat*) pDN->Infra() ;
        if (pInfra && pInfra->Usage() < HZ_T_ISAMNODE_FULL)
        {
            //  Space found on infra node
            if (nSlot == 0)
            {
                //  Store new object direcly in top slot in infra node
                nSlot = pInfra->Usage() ;
                pInfra->IncUsage() ;
                for (pIdx = pInfra->parent ; pIdx ; pIdx = pIdx->parent)
                    pIdx->m_nCumulative++ ;
                    //pIdx->SetCumulatives() ;
                return pInfra ;
            }
            //  Move element 0 to infra node, decrement slot move all elements upto slot down one place, store object in slot
            _moveElement(pInfra, pDN, pInfra->Usage(), 0) ;
            pInfra->IncUsage() ;
            for (pIdx = pInfra->parent ; pIdx ; pIdx = pIdx->parent)
                pIdx->m_nCumulative++ ;
                //pIdx->SetCumulatives() ;
            nSlot-- ;
            for (n = 0 ; n < nSlot ; n++)
                _moveElement(pDN, pDN, n, n+1) ;
            return pDN ;
        }
    }
    //  No room on the infra so we try the ultra. If this is not present or full, create a new empty ultra.
    pUltra = pDN->Ultra() ;
    if (!pUltra || pUltra->Usage() == HZ_T_ISAMNODE_FULL)
    {
        //  Could not find space on either infra or ultra node so create a new ultra node
        if (!pUltra)
        {
            pUltra = pDN->ultra = new _hz_vn_Dat ;
            pUltra->infra = pDN ;
        }
        else
        {
            pUltra->infra = pDN->ultra = new _hz_vn_Dat ;
            pUltra->infra->ultra = pUltra ;
            pUltra->infra->infra = pDN ;
            pUltra = pUltra->infra ;
        }
        pUltra->parent = pDN->parent ;
        pUltra->m_isamId = m_isamId ;
        pUltra->m_pElements = new uchar[HZ_T_ISAMNODE_FULL * (m_nSizeKey + m_nSizeObj)] ;
        memset(pUltra->m_pElements, 0, HZ_T_ISAMNODE_FULL * (m_nSizeKey + m_nSizeObj)) ;
        //  Put this new node into the index
        _allocIndxSlot(pDN, pUltra) ;
        if (nSlot == HZ_T_ISAMNODE_FULL)
        {
            //  This can only happen when current data node is the last. We can directly load into slot 0 of the new ultra node
            pUltra->IncUsage() ;
            for (pIdx = pUltra->parent ; pIdx ; pIdx = pIdx->parent)
                //pIdx->m_nCumulative++ ;
                pIdx->SetCumulatives() ;
            nSlot = 0 ;
            return pUltra ;
        }
    }
    if (pUltra && pUltra->Usage() < HZ_T_ISAMNODE_FULL)
    {
        //  Space found on ultra node so move top element to it
        for (n = pUltra->Usage() ; n ; n--)
            _moveElement(pUltra, pUltra, n, n-1) ;
        _moveElement(pUltra, pDN, 0, pDN->Usage()-1) ;
        pUltra->IncUsage() ;
        pDN->DecUsage() ; 
        //  Now insert new element in current node
        for (n = pDN->Usage() ; n > nSlot ; n--)
            _moveElement(pDN, pDN, n, n-1) ;
        pDN->IncUsage() ;
        for (pIdx = pDN->parent ; pIdx ; pIdx = pIdx->parent)
            //pIdx->m_nCumulative++ ;
            pIdx->SetCumulatives() ;
        if (pDN->parent != pUltra->parent)
            for (pIdx = pUltra->parent ; pIdx ; pIdx = pIdx->parent)
                pIdx->SetCumulatives() ;
        return pDN ;
    }
    threadLog("unhandled case\n") ;
    return pDN ;
}
_hz_vn_Dat* _hz_tmpl_ISAM::_expelDataSlot   (_hz_vn_Dat* pDN, int32_t nSlot)
{
    //  The node can be removed if empty or if all remaining elements can be migrated to its infra and ultra adjacents. By convention we migrate what
    //  we can to the infra adjacent first and then migrate any remaining to the ultra adjacent.
    //
    //  Arguments:  1)  pDN     The node from which an element is to be removed
    //              2)  nSlot   The slot number of the depricated element
    //
    //  Returns:    Pointer to the current node remanent
    //              NULL if the current node is emptied by this action
    //
    //  Note: In most cases, the return is ignored by the caller.
    _hzfunc("_hz_tmpl_ISAM::_expelDataSlot") ;
    _hz_vn_Idx* pPar ;      //  For setting cumulatives in index
    _hz_vn_Dat* pInfra ;    //  For setting cumulatives in index
    _hz_vn_Dat* pUltra ;    //  For setting cumulatives in index
    uchar*      pKeys ;     //  Keys on node
    int32_t     x = 0 ;     //  Loop control, doubles as No of elements moved to infra
    int32_t     y = 0 ;     //  Loop control, doubles as No of elements moved to ultra
    uint32_t    ino ;       //  ISAM number
    //  Test inputs
    if (!pDN)
        Fatal("Isam=%d (%s): No taget data node supplied\n", m_isamId, *m_Name) ;
    ino = pDN->IsamId() ;
    if (pDN->Usage() > HZ_T_ISAMNODE_FULL)          Fatal("Isam=%d (%s): Garbage usage value (%d)\n", m_isamId, *m_Name, x) ;
    if (nSlot < 0 || nSlot > HZ_T_ISAMNODE_FULL)    Fatal("Isam=%d (%s): Illegal slot value (%d)\n", m_isamId, *m_Name, nSlot) ;
    if (nSlot >= pDN->Usage())                      Fatal("Isam=%d (%s): Excessive slot value (%d)\n", m_isamId, *m_Name, nSlot) ;
    m_nElements-- ;
    //  Exclude the slot
    pDN->DecUsage() ;
    for (x = nSlot, y = pDN->Usage() ; x < y ; x++)
        _moveElement(pDN, pDN, x, x+1) ;
    //  Set end slot to null
    pKeys = (uchar*) pDN->m_pElements ;
    memset(pKeys + (pDN->Usage() * m_nSizeKey), 0, m_nSizeKey) ;
    //  Decrement parent cumulatives
    for (pPar = pDN->parent ; pPar ; pPar = pPar->parent)
        //pPar->m_nCumulative-- ;
        pPar->SetCumulatives() ;
    //  See if reduced node can be purged
    if (pDN->Usage() && pDN->Usage() < HZ_T_ISAMNODE_LOW)
    {
        pInfra = (_hz_vn_Dat*) pDN->Infra() ;
        pUltra = (_hz_vn_Dat*) pDN->Ultra() ;
        x = pInfra ? HZ_T_ISAMNODE_FULL - pInfra->Usage() : 0 ;
        y = pUltra ? HZ_T_ISAMNODE_FULL - pUltra->Usage() : 0 ;
        if ((x + y) >= pDN->Usage())
        {
            //  All remaining slots on this node can be accomodated by adjacents. Begin by moving all that we can to infra node first
            x = y = 0 ;
            if (pInfra && pInfra->Usage() < HZ_T_ISAMNODE_FULL)
            {
                for (x = 0 ; pDN->Usage() && pInfra->Usage() < HZ_T_ISAMNODE_FULL ; x++)
                {
                    //pInfra->setfromUlt(pInfra->Usage(), x) ;
                    _moveElement(pDN, pUltra, pInfra->Usage(), x) ;
                    pInfra->IncUsage() ;
                    pDN->DecUsage() ;
                }
                for (pPar = pInfra->Parent() ; pPar ; pPar = pPar->parent)
                    //pPar->m_nCumulative += x ;
                    pPar->SetCumulatives() ;
                if (pInfra->IsamId() != ino || pInfra->Usage() < 0 || pInfra->Usage() > HZ_T_ISAMNODE_FULL)
                    Fatal("Isam=%d (%s): Infra %d Invalid: isam id (%d) usage (%d)\n", ino, *m_Name, pInfra->Ident(), pInfra->IsamId(), pInfra->Usage()) ;
            }
            if (pUltra && pDN->Usage() && pUltra->Usage() < HZ_T_ISAMNODE_FULL)
            {
                //  If any elements remain on the current block we need to shift upwards the elements on the ultra to accomodate them
                for (y = pUltra->Usage() + pDN->Usage() - 1 ; y >= pDN->Usage() ; y--)
                    _moveElement(pDN, pDN, y, y-pDN->Usage()) ;
                    //pUltra->setfromCur(y, y - pDN->Usage()) ;
                pUltra->SetUsage(pUltra->Usage() + pDN->Usage()) ;
                //  Move remainder to ultra node
                for (y = 0 ; y < pDN->Usage() ; y++)
                    _moveElement(pDN, pInfra, y, x+y) ;
                    //pUltra->setfromInf(y, x + y) ;
                pDN->SetUsage(0) ;
                for (pPar = pUltra->Parent() ; pPar ; pPar = pPar->parent)
                    //pPar->m_nCumulative += y ;
                    pPar->SetCumulatives() ;
                if (pUltra->IsamId() != ino || pUltra->Usage() < 0 || pUltra->Usage() > HZ_T_ISAMNODE_FULL)
                    Fatal("Isam=%d (%s): Ultra %d Invalid: isam id (%d) usage (%d)\n", ino, *m_Name, pUltra->Ident(), pUltra->IsamId(), pUltra->Usage()) ;
            }
            x += y ;
            for (pPar = pDN->Parent() ; pPar ; pPar = pPar->parent)
                //pPar->m_nCumulative -= x ;
                pPar->SetCumulatives() ;
        }
    }
    if (pDN->Usage())
        return pDN ;
    //  Eliminate node: Connect adjacents
    pInfra = pDN->Infra() ;
    pUltra = pDN->Ultra() ;
    if (pUltra)
        pUltra->SetInfra(pInfra) ;
    if (pInfra)
        pInfra->SetUltra(pUltra) ;
    if (pDN->Parent())
        _expelIndxSlot(pDN->Parent(), pDN) ;
    else
    {
        delete pDN ;
        m_pRoot = 0 ;
    }
    return 0 ;
}
_hz_vn_Dat* _hz_tmpl_ISAM::_findDnodeByPos  (int32_t& nSlot, uint32_t nPosn, bool bExpand) const
{
    //  Purpose:    This function finds for an absolute element position, the data node and slot where the element resides. It is the first of three 'data-node
    //              locator' functions and the only one that can reside in a base ISAM as it does not rely on key comparison. These functions are critical since
    //              all access to elements depend on them.
    //
    //  Method:     Starting at the root we add up the cumulatives until they exceed the required position. Then go up to the next level and do the same.
    //              The exceptions to this are where there is no data and therefore no root or where the only node is the root.
    //
    //  Arguments:  1)  nSlot   A reference to store the node offset of the element
    //              2)  nPosn   The required position
    //              3)  bExpand Expand flag. If set function can find the end postion + 1 (set for insert, clear for lookup)
    //
    //  Returns:    Pointer to the data node for the element
    _hzfunc("_hz_tmpl_ISAM::_findDnodeByPos") ;
    _hz_vn_Idx* pIdx = 0 ;      //  Current index node
    _hz_vn_Any* pChild = 0 ;    //  Can be index node or outer level data node
    _hz_vn_Dat* pDN = 0 ;       //  Target data node
    uint32_t    nStart = 0 ;    //  True starting position of current node as per cumulatives
    uint32_t    nMin = 0 ;      //  The real position within the collection of the current node's lowest element.
    uint32_t    nMax = 0 ;      //  The real position within the collection of the current node's lowest element plus current node usage
    int32_t     n ;             //  Element iterator
    //  Check if there is a root
    if (!this)
        Fatal("No instance\n") ;
    if (!m_pRoot)
        { nSlot = -1 ; return 0 ; }
    //  Check limits
    nMax = bExpand ? m_pRoot->Cumulative() : m_pRoot->Cumulative()-1 ;
    if (nPosn > nMax)
        { nSlot = -1 ; return 0 ; }
    //  If the root is the only node
    if (m_pRoot->Level() == 0)
    {
        pDN = (_hz_vn_Dat*) m_pRoot ;
        nSlot = nPosn ;
        return pDN ;
    }
    //  Common case, start at root which is an index node
    pIdx = (_hz_vn_Idx*) m_pRoot ;
    nMax = nMin = nStart = 0 ;
    for (;;)
    {
        //  Look on the node for the offset/pointer to the next node or the
        nMin = nStart ;
        if (!pIdx->Usage())
            Fatal("Empty index node %p\n", pIdx) ;
        for (n = 0 ; n < pIdx->Usage() ; n++)
        {
            pChild = (_hz_vn_Any*) pIdx->m_Ptrs[n] ;
            if (!pChild)
                Fatal("Isam=%d (%s): Missing node pointer on non leaf node\n", m_isamId, *m_Name) ;
            if (pChild->Parent() != pIdx)
                Fatal("isam=%d (%s): Child node %p (level %d) does not point back to current node %p (level %d)\n", m_isamId, *m_Name, pChild, pChild->Level(), pIdx, pIdx->level) ;
            //  If the cumulative exceeds the desired posn we have the node
            nStart = nMin ;
            nMax = nMin + pChild->Cumulative() ;
            if (nPosn < nMax)
                break ;
            nMin = nMax ;
        }
        //  Go to next level or accept data node as target
        if (pChild->Level())
            pIdx = (_hz_vn_Idx*) pChild ;
        else
        {
            pDN = (_hz_vn_Dat*) pChild ;
            break ;
        }
    }
    nSlot = nPosn - nStart ;
    if (pDN && nSlot > pDN->Usage())
        Fatal("Isam=%d (%s): Node %d usage %u has offset %u\n", m_isamId, *m_Name, pDN->Ident(), pDN->Usage(), nSlot) ;
    return pDN ;
}
hzEcode _hz_tmpl_ISAM::_allocIndxSlot   (_hz_vn_Any* pOld, _hz_vn_Any* pNew)
{
    //  This function is called when a node (pOld) spawns an ultra sibling (pNew). Note that nodes never spawn infra siblings. pOld and pNew can be either a pair of data nodes or a
    //  pair of index nodes. Both must be supplied and both must be at the same level.
    //
    //  pOld as the established node, will either have a parent or be the root. Adding a sibling to the root always results in a new root, in which case, both pOld and pNew become
    //  child nodes of the new root.
    //
    //  Where pOld already has a parent, the action is to add pNew to the parent's list of child nodes, one place up from where pOld is placed. As a result of this action, pOld and
    //  pNew may have the same parent OR the parent of pNew can be the ultra sibling of the parent of pOld. It is possible for pOld to become a child of its original parent'e infra
    //  sibling. This function will set the parents of both pOld and pNew accordingly.
    //
    //
    //
    //  having different parents
    //  The action takes place in the parent of pOld
    //
    //  This function is called on an index node which may have either data or index node pointers as elements, but when a child node pOld spawns an ultra sibling pNew. Note that
    //  nodes never spawn infra siblings. The action is to find the slot occupied by pOld and place pNew in the next slot up. This is an insert action with all elements higher than pOld moving up one place to make room for pNew.
    //
    //  In the event the current node pCur is null this is because pOld is currently the root and has no parent. A new root is formed with pOld in slot 0 and pNew
    //  (which must be pOld's ultra node) in slot 1.
    //
    //  In the event the current node is full we first check if the infra exists and has space. If it does we move slot 0 to the top slot of the infra, move
    //  elements upto and including pOld down one place and put pNew in pOld's former slot. If the infra is full we see if a ultra has space. If not create
    //  a new ultra node. We then insert this node's top top element in the ultra's slot 0. In the special case where pOld is in the top slot of this node
    //  we just put pNew in the ultra slot 0.
    //
    //  Note that if a new ultra sibling is created a pointer to it must be entered in the parent node. _allocIndxSlot() is called on the parent node with this node
    //  as pOld and the new sibling as pNew. If there is no parent a parent is created and both this node as the sibling added as children. This new parent node
    //  is returned. The calling function (_allocDataSlot) then sets this as the new root to the ISAM.
    //
    //  Arguments:  1)  pOld    The pointer to the pre-existing node (prognitor)
    //              2)  pNew    The pointer to the newly created ultra sibling of the prognitor
    //
    //  Returns:    E_OK    Always
    _hzfunc("_hz_tmpl_ISAM::_allocIndxSlot") ;
    _hz_vn_Any*     pNode = 0 ;     //  Shift element
    _hz_vn_Idx*     pIdxNew = 0 ;   //  New ultra sibling
    _hz_vn_Idx*     pCur = 0 ;      //  Original parent of pOld
    _hz_vn_Idx*     pPar = 0 ;      //  Parent for cumulatives
    int32_t         slot = 0 ;      //  Slot found for existing pOld
    int32_t         n = 0 ;         //  Slot iterator
    int32_t         za ;            //  Shift cumulative 1
    int32_t         zb ;            //  Shift cumulative 2
    //  Verify arguments
    if (!pOld)  Fatal("Isam=%d (%s): No pre-existing item supplied\n", m_isamId, *m_Name) ;
    if (!pNew)  Fatal("Isam=%d (%s): No new item supplied\n", m_isamId, *m_Name) ;
    if (pNew->Infra() != pOld)  Fatal("Isam=%d (%s): new %p does not point back to old %p\n", m_isamId, *m_Name, pNew, pOld) ;
    if (pOld->Ultra() != pNew)  Fatal("Isam=%d (%s): old %p does not point forward to new %p\n", m_isamId, *m_Name, pOld, pNew) ;
    //  Do we need a new root?
    if (!pOld->Parent())
    {
        pIdxNew = new _hz_vn_Idx() ;
        pIdxNew->m_Ptrs[0] = pOld ;
        pIdxNew->m_Ptrs[1] = pNew ;
        pOld->SetParent(pIdxNew) ;
        pNew->SetParent(pIdxNew) ;
        pIdxNew->m_isamId = m_isamId ;
        pIdxNew->usage = 2 ;
        pIdxNew->m_nCumulative = pOld->Cumulative() + pNew->Cumulative() ;
        pIdxNew->level = pOld->Level()+1 ;
        m_pRoot = pIdxNew ;
        return E_OK ;
    }
    //  Given we have a current node, further checks
    pCur = pOld->Parent() ;
    if ((pOld->Level()+1) != pCur->level)   Fatal("Isam=%d (%s): At level %d but old element level is %d\n", m_isamId, *m_Name, pCur->level, pOld->Level()) ;
    if ((pNew->Level()+1) != pCur->level)   Fatal("Isam=%d (%s): At level %d but new element level is %d\n", m_isamId, *m_Name, pCur->level, pNew->Level()) ;
    if (!pNew->Parent())
        pNew->SetParent(pCur) ;
    if (pNew->Parent() != pCur)
        Fatal("Isam=%d (%s): new node %p does not have this node (%p) as its parent. Instead it has %p\n", m_isamId, *m_Name, pNew, pCur, pNew->Parent()) ;
    //  Find out which slot pOld is on
    for (slot = 0 ; slot < pCur->usage ; slot++)
    {
        if (pCur->m_Ptrs[slot] == pOld)
            break ;
    }
    if (slot == pCur->usage)
        Fatal("Isam=%d (%s): On node %p: Child node %p not found\n", m_isamId, *m_Name, pCur, pOld) ;
    slot++ ;
    //  If there is space on this node just insert the new element
    if (pCur->usage < HZ_T_ISAMNODE_FULL)
    {
        pNew->SetParent(pCur) ;
        for (n = pCur->usage ; n > slot ; n--)
            pCur->m_Ptrs[n] = pCur->m_Ptrs[n-1] ;
        pCur->m_Ptrs[slot] = pNew ;
        pCur->usage++ ;
        //  Update cumulatives
        //pCur->m_nCumulative += pNew->Cumulative() ;
        for (pPar = pCur->parent ; pPar ; pPar = pPar->parent)
            //pPar->m_nCumulative += pNew->Cumulative() ;
            pPar->SetCumulatives() ;
        return E_OK ;
    }
    //  This node is full so we first see if we have an infra and with space
    if (pCur->infra && pCur->infra->usage < HZ_T_ISAMNODE_FULL)
    {
        //  Move element 0 to infra node
        pNode = pCur->m_Ptrs[0] ;
        za = pNode->Cumulative() ;
        pNode->SetParent(pCur->infra) ;
        pCur->infra->m_Ptrs[pCur->infra->usage] = pNode ;
        pCur->infra->usage++ ;
        //  Insert the new element
        slot-- ;
        for (n = 0 ; n < slot ; n++)
            pCur->m_Ptrs[n] = pCur->m_Ptrs[n+1] ;
        zb = pNew->Cumulative() ;
        pNew->SetParent(pCur) ;
        pCur->m_Ptrs[slot] = pNew ;
        //  Update cumulatives
        pCur->infra->m_nCumulative += za ;
        pCur->m_nCumulative += (zb - za) ;
        if (pCur->parent == pCur->infra->parent)
        {
            for (pPar = pCur->parent ; pPar ; pPar = pPar->parent)
                //pPar->m_nCumulative += zb ;
                pPar->SetCumulatives() ;
        }
        else
        {
            for (pPar = pCur->infra->parent ; pPar ; pPar = pPar->parent)
                //pPar->m_nCumulative += za ;
                pPar->SetCumulatives() ;
            for (pPar = pCur->parent ; pPar ; pPar = pPar->parent)
                //pPar->m_nCumulative += (zb - za) ;
                pPar->SetCumulatives() ;
        }
        return E_OK ;
    }
    //  No room on the infra so we try the ultra. If full or non-existant, create an empty one.
    if (!pCur->ultra || pCur->ultra->usage == HZ_T_ISAMNODE_FULL)
    {
        pIdxNew = new _hz_vn_Idx ;
        if (!pIdxNew)
            Fatal("Could not allocate sibling\n") ;
        pIdxNew->m_isamId = m_isamId ;
        pIdxNew->level = pCur->level ;
        if (pCur->ultra)
            pCur->ultra->infra = pIdxNew ;
        pIdxNew->ultra = pCur->ultra ;
        pIdxNew->infra = pCur ;
        pCur->ultra = pIdxNew ;
        //  The new sibling must be given a parent.
        _allocIndxSlot(pCur, pIdxNew) ;
    }
    //  OK now we can boogie as there is a ultra with space and parent
    if (slot == HZ_T_ISAMNODE_FULL)
    {
        //  pOld is in this node's top slot so pNew goes into slot 0 of ultra
        zb = pNew->Cumulative() ;
        for (n = pCur->ultra->usage ; n ; n--)
            pCur->ultra->m_Ptrs[n] = pCur->ultra->m_Ptrs[n-1] ;
        pCur->ultra->m_Ptrs[0] = pNew ;
        pNew->SetParent(pCur->ultra) ;
        pCur->ultra->usage++ ;
        pCur->ultra->m_nCumulative += zb ;
        for (pPar = pCur->ultra->parent ; pPar ; pPar = pPar->parent)
            //pPar->m_nCumulative += zb ;
            pPar->SetCumulatives() ;
        return E_OK ;
    }
    //  Higest element of this node goes into slot 0 of ultra, then new element inserted into this node
    for (n = pCur->ultra->usage ; n ; n--)
        pCur->ultra->m_Ptrs[n] = pCur->ultra->m_Ptrs[n-1] ;
    pNode = pCur->m_Ptrs[pCur->usage-1] ;
    za = pNode->Cumulative() ;
    pNode->SetParent(pCur->ultra) ;
    pCur->ultra->m_Ptrs[0] = pNode ;
    pCur->ultra->usage++ ;
    pCur->ultra->m_nCumulative += za ;
    //  Insert new element into this node
    zb = pNew->Cumulative() ;
    for (n = pCur->usage-1 ; n > slot ; n--)
        pCur->m_Ptrs[n] = pCur->m_Ptrs[n-1] ;
    pCur->m_Ptrs[slot] = pNew ;
    pCur->m_nCumulative += (zb - za) ;
    if (pCur->parent == pCur->ultra->parent)
    {
        for (pPar = pCur->parent ; pPar ; pPar = pPar->parent)
            //pPar->m_nCumulative += zb ;
            pPar->SetCumulatives() ;
    }
    else
    {
        for (pPar = pCur->ultra->parent ; pPar ; pPar = pPar->parent)
            //pPar->m_nCumulative += za ;
            pPar->SetCumulatives() ;
        for (pPar = pCur->parent ; pPar ; pPar = pPar->parent)
            //pPar->m_nCumulative += (zb - za) ;
            pPar->SetCumulatives() ;
    }
    return E_OK ;
}
void    _hz_tmpl_ISAM::_expelIndxSlot   (_hz_vn_Idx* pCur, _hz_vn_Any* pChild)
{
    //  Expel the defunct child from this node. If this action leaves the current node disposable (empty or sparse enough to migrate its elements to its siblings)
    //  the current node will be excluded from its peers by connecting the infra and ultra siblings to each other. In this event and if this node has a parent
    //  ExpelIndex is called for the parent with this node as the child to be disposed of.
    //
    //  Arguments:  1)  pCur    Parent ISAM block
    //              2)  pChild  Child ISAM block to be removed
    //
    //  Returns:    None
    _hzfunc("_hz_vn_Idx::_expelIndxSlot") ;
    _hz_vn_Any* pXfer ;     //  Element (block pointer) being moved
    _hz_vn_Idx* pPar = 0 ;  //  Parent for cumulatives
    int32_t     x = 0 ;     //  No of elements moved to infra
    int32_t     y = 0 ;     //  Offset into current node for copying elements to ultra
    int32_t     slot ;      //  Slot of element being deleted
    int32_t     za = 0 ;    //  Shift cumulative 1
    int32_t     zb = 0 ;    //  Shift cumulative 2
    if (!pCur)      Fatal("Isam=%d (%s): No node supplied from which to expell\n", *m_Name, m_isamId) ;
    if (!pChild)    Fatal("Isam=%d (%s): No child node supplied to expell from node %p\n", m_isamId, *m_Name, pCur) ;
    for (slot = 0 ; slot < pCur->usage ; slot++)
    {
        if (pCur->m_Ptrs[slot] == pChild)
            break ;
    }
    if (slot == pCur->usage)
        Fatal("Isam=%d (%s): No pointer to depricated child node found on this node (%p)\n", m_isamId, *m_Name, pCur) ;
    if (slot < 0 || slot >= pCur->usage)
        Fatal("Isam=%d (%s): Node %p Illegal slot value (%d)\n", m_isamId, *m_Name, pCur, slot) ;
    pCur->usage-- ;
    for (x = slot, y = pCur->usage ; x < y ; x++)
        pCur->m_Ptrs[x] = pCur->m_Ptrs[x+1] ;
    pCur->m_Ptrs[pCur->usage] = 0 ;
    pCur->m_nCumulative -= pChild->Cumulative() ;
    for (pPar = pCur->parent ; pPar ; pPar = pPar->parent)
        //pPar->m_nCumulative -= pChild->Cumulative() ;
        pPar->SetCumulatives() ;
    delete pChild ;
    if (pCur->usage && pCur->usage < HZ_T_ISAMNODE_LOW)
    {
        //  Now that an entry has been removed, can we merge the current node with either or both adjacents?
        x = pCur->infra ? HZ_T_ISAMNODE_FULL - pCur->infra->usage : 0 ;
        y = pCur->ultra ? HZ_T_ISAMNODE_FULL - pCur->ultra->usage : 0 ;
        if ((x + y) >= pCur->usage)
        {
            //  Move all that we can to infra node first
            if (pCur->infra && pCur->infra->usage < HZ_T_ISAMNODE_FULL)
            {
                for (x = 0 ; pCur->usage && pCur->infra->usage < HZ_T_ISAMNODE_FULL ; x++)
                {
                    pXfer = pCur->m_Ptrs[x] ;
                    pXfer->SetParent(pCur->infra) ;
                    pCur->infra->m_Ptrs[pCur->infra->usage] = pXfer ;
                    za += pXfer->Cumulative() ;
                    pCur->infra->usage++ ;
                    pCur->usage-- ;
                }
                pCur->infra->m_nCumulative += za ;
                for (pPar = pCur->infra->parent ; pPar ; pPar = pPar->parent)
                    //pPar->m_nCumulative += za ;
                    pPar->SetCumulatives() ;
            }
            if (pCur->ultra && pCur->ultra->usage < HZ_T_ISAMNODE_FULL)
            {
                //  If any elements remain on the current block we need to shift upwards the elements on the ultra to accomodate them
                for (y = pCur->ultra->usage + pCur->usage - 1 ; y >= pCur->usage ; y--)
                    pCur->ultra->m_Ptrs[y] = pCur->ultra->m_Ptrs[y - pCur->usage] ;
                //  Move remainder to ultra node
                for (y = 0 ; y < pCur->usage ; y++)
                {
                    pXfer = pCur->m_Ptrs[x + y] ;
                    pXfer->SetParent(pCur->ultra) ;
                    pCur->ultra->m_Ptrs[y] = pXfer ;
                    zb += pXfer->Cumulative() ;
                }
                pCur->usage = 0 ;
                pCur->ultra->m_nCumulative += zb ;
                pCur->ultra->usage += y ;
                for (pPar = pCur->ultra->parent ; pPar ; pPar = pPar->parent)
                    //pPar->m_nCumulative += zb ;
                    pPar->SetCumulatives() ;
            }
        }
    }
    if (!pCur->usage)
    {
        //  Connect adjacents
        if (pCur->ultra)
            pCur->ultra->infra = pCur->infra ;
        if (pCur->infra)
            pCur->infra->ultra = pCur->ultra ;
        if (pCur->parent)
            _expelIndxSlot(pCur->parent, pCur) ;
    }
}
_hz_vn_Dat* _hz_tmpl_ISAM::_findDnodeByKey  (int32_t& nSlot, const void* pKey, _hz_sbias eBias) const
{
    //  Find data node and slot for the supplied key and bias.
    //
    //  This is the second data-node locator function and is best suited to unique key ISAMs. On the assumption the ISAM in question is in ascending
    //  order KEY value, this function supports searches of type HZ_ISAMSRCH_LO and HZ_ISAMSRCH_UIN.
    //
    //  Note this function impliments a binary chop using 'pointer forwarding'. The lookup begins with a binary chop on the root node but since the
    //  elements for comparison are found only on data nodes, when on an index node we recurively follow the pointers to the zeroth element on the
    //  next node up until we are on a data node. Then we do the compare as though the data node value was the value of the slot in the (current)
    //  index node. We then follow the winning slot up the next level and binary chop that. Eventually we get to binary chop a data node and get the
    //  actual position that either matches the key or is just above or just below the key.
    //
    //  Arguments:  1)  A reference to store the node offset of the element
    //              2)  The isam to be searched
    //              3)  The search key
    //              4)  The pointer to the compare function
    //              5)  The lookup bias (HZ_ISAMSRCH_LO/HZ_ISAMSRCH_HI)
    //
    //  Returns:    Pointer to the data node if the key is matched,
    //              NULL pointer otherwise.
    _hzfunc("_hz_tmpl_ISAM::_findDnodeByKey") ;
    _hz_vn_Dat* pTgt = 0 ;  //  Target data node
    _hz_vn_Dat* pTst = 0 ;  //  Test data node (from forward winding pointer)
    _hz_vn_Idx* pIdx = 0 ;  //  Current index node
    _hz_vn_Idx* pFwd = 0 ;  //  Forward pointer
    int32_t     nRes ;      //  Comparison result
    int32_t     nPos ;      //  Middle of index
    int32_t     nDiv ;      //  Divider
    //  If no root, retunr
    nSlot = -1 ;
    if (!Root())
        return 0 ;
    //  Find target data node
    if (Root() && Level() == 0)
        pTgt = (_hz_vn_Dat*) Root() ;
    else
    {
        //  Determine next level node
        for (pIdx = (_hz_vn_Idx*) Root() ; pIdx ;)
        {
            for (nPos = HZ_T_ISAMNODE_FULL - 1, nDiv = HZ_T_ISAMNODE_HALF ;; nDiv /= 2)
            {
                if (nPos >= pIdx->usage)
                {
                    if (!nDiv)
                        { nPos = pIdx->usage - 1 ; break ; }
                    nPos -= nDiv ;
                    continue ;
                }
                if (pIdx->level == 1)
                    pTst = (_hz_vn_Dat*) pIdx->m_Ptrs[nPos] ;
                else
                {
                    for (pFwd = (_hz_vn_Idx*) pIdx->m_Ptrs[nPos] ; pFwd->Level() > 1 ; pFwd = (_hz_vn_Idx*) pFwd->m_Ptrs[0]) ;
                    pTst = (_hz_vn_Dat*) pFwd->m_Ptrs[0] ;
                }
                //  Now do the compare
                nRes = m_compare(pKey, pTst, 0) ;
                if (!nRes)
                    { pTgt = pTst ; break ; }
                if (nRes < 0)
                {
                    if (nPos == 1)
                        { nPos = 0 ; break ; }
                    if (!nDiv)
                        { nPos-- ; break ; }
                    nPos -= nDiv ;
                }
                else
                {
                    if (pIdx->usage == (nPos+1))
                        break ;
                    if (!nDiv)
                        break ;
                    nPos += nDiv ;
                }
            }
            if (pTgt)
                break ;
            if (pIdx->Level() > 1)
                pIdx = (_hz_vn_Idx*) pIdx->m_Ptrs[nPos] ;
            else
                { pTgt = (_hz_vn_Dat*) pIdx->m_Ptrs[nPos] ; break ; }
        }
    }
    if (!pTgt)
        return 0 ;
    //  Identify the exact slot
    for (nRes = 0, nSlot = HZ_T_ISAMNODE_FULL - 1, nDiv = HZ_T_ISAMNODE_HALF ;; nDiv /= 2)
    {
        //  Ignore positions higher than highest used slot
        if (nSlot >= pTgt->Usage())
        {
            nRes = 1 ;
            if (!nDiv)
                { nSlot = pTgt->Usage() ; break ; }
            nSlot -= nDiv ;
            continue ;
        }
        //  Do the compare
        nRes = m_compare(pKey, pTgt, nSlot) ;
        if (nRes == 0)
            break ;
        if (!nDiv)
            break ;
        if (nRes > 0)
            nSlot += nDiv ;
        else
            nSlot -= nDiv ;
    }
    if (eBias == HZ_ISAMSRCH_LO)
    {
        //  Exact match
        if (nRes)
            return 0 ;
        if (nSlot >= pTgt->Usage())
            Fatal("Isam=%d: Node %d usage %d has offset %d\n", IsamId(), pTgt->Ident(), pTgt->Usage(), nSlot) ;
        return pTgt ;
    }
    //  Deal with HZ_ISAMSRCH_END for inserts.
    if (nRes > 0)
    {
        if (nSlot < pTgt->Usage())
            nSlot++ ;
        if (nSlot == HZ_T_ISAMNODE_FULL && pTgt->Ultra())
        {
            pTgt = (_hz_vn_Dat*) pTgt->Ultra() ;
            nSlot = 0 ;
        }
    }
    if (nSlot > pTgt->Usage())
        Fatal("Isam=%d: Node %d usage %d has offset %d\n", IsamId(), pTgt->Ident(), pTgt->Usage(), nSlot) ;
    return pTgt ;
}
_hz_vn_Dat* _hz_tmpl_ISAM::_findAllByKey    (int32_t& nSlot, uint32_t& nAbs, const void* pKey, _hz_sbias eBias) const
{
    //  Category:   Collection Support
    //
    //  This is the final data-node locator function. On the assumption the ISAM is in ascending order of KEY
    //  value, this function supports searches of types HZ_ISAMSRCH_LO, HZ_ISAMSRCH_HI and HZ_ISAMSRCH_END. It also provides the absolute position of
    //  the element in the ISAM in order to facilitate numeric iteration.
    //
    //  This function impliments a binary chop using relative positions. The ISAM is treated as an array and numerically chopped using _findDnodeByPos(). If
    //  matches are found the lowest and highest matches are recorded. The nature of the chop is such that, assuming the ISAM elements are actually
    //  ordered, once a match is found the final test position must be within one place of either the lowest or the highest match. On the first match,
    //  both the lowest and highest matches are set to the position and it is possible, if this occurs ...
    //
    //  Arguments:  1)  A reference to store the absolute position
    //              2)  A reference to store the node offset of the element
    //              3)  The isam to be searched
    //              4)  The search key
    //              5)  The pointer to the compare function
    //              6)  The lookup bias (HZ_ISAMSRCH_LO/HZ_ISAMSRCH_HI)
    //
    //  Returns:    Pointer to the data node if the key is matched,
    //              NULL pointer otherwise.
    //
    //  Note that numeric iterators are invalidated by insert and delete operations.
    _hzfunc("_hz_tmpl_ISAM::_findAllByKey") ;
    _hz_vn_Dat* pDN = 0 ;   //  Target data node
    _hz_vn_Dat* pMK = 0 ;   //  Marked data node
    uint32_t    max ;       //  Top position
    uint32_t    posn ;      //  Test position
    uint32_t    posnMk ;    //  Marked position
    int32_t     slot ;      //  Slot
    uint32_t    slotMk ;    //  Marked slot
    uint32_t    nDiv ;      //  divider
    int32_t     res ;       //  result of compare
    //  if (eBias != HZ_ISAMSRCH_LO && eBias != HZ_ISAMSRCH_HI && eBias != HZ_ISAMSRCH_END)
    //      Fatal("_findAbsoluteByKey: Illegal bias %d", eBias) ;
    nAbs = nSlot = posnMk = slotMk = 0xffffffff ;
    if (!Root())
        return 0 ;
    //  Set up initial test position and the divider
    max = Count()-1 ;
    for (posn = 2 ; posn < max ; posn *= 2) ;
    nDiv = posn / 2 ;
    posn-- ;
    //  Run up and down collection until we can divide no more
    for (;;)
    {
        //  Ignore positions higher than highest used slot
        if (posn > max)
        {
            res = -1 ;
            if (!nDiv)
                break ;
            posn -= nDiv ;
            nDiv /= 2 ;
            continue ;
        }
        //  Do the compare
        pDN = _findDnodeByPos(slot, posn, false) ;
        //pKeys = (KEY*) pDN->Keys() ;
        res = m_compare(pKey, pDN, slot) ;
        if (res == 0)
        {
            //  Have an exact match so mark it but is it the higest or lowest?
            pMK = pDN ;
            slotMk = slot ;
            posnMk = posn ;
            if (!nDiv)
                break ;
            //  After marking posn, go lower or higher according to bias
            if (eBias == HZ_ISAMSRCH_LO)
                posn -= nDiv ;
            else
                posn += nDiv ;
        }
        else
        {
            //  No match, go higher or lower if you can, otherwise fail
            if (!nDiv)
                break ;
            if (res > 0)
                posn += nDiv ;
            else
                posn -= nDiv ;
        }
        nDiv /= 2 ;
    }
    //  If looking for lowest or highest and nothing found, give up
    if (eBias == HZ_ISAMSRCH_LO || eBias == HZ_ISAMSRCH_HI)
    {
        if (!pMK)
            return 0 ;
        nAbs = posnMk ;
        nSlot = slotMk ;
        return pMK ;
    }
    //  For HZ_ISAMSRCH_END if we have found highest entry, go up one place
    if (pMK)
    {
        pDN = _findDnodeByPos(slot, posnMk+1, true) ;
        nAbs = posnMk+1 ;
        nSlot = slot ;
        return pDN ;
    }
    //  Otherwise go up one place ONLY if the last compare was > 0
    if (res > 0)
        posn++ ;
    if (posn > max)
        posn = max+1 ;
    pDN = _findDnodeByPos(slot, posn, true) ;
    nAbs = posn ;
    nSlot = slot ;
    return pDN ;
}
_hz_vn_Dat* _hz_tmpl_ISAM::InsertKeyU   (int32_t& nSlot, const void* pKey)
{
    //  Purpose:    Insert an element by unique value.
    //
    //  Arguments:  1)  key     The key to insert
    //
    //  Returns:    E_OK        If operation successful
    //
    //  Exits:      E_CORRUPT   If a target data node and slot was not located (integrity) or a population test fails
    //              E_MEMORY    If there was insufficient memory
    _hzfunc("_hz_tmpl_ISAM::InsertKeyU") ;
    _hz_vn_Dat* pDN = 0 ;       //  Current data node
    //  If ordered list is empty
    if (!Root())
    {
        pDN = new _hz_vn_Dat ;
        if (!pDN)
            Fatal("Isam=%d: Could not allocate root node\n", IsamId()) ;
        pDN->m_pElements = new uchar[HZ_T_ISAMNODE_FULL * (m_nSizeKey + m_nSizeObj)] ;
        memset(pDN->m_pElements, 0, HZ_T_ISAMNODE_FULL * (m_nSizeKey + m_nSizeObj)) ;
        pDN->m_isamId = IsamId() ;
        pDN->usage = 1 ;
        m_nElements = 1 ;
        m_pRoot = pDN ;
        nSlot = 0 ;
        return pDN ;
    }
    //  Locate the 'target' top level node
    pDN = _findDnodeByKey(nSlot, pKey, HZ_ISAMSRCH_END) ;
    if (!pDN)
        Fatal("Isam=%d: No data node found\n", IsamId()) ;
    if (nSlot > HZ_T_ISAMNODE_FULL)
        Fatal("Isam=%d: Illegal slot no %d for node %d\n", IsamId(), nSlot, pDN->m_Id) ;
    //  If new element has identical key then ignore
    if (nSlot < HZ_T_ISAMNODE_FULL)
    {
        if (m_compare(pKey, pDN, nSlot) == 0)
        {
            return pDN ;
        }
    }
    pDN = (_hz_vn_Dat*) _allocDataSlot(pDN, nSlot) ;
    if (Count() != Population())    Fatal("Isam=%d: Expected population %d, actual %d\n", IsamId(), Population(), Count()) ;
    if (IntegBase() != E_OK)        Fatal("Isam=%d: INTEGRITY FAILURE\n", IsamId()) ;
    return pDN ;
}
_hz_vn_Dat* _hz_tmpl_ISAM::InsertKeyM   (int32_t& nSlot, const void* pKey)
{
    //  Purpose:    Insert an element by non-unique value.
    //
    //  Arguments:  1)  key     The key to insert
    //
    //  Returns:    E_OK        If operation successful
    //
    //  Exits:      E_CORRUPT   If a target data node and slot was not located (integrity) or a population test fails
    //              E_MEMORY    If there was insufficient memory
    _hzfunc("_hz_tmpl_ISAM::InsertKeyM") ;
    _hz_vn_Dat* pDN = 0 ;       //  Current data node
    uint32_t    nPosn ;         //  Needed for _findAllByKey()
    //  If ordered list is empty
    if (!Root())
    {
        pDN = new _hz_vn_Dat ;
        if (!pDN)
            Fatal("Isam=%d: Could not allocate root node\n", IsamId()) ;
        pDN->m_pElements = new uchar[HZ_T_ISAMNODE_FULL * (m_nSizeKey + m_nSizeObj)] ;
        memset(pDN->m_pElements, 0, HZ_T_ISAMNODE_FULL * (m_nSizeKey + m_nSizeObj)) ;
        pDN->m_isamId = IsamId() ;
        pDN->usage = 1 ;
        m_nElements = 1 ;
        m_pRoot = pDN ;
        nSlot = 0 ;
        return pDN ;
    }
    //  Locate the 'target' top level node
    pDN = _findAllByKey(nSlot, nPosn, pKey, HZ_ISAMSRCH_END) ;
    if (!pDN)
        Fatal("Isam=%d: No data node found\n", IsamId()) ;
    if (nSlot > HZ_T_ISAMNODE_FULL)
        Fatal("Isam=%d: Illegal slot no %d for node %d\n", IsamId(), nSlot, pDN->m_Id) ;
    //  If new element has identical key then ignore
    if (nSlot < HZ_T_ISAMNODE_FULL)
    {
        if (m_compare(pKey, pDN, nSlot) == 0)
            return pDN ;
    }
    pDN = (_hz_vn_Dat*) _allocDataSlot(pDN, nSlot) ;
    if (Count() != Population())
        Fatal("Isam=%d: Expected population %d, actual %d\n", IsamId(), Population(), Count()) ;
    if (IntegBase() != E_OK)
        Fatal("Isam=%d: INTEGRITY FAILURE\n", IsamId()) ;
    return pDN ;
}
_hz_vn_Dat* _hz_tmpl_ISAM::DeleteKey    (int32_t& nSlot, const void* pKey)
{
    //  Arguments:  1)  key     The key be removed
    //
    //  Returns:    E_OK        If operation successful
    //              E_NOTFOUND  If the key was not found in the ISAM
    _hzfunc("_hz_tmpl_ISAM::DeleteKey") ;
    _hz_vn_Dat* pDN = 0 ;   //  Target data node (from which delete will occur)
    //  Check args
    if (!Root())
        return 0 ;
    //  Locate the 'target' top level node
    pDN = _findDnodeByKey(nSlot, pKey, HZ_ISAMSRCH_LO) ;
    if (!pDN)
        return 0 ;
    if (nSlot >= HZ_T_ISAMNODE_FULL)
        Fatal("Isam=%d: Illegal slot no %d for node %d\n", IsamId(), nSlot, pDN->m_Id) ;
    //  Expel the entity from the node
    pDN = _expelDataSlot(pDN, nSlot) ;
    if (Count() != Population())
        Fatal("Isam=%d: Expected population %d, actual %d\n", IsamId(), Population(), Count()) ;
    return pDN ;
}
bool    _hz_tmpl_ISAM::_shownode    (_hz_vn_Any* pNode, hzLogger* plog, bool bData) const
{
    //  Support function to Show(). Prints out elements for a given node.
    //
    //  Arguments:  1)  pNode   The current node
    //              2)  plog    The logger to use for output
    //              3)  bData   If set, output data part of element
    //
    //  Returns:    True    If there were no integrity issues
    //              False   If there were
    _hzfunc("_hz_tmpl_ISAM::_shownode") ;
    _hz_vn_Idx* pIdx = 0 ;          //  Index node
    _hz_vn_Any* pChild ;            //  Child from slot
    _hz_vn_Any* pPrev ;             //  Lower adjacent sibling
    _hz_vn_Any* pNext ;             //  Higher adjacent sibling
    _hz_vn_Idx* testA ;             //  Address of derived node
    _hz_vn_Idx* testB ;             //  Address of derived node
    uint32_t    nTotal ;            //  Cumulative
    int32_t     nIndex ;            //  Iterator of children
    bool        bError = false ;    //  Error indicator
    if (!pNode->Level())
    {
        if (bData)
            plog->Out("\treport isam=%d Data Node %p parent %p inf %p sup %p level %d, usage %2d\n",
                m_isamId, pNode, pNode->Parent(), pNode->Infra(), pNode->Ultra(), pNode->Level(), pNode->Usage()) ;
        return true ;
    }
    pIdx = (_hz_vn_Idx*) pNode ;
    if (bData)
        plog->Out("report isam=%d Index Node %p parent %p inf %p sup %p level %d, usage %2d cumulate %d\n",
            m_isamId, pNode, pNode->Parent(), pNode->Infra(), pNode->Ultra(), pNode->Level(), pNode->Usage(), pNode->Cumulative()) ;
    if (pNode->Level() < 0 || pNode->Level() > 10)
        { plog->Out("\tOut of range level - no furthur analysis of node.\n") ; return false ; }
    if (pNode->Usage() < 0 || pNode->Usage() > HZ_T_ISAMNODE_FULL)
        { plog->Out("\tOut of range usage - no furthur analysis of node.\n") ; return false ; }
    //  Check pointers
    for (nIndex = 0 ; nIndex < pIdx->Usage() ; nIndex++)
    {
        pChild = pIdx->m_Ptrs[nIndex] ;
        if (!pChild)
        {
            bError = true ;
            plog->Out("\tInvalid pointer in posn %d\n", nIndex) ;
        }
    }
    //  Check cumulatives
    nTotal = 0 ;
    for (nIndex = 0 ; nIndex < pIdx->Usage() ; nIndex++)
    {
        pChild = (_hz_vn_Any*) pIdx->m_Ptrs[nIndex] ;
        nTotal += pChild->Cumulative() ;
    }
    if (nTotal != pIdx->Cumulative())
    {
        bError = true ;
        plog->Out(" -- ERROR: Counted cumulative for index node %p is %d, but empirical cumulatives is %d\n", pIdx, nTotal, pIdx->Cumulative()) ;
    }
    //  Recurse to higher nodes
    for (nIndex = 0 ; nIndex < pIdx->Usage() ; nIndex++)
    {
        pChild = (_hz_vn_Any*) pIdx->m_Ptrs[nIndex] ;
        if (!_shownode(pChild, plog, bData))
            bError = true ;
    }
    //  Check integrity of this node in connection with its neighbours
    for (nIndex = 0 ; nIndex < pIdx->Usage() ; nIndex++)
    {
        pChild = (_hz_vn_Any*) pIdx->m_Ptrs[nIndex] ;
        //  We are not on a data node
        if (pChild->Parent() != pNode)
        {
            bError = true ;
            plog->Out(" -- ERROR: Child in posn %d (%p) does not have this node (%p) as it's parent. Instead it has (%p)\n",
                nIndex, pChild, pNode, pChild->Parent()) ;
            continue ;
        }
        //  Test siblings
        if (nIndex == 0)
        {
            if (!pIdx->infra && pChild->Infra())
            {
                bError = true ;
                plog->Out(" -- ERROR: Node %p While there is no infra node to this, the zeroth child has an infra (%p)\n",
                    pIdx, pChild->Infra()) ;
            }
            if (pIdx->infra && !pChild->Infra())
            {
                bError = true ;
                plog->Out(" -- ERROR: Node %p There is an infra to this (%p) but the zeroth child does not have an infra\n", pIdx, pIdx->infra) ;
            }
            if (pIdx->infra && pChild->Infra())
            {
                testA = pChild->Infra() && pChild->Infra()->Parent() ? pChild->Infra()->Parent() : 0 ;
                testB = pIdx->infra ? pIdx->infra : 0 ;
                if (testA != testB)
                {
                    bError = true ;
                    plog->Out(" -- ERROR: Node %p The parent of the zeroth child infra (%p) is not the infra of this (%p)\n",
                        pIdx, testA, testB) ;
                }
            }
        }
        else if (nIndex == (pIdx->Usage()-1))
        {
            if (!pIdx->ultra && pChild->Ultra())
                { bError = true ; plog->Out(" -- ERROR: While there is no ultra node to this node, the highest child has a ultra\n") ; }
            if (pIdx->ultra && !pChild->Ultra())
                { bError = true ; plog->Out(" -- ERROR: There is an ultra to this node but the highest child does not have a ultra\n") ; }
            if (pIdx->infra && pChild->Ultra())
            {
                if (pChild->Ultra()->Parent() != pIdx->ultra)
                    { bError = true ; plog->Out(" -- ERROR: The parent of the highest child ultra is not the ultra of this node\n") ; }
            }
        }
        else
        {
            pPrev = (_hz_vn_Any*) pIdx->m_Ptrs[nIndex-1] ;
            pNext = (_hz_vn_Any*) pIdx->m_Ptrs[nIndex+1] ;
            if (pChild->Infra() != pPrev)
                { bError = true ; plog->Out(" -- ERROR: Child %d infra is not the same as child %d\n", nIndex, nIndex-1) ; }
            if (pChild->Ultra() != pNext)
                { bError = true ; plog->Out(" -- ERROR: Child %d ultra is not the same as child %d\n", nIndex, nIndex+1) ; }
        }
    }
    return bError ? false : true ;
}
hzEcode _hz_tmpl_ISAM::NodeReport   (bool bData) const
{
    //  Category:   Diagnostics
    //
    //  Starting from the root node and moving out to data nodes, this checks that nodes do not contain null pointers, have correct parent pointers and have
    //  correct cumulative values
    //
    //  Arguments:  1)  bData   If set, print data object
    //
    //  Returns:    E_CORRUPT   If any node has integrity issues
    //              E_OK        If all nodes succeed
    _hzfunc("_hz_tmpl_ISAM::NodeReport") ;
    hzLogger*   plog ;          //  Current thread logger
    hzEcode     rc = E_OK ;     //  Return code
    plog = GetThreadLogger() ;
    if (plog)
    {
        if (!m_pRoot)
            { plog->Out("The _hz_tmpl_ISAM object is empty\n") ; return E_OK ; }
        if (!_shownode(m_pRoot, plog, bData))
            rc = E_CORRUPT ;
    }
    
    return rc ;
}
hzEcode _hz_tmpl_ISAM::_integBase   (hzLogger* plog, _hz_vn_Idx* pN) const
{
    //  Quick index node integrity test
    //
    //  Arguments:  1)  plog    Logger
    //              2)  pN      Current node
    //
    //  Returns:    E_CORRUPT   If node fails integrity test
    //              E_OK        If node passes
    _hzfunc("_hz_tmpl_ISAM::_integBase") ;
    _hz_vn_Any* pInf ;  //  Infra to the current node
    _hz_vn_Any* pUlt ;  //  Ultra to the current node
    _hz_vn_Any* pCA ;   //  Child node A
    _hz_vn_Any* pCB ;   //  Child node B
    _hz_vn_Idx* par ;   //  Parent of test node
    uint32_t    err = 0 ;   //  Set if error
    int32_t     n ;         //  Loop control
    pInf = pN->Infra() ;
    pUlt = pN->Ultra() ;
    //  Test if the infra and ultra pointers point back to current node
    if (pInf && (pInf->Ultra() != pN))  { err=1 ; plog->Out("On node %p: Infra node %p does not point back to this node\n", pN, pInf) ; }
    if (pUlt && (pUlt->Infra() != pN))  { err=1 ; plog->Out("On node %p: Infra node %p does not point back to this node\n", pN, pInf) ; }
    //  Test if the slot 0 child has an infra whose parent is this node's infra
    pCA = pN->m_Ptrs[0] ;
    if (pInf)
    {
        if (!pCA->Infra())
            { err=1 ; plog->Out("On node %p: Child 0 (%p) has no infra while this node does\n", pN, pCA) ; }
        else
        {
            par = pCA->Infra()->Parent() ;
            if (!par)
                { err=1 ; plog->Out("On node %p: Child 0 (%p) has parent -1 diff to the infra %p\n", pN, pCA, pInf) ; }
            if (par && par != pInf)
                { err=1 ; plog->Out("On node %p: Child 0 (%p) has parent %p diff to the infra %p\n", pN, pCA, par, pInf) ; }
        }
    }
    //  Test if the top child has an ultra whose parent is this node's ultra
    pCB = pN->m_Ptrs[pN->Usage()-1] ;
    if (pUlt)
    {
        if (!pCB->Ultra())
            { err=1 ; plog->Out("On node %p: Child 0 (%p) has no infra while this node does\n", pN, pCB) ; }
        else
        {
            par = pCB->Ultra()->Parent() ;
            if (!par)
                { err=1 ; plog->Out("On node %p: Child 0 (%p) has parent -1 diff to the infra %p\n", pN, pCB, pUlt) ; }
            if (par && par != pUlt)
                { err=1 ; plog->Out("On node %p: Child 0 (%p) has parent %p diff to the infra %p\n", pN, pCB, par, pUlt) ; }
        }
    }
    //  For each slot test if the child's infra points to the child of the next lower slot
    //  For each slot test if the child's ultra points to the child of the next higher slot
    for (n = pN->Usage()-1 ; n > 0 ; n--)
    {
        pCA = pN->m_Ptrs[n-1] ;
        if (pCA->Ultra() != pCB)
            { err=1; plog->Out("On node %p: Child in slot %d has infra of %p but slot %d points to %p\n", pN, n+1, pCA, n, pCB) ; }
        if (pCB->Infra() != pCA)
            { err=1; plog->Out("On node %p: Child in slot %d has ultra of %p but slot %d points to %p\n", pN, n, pCA, n+1, pCA) ; }
        pCB = pN->m_Ptrs[n-1] ;
    }
    //  Recurse to child nodes
    if (pN->Level() > 1)
    {
        for (n = 0 ; n < pN->Usage() ; n++)
        {
            if (_integBase(plog, (_hz_vn_Idx*) pN->m_Ptrs[n]) != E_OK)
                err=1;
        }
    }
    return err ? E_CORRUPT : E_OK ;
}
hzEcode _hz_tmpl_ISAM::IntegBase    (void) const
{
    //  This test that index node are consistent. For each node pointer in an index node, the node pointed to is tested to ensure it's adjacents agree with ..
    //
    //  Arguments:  None
    //
    //  Returns:    E_CORRUPT   If any ode fails integrity test
    //              E_OK        If all nodes pass
    _hzfunc("_hz_tmpl_ISAM::IntegBase") ;
    _hz_vn_Idx* pN ;    //  ISAM root
    hzLogger*   plog ;  //  Current thread logger
    if (!m_pRoot)
        return E_OK ;
    if (!m_pRoot->Level())
        return E_OK ;
    pN = (_hz_vn_Idx*) m_pRoot ;
    plog = GetThreadLogger() ;
    return _integBase(plog, pN) ;
}
_hz_vn_Dat* _hz_tmpl_ISAM::InsertPosn   (int32_t& nSlot, uint32_t nPosn)
{
    //  Purpose:    Insert an element at position.
    //
    //  Arguments:  1)  nPosn   Position to insert object
    //              2)  key     The key to insert
    //
    //  Returns:    E_OK        If operation successful
    //              E_RANGE     If position was invalid
    //              E_CORRUPT   If there were integrity issues
    _hzfunc("_hz_tmpl_ISAM::InsertPosn") ;
    _hz_vn_Dat* pDN = 0 ;       //  Current data node
    //  Check args
    if (Count() != Population())    Fatal("Isam=%d Found to have wrong population: Abs %d, root cumulative %d\n", IsamId(), Population(), Count()) ;
    if (nPosn > Population())       Fatal("Isam=%d: Attempt to add element at position %d. Element set ends at %d\n", IsamId(), nPosn, Population()) ;
    //  If ordered list is empty
    if (!Root())
    {
        pDN = new _hz_vn_Dat ;
        if (!pDN)
            Fatal("Isam=%d: Could not allocate root node\n", IsamId()) ;
        pDN->m_pElements = new uchar[HZ_T_ISAMNODE_FULL * (m_nSizeKey + m_nSizeObj)] ;
        memset(pDN->m_pElements, 0, HZ_T_ISAMNODE_FULL * (m_nSizeKey + m_nSizeObj)) ;
        pDN->m_isamId = IsamId() ;
        pDN->usage = 1 ;
        m_nElements = 1 ;
        m_pRoot = pDN ;
        nSlot = 0 ;
        return pDN ;
    }
    //  Locate the 'target' top level node
    pDN = (_hz_vn_Dat*) _findDnodeByPos(nSlot, nPosn, true) ;
    if (!pDN)
        Fatal("Isam=%%d: No target data node found for posn %d (pop %d)\n", IsamId(), nPosn, Population()) ;
    if (nSlot < 0 || nSlot > HZ_T_ISAMNODE_FULL)
        Fatal("Isam=%d: Illegal slot no %d for node %d\n", IsamId(), nSlot, pDN->m_Id) ;
    pDN = _allocDataSlot(pDN, nSlot) ;
    if (Count() != Population())
        Fatal("Isam=%d: Expected population %d, actual %d\n", IsamId(), Population(), Count()) ;
    return pDN ;
}
hzEcode _hz_tmpl_ISAM::DeletePosn   (uint32_t nPosn)
{
    //  Purpose:    Delete an element at position.
    //
    //  Method:     This calls _findDnodeByPos to eastablish the target data node and slot for the requested position within the ISAM. It then calls _expelData() to delete
    //              the slot from the target node.
    //
    //  Arguments:  1)  nPosn   Position of object to delete
    //
    //  Returns:    E_OK        If operation successful
    //              E_RANGE     If position was invalid
    //              E_NODATA    If element was a null
    //              E_MEMORY    If there was insufficient memory
    _hzfunc("_hz_tmpl_ISAM::DeletePosn") ;
    _hz_vn_Dat* pTgt = 0 ;  //  Target data node (from which delete will occur)
    int32_t     nSlot ;     //  Position within node
    //  Check args
    if (!Root())
        return E_NOTFOUND ;
    if (nPosn < 0 || nPosn >= Cumulative())
        return E_RANGE ;
    //  Locate the 'target' top level node
    pTgt = (_hz_vn_Dat*) _findDnodeByPos(nSlot, nPosn, false) ;
    if (!pTgt)
        Fatal("Isam=%d: No data node found in spite of position %d being within range %d\n", IsamId(), nPosn, Cumulative()) ;
    if (nSlot >= HZ_T_ISAMNODE_FULL)
        Fatal("Isam=%d: Illegal slot no %d for node %d\n", IsamId(), nSlot, pTgt->m_Id) ;
    //  Expel the entity from the node
    //mx->m_nElements-- ;
    _expelDataSlot(pTgt, nSlot) ;
    if (Count() != Population())
        Fatal("Isam=%d: Expected population %d, actual %d\n", IsamId(), Population(), Count()) ;
    return E_OK ;
}