/*****************************************************************************/
/* Map.cpp                                Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Implementation of map for CascLib                                         */
/*---------------------------------------------------------------------------*/
/*   Date    Ver   Who  Comment                                              */
/* --------  ----  ---  -------                                              */
/* 10.06.14  1.00  Lad  The first version of Map.cpp                         */
/*****************************************************************************/

#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"

//-----------------------------------------------------------------------------
// Local functions

// Returns the extension, right after "."
static const char * String_GetExtension(const char * szString)
{
    const char * szExtension = strrchr(szString, '.');
    return (szExtension != NULL) ? szExtension + 1 : NULL;
}

static DWORD CalcHashIndex_Key(PCASC_MAP pMap, void * pvKey)
{
    LPBYTE pbKey = (LPBYTE)pvKey;
    DWORD dwHash = 0x7EEE7EEE;

    // Construct the hash from the first 8 digits
    dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[0];
    dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[1];
    dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[2];
    dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[3];
    dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[4];
    dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[5];
    dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[6];
    dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[7];

    // Return the hash limited by the table size
    return (dwHash % pMap->TableSize);
}

static DWORD CalcHashIndex_String(PCASC_MAP pMap, const char * szString, const char * szStringEnd)
{
    LPBYTE pbKeyEnd = (LPBYTE)szStringEnd;
    LPBYTE pbKey = (LPBYTE)szString;
    DWORD dwHash = 0x7EEE7EEE;

    // Hash the string itself
    while(pbKey < pbKeyEnd)
    {
        dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ AsciiToUpperTable_BkSlash[pbKey[0]];
        pbKey++;
    }

    // Return the hash limited by the table size
    return (dwHash % pMap->TableSize);
}

static bool CompareObject_Key(PCASC_MAP pMap, void * pvObject, void * pvKey)
{
    LPBYTE pbObjectKey = (LPBYTE)pvObject + pMap->KeyOffset;

    return (memcmp(pbObjectKey, pvKey, pMap->KeyLength) == 0);
}

static bool CompareObject_String(
    PCASC_MAP pMap,
    const char * szExistingString,
    const char * szString,
    const char * szStringEnd)
{
    // Keep compiler happy
    CASCLIB_UNUSED(pMap);

    // Compare the whole part, case insensitive
    while(szString < szStringEnd)
    {
        if(AsciiToUpperTable_BkSlash[*szExistingString] != AsciiToUpperTable_BkSlash[*szString])
            return false;

        szExistingString++;
        szString++;
    }

    return true;
}

//-----------------------------------------------------------------------------
// Public functions

PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwKeyOffset)
{
    PCASC_MAP pMap;
    size_t cbToAllocate;
    size_t dwTableSize;

    // Calculate the size of the table
    dwTableSize = (dwMaxItems * 3 / 2) | 0x01;

    // Allocate new map for the objects
    cbToAllocate = sizeof(CASC_MAP) + (dwTableSize * sizeof(void *));
    pMap = (PCASC_MAP)CASC_ALLOC(LPBYTE, cbToAllocate);
    if(pMap != NULL)
    {
        memset(pMap, 0, cbToAllocate);
        pMap->KeyLength = dwKeyLength;
        pMap->TableSize = dwTableSize;
        pMap->KeyOffset = dwKeyOffset;
    }

    // Return the allocated map
    return pMap;
}

size_t Map_EnumObjects(PCASC_MAP pMap, void **ppvArray)
{
    size_t nIndex = 0;

    // Verify pointer to the map
    if(pMap != NULL && ppvArray != NULL)
    {
        // Enumerate all items in main table
        for(size_t i = 0; i < pMap->TableSize; i++)
        {
            // Is that cell valid?
            if(pMap->HashTable[i] != NULL)
            {
                ppvArray[nIndex++] = pMap->HashTable[i];
            }
        }

        return pMap->ItemCount;
    }

    return 0;
}

void * Map_FindObject(PCASC_MAP pMap, void * pvKey, PDWORD PtrIndex)
{
    void * pvObject;
    DWORD dwHashIndex;

    // Verify pointer to the map
    if(pMap != NULL)
    {
        // Construct the main index
        dwHashIndex = CalcHashIndex_Key(pMap, pvKey);
        while(pMap->HashTable[dwHashIndex] != NULL)
        {
            // Get the pointer at that position
            pvObject = pMap->HashTable[dwHashIndex];

            // Compare the hash
            if(CompareObject_Key(pMap, pvObject, pvKey))
            {
                if(PtrIndex != NULL)
                    PtrIndex[0] = dwHashIndex;
                return pvObject;
            }

            // Move to the next entry
            dwHashIndex = (dwHashIndex + 1) % pMap->TableSize;
        }
    }

    // Not found, sorry
    return NULL;
}

bool Map_InsertObject(PCASC_MAP pMap, void * pvNewObject, void * pvKey)
{
    void * pvExistingObject;
    DWORD dwHashIndex;

    // Verify pointer to the map
    if(pMap != NULL)
    {
        // Limit check
        if((pMap->ItemCount + 1) >= pMap->TableSize)
            return false;

        // Construct the hash index
        dwHashIndex = CalcHashIndex_Key(pMap, pvKey);
        while(pMap->HashTable[dwHashIndex] != NULL)
        {
            // Get the pointer at that position
            pvExistingObject = pMap->HashTable[dwHashIndex];

            // Check if hash being inserted conflicts with an existing hash
            if(CompareObject_Key(pMap, pvExistingObject, pvKey))
                return false;

            // Move to the next entry
            dwHashIndex = (dwHashIndex + 1) % pMap->TableSize;
        }

        // Insert at that position
        pMap->HashTable[dwHashIndex] = pvNewObject;
        pMap->ItemCount++;
        return true;
    }

    // Failed
    return false;
}

bool Map_InsertString(PCASC_MAP pMap, const char * szString, bool bCutExtension)
{
    const char * szExistingString;
    const char * szStringEnd = NULL;
    DWORD dwHashIndex;

    // Verify pointer to the map
    if(pMap != NULL)
    {
        // Limit check
        if((pMap->ItemCount + 1) >= pMap->TableSize)
            return false;

        // Retrieve the length of the string without extension
        if(bCutExtension)
            szStringEnd = String_GetExtension(szString);
        if(szStringEnd == NULL)
            szStringEnd = szString + strlen(szString);

        // Construct the hash index
        dwHashIndex = CalcHashIndex_String(pMap, szString, szStringEnd);
        while(pMap->HashTable[dwHashIndex] != NULL)
        {
            // Get the pointer at that position
            szExistingString = (const char *)pMap->HashTable[dwHashIndex];

            // Check if hash being inserted conflicts with an existing hash
            if(CompareObject_String(pMap, szExistingString, szString, szStringEnd))
                return false;

            // Move to the next entry
            dwHashIndex = (dwHashIndex + 1) % pMap->TableSize;
        }

        // Insert at that position
        pMap->HashTable[dwHashIndex] = (void *)szString;
        pMap->ItemCount++;
        return true;
    }

    // Failed
    return false;
}

const char * Map_FindString(PCASC_MAP pMap, const char * szString, const char * szStringEnd)
{
    const char * szExistingString;
    DWORD dwHashIndex;

    // Verify pointer to the map
    if(pMap != NULL)
    {
        // Construct the main index
        dwHashIndex = CalcHashIndex_String(pMap, szString, szStringEnd);
        while(pMap->HashTable[dwHashIndex] != NULL)
        {
            // Get the pointer at that position
            szExistingString = (const char *)pMap->HashTable[dwHashIndex];

            // Compare the hash
            if(CompareObject_String(pMap, szExistingString, szString, szStringEnd))
                return szExistingString;

            // Move to the next entry
            dwHashIndex = (dwHashIndex + 1) % pMap->TableSize;
        }
    }

    // Not found, sorry
    return NULL;
}

void Map_Free(PCASC_MAP pMap)
{
    if(pMap != NULL)
    {
        CASC_FREE(pMap);
    }
}
