/*++

Copyright (c) 1999  Microsoft Corporation

Module Name:

    hash.c

Abstract:

    This is a fairly generic hash table implementation.  This is used to form
    a lookup table for mapping pointers to dwords, so we can send dwords over
    the wire.  This is for sundown.

Author:

    Ken Peery (kpeery) 26-Feb-1999

Revision History:

--*/
#include "clusrtlp.h"

//
//  PLIST_ENTRY
//  NextListEntry(
//      PLIST_ENTRY ListHead
//      );
//

#define NextListEntry(ListHead)  (ListHead)->Flink



//
// local routines
//
DWORD
ClRtlFindUniqueIdHashUnSafe(
    IN PCL_HASH pTable,
    OUT PDWORD  pId
);

PVOID
ClRtlGetEntryHashUnSafe(
    IN PCL_HASH pTable,
    IN DWORD    Id
    );



VOID
ClRtlInitializeHash(
    PCL_HASH pTable
    )
/*++

Routine Description:

    Initializes a hash table for use.

Arguments:

    pTable - Supplies a pointer to a hash table structure to initialize

Return Value:

    None.

--*/

{
    DWORD index;

    if (NULL == pTable) {
        // 
        // We should never call this routine with NULL
        //
        return;
    }

    ZeroMemory(pTable,sizeof(CL_HASH));

    for(index=0; index < MAX_CL_HASH; index++) {
        InitializeListHead(&pTable->Head[index].ListHead);
    }
    
    InitializeCriticalSection(&pTable->Lock);
}



VOID
ClRtlDeleteHash(
    IN PCL_HASH pTable
    )
/*++

Routine Description:

    Releases all resources used by a hash table

Arguments:

    pTable - supplies the hash table to be deleted

Return Value:

    None.

--*/

{
    DWORD index;

    PLIST_ENTRY pItem;

    if (NULL == pTable)
        return;

    EnterCriticalSection(&pTable->Lock);

    for(index=0; index < MAX_CL_HASH; index++) {

        while(!IsListEmpty(&pTable->Head[index].ListHead)) {

            pItem=RemoveHeadList(&pTable->Head[index].ListHead);
            LocalFree(pItem);
        }

    }

    LeaveCriticalSection(&pTable->Lock);

    DeleteCriticalSection(&pTable->Lock);
}



PVOID
ClRtlRemoveEntryHash(
    IN PCL_HASH pTable,
    IN DWORD    Id
    )
/*++

Routine Description:

    Removes the item specified by the Id from the list.  If the item is
    not there then return NULL.  Then save off the pData field and delete this
    entry from the list.  Then return the pData field.

Arguments:

    Id     - the id for the entry to remove
    pTable - the hash table to search

Return Value:

    The pData field of the entry with the matching id, or NULL.

--*/

{
    DWORD index;
    PVOID pData;

    PCL_HASH_ITEM  pItem;

    pData=NULL;
    pItem=NULL;

    if ((Id == 0) || (pTable == NULL)) {
        SetLastError(ERROR_INVALID_PARAMETER);  
        return NULL;
    }

    index = Id % MAX_CL_HASH;

    //
    // Lock the table for the search
    //

    EnterCriticalSection(&pTable->Lock);

    if (pTable->Head[index].Id == Id) {

        //
        // if the entry is in the head
        //

        pData=pTable->Head[index].pData;

        if (IsListEmpty(&pTable->Head[index].ListHead)) {
    
            //
            // there are no other entries so just zero this one out
            //

            pTable->Head[index].Id = 0;
            pTable->Head[index].pData = NULL;

        } else {
    
            //
            // if there is at least one other entry move that one into the 
            // head and delete it
            //

            pItem=(PCL_HASH_ITEM)RemoveHeadList(&pTable->Head[index].ListHead);
        
            pTable->Head[index].Id = pItem->Id;
            pTable->Head[index].pData = pItem->pData;

            LocalFree(pItem);
        }

    } else {

        pItem=(PCL_HASH_ITEM)NextListEntry(&pTable->Head[index].ListHead);
        do
        {
            if (pItem->Id == Id)
            {
                pData=pItem->pData;

                RemoveEntryList(&pItem->ListHead);
                LocalFree(pItem);

                break;
            }

            pItem=(PCL_HASH_ITEM)NextListEntry(&pItem->ListHead);

        } while(pItem != &pTable->Head[index]);
        
    }

    // cache the now free value

    pTable->CacheFreeId[index]=Id;

    LeaveCriticalSection(&pTable->Lock);

    return(pData);
}



DWORD
ClRtlFindUniqueIdHashUnSafe(
    IN PCL_HASH pTable,
    OUT PDWORD  pId
)

/*++

Routine Description:

    If the tables last id value should rollover we have to make sure that the
    id choosen is unique.  This should only happen under extreme conditions
    but even still we must find a unique id as quickly as possible, the calling
    routine should already have the critical section at this point. 

Arguments:

    pTable - Supplies the hash table to search 

    pId    - sideffect to hold the id or 0 on error

Return Value:

    ERROR_SUCCESS or the appropate Win32 error code.

NOTENOTE:
    This algorithm is fairly slow essentially it is a sequential search with
    a small cache for previously freed values.  We would do better if we kept
    a ranged free list somewhere so that if we rollover we pick from the list.
    The free list would have to be maintained even before we rollover to make 
    sure we had all the available values.

--*/

{
    DWORD OldId;
    DWORD dwErr, index;
    BOOL  bFoundUniqueId;

    PCL_HASH_ITEM pItem;

    dwErr=ERROR_INVALID_PARAMETER;
    bFoundUniqueId=FALSE;
    *pId=0;

    OldId=pTable->LastId;
    
    do
    {
        index=pTable->LastId % MAX_CL_HASH;

        //
        // first check to see if there is a free value in the cache
        //
        if (pTable->CacheFreeId[index] != 0)
        {
            bFoundUniqueId=TRUE;
            *pId=pTable->CacheFreeId[index];
            pTable->CacheFreeId[index]=0;
            break;
        }

        //
        // if the cache is empty at this index, determine if this value
        // is in use.
        //
        if (NULL == ClRtlGetEntryHashUnSafe(pTable, pTable->LastId)) {
            bFoundUniqueId=TRUE;
            *pId=pTable->LastId;
            break; 
        } 

        //
        // ok, this id is in use and nothing in the cache, try the next id 
        //
        pTable->LastId++;
    
        if (pTable->LastId == 0) {
            pTable->LastId++;
        }

    } while(!bFoundUniqueId && (OldId != pTable->LastId));

    if (bFoundUniqueId) {
        dwErr=ERROR_SUCCESS;
    }

    return(dwErr);
}



DWORD
ClRtlInsertTailHash(
    IN PCL_HASH pTable,
    IN PVOID    pData,
    OUT PDWORD  pId
    )

/*++

Routine Description:

    Inserts a new pData value into the tail of one of the entries for the 
    hash table.  The unique id for this entry is returned or 0 on failure.

Arguments:

    pTable - Supplies the hash table to add the entry.

    pData  - Supplies the data entry to be added to the table.

    pId    - sideffect to hold the id or 0 on error

Return Value:

    ERROR_SUCCESS or the appropate Win32 error code.

--*/

{
    DWORD index;
    DWORD dwErr;

    PCL_HASH_ITEM pItem;

    *pId=0;
    dwErr=ERROR_SUCCESS;
    
    if (pTable == NULL) {
        return(ERROR_INVALID_PARAMETER);
    }
    
    EnterCriticalSection(&pTable->Lock);

    pTable->LastId++;

    if (pTable->LastId == 0) {
        pTable->bRollover = TRUE;
        pTable->LastId++;
    }

    index=pTable->LastId % MAX_CL_HASH;

    *pId=pTable->LastId;

    if (pTable->Head[index].Id == 0) {

        //
        // if the first entry then add it to the head
        // 
        // if we rollover, but the head is empty then the id is unique.
        //
        
        pTable->Head[index].Id = *pId;
        pTable->Head[index].pData = pData;

        if (pTable->CacheFreeId[index] == *pId) {
            pTable->CacheFreeId[index]=0;
        }

    } else {

        // if this is not the first entry then add it to the end.

        pItem=(PCL_HASH_ITEM)LocalAlloc(LMEM_FIXED,sizeof(CL_HASH_ITEM));

        if (NULL == pItem) {

            dwErr=ERROR_NOT_ENOUGH_MEMORY;

        } else {

            if (pTable->bRollover) {
                dwErr=ClRtlFindUniqueIdHashUnSafe(pTable, pId);
            }

            if (dwErr == ERROR_SUCCESS)
            {
                pItem->Id = *pId;
                pItem->pData = pData;
    
                index= *pId % MAX_CL_HASH;

                if (pTable->CacheFreeId[index] == *pId) {
                    pTable->CacheFreeId[index]=0;
                }

                InsertTailList(&pTable->Head[index].ListHead,&pItem->ListHead);
            }
            else
            {
                LocalFree(pItem);
            }
        }
    }

    LeaveCriticalSection(&pTable->Lock);

    return(dwErr);
}



PVOID
ClRtlGetEntryHash(
    IN PCL_HASH pTable,
    IN DWORD    Id
    )
/*++

Routine Description:

    Gets the data portion of the item specified by the Id from the hash table.
    If the item is not there then return NULL. 

Arguments:

    Id     - the id for the entry to find
    pTable - the hash table to search

Return Value:

    The pData field of the entry with the matching id, or NULL.

--*/

{
    PVOID pData;

    pData=NULL;

    if ((Id == 0) || (pTable == NULL)) {
        SetLastError(ERROR_INVALID_PARAMETER);  
        return NULL;
    }

    //
    // Lock the table for the search
    //

    EnterCriticalSection(&pTable->Lock);

    pData=ClRtlGetEntryHashUnSafe(pTable,Id);

    LeaveCriticalSection(&pTable->Lock);

    if (pData == NULL) {
        SetLastError(ERROR_INVALID_PARAMETER);  
    }

    return(pData);
}



PVOID
ClRtlGetEntryHashUnSafe(
    IN PCL_HASH pTable,
    IN DWORD    Id
    )
/*++

Routine Description:

    Gets the data portion of the item specified by the Id from the hash table.
    If the item is not there then return NULL. 

Arguments:

    Id     - the id for the entry to find
    pTable - the hash table to search

Return Value:

    The pData field of the entry with the matching id, or NULL.

--*/

{
    DWORD index;
    PVOID pData;

    PCL_HASH_ITEM  pItem;

    pData=NULL;
    pItem=NULL;

    if (Id == 0) { 
        return NULL;
    }

    index = Id % MAX_CL_HASH;

    if (pTable->Head[index].Id == Id) {

        //
        // if the entry is in the head
        //

        pData=pTable->Head[index].pData;

    } else {

        pItem=(PCL_HASH_ITEM)NextListEntry(&pTable->Head[index].ListHead);
        do 
        {
            if (pItem->Id == Id) {

                pData=pItem->pData;
                break;
            }

            pItem=(PCL_HASH_ITEM)NextListEntry(&pItem->ListHead);

        } while(pItem != &pTable->Head[index]);
        
    }

    return(pData);
}