#include "precomp.h"
#include "cntlist.h"


CList::CList(void)
:
    m_fQueue(FALSE),
    m_cMaxEntries(CLIST_DEFAULT_MAX_ITEMS)
{
    Init(1);
}


CList::CList(ULONG cMaxItems)
:
    m_fQueue(FALSE),
    m_cMaxEntries(cMaxItems)
{
    Init(1);
}


CList::CList(ULONG cMaxItems, ULONG cSubItems)
:
    m_fQueue(FALSE),
    m_cMaxEntries(cMaxItems)
{
    Init(cSubItems);
}


CList::CList(ULONG cMaxItems, ULONG cSubItems, BOOL fQueue)
:
    m_fQueue(fQueue),
    m_cMaxEntries(cMaxItems)
{
    Init(cSubItems);
}


CList::CList(CList *pSrc)
:
    m_fQueue(pSrc->m_fQueue),
    m_cMaxEntries(pSrc->GetCount())
{

    Init(1);

    LPVOID p;
    pSrc->Reset();
    while (NULL != (p = pSrc->Iterate()))
    {
        Append(p);
    }
}


BOOL CList::Init(ULONG cSubItems)
{
    if (m_cMaxEntries < CLIST_DEFAULT_MAX_ITEMS)
    {
        m_cMaxEntries = CLIST_DEFAULT_MAX_ITEMS;
    }

    m_cEntries = 0;
    m_nHeadOffset = 0;
    m_nCurrOffset = CLIST_END_OF_ARRAY_MARK;
    m_cSubItems = cSubItems;

    // it is kind of bad here because there is no way to return an error.
    // unfortunately it won't fault here and later.
    DBG_SAVE_FILE_LINE
    m_aEntries = (LPVOID *) new char[m_cMaxEntries * m_cSubItems * sizeof(LPVOID)];
    CalcKeyArray();
    return m_aEntries ? TRUE : FALSE;
}


CList::~CList(void)
{
    delete m_aEntries;
}


void CList::CalcKeyArray(void)
{
    if (1 == m_cSubItems)
    {
        m_aKeys = NULL;
    }
    else
    {
        ASSERT(2 == m_cSubItems);
        m_aKeys = (NULL != m_aEntries) ?
                        (UINT_PTR *) &m_aEntries[m_cMaxEntries] :
                        NULL;
    }
}


BOOL CList::Expand(void)
{
    if (NULL == m_aEntries)
    {
        // it is impossible.
        ASSERT(FALSE);
        return Init(m_cSubItems);
    }

    // the current array is full
    ASSERT(m_cEntries == m_cMaxEntries);

    // remember the old array to free or to restore
    LPVOID  *aOldEntries = m_aEntries;

    // we need to allocate a bigger array to hold more data.
    // the new array has twice the size of the old one
    ULONG cNewMaxEntries = m_cMaxEntries << 1;
    DBG_SAVE_FILE_LINE
    m_aEntries = (LPVOID *) new char[cNewMaxEntries * m_cSubItems * sizeof(LPVOID)];
    if (NULL == m_aEntries)
    {
        // we failed; we have to restore the array and return
        m_aEntries = aOldEntries;
        return FALSE;
    }

    // copy the old entries into the new array, starting from the beginning
    ULONG nIdx = m_cMaxEntries - m_nHeadOffset;
    ::CopyMemory(m_aEntries, &aOldEntries[m_nHeadOffset], nIdx * sizeof(LPVOID));
    ::CopyMemory(&m_aEntries[nIdx], aOldEntries, m_nHeadOffset * sizeof(LPVOID));

    // set the new max entries (required for the key array)
    m_cMaxEntries = cNewMaxEntries;

    if (m_cSubItems > 1)
    {
        ASSERT(2 == m_cSubItems);
        UINT_PTR *aOldKeys = m_aKeys;
        CalcKeyArray();
        ::CopyMemory(m_aKeys, &aOldKeys[m_nHeadOffset], nIdx * sizeof(UINT));
        ::CopyMemory(&m_aKeys[nIdx], aOldKeys, m_nHeadOffset * sizeof(UINT));
    }

    // Free the old array of entries
    delete aOldEntries;

    // Set the instance variables
    m_nHeadOffset = 0;
    m_nCurrOffset = CLIST_END_OF_ARRAY_MARK;

    return TRUE;
}


BOOL CList::Append(LPVOID pData)
{
    if (NULL == m_aEntries || m_cEntries >= m_cMaxEntries)
    {
        if (! Expand())
        {
            return FALSE;
        }
    }

    ASSERT(NULL != m_aEntries);
    ASSERT(m_cEntries < m_cMaxEntries);

    m_aEntries[(m_nHeadOffset + (m_cEntries++)) % m_cMaxEntries] = pData;

    return TRUE;
}


BOOL CList::Prepend(LPVOID pData)
{
    if (NULL == m_aEntries || m_cEntries >= m_cMaxEntries)
    {
        if (! Expand())
        {
            return FALSE;
        }
    }

    ASSERT(NULL != m_aEntries);
    ASSERT(m_cEntries < m_cMaxEntries);

    m_cEntries++;
    m_nHeadOffset = (0 == m_nHeadOffset) ? m_cMaxEntries - 1 : m_nHeadOffset - 1;
    m_aEntries[m_nHeadOffset] = pData;

    return TRUE;
}


BOOL CList::Find(LPVOID pData)
{
    for (ULONG i = 0; i < m_cEntries; i++)
    {
        if (pData == m_aEntries[(m_nHeadOffset + i) % m_cMaxEntries])
        {
            return TRUE;
        }
    }
    return FALSE;
}


BOOL CList::Remove(LPVOID pData)
{
    ULONG nIdx, nIdxSrc;
    for (ULONG i = 0; i < m_cEntries; i++)
    {
        nIdx = (m_nHeadOffset + i) % m_cMaxEntries;
        if (pData == m_aEntries[nIdx])
        {
            if (! m_fQueue)
            {
                // to remove the current, we simply move the last to here.
                nIdxSrc = (m_nHeadOffset + (m_cEntries - 1)) % m_cMaxEntries;
                m_aEntries[nIdx] = m_aEntries[nIdxSrc];
                if (m_cSubItems > 1)
                {
                    ASSERT(2 == m_cSubItems);
                    m_aKeys[nIdx] = m_aKeys[nIdxSrc];
                }
            }
            else
            {
                // to preserve the ordering
                if (0 == i)
                {
                    m_nHeadOffset = (m_nHeadOffset + 1) % m_cMaxEntries;
                }
                else
                {
                    for (ULONG j = i + 1; j < m_cEntries; j++)
                    {
                        nIdx = (m_nHeadOffset + j - 1) % m_cMaxEntries;
                        nIdxSrc = (m_nHeadOffset + j) % m_cMaxEntries;
                        m_aEntries[nIdx] = m_aEntries[nIdxSrc];
                        if (m_cSubItems > 1)
                        {
                            ASSERT(2 == m_cSubItems);
                            m_aKeys[nIdx] = m_aKeys[nIdxSrc];
                        }
                    }
                }
            }

            m_cEntries--;
            return TRUE;
        }
    }
    return FALSE;
}


LPVOID CList::Get(void)
{
    LPVOID pRet = NULL;

    if (m_cEntries > 0)
    {
        pRet = m_aEntries[m_nHeadOffset];
        m_cEntries--;
        m_nHeadOffset = (m_nHeadOffset + 1) % m_cMaxEntries;
    }
    else
    {
        pRet = NULL;
    }
    return pRet;
}


LPVOID CList::Iterate(void)
{
    if (0 == m_cEntries)
    {
        return NULL;
    }

    if (m_nCurrOffset == CLIST_END_OF_ARRAY_MARK)
    {
        // start from the beginning
        m_nCurrOffset = 0;
    }
    else
    {
        if (++m_nCurrOffset >= m_cEntries)
        {
            // reset the iterator
            m_nCurrOffset = CLIST_END_OF_ARRAY_MARK;
            return NULL;
        }
    }

    return m_aEntries[(m_nHeadOffset + m_nCurrOffset) % m_cMaxEntries];
}





CList2::CList2(CList2 *pSrc)
:
    CList(pSrc->GetCount(), 2, pSrc->m_fQueue)
{
    CalcKeyArray();

    LPVOID p;
    UINT_PTR n;
    pSrc->Reset();
    while (NULL != (p = pSrc->Iterate(&n)))
    {
        Append(n, p);
    }
}


BOOL CList2::Append(UINT_PTR nKey, LPVOID pData)
{
    if (! CList::Append(pData))
    {
        return FALSE;
    }

    // after CList::append(), m_cEntries has been incremented,
    // therefore, we need decrement it again.
    m_aKeys[(m_nHeadOffset + (m_cEntries - 1)) % m_cMaxEntries] = nKey;
    return TRUE;
}


BOOL CList2::Prepend(UINT_PTR nKey, LPVOID pData)
{
    if (! CList::Prepend(pData))
    {
        return FALSE;
    }

    m_aKeys[m_nHeadOffset] = nKey;
    return TRUE;
}


LPVOID CList2::Find(UINT_PTR nKey)
{
    ULONG nIdx;
    for (ULONG i = 0; i < m_cEntries; i++)
    {
        nIdx = (m_nHeadOffset + i) % m_cMaxEntries;
        if (nKey == m_aKeys[nIdx])
        {
            return m_aEntries[nIdx];
        }
    }
    return NULL;
}


LPVOID CList2::Remove(UINT_PTR nKey)
{
    ULONG nIdx, nIdxSrc;
    for (ULONG i = 0; i < m_cEntries; i++)
    {
        nIdx = (m_nHeadOffset + i) % m_cMaxEntries;
        if (nKey == m_aKeys[nIdx])
        {
            LPVOID pRet = m_aEntries[nIdx];
            if (! m_fQueue)
            {
                // to remove the current, we simply move the last to here.
                nIdxSrc = (m_nHeadOffset + (m_cEntries - 1)) % m_cMaxEntries;
                m_aEntries[nIdx] = m_aEntries[nIdxSrc];
                m_aKeys[nIdx] = m_aKeys[nIdxSrc];
            }
            else
            {
                // to preserve the ordering
                if (0 == i)
                {
                    m_nHeadOffset = (m_nHeadOffset + 1) % m_cMaxEntries;
                }
                else
                {
                    for (ULONG j = i + 1; j < m_cEntries; j++)
                    {
                        nIdx = (m_nHeadOffset + j - 1) % m_cMaxEntries;
                        nIdxSrc = (m_nHeadOffset + j) % m_cMaxEntries;
                        m_aEntries[nIdx] = m_aEntries[nIdxSrc];
                        m_aKeys[nIdx] = m_aKeys[nIdxSrc];
                    }
                }
            }

            m_cEntries--;
            return pRet;
        }
    }
    return NULL;
}


LPVOID CList2::Get(UINT_PTR *pnKey)
{
    LPVOID pRet;
    if (m_cEntries > 0)
    {
        pRet = m_aEntries[m_nHeadOffset];
        *pnKey = m_aKeys[m_nHeadOffset];
        m_cEntries--;
        m_nHeadOffset = (m_nHeadOffset + 1) % m_cMaxEntries;
    }
    else
    {
        pRet = NULL;
        *pnKey = 0;
    }
    return pRet;
}


LPVOID CList2::PeekHead(UINT_PTR *pnKey)
{
    LPVOID pRet;
    if (m_cEntries > 0)
    {
        pRet = m_aEntries[m_nHeadOffset];
        *pnKey = m_aKeys[m_nHeadOffset];
    }
    else
    {
        pRet = NULL;
        *pnKey = 0;
    }
    return pRet;
}


LPVOID CList2::Iterate(UINT_PTR *pnKey)
{
    LPVOID p = CList::Iterate();
    *pnKey = (NULL != p) ? m_aKeys[(m_nHeadOffset + m_nCurrOffset) % m_cMaxEntries] : 0;
    return p;
}





#ifdef ENABLE_HASHED_LIST2

CHashedList2::CHashedList2(ULONG cBuckets, ULONG cInitItemsPerBucket)
:
    m_cBuckets(cBuckets),
    m_cInitItemsPerBucket(cInitItemsPerBucket),
    m_cEntries(0),
    m_nCurrBucket(0)
{
    m_aBuckets = new CList2* [m_cBuckets];
    ASSERT(NULL != m_aBuckets);
    if (NULL != m_aBuckets)
    {
        ::ZeroMemory(m_aBuckets, m_cBuckets * sizeof(CList2*));
    }
}


CHashedList2::CHashedList2(CHashedList2 *pSrc)
:
    m_cBuckets(pSrc->m_cBuckets),
    m_cInitItemsPerBucket(pSrc->m_cInitItemsPerBucket),
    m_cEntries(0),
    m_nCurrBucket(0)
{
    LPVOID p;
    UINT n;

    m_aBuckets = new CList2* [m_cBuckets];
    ASSERT(NULL != m_aBuckets);
    if (NULL != m_aBuckets)
    {
        ::ZeroMemory(m_aBuckets, m_cBuckets * sizeof(CList2*));
        pSrc->Reset();
        while (NULL != (p = pSrc->Iterate(&n)))
        {
            Insert(n, p);
        }
    }
}


CHashedList2::~CHashedList2(void)
{
    if (NULL != m_aBuckets)
    {
        for (ULONG i = 0; i < m_cBuckets; i++)
        {
            delete m_aBuckets[i];
        }
        delete [] m_aBuckets;
    }
}


BOOL CHashedList2::Insert(UINT nKey, LPVOID pData)
{
    if (NULL != m_aBuckets)
    {
        ULONG nBucket = GetHashValue(nKey);
        if (NULL == m_aBuckets[nBucket])
        {
            m_aBuckets[nBucket] = new CList2(m_cInitItemsPerBucket);
        }
        ASSERT(NULL != m_aBuckets[nBucket]);
        if (NULL != m_aBuckets[nBucket])
        {
            if (m_aBuckets[nBucket]->Append(nKey, pData))
            {
                m_cEntries++;
                return TRUE;
            }
        }
    }
    return FALSE;
}


LPVOID CHashedList2::Find(UINT nKey)
{
    if (NULL != m_aBuckets)
    {
        ULONG nBucket = GetHashValue(nKey);
        if (NULL != m_aBuckets[nBucket])
        {
            return m_aBuckets[nBucket]->Find(nKey);
        }
    }
    return NULL;
}


LPVOID CHashedList2::Remove(UINT nKey)
{
    if (NULL != m_aBuckets)
    {
        ULONG nBucket = GetHashValue(nKey);
        if (NULL != m_aBuckets[nBucket])
        {
            LPVOID pRet = m_aBuckets[nBucket]->Remove(nKey);
            if (NULL != pRet)
            {
                m_cEntries--;
                return pRet;
            }
        }
    }
    return NULL;
}


LPVOID CHashedList2::Get(UINT *pnKey)
{
    if (NULL != m_aBuckets)
    {
        if (m_cEntries)
        {
            for (ULONG i = 0; i < m_cBuckets; i++)
            {
                if (NULL != m_aBuckets[i])
                {
                    LPVOID pRet = m_aBuckets[i]->Get(pnKey);
                    if (NULL != pRet)
                    {
                        m_cEntries--;
                        return pRet;
                    }
                }
            }
        }
    }
    return NULL;
}


LPVOID CHashedList2::Iterate(UINT *pnKey)
{
    if (NULL != m_aBuckets)
    {
        for (ULONG i = m_nCurrBucket; i < m_cBuckets; i++)
        {
            if (NULL != m_aBuckets[i])
            {
                LPVOID pRet = m_aBuckets[i]->Iterate(pnKey);
                if (NULL != pRet)
                {
                    m_nCurrBucket = i;
                    return pRet;
                }
            }
        }
    }
    m_nCurrBucket = m_cBuckets; // over
    return NULL;
}


void CHashedList2::Reset(void)
{
    m_nCurrBucket = 0;

    if (NULL != m_aBuckets)
    {
        for (ULONG i = 0; i < m_cBuckets; i++)
        {
            if (NULL != m_aBuckets[i])
            {
                m_aBuckets[i]->Reset();
            }
        }
    }
}


void CHashedList2::Clear(void)
{
    m_cEntries = 0;
    m_nCurrBucket = 0;

    if (NULL != m_aBuckets)
    {
        for (ULONG i = 0; i < m_cBuckets; i++)
        {
            if (NULL != m_aBuckets[i])
            {
                m_aBuckets[i]->Clear();
            }
        }
    }
};


ULONG CHashedList2::GetHashValue(UINT nKey)
{
    const UINT PRIME_NUMBER_1 = 89;
    const UINT PRIME_NUMBER_2 = 13;
    return ((ULONG) (nKey * PRIME_NUMBER_1 + PRIME_NUMBER_2) % m_cBuckets);
}

#endif // ENABLE_HASHED_LIST2