/*===================================================================
Microsoft Denali

Microsoft Confidential.
Copyright 1997 Microsoft Corporation. All Rights Reserved.

File: idhash.cpp

Owner: DmitryR

Source file for the new hashing stuff
===================================================================*/
#include "denpre.h"
#pragma hdrstop

#include "idhash.h"
#include "memcls.h"

#include "memchk.h"

/*===================================================================
  C  P t r  A r r a y
===================================================================*/

HRESULT CPtrArray::Insert
(
int iPos,
void *pv
)
    {
    if (!m_rgpvPtrs) // empty?
        {
        m_rgpvPtrs = (void **)malloc(m_dwInc * sizeof(void *));
        if (!m_rgpvPtrs)
            return E_OUTOFMEMORY;
        m_dwSize = m_dwInc;
        m_cPtrs = 0;
        }
    else if (m_cPtrs == m_dwSize) // full?
        {
        void **pNewPtrs = (void **)realloc
            (
            m_rgpvPtrs,
            (m_dwSize + m_dwInc) * sizeof(void *)
            );
        if (!pNewPtrs)
            return E_OUTOFMEMORY;
        m_rgpvPtrs = pNewPtrs;
        m_dwSize += m_dwInc;
        }

    if (iPos < 0)
        iPos = 0;
    if ((DWORD)iPos >= m_cPtrs) // append?
        {
        m_rgpvPtrs[m_cPtrs++] = pv;
        return S_OK;
        }

    memmove
        (
        &m_rgpvPtrs[iPos+1],
        &m_rgpvPtrs[iPos],
        (m_cPtrs-iPos) * sizeof(void *)
        );

    m_rgpvPtrs[iPos] = pv;
    m_cPtrs++;
    return S_OK;
    }

HRESULT CPtrArray::Find
(
void *pv,
int *piPos
)
    const
    {
    Assert(piPos);

    for (DWORD i = 0; i < m_cPtrs; i++)
        {
        if (m_rgpvPtrs[i] == pv)
            {
            *piPos = i;
            return S_OK;
            }
        }

     // not found
    *piPos = -1;
    return S_FALSE;
    }

HRESULT CPtrArray::Remove
(
void *pv
)
    {
    HRESULT hr = S_FALSE;

    for (DWORD i = 0; i < m_cPtrs; i++)
        {
        if (m_rgpvPtrs[i] == pv)
            hr = Remove(i);
        }

    return hr;
    }

HRESULT CPtrArray::Remove
(
int iPos
)
    {
    Assert(iPos >= 0 && (DWORD)iPos < m_cPtrs);
    Assert(m_rgpvPtrs);

    // remove the element
    DWORD dwMoveSize = (m_cPtrs - iPos - 1) * sizeof(void *);
    if (dwMoveSize)
        memmove(&m_rgpvPtrs[iPos], &m_rgpvPtrs[iPos+1], dwMoveSize);
    m_cPtrs--;

    if (m_dwSize > 4*m_dwInc && m_dwSize > 8*m_cPtrs)
        {
        // collapse to 1/4 if size > 4 x increment and less < 1/8 full

        void **pNewPtrs = (void **)realloc
            (
            m_rgpvPtrs,
            (m_dwSize / 4) * sizeof(void *)
            );

        if (pNewPtrs)
            {
            m_rgpvPtrs = pNewPtrs;
            m_dwSize /= 4;
            }
        }

    return S_OK;
    }

HRESULT CPtrArray::Clear()
    {
    if (m_rgpvPtrs)
        free(m_rgpvPtrs);

    m_dwSize = 0;
    m_rgpvPtrs = NULL;
    m_cPtrs = 0;
    return S_OK;
    }

/*===================================================================
  C  I d  H a s h  U n i t
===================================================================*/

// Everything is inline for this structure. See the header file.

/*===================================================================
  C  I d  H a s h  A r r a y
===================================================================*/

/*===================================================================
For some hardcoded element counts (that relate to session hash),
ACACHE is used for allocations
This is wrapped in the two functions below.
===================================================================*/
ACACHE_FSA_EXTERN(MemBlock128)
ACACHE_FSA_EXTERN(MemBlock256)
static inline void *AcacheAllocIdHashArray(DWORD cElems)
    {
/* removed because in 64bit land it doesn't work because of padding
    void *pvMem;
    if      (cElems == 13) { pvMem = ACACHE_FSA_ALLOC(MemBlock128); }
    else if (cElems == 31) { pvMem = ACACHE_FSA_ALLOC(MemBlock256); }
   else { pvMem = malloc(2*sizeof(USHORT) + cElems*sizeof(CIdHashElem)); }
*/

    return malloc(offsetof(CIdHashArray, m_rgElems) + cElems*sizeof(CIdHashElem));
    }

static inline void AcacheFreeIdHashArray(CIdHashArray *pArray)
    {
/* removed because in 64bit land it doesn't work because of padding
    if (pArray->m_cElems == 13)      { ACACHE_FSA_FREE(MemBlock128, pArray); }
    else if (pArray->m_cElems == 31) { ACACHE_FSA_FREE(MemBlock256, pArray); }
    else                             { free(pArray); }
*/
    free(pArray);
    }

/*===================================================================
CIdHashArray::Alloc

Static method.
Allocates CIdHashArray as a memory block containing var length array.

Parameters:
    cElems          # of elements in the array

Returns:
    Newly created CIdHashArray
===================================================================*/
CIdHashArray *CIdHashArray::Alloc
(
DWORD cElems
)
    {
    CIdHashArray *pArray = (CIdHashArray *)AcacheAllocIdHashArray(cElems);
    if (!pArray)
        return NULL;

    pArray->m_cElems = (USHORT)cElems;
    pArray->m_cNotNulls = 0;
    memset(&(pArray->m_rgElems[0]), 0, cElems * sizeof(CIdHashElem));
    return pArray;
    }

/*===================================================================
CIdHashArray::Alloc

Static method.
Frees allocated CIdHashArray as a memory block.
Also frees any sub-arrays.

Parameters:
    pArray          CIdHashArray object to be freed

Returns:
===================================================================*/
void CIdHashArray::Free
(
CIdHashArray *pArray
)
    {
    if (pArray->m_cNotNulls > 0)
        {
        for (DWORD i = 0; i < pArray->m_cElems; i++)
            {
            if (pArray->m_rgElems[i].FIsArray())
                Free(pArray->m_rgElems[i].PArray());
            }
        }

    AcacheFreeIdHashArray(pArray);
    }

/*===================================================================
CIdHashArray::Find

Searches this array and any sub-arrays for an objects with the
given id.

Parameters:
    dwId            id to look for
    ppvObj          object found (if any)

Returns:
    S_OK = found, S_FALSE = not found
===================================================================*/
HRESULT CIdHashArray::Find
(
DWORD_PTR dwId,
void **ppvObj
)
    const
    {
    DWORD i = (DWORD)(dwId % m_cElems);

    if (m_rgElems[i].DWId() == dwId)
        {
        if (ppvObj)
            *ppvObj = m_rgElems[i].PObject();
        return S_OK;
        }

    if (m_rgElems[i].FIsArray())
        return m_rgElems[i].PArray()->Find(dwId, ppvObj);

    // Not found
    if (ppvObj)
        *ppvObj = NULL;
    return S_FALSE;
    }

/*===================================================================
CIdHashArray::Add

Adds an object to this (or sub-) array by Id.
Creates new sub-arrays as needed.

Parameters:
    dwId            object id
    pvObj           object to add
    rgusSizes       array of sizes (used when creating sub-arrays)

Returns:
    HRESULT (S_OK = added)
===================================================================*/
HRESULT CIdHashArray::Add
(
DWORD_PTR dwId,
void *pvObj,
USHORT *rgusSizes
)
{
    DWORD i = (DWORD)(dwId % m_cElems);

    if (m_rgElems[i].FIsEmpty()) {
        m_rgElems[i].SetToObject(dwId, pvObj);
        m_cNotNulls++;
        return S_OK;
    }

    // Advance array of sizes one level deeper
    if (rgusSizes[0]) // not at the end
        ++rgusSizes;

    if (m_rgElems[i].FIsObject()) {

        // this array logic can't handle adding the same ID twice.  It will
        // loop endlessly.  So, if an attempt is made to add the same
        // ID a second, return an error
        if (m_rgElems[i].DWId() == dwId) {
            return E_INVALIDARG;
        }

        // Old object already taken the slot - need to create new array
        // the size of first for three levels is predefined
        // increment by 1 thereafter
        CIdHashArray *pArray = Alloc (rgusSizes[0] ? rgusSizes[0] : m_cElems+1);
        if (!pArray)
            return E_OUTOFMEMORY;

        // Push the old object down into the array
        HRESULT hr = pArray->Add(m_rgElems[i].DWId(),
                                 m_rgElems[i].PObject(),
                                 rgusSizes);
        if (FAILED(hr))
            return hr;

        // Put array into slot
        m_rgElems[i].SetToArray(pArray);
    }

    Assert(m_rgElems[i].FIsArray());
    return m_rgElems[i].PArray()->Add(dwId, pvObj, rgusSizes);
}

/*===================================================================
CIdHashArray::Remove

Removes an object by id from this (or sub-) array.
Removes empty sub-arrays.

Parameters:
    dwId            object id
    ppvObj          object removed (out, optional)

Returns:
    HRESULT (S_OK = removed, S_FALSE = not found)
===================================================================*/
HRESULT CIdHashArray::Remove
(
DWORD_PTR dwId,
void **ppvObj
)
    {
    DWORD i = (DWORD)(dwId % m_cElems);

    if (m_rgElems[i].DWId() == dwId)
        {
        if (ppvObj)
            *ppvObj = m_rgElems[i].PObject();
        m_rgElems[i].SetToEmpty();
        m_cNotNulls--;
        return S_OK;
        }

    if (m_rgElems[i].FIsArray())
        {
        HRESULT hr = m_rgElems[i].PArray()->Remove(dwId, ppvObj);
        if (hr == S_OK && m_rgElems[i].PArray()->m_cNotNulls == 0)
            {
            Free(m_rgElems[i].PArray());
            m_rgElems[i].SetToEmpty();
            }
        return hr;
        }

    // Not found
    if (ppvObj)
        *ppvObj = NULL;
    return S_FALSE;
    }

/*===================================================================
CIdHashArray::Iterate

Calls a supplied callback for each object in the array and sub-arrays.

Parameters:
    pfnCB               callback
    pvArg1, pvArg2      args to path to the callback

Returns:
    IteratorCallbackCode  what to do next?
===================================================================*/
IteratorCallbackCode CIdHashArray::Iterate
(
PFNIDHASHCB pfnCB,
void *pvArg1,
void *pvArg2
)
    {
    IteratorCallbackCode rc = iccContinue;

    for (DWORD i = 0; i < m_cElems; i++)
        {
        if (m_rgElems[i].FIsObject())
            {
            rc = (*pfnCB)(m_rgElems[i].PObject(), pvArg1, pvArg2);

            // remove if requested
            if (rc & (iccRemoveAndContinue|iccRemoveAndStop))
                {
                m_rgElems[i].SetToEmpty();
                m_cNotNulls--;
                }
            }
        else if (m_rgElems[i].FIsArray())
            {
            rc = m_rgElems[i].PArray()->Iterate(pfnCB, pvArg1, pvArg2);

            // remove sub-array if empty
            if (m_rgElems[i].PArray()->m_cNotNulls == 0)
                {
                Free(m_rgElems[i].PArray());
                m_rgElems[i].SetToEmpty();
                }
            }
        else
            {
            continue;
            }

        // stop if requested
        if (rc & (iccStop|iccRemoveAndStop))
            {
            rc = iccStop;
            break;
            }
        }

    return rc;
    }

#ifdef DBG
/*===================================================================
CIdHashTable::Dump

Dump hash table to a file (for debugging).

Parameters:
    szFile      file name where to dump

Returns:
===================================================================*/
void CIdHashArray::DumpStats
(
FILE *f,
int   nVerbose,
DWORD iLevel,
DWORD &cElems,
DWORD &cSlots,
DWORD &cArrays,
DWORD &cDepth
)
    const
    {
    if (nVerbose > 0)
        {
        for (DWORD t = 0; t < iLevel; t++) fprintf(f, "\t");
        fprintf(f, "Array (level=%d addr=%p) %d slots, %d not null:\n",
            iLevel, this, m_cElems, m_cNotNulls);
        }

    cSlots += m_cElems;
    cArrays++;

    if (iLevel > cDepth)
        cDepth = iLevel;

    for (DWORD i = 0; i < m_cElems; i++)
        {
        if (nVerbose > 1)
            {
            for (DWORD t = 0; t < iLevel; t++) fprintf(f, "\t");
            fprintf(f, "%[%08x:%p@%04d] ", m_rgElems[i].m_dw, m_rgElems[i].m_pv, i);
            }

        if (m_rgElems[i].FIsEmpty())
            {
            if (nVerbose > 1)
                fprintf(f, "NULL\n");
            }
        else if (m_rgElems[i].FIsObject())
            {
            if (nVerbose > 1)
                fprintf(f, "Object\n");
            cElems++;
            }
        else if (m_rgElems[i].FIsArray())
            {
            if (nVerbose > 1)
                fprintf(f, "Array:\n");
            m_rgElems[i].PArray()->DumpStats(f, nVerbose, iLevel+1,
                cElems, cSlots, cArrays, cDepth);
            }
        else
            {
            if (nVerbose > 1)
                fprintf(f, "BAD\n");
            }
        }
    }
#endif
/*===================================================================
  C  I d  H a s h  T a b l e
===================================================================*/

/*===================================================================
CIdHashTable::Init

Initialize id hash table. Does not allocate anything.

Parameters:
    usSize1         size of the first level array
    usSize2         size of the 2nd level arrays (optional)
    usSize3         size of the 3rd level arrays (optional)

Returns:
    S_OK
===================================================================*/
HRESULT CIdHashTable::Init
(
USHORT usSize1,
USHORT usSize2,
USHORT usSize3
)
    {
    Assert(!FInited());
    Assert(usSize1);

    m_rgusSizes[0] = usSize1;   // size of first level array
    m_rgusSizes[1] = usSize2 ? usSize2 : 7;
    m_rgusSizes[2] = usSize3 ? usSize3 : 11;
    m_rgusSizes[3] = 0;         // last one stays 0 to indicate
                                // the end of predefined sizes
    m_pArray = NULL;
    return S_OK;
    }

/*===================================================================
CIdHashTable::UnInit

Uninitialize id hash table. Frees all arrays.

Parameters:

Returns:
    S_OK
===================================================================*/
HRESULT CIdHashTable::UnInit()
    {
    if (!FInited())
        {
        Assert(!m_pArray);
        return S_OK;
        }

    if (m_pArray)
        CIdHashArray::Free(m_pArray);

    m_pArray = NULL;
    m_rgusSizes[0] = 0;
    return S_OK;
    }

#ifdef DBG
/*===================================================================
CIdHashTable::AssertValid

Validates id hash table.

Parameters:

Returns:
===================================================================*/
void CIdHashTable::AssertValid() const
    {
    Assert(FInited());
    }

/*===================================================================
CIdHashTable::Dump

Dump hash table to a file (for debugging).

Parameters:
    szFile      file name where to dump

Returns:
===================================================================*/
void CIdHashTable::Dump
(
const char *szFile
)
    const
    {
    Assert(FInited());
    Assert(szFile);

    FILE *f = fopen(szFile, "a");
    if (!f)
        return;

    fprintf(f, "ID Hash Table Dump:\n");

    DWORD cElems = 0;
    DWORD cSlots = 0;
    DWORD cArrays = 0;
    DWORD cDepth = 0;

    if (m_pArray)
        m_pArray->DumpStats(f, 1, 1, cElems, cSlots, cArrays, cDepth);

    fprintf(f, "Total %d Objects in %d Slots, %d Arrays, %d Max Depth\n\n",
        cElems, cSlots, cArrays, cDepth);
    fclose(f);
    }
#endif

/*===================================================================
  C  H a s h  L o c k
===================================================================*/

/*===================================================================
CHashLock::Init

Initialize the critical section.

Parameters:

Returns:
    S_OK
===================================================================*/
HRESULT CHashLock::Init()
    {
    Assert(!m_fInited);

    HRESULT hr;
    ErrInitCriticalSection(&m_csLock, hr);
    if (FAILED(hr))
        return hr;

    m_fInited = TRUE;
    return S_OK;
    }

/*===================================================================
CHashLock::UnInit

Uninitialize the critical section.

Parameters:

Returns:
    S_OK
===================================================================*/
HRESULT CHashLock::UnInit()
    {
    if (m_fInited)
        {
        DeleteCriticalSection(&m_csLock);
        m_fInited = FALSE;
        }
    return S_OK;
    }