/*++

   Copyright    (c)    1995-1996    Microsoft Corporation

   Module  Name :
       hashtab.cxx

   Abstract:
       Implements the member functions for Hash table

   Author:

       Murali R. Krishnan    ( MuraliK )     02-Oct-1996

   Environment:
       Win32 - User Mode

   Project:

       Internet Server DLL

   Functions Exported:



   Revision History:

--*/


/************************************************************
 *     Include Headers
 ************************************************************/

#include "precomp.hxx"

# if !defined(dllexp)
# define dllexp __declspec( dllexport)
# endif

# include <hashtab.hxx>



/*++
  Organization of Hash Table

  The hash table consists of a set of hash buckets controlled
  by the number of buckets specified during creation.

  Each bucket consists of a set of bucket chunks. Each bucket
  owns a separate critical section to protect the entries in
  the bucket itself.

  Each bucket chunk consists of an array of MAX_ELEMENTS_PER_BUCKET
   HashTableBucketElement Entries (HTBE_ENTRY).

  Each HTBE_ENTRY maintains a hash value and pointer to the Hash Element.

--*/

/************************************************************
 *    HASH_TABLE_BUCKET
 ************************************************************/

struct HTBE_ENTRY {
    DWORD        m_hashValue;
    HT_ELEMENT * m_phte;

    inline
    BOOL IsMatch( DWORD hashValue, LPCSTR pszKey, DWORD cchKey) const
    { return ((hashValue == m_hashValue) &&
              (NULL != m_phte) &&
              m_phte->IsMatch( pszKey, cchKey)
              );
    }

    inline
    BOOL IsMatch( IN HT_ELEMENT * phte) const
    { return ( phte == m_phte); }

    inline BOOL
    IsEmpty( VOID) const { return ( NULL == m_phte); }

    VOID Print( VOID) const
    { m_phte->Print(); }
};

typedef HTBE_ENTRY * PHTBE_ENTRY;

//
// Chunk size should be carefully (empirically) chosen.
// Small Chunk size => large number of chunks
// Large Chunk size => high cost of search on failures.
// For now we choose the chunk size to be 20 entries.
# define MAX_ELEMENTS_PER_BUCKET    ( 20 )

struct dllexp HTB_ELEMENT {

    HTBE_ENTRY   m_rgElements[MAX_ELEMENTS_PER_BUCKET];
    DWORD        m_nElements;
    LIST_ENTRY   m_ListEntry;

    HTB_ELEMENT(VOID)
        : m_nElements ( 0)
    {
        InitializeListHead( &m_ListEntry);
        ZeroMemory( m_rgElements, sizeof( m_rgElements));
    }

    ~HTB_ELEMENT(VOID)
    { Cleanup(); }

    VOID Cleanup( VOID);

    inline
    HT_ELEMENT * Lookup( IN DWORD hashValue, IN LPCSTR pszKey, DWORD cchKey);

    inline
    BOOL Insert( IN DWORD hashVal, IN HT_ELEMENT * phte);

    inline
    BOOL Delete( IN HT_ELEMENT * phte);

    VOID Print( IN DWORD level) const;

    HTBE_ENTRY * FirstElement(VOID) { return ( m_rgElements); }
    HTBE_ENTRY * LastElement(VOID)
    { return ( m_rgElements + MAX_ELEMENTS_PER_BUCKET); }
    VOID NextElement( HTBE_ENTRY * & phtbe)
    { phtbe++; }

    VOID IncrementElements(VOID) { m_nElements++; }
    VOID DecrementElements(VOID) { m_nElements--; }
    DWORD NumElements( VOID) const { return ( m_nElements); }
    BOOL IsSpaceAvailable(VOID) const
    { return ( NumElements() < MAX_ELEMENTS_PER_BUCKET); }

    DWORD FindNextElement( IN OUT LPDWORD pdwPos,
                           OUT HT_ELEMENT ** pphte);

};

typedef HTB_ELEMENT * PHTB_ELEMENT;

class dllexp HASH_TABLE_BUCKET {

public:
    HASH_TABLE_BUCKET(VOID);
    ~HASH_TABLE_BUCKET( VOID);

    HT_ELEMENT * Lookup( IN DWORD hashValue, IN LPCSTR pszKey, DWORD cchKey);
    BOOL Insert( IN DWORD       hashVal,
                 IN HT_ELEMENT * phte,
                 IN BOOL        fCheckForDuplicate);

    BOOL Delete( IN HT_ELEMENT * phte);
    VOID Print( IN DWORD level);

    DWORD NumEntries( VOID);

    DWORD  InitializeIterator( IN HT_ITERATOR * phti);

    DWORD  FindNextElement( IN HT_ITERATOR * phti,
                                   OUT HT_ELEMENT ** pphte);
    DWORD  CloseIterator( IN HT_ITERATOR * phti);

private:
    CRITICAL_SECTION   m_csLock;

    LIST_ENTRY         m_lHead;
    DWORD              m_nEntries;

    HTB_ELEMENT        m_htbeFirst; // the first bucket chunk

    VOID Lock(VOID) { EnterCriticalSection( &m_csLock); }
    VOID Unlock( VOID) { LeaveCriticalSection( &m_csLock); }
};




/************************************************************
 *    Member Functions of HTB_ELEMENT
 ************************************************************/

VOID
HTB_ELEMENT::Cleanup( VOID)
{

    if ( m_nElements > 0) {
        PHTBE_ENTRY phtbeEntry;

        // free up all the entries in this bucket.
        for (phtbeEntry = FirstElement();
             phtbeEntry < (LastElement());
             NextElement( phtbeEntry)) {

            if ( !phtbeEntry->IsEmpty() ) {

                // release the object now.
                DecrementElements();

                // Assert that ref == 1
                DerefAndKillElement( phtbeEntry->m_phte);
                phtbeEntry->m_phte = NULL;
                phtbeEntry->m_hashValue = 0;
            }
        } // for
    }

    DBG_ASSERT( 0 == m_nElements);
    return;
} // HTB_ELEMENT::Cleanup()


inline
HT_ELEMENT *
HTB_ELEMENT::Lookup( IN DWORD hashValue, IN LPCSTR pszKey, DWORD cchKey)
{
    HT_ELEMENT * phte = NULL;

    if ( m_nElements > 0) {

        PHTBE_ENTRY phtbeEntry;
        // find the entry by scanning all entries in this bucket chunk
        // if found, increment ref count and return a pointer to the object
        for (phtbeEntry = FirstElement();
             phtbeEntry < (LastElement());
             NextElement( phtbeEntry)) {

            //
            // If the hash values match and the strings match up, return
            //  the corresponding hash table entry object
            //
            if ( phtbeEntry->IsMatch( hashValue, pszKey, cchKey)) {

                // we found the entry. return it.
                phte = phtbeEntry->m_phte;
                DBG_REQUIRE( phte->Reference() > 0);
                break;
            }
        } // for
    }

    return ( phte);
} // HTB_ELEMENT::Lookup()


inline BOOL
HTB_ELEMENT::Insert( IN DWORD hashVal,
                     IN HT_ELEMENT * phte
                     )
{
    if ( m_nElements < MAX_ELEMENTS_PER_BUCKET) {

        // there is some empty space.
        // Find one such a slot and add this new entry

        PHTBE_ENTRY phtbeEntry;

        for (phtbeEntry = FirstElement();
             phtbeEntry < LastElement();
             NextElement( phtbeEntry)) {

            if ( phtbeEntry->IsEmpty() ) {

                DBG_ASSERT( NULL != phte);

                // Assume that the object phte already has non-zero ref count

                // we found a free entry. insert the new element here.
                phtbeEntry->m_hashValue = hashVal;
                phtbeEntry->m_phte = phte;
                IncrementElements();
                return ( TRUE);
            }
        } // for

        // we should not come here. If we do then there is trouble :(
        DBG_ASSERT( FALSE);
    }

    SetLastError( ERROR_INSUFFICIENT_BUFFER);
    return ( FALSE);
} // HTB_ELEMENT::Insert()


DWORD
HTB_ELEMENT::FindNextElement( IN OUT LPDWORD pdwPos, OUT HT_ELEMENT ** pphte)
{
    DWORD dwErr = ERROR_NO_MORE_ITEMS;

    DBG_ASSERT( NULL != pdwPos );
    DBG_ASSERT( NULL != pphte );

    // Find the first valid element to return back.

    //
    // Given that deletion might happen any time, we cannot rely on the
    //   comparison  *pdwPos < m_nElements
    //
    // Do scans with *pdwPos < MAX_ELEMENTS_PER_BUCKET
    //

    if ( *pdwPos < MAX_ELEMENTS_PER_BUCKET ) {

        PHTBE_ENTRY phtbeEntry;

        // find the entry by scanning all entries in this bucket chunk
        // if found, increment ref count and return a pointer to the object
        for (phtbeEntry = m_rgElements + *pdwPos;
             phtbeEntry < (LastElement());
             NextElement( phtbeEntry)) {

            if ( phtbeEntry->m_phte != NULL ) {

                //
                // Store the element pointer and the offset
                // and return after referencing the element
                //
                *pphte = phtbeEntry->m_phte;
                (*pphte)->Reference();
                *pdwPos = ( 1 + DIFF(phtbeEntry - FirstElement()));
                dwErr = NO_ERROR;
                break;
            }
        } // for
    }

    return ( dwErr);
} // HTB_ELEMENT::FindNextElement()


inline BOOL
HTB_ELEMENT::Delete( IN HT_ELEMENT * phte)
{
    DBG_ASSERT( NULL != phte);

    if ( m_nElements > 0) {

        PHTBE_ENTRY phtbeEntry;
        // find the entry by scanning all entries in this bucket chunk
        // if found, increment ref count and return a pointer to the object
        for (phtbeEntry = FirstElement();
             phtbeEntry < (LastElement());
             NextElement( phtbeEntry)) {

            //
            // If the hash values match and the strings match up,
            //  decrement ref count and kill the element.
            //
            if ( phtbeEntry->IsMatch( phte)) {

                // We found the entry.  Remove it from the table

                phtbeEntry->m_phte = NULL;
                DecrementElements();

                DerefAndKillElement( phte);

                return ( TRUE);
            }
        } // for
    }

    return ( FALSE);
} // HTB_ELEMENT::Delete()


VOID
HTB_ELEMENT::Print(IN DWORD level) const
{
    const HTBE_ENTRY * phtbeEntry;
    CHAR rgchBuffer[MAX_ELEMENTS_PER_BUCKET * 22 + 200];
    DWORD cch;
    DWORD i;

    cch = wsprintf( rgchBuffer,
                    "HTB_ELEMENT(%08x)  # Elements %4d; "
                    "Flink: %08x  Blink: %08x\n"
                    ,
                    this, m_nElements,
                    m_ListEntry.Flink, m_ListEntry.Blink);

    if ( level > 0) {

        // NYI: I need to walk down the entire array.
        // Not just the first few entries
        for( i = 0; i < m_nElements; i++) {

            phtbeEntry = &m_rgElements[i];
            cch += wsprintf( rgchBuffer + cch,
                             "  %08x %08x",
                         phtbeEntry->m_hashValue,
                             phtbeEntry->m_phte
                             );
            if ( i % 4 == 0) {
                rgchBuffer[cch++] = '\n';
                rgchBuffer[cch] = '\0';
            }
        } // for
    }

    DBGDUMP(( DBG_CONTEXT, rgchBuffer));
    return;
} // HTB_ELEMENT::Print()



/************************************************************
 *    Member Functions of HASH_TABLE_BUCKET
 ************************************************************/

HASH_TABLE_BUCKET::HASH_TABLE_BUCKET(VOID)
    : m_nEntries ( 0),
      m_htbeFirst()
{
    InitializeListHead( &m_lHead);
    INITIALIZE_CRITICAL_SECTION( & m_csLock);
} // HASH_TABLE_BUCKET::HASH_TABLE_BUCKET()


HASH_TABLE_BUCKET::~HASH_TABLE_BUCKET( VOID)
{
    PLIST_ENTRY pl;
    PHTB_ELEMENT phtbe;

    // Free up the elements in the list
    Lock();
    while ( !IsListEmpty( &m_lHead)) {
        pl = RemoveHeadList( &m_lHead);
        phtbe = CONTAINING_RECORD( pl, HTB_ELEMENT, m_ListEntry);
        delete phtbe;
    } // while

    m_htbeFirst.Cleanup();
    Unlock();

    DeleteCriticalSection( &m_csLock);
} // HASH_TABLE_BUCKET::~HASH_TABLE_BUCKET()



HT_ELEMENT *
HASH_TABLE_BUCKET::Lookup( IN DWORD hashValue, IN LPCSTR pszKey, DWORD cchKey)
{
    HT_ELEMENT * phte;

    Lock();
    // 1. search in the first bucket
    phte = m_htbeFirst.Lookup( hashValue, pszKey, cchKey);

    if ( NULL == phte ) {

        // 2. search in the auxiliary buckets
        PLIST_ENTRY pl;

        for ( pl = m_lHead.Flink; (phte == NULL) && (pl != &m_lHead);
              pl = pl->Flink) {

            HTB_ELEMENT * phtbe = CONTAINING_RECORD( pl,
                                                     HTB_ELEMENT,
                                                     m_ListEntry);
            phte = phtbe->Lookup( hashValue, pszKey, cchKey);
        } // for
    }

    Unlock();

    return (phte);
} // HASH_TABLE_BUCKET::Lookup()


BOOL
HASH_TABLE_BUCKET::Insert( IN DWORD hashValue,
                           IN HT_ELEMENT * phte,
                           IN BOOL fCheckForDuplicate)
{
    BOOL fReturn = FALSE;

    if ( fCheckForDuplicate) {

        Lock();

        // do a lookup and find out if this data exists.
        HT_ELEMENT * phteLookedup = Lookup( hashValue,
                                            phte->QueryKey(),
                                            phte->QueryKeyLen()
                                            );

        if ( NULL != phteLookedup) {
            // the element is already present - return failure

            DerefAndKillElement( phteLookedup);
        }

        Unlock();

        if ( NULL != phteLookedup) {
            SetLastError( ERROR_DUP_NAME);
            return ( FALSE);
        }
    }

    Lock();

    // 1. try inserting in the first bucket chunk, if possible
    if ( m_htbeFirst.IsSpaceAvailable()) {

        fReturn = m_htbeFirst.Insert( hashValue, phte);
    } else {

        // 2. Find the first chunk that has space and insert it there.
        PLIST_ENTRY pl;
        HTB_ELEMENT * phtbe;

        for ( pl = m_lHead.Flink; (pl != &m_lHead);
              pl = pl->Flink) {

            phtbe = CONTAINING_RECORD( pl, HTB_ELEMENT, m_ListEntry);

            if ( phtbe->IsSpaceAvailable()) {
                fReturn = phtbe->Insert( hashValue, phte);
                break;
            }
        } // for

        if ( !fReturn ) {

            //
            // We ran out of space.
            // Allocate a new bucket and insert the new element.
            //

            phtbe = new HTB_ELEMENT();
            if ( NULL != phtbe) {

                // add the bucket to the list of buckets and
                // then add the element to the bucket
                InsertTailList( &m_lHead, &phtbe->m_ListEntry);
                fReturn = phtbe->Insert(hashValue, phte);
            } else {

                IF_DEBUG( ERROR) {
                    DBGPRINTF(( DBG_CONTEXT,
                                " HTB(%08x)::Insert: Unable to add a chunk\n",
                                this));
                }
                SetLastError( ERROR_NOT_ENOUGH_MEMORY);
            }
        }
    }

    Unlock();

    return ( fReturn);
} // HASH_TABLE_BUCKET::Insert()



BOOL
HASH_TABLE_BUCKET::Delete( IN HT_ELEMENT * phte)
{
    BOOL fReturn = FALSE;


    // We do not know which bucket this element belongs to.
    // So we should try all chunks to delete this element.

    Lock();

    // 1. try deleting the element from first bucket chunk, if possible
    fReturn = m_htbeFirst.Delete( phte);

    if (!fReturn) {

        // it was not on the first bucket chunk.

        // 2. Find the first chunk that might contain this element
        // and delete it.
        PLIST_ENTRY pl;

        for ( pl = m_lHead.Flink;
              !fReturn && (pl != &m_lHead);
              pl = pl->Flink) {

            HTB_ELEMENT * phtbe = CONTAINING_RECORD( pl,
                                                     HTB_ELEMENT,
                                                     m_ListEntry);
            fReturn = phtbe->Delete( phte);
        } // for

        // the element should have been in the hash table,
        // otherwise the app is calling with wrong entry
        DBG_ASSERT( fReturn);
    }

    Unlock();

    return ( fReturn);
} // HASH_TABLE_BUCKET::Delete()


DWORD
HASH_TABLE_BUCKET::NumEntries( VOID)
{
    DWORD nEntries;

    Lock();

    nEntries = m_htbeFirst.NumElements();

    PLIST_ENTRY pl;

    for ( pl = m_lHead.Flink;
          (pl != &m_lHead);
          pl = pl->Flink) {

        HTB_ELEMENT * phtbe = CONTAINING_RECORD( pl, HTB_ELEMENT,
                                                 m_ListEntry);
        nEntries += phtbe->NumElements();
    } // for

    Unlock();

    return (nEntries);

} // HASH_TABLE_BUCKET::NumEntries()


DWORD
HASH_TABLE_BUCKET::InitializeIterator( IN HT_ITERATOR * phti)
{
    DWORD dwErr = ERROR_NO_MORE_ITEMS;

    //
    // find the first chunk that has a valid element.
    // if we find one, leave the lock on for subsequent accesses.
    // CloseIterator will shut down the lock
    // If we do not find one, we should unlock and return
    //

    phti->nChunkId = NULL;
    phti->nPos = 0;

    Lock();
    if ( m_htbeFirst.NumElements() > 0) {
        phti->nChunkId = (PVOID ) &m_htbeFirst;
        dwErr = NO_ERROR;
    } else {

        // find the first chunk that has an element

        PLIST_ENTRY pl;

        for ( pl = m_lHead.Flink; (pl != &m_lHead); pl = pl->Flink) {

            HTB_ELEMENT * phtbe = CONTAINING_RECORD( pl, HTB_ELEMENT,
                                                     m_ListEntry);
            if ( phtbe->NumElements() > 0) {
                phti->nChunkId = (PVOID ) phtbe;
                dwErr = NO_ERROR;
                break;
            }
        } // for
    }

    // if we did not find any elements, then unlock and return
    // Otherwise leave the unlocking to the CloseIterator()
    if ( dwErr == ERROR_NO_MORE_ITEMS) {

        // get out of this bucket completely.
        Unlock();
    }

    return ( dwErr);

} // HASH_TABLE_BUCKET::InitializeIterator()


DWORD
HASH_TABLE_BUCKET::FindNextElement( IN HT_ITERATOR * phti,
                                    OUT HT_ELEMENT ** pphte)
{
    //  this function should be called only when the bucket is locked.

    DWORD dwErr;
    HTB_ELEMENT * phtbe = (HTB_ELEMENT * )phti->nChunkId;

    //
    // phti contains the <chunk, pos> from which we should start scan for
    //   next element.
    //

    DBG_ASSERT( NULL != phtbe);
    dwErr = phtbe->FindNextElement( &phti->nPos, pphte);

    if ( ERROR_NO_MORE_ITEMS == dwErr ) {

        // scan the rest of the chunks for next element

        PLIST_ENTRY pl = ((phtbe == &m_htbeFirst) ? m_lHead.Flink :
                          phtbe->m_ListEntry.Flink);

        for ( ; (pl != &m_lHead); pl = pl->Flink) {

            phtbe = CONTAINING_RECORD( pl, HTB_ELEMENT,
                                       m_ListEntry);
            if ( phtbe->NumElements() > 0) {
                phti->nPos = 0;
                dwErr = phtbe->FindNextElement( &phti->nPos, pphte);
                DBG_ASSERT( NO_ERROR == dwErr);
                phti->nChunkId = (PVOID ) phtbe;
                break;
            }
        } // for
    }

    if ( dwErr == ERROR_NO_MORE_ITEMS) {

        phti->nChunkId = NULL;
    }

    return ( dwErr);
} // HASH_TABLE_BUCKET::FindNextElement()


DWORD
HASH_TABLE_BUCKET::CloseIterator( IN HT_ITERATOR * phti)
{
    // just unlock the current bucket.
    Unlock();

    return ( NO_ERROR);
} // HASH_TABLE_BUCKET::CloseIterator()


VOID
HASH_TABLE_BUCKET::Print( IN DWORD level)
{
    Lock();
    DBGPRINTF(( DBG_CONTEXT,
                "\n\nHASH_TABLE_BUCKET (%08x): Head.Flink=%08x; Head.Blink=%08x\n"
                " Bucket Chunk # 0:\n"
                ,
                this, m_lHead.Flink, m_lHead.Blink
                ));

    m_htbeFirst.Print( level);

    if ( level > 0) {
        PLIST_ENTRY pl;
        DWORD i;

        for ( pl = m_lHead.Flink, i = 1;
              (pl != &m_lHead);
              pl = pl->Flink, i++) {

            HTB_ELEMENT * phtbe = CONTAINING_RECORD( pl, HTB_ELEMENT,
                                                     m_ListEntry);
            DBGPRINTF(( DBG_CONTEXT, "\n Bucket Chunk # %d\n", i));
            phtbe->Print( level);
        } // for
    }

    Unlock();
    return;
} // HASH_TABLE_BUCKET::Print()




/************************************************************
 *    Member Functions of HASH_TABLE
 ************************************************************/

HASH_TABLE::HASH_TABLE( IN DWORD   nBuckets,
                        IN LPCSTR  pszIdentifier,
                        IN DWORD   dwHashTableFlags
                        )
    : m_nBuckets   ( nBuckets),
      m_dwFlags    ( dwHashTableFlags),
      m_nEntries   ( 0),
      m_nLookups   ( 0),
      m_nHits      ( 0),
      m_nInserts   ( 0),
      m_nFlushes   ( 0)
{
    if ( NULL != pszIdentifier) {

        lstrcpynA( m_rgchId, pszIdentifier, sizeof( m_rgchId));
    }

    m_prgBuckets = new HASH_TABLE_BUCKET[nBuckets];

} // HASH_TABLE::HASH_TABLE()



DWORD
HASH_TABLE::CalculateHash( IN LPCSTR pszKey, DWORD cchKey) const
{
    DWORD hash = 0;

    DBG_ASSERT( pszKey != NULL );

    if ( cchKey > 8) {
        //
        // hash the last 8 characters
        //
        pszKey = (pszKey + cchKey - 8);
    }

    while ( *pszKey != '\0') {

        //
        // This is an extremely slimey way of getting upper case.
        // Kids, don't try this at home
        // -johnson
        //

        DWORD ch = ((*pszKey++) & ~0x20);

        // NYI: this is a totally pipe-line unfriendly code. Improve this.
        hash <<= 2;
        hash ^= ch;
        hash += ch;
    } // while

    //
    // Multiply by length (to introduce some randomness.  Murali said so.
    //

    return( hash * cchKey);
} // CalculateHash()


VOID
HASH_TABLE::Cleanup(VOID)
{
    if ( NULL != m_prgBuckets ) {

        delete [] m_prgBuckets;
        m_prgBuckets = NULL;
    }

} // HASH_TABLE::Cleanup()



# define INCREMENT_LOOKUPS()  \
       { InterlockedIncrement( (LPLONG ) &m_nLookups); }

# define INCREMENT_HITS( phte)  \
       if ( NULL != phte) { InterlockedIncrement( (LPLONG ) &m_nHits); }

# define INCREMENT_INSERTS()  \
       { InterlockedIncrement( (LPLONG ) &m_nInserts); }

# define INCREMENT_FLUSHES()  \
       { InterlockedIncrement( (LPLONG ) &m_nFlushes); }

# define INCREMENT_ENTRIES( fRet)  \
       if ( fRet) { InterlockedIncrement( (LPLONG ) &m_nEntries); }

# define DECREMENT_ENTRIES( fRet)  \
       if ( fRet) { InterlockedDecrement( (LPLONG ) &m_nEntries); }

HT_ELEMENT *
HASH_TABLE::Lookup( IN LPCSTR pszKey, DWORD cchKey)
{
    // 1. Calculate the hash value for pszKey
    // 2. Find the bucket for the hash value
    // 3. Search for given item in the bucket
    // 4. return the result, after updating statistics

    DWORD hashVal = CalculateHash( pszKey, cchKey);
    HT_ELEMENT * phte;

    INCREMENT_LOOKUPS();

    DBG_ASSERT( NULL != m_prgBuckets);
    phte = m_prgBuckets[hashVal % m_nBuckets].Lookup( hashVal, pszKey, cchKey);

    INCREMENT_HITS( phte);

    return ( phte);
} // HASH_TABLE::Lookup()


BOOL
HASH_TABLE::Insert( HT_ELEMENT * phte, IN BOOL fCheckBeforeInsert)
{
    // 1. Calculate the hash value for key of the HT_ELEMENT object
    // 2. Find the bucket for the hash value
    // 3. Check if this item is not already present and insert
    //     it into the hash table.
    //  (the check can be bypassed if fCheck is set to FALSE)
    // 4. return the result, after updating statistics

    DWORD hashVal = CalculateHash( phte->QueryKey(),
                                   phte->QueryKeyLen() );
    BOOL  fRet;

    INCREMENT_INSERTS();

    DBG_ASSERT( NULL != m_prgBuckets);
    fRet = m_prgBuckets[hashVal % m_nBuckets].Insert( hashVal,
                                                      phte,
                                                      fCheckBeforeInsert);

    IF_DEBUG( ERROR) {
        if ( !fRet) {
            DBGPRINTF(( DBG_CONTEXT,
                        " Unable to insert %08x into bucket %d."
                        "  Bucket has %d elements. Error = %d\n",
                        phte, hashVal % m_nBuckets,
                        m_prgBuckets[hashVal % m_nBuckets].NumEntries(),
                        GetLastError()
                        ));
        }
    }
    INCREMENT_ENTRIES( fRet);

    return ( fRet);
} // HASH_TABLE::Insert()



BOOL
HASH_TABLE::Delete( HT_ELEMENT * phte)
{
    BOOL  fRet;
    DWORD hashVal = CalculateHash( phte->QueryKey(), phte->QueryKeyLen());

    DBG_ASSERT( NULL != m_prgBuckets);
    fRet = m_prgBuckets[hashVal % m_nBuckets].Delete( phte);

    DECREMENT_ENTRIES( fRet);

    return ( fRet);
} // HASH_TABLE::Delete()



VOID
HASH_TABLE::Print( IN DWORD level)
{
    DWORD i;

    DBGPRINTF(( DBG_CONTEXT,
                "HASH_TABLE(%08x) "
                "%s: nBuckets = %d; dwFlags = %d;"
                " nEntries = %d; nLookups = %d; nHits = %d;"
                " nInserts = %d; nFlushes = %d;"
                " m_prgBuckets = %d\n",
                this, m_rgchId, m_nBuckets, m_dwFlags,
                m_nEntries, m_nLookups, m_nHits, m_nInserts,
                m_nFlushes, m_prgBuckets));

    if ( level == 0 ) {

        CHAR rgchBuff[2000];
        DWORD cch;

        cch = wsprintfA( rgchBuff, "\tBucket  NumEntries\n");
        DBG_ASSERT( NULL != m_prgBuckets);
        for (i = 0; i < m_nBuckets; i++) {

            cch += wsprintf( rgchBuff + cch, "\t[%4d]  %4d,\n",
                             i, m_prgBuckets[i].NumEntries());
        } // for

        DBGDUMP(( DBG_CONTEXT, rgchBuff));
    } else {

        DBG_ASSERT( NULL != m_prgBuckets);
        for (i = 0; i < m_nBuckets; i++) {

            m_prgBuckets[i].Print( level);
        } // for
    }

    return;
} // HASH_TABLE::Print()



DWORD
HASH_TABLE::InitializeIterator( IN HT_ITERATOR * phti)
{
    DWORD dwErr = ERROR_NO_MORE_ITEMS;
    DBG_ASSERT( IsValid());
    DBG_ASSERT( NULL != phti);

    // initialize the iterator
    phti->nBucketNumber = INFINITE;
    phti->nChunkId = NULL;
    phti->nPos = 0;

    if ( m_nEntries > 0) {
        // set the iterator to point to the first bucket with some elements.
        for ( DWORD i = 0; (i < m_nBuckets); i++) {

            dwErr = m_prgBuckets[i].InitializeIterator( phti);
            if ( dwErr == NO_ERROR) {
                phti->nBucketNumber = i;
                break;
            }
        }
    }

    return ( dwErr);
} // HASH_TABLE::InitializeIterator()


DWORD
HASH_TABLE::FindNextElement( IN HT_ITERATOR * phti,
                             OUT HT_ELEMENT ** pphte)
{
    DWORD dwErr = ERROR_NO_MORE_ITEMS;
    DBG_ASSERT( IsValid());
    DBG_ASSERT( NULL != phti);
    DBG_ASSERT( NULL != pphte);

    if ( INFINITE != phti->nBucketNumber) {

        // iterator has some valid state use it.
        DBG_ASSERT( phti->nBucketNumber < m_nBuckets);

        dwErr =
            m_prgBuckets[ phti->nBucketNumber].FindNextElement( phti, pphte);

        if ( ERROR_NO_MORE_ITEMS == dwErr) {

            DBG_REQUIRE( m_prgBuckets[ phti->nBucketNumber].
                            CloseIterator( phti)
                         == NO_ERROR
                        );

            // hunt for the next bucket with an element.
            for ( DWORD i = (phti->nBucketNumber + 1); (i < m_nBuckets); i++) {

                dwErr = m_prgBuckets[i].InitializeIterator( phti);

                if ( dwErr == NO_ERROR) {
                    phti->nBucketNumber = i;
                    dwErr = m_prgBuckets[ i].FindNextElement( phti, pphte);
                    DBG_ASSERT( dwErr == NO_ERROR);
                    break;
                }
            } // for

            if ( ERROR_NO_MORE_ITEMS == dwErr) {
                // reset the bucket number
                phti->nBucketNumber = INFINITE;
            }
        }
    }

    return ( dwErr);
} // HASH_TABLE::FindNextElement()


DWORD
HASH_TABLE::CloseIterator( IN HT_ITERATOR * phti)
{
    DBG_ASSERT( IsValid());
    DBG_ASSERT( NULL != phti);

    if ( INFINITE != phti->nBucketNumber) {
        DBG_ASSERT( phti->nBucketNumber < m_nBuckets);
        DBG_REQUIRE( m_prgBuckets[ phti->nBucketNumber].
                     CloseIterator( phti)
                     == NO_ERROR
                     );
        phti->nBucketNumber = INFINITE;
    }

    return ( NO_ERROR);
} // HASH_TABLE::CloseIterator()


/************************ End of File ***********************/