// HashTable.cpp: implementation of the COXHashTable class.
// 
// Simple hash table class based on code in K&R's "The C programming 
// language". 
//
// Author: Chris Maunder
//
//////////////////////////////////////////////////////////////////////
// Version: 9.3


#include "stdafx.h"
#include "OXHashTable.h"

#include <malloc.h>     // for free() (needed becuase of strdup())

// TODO: add API to traverse elements (FindFirst, FindNext and Count)

//////////////////////////////////////////////////////////////////////
// HashNode structure - auto initialise and cleanup
//////////////////////////////////////////////////////////////////////

HashNode::HashNode()
{
    szName = NULL;
    pNext = NULL;
    dwData = 0;
}

HashNode::~HashNode()
{
    if (szName)
        free(szName);
}

//////////////////////////////////////////////////////////////////////
// COXHashTable - Construction/Destruction
//////////////////////////////////////////////////////////////////////

COXHashTable::COXHashTable(int nHashTableSize)
{
    CommonConstruct(nHashTableSize);
}

COXHashTable::COXHashTable()
{
    CommonConstruct(67);
}

BOOL COXHashTable::CommonConstruct(int nHashTableSize)
{
    m_pHashTable = NULL;
    m_nHashTableSize = 0;
    m_bCaseSensitive = FALSE;

    return InitHashTable(nHashTableSize); 
}

COXHashTable::~COXHashTable()
{
    RemoveAll();
    delete [] m_pHashTable;
}

//////////////////////////////////////////////////////////////////////
// COXHashTable - Public Attributes

void COXHashTable::SetCaseSensitive(BOOL bCase)
{
    m_bCaseSensitive = bCase;
}

BOOL COXHashTable::GetCaseSensitive()
{
    return m_bCaseSensitive;
}

//////////////////////////////////////////////////////////////////////
// COXHashTable - Public Operations

BOOL COXHashTable::InitHashTable(UINT nHashSize)
{
    if (nHashSize < 1)
        return FALSE;

    RemoveAll();
    delete [] m_pHashTable;

    m_nHashTableSize = nHashSize;
    m_pHashTable = new HashNode*[m_nHashTableSize];
    if (!m_pHashTable)
        return FALSE;

    for (UINT i = 0; i < m_nHashTableSize; i++)
        m_pHashTable[i] = NULL;

    return TRUE;
}

UINT COXHashTable::GetHashTableSize() const
{
    return m_nHashTableSize;
}

void COXHashTable::RemoveAll()
{
    if (!m_pHashTable)
        return;

    for (UINT i = 0; i < m_nHashTableSize; i++)
    {
        HashNode* pPrev = NULL;
        HashNode* pNext = m_pHashTable[i];

        while (pNext)
        {
            pPrev = pNext->pNext;
            delete pNext;
            pNext = pPrev;
        }

        m_pHashTable[i] = NULL;
    }
}

BOOL COXHashTable::Add(LPCTSTR sz, DWORD dwData)
{
    HashNode *pNode = Lookup(sz);

    // Can't find entry - so add a new one
    if (pNode == NULL)
    {
        // Create a new node with the given name
        pNode = new HashNode;

        if (pNode == NULL)
            return FALSE;

        // Copy over the string
        pNode->szName = _tcsdup(sz);
        if (pNode->szName == NULL)
        {
            delete pNode;
            return FALSE;
        }

        // Find it's position in the hash table and insert it at the head of the
        // list at that point
        pNode->nHashValue = Hash(sz);
        pNode->pNext = m_pHashTable[pNode->nHashValue];
        m_pHashTable[pNode->nHashValue] = pNode;
    }

    // Set the data
    pNode->dwData = dwData;
    
    return TRUE;
}

BOOL COXHashTable::Lookup(LPCTSTR sz, DWORD& dwData) const
{
    dwData = 0;
    HashNode *pNode = Lookup(sz);
    if (!pNode)
        return FALSE;

    dwData = pNode->dwData;
    return TRUE;
}

BOOL COXHashTable::Remove(LPCTSTR sz)
{
    HashNode *pNode, *pPrev = NULL; 
    UINT nHashVal = Hash(sz);

    for (pNode = m_pHashTable[nHashVal]; pNode != NULL; pNode = pNode->pNext)
    {
        BOOL bFound = FALSE;

        if (m_bCaseSensitive)
            bFound = (_tcscmp(sz, pNode->szName) == 0);
        else
            bFound = (_tcsicmp(sz, pNode->szName) == 0);
          
        if (bFound)
            break;

        pPrev = pNode;
    }

    // No entry found
    if (pNode == NULL)
        return FALSE;

    if (pPrev == NULL)      // Entry is first entry at Hash table entry site
    {
        m_pHashTable[nHashVal] = pNode->pNext;
        delete pNode;
    }
    else    // More than one entry with this hash val - remove entry from list
    {
        pPrev->pNext = pNode->pNext;
        delete pNode;
    }

    return TRUE;
}

//////////////////////////////////////////////////////////////////////
// COXHashTable - protected implementation

UINT COXHashTable::Hash(LPCTSTR sz) const
{
    UINT nHashVal = 0;

    if (m_bCaseSensitive)
    {
        while (*sz)
            nHashVal = (nHashVal<<5) + nHashVal + *sz++;
    }
    else
    {
        while (*sz)
        {
            if (_istupper(*sz))
                nHashVal = (nHashVal<<5) + nHashVal + _tolower(*sz++);
            else
                nHashVal = (nHashVal<<5) + nHashVal + *sz++;
        }
    }

    return nHashVal % m_nHashTableSize;
}

HashNode *COXHashTable::Lookup(LPCTSTR sz) const
{
    HashNode *pNode;
    UINT nHashVal = Hash(sz);

    for (pNode = m_pHashTable[nHashVal]; pNode != NULL; pNode = pNode->pNext)
    {
        BOOL bFound = FALSE;

        if (m_bCaseSensitive)
            bFound = (_tcscmp(sz, pNode->szName) == 0);
        else
            bFound = (_tcsicmp(sz, pNode->szName) == 0);
          
        if (bFound)
            return pNode;
    }

    // Did not find.
    return NULL;
}

HASH_POS COXHashTable::GetStartPosition() const
{
    if (!m_pHashTable)
        return NULL;

    // Check there are elements
    for (UINT nNode = 0; nNode < m_nHashTableSize; nNode++)
        if (m_pHashTable[nNode])
            return HASHTABLE_START_POS;

    return NULL;
}

void COXHashTable::GetNextAssoc(HASH_POS& pos, LPCTSTR& sz, DWORD& dwData) const
{
    if (!m_pHashTable || !pos)
    {
        pos = NULL;
        return;
    }

    // If this is the first call, then find the first item
	if (pos == HASHTABLE_START_POS)
	{
        pos = NULL;
        for (UINT nNode = 0; nNode < m_nHashTableSize; nNode++)
        {
            if (m_pHashTable[nNode])
            {
                pos = m_pHashTable[nNode];
                break;
            }
        }
	}

	// fill in return data
    if (pos)
    {
        sz = pos->szName;
        dwData = pos->dwData;
    }

    // Find the next item
    if (pos)
    {
        HashNode* pNode = (HashNode*) pos;
        if (pNode->pNext == NULL)
        {
            pos = NULL;
            for (UINT nNode = pNode->nHashValue+1; nNode < m_nHashTableSize; nNode++)
            {
                if (m_pHashTable[nNode])
                {
                    pos = m_pHashTable[nNode];
                    break;
                }
            }
        }
        else
            pos = pNode->pNext;
    }
}
