#include "pch.h"
#pragma hdrstop
#include "modset.h"
#include "modtree.h"
#include "ncstl.h"

struct GMBCONTEXT
{
    // The tree to reference for generating the set.
    //
    IN const CModuleTree*   pTree;

    // The module to start with when generating the set.
    //
    IN const CModule*       pSourceMod;

    // INS_FLAGS to use when adding DepChain to the set.
    //
    IN DWORD                dwFlags;

    // The module list set to generate based on pSourceMod.
    //
    IN OUT CModuleListSet*  pSet;

    // The result of the operation.
    //
    OUT HRESULT             hr;

    // This module list is built up via recursion.  It is
    // temporary.  It represents a depenedency chain sourced
    // at pSourceMod.  It is added to the set when the depth
    // of the chain (or a circular reference) is detected.
    //
    CModuleList             DepChain;
};

VOID
GetModuleBindings (
    IN const CModule* pMod,
    IN OUT GMBCONTEXT* pCtx)
{
    BOOL fFoundOne = FALSE;
    const CModuleTreeEntry* pScan;

    Assert (pCtx);
    Assert (pCtx->pSourceMod);
    Assert (pCtx->pSet);
    Assert (pCtx->pTree);

    // Append this module to te end of the context's working
    // dependency chain.
    //
    pCtx->hr = pCtx->DepChain.HrInsertModule (pMod,
                                INS_ASSERT_IF_DUP | INS_APPEND);
    if (S_OK != pCtx->hr)
    {
        return;
    }

    // For all rows in the tree where the module is the one passed in...
    //
    for (pScan  = pCtx->pTree->PFindFirstEntryWithModule (pMod);
         (pScan != pCtx->pTree->end()) && (pScan->m_pModule == pMod);
         pScan++)
    {
        fFoundOne = TRUE;

        // Detect circular import chains.
        //
        if (pCtx->DepChain.FLinearFindModuleByPointer (pScan->m_pImportModule))
        {
            pCtx->DepChain.m_fCircular = TRUE;
            continue;
        }

        GetModuleBindings (pScan->m_pImportModule, pCtx);
        if (S_OK != pCtx->hr)
        {
            return;
        }
    }

    // If we didnt find any rows with pMod as a module, it means we
    // hit the depth of the dependency chain.  Time to add it to the set
    // unless this is the original module we were asked to find the
    // set for.
    //
    if (!fFoundOne && (pMod != pCtx->pSourceMod))
    {

    CHAR pszBuf [4096];
    ULONG cch = celems(pszBuf);
    pCtx->DepChain.FDumpToString (pszBuf, &cch);
    strcat(pszBuf, "\n");
    printf(pszBuf);

        pCtx->hr = pCtx->pSet->HrAddModuleList (&pCtx->DepChain,
                        INS_APPEND | pCtx->dwFlags);
    }

    const CModule* pRemoved;

    pRemoved = pCtx->DepChain.RemoveLastModule();

    // This should be the component we appened above.
    //
    Assert (pRemoved == pMod);
}


CModuleTree::~CModuleTree ()
{
    FreeCollectionAndItem (Modules);
}

HRESULT
CModuleTree::HrAddEntry (
    IN CModule* pMod,
    IN CModule* pImport,
    IN DWORD dwFlags)
{
    HRESULT hr;
    iterator InsertPosition = NULL;
    CModuleTreeEntry* pEntry;

    Assert (pMod);
    Assert (pImport);

    if (size() == capacity())
    {
        //fprintf(stderr, "growing module tree buffer\n");

        __try
        {
            reserve (size() + 16384);
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            return E_OUTOFMEMORY;
        }
    }

    hr = S_OK;
    //pEntry = PFindFirstEntryAfterModuleGroup (pMod);

    pEntry = PBinarySearchEntryByModule (pMod, &InsertPosition);

    if (pEntry != end())
    {
        Assert (pEntry);

        CModuleTreeEntry* pScan;

        // Found an entry with a matching module.  Need to scan backwards
        // in the module group looking for a duplicate.  If not found,
        // Scan to the end looking for a duplicate and if we reach the
        // end of the group, we can insert this entry there.
        //
        pScan = pEntry;

        while (pScan != begin())
        {
            pScan--;

            if (pScan->m_pModule != pMod)
            {
                // Left the group without finding a dupliate.
                //
                break;
            }

            if (pScan->m_pImportModule == pImport)
            {
                // Don't insert duplicate entries.
                //
                return S_OK;
            }
        }

        Assert (pMod == pEntry->m_pModule);
        while (pEntry != end() && pEntry->m_pModule == pMod)
        {
            // Don't insert duplicate entries.
            //
            if (pEntry->m_pImportModule == pImport)
            {
                return S_OK;
            }
            pEntry++;
        }

        // Looks like we'll be inserting it.
        //
        InsertPosition = pEntry;
    }
    else
    {
        // InsertPosition is the correct insertion point.
        //
        Assert (InsertPosition);
    }

    __try
    {
        CModuleTreeEntry Entry;

        Entry.m_pModule = pMod;
        Entry.m_pImportModule = pImport;
        Entry.m_dwFlags = dwFlags;

        Assert (InsertPosition);
        insert (InsertPosition, Entry);
        Assert (S_OK == hr);

        DbgVerifySorted();
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}

HRESULT
CModuleTree::HrGetModuleBindings (
    IN const CModule* pMod,
    IN DWORD dwFlags /* GMB_FLAGS */,
    OUT CModuleListSet* pSet) const
{
    GMBCONTEXT Ctx;

    Assert (pMod);
    Assert (dwFlags);
    Assert (pSet);

    // Initialize the output parameter.
    //
    if (!(dwFlags & GMBF_ADD_TO_MLSET))
    {
        pSet->clear();
    }

    // Initialize members of the context structure for recursion.
    //
    ZeroMemory (&Ctx, sizeof(Ctx));
    Ctx.pTree = this;
    Ctx.pSourceMod = pMod;
    Ctx.dwFlags = (dwFlags & GMBF_ADD_TO_MLSET)
                    ? INS_IGNORE_IF_DUP
                    : INS_ASSERT_IF_DUP;
    Ctx.pSet = pSet;

    GetModuleBindings (pMod, &Ctx);

    return Ctx.hr;
}


CModuleTreeEntry*
CModuleTree::PBinarySearchEntryByModule (
    IN const CModule* pMod,
    OUT CModuleTreeEntry** pInsertPosition OPTIONAL) const
{
    Assert (pMod);

    // Find the module using a binary search.
    //
    if (size())
    {
        LONG Lo;
        LONG Hi;
        LONG Mid;
        INT Result;
        const CModuleTreeEntry* pScan;
        PCSTR pszFileName = pMod->m_pszFileName;

        Lo = 0;
        Hi = size() - 1;

        while (Hi >= Lo)
        {
            Mid = (Lo + Hi) / 2;

            Assert ((UINT)Mid < size());
            pScan = (begin() + Mid);

            Result = strcmp (pszFileName, pScan->m_pModule->m_pszFileName);

            if (Result < 0)
            {
                Hi = Mid - 1;
            }
            else if (Result > 0)
            {
                Lo = Mid + 1;
            }
            else
            {
                Assert (pMod == pScan->m_pModule);
                return const_cast<CModuleTreeEntry*>(pScan);
            }
        }

        // If we make it to here, the module was not found.
        //
        if (pInsertPosition)
        {
            CModule* pGroupMod;
            const CModuleTreeEntry* pPrev;

            // Seek to the beginning of this group.  We need to insert
            // before the entire group, not just the one item we last found.
            //
            pScan = begin() + Lo;

            if (pScan != begin())
            {
                pGroupMod = pScan->m_pModule;

                do
                {
                    pPrev = pScan - 1;

                    if (pPrev->m_pModule == pGroupMod)
                    {
                        pScan = pPrev;
                    }
                    else
                    {
                        break;
                    }

                } while (pPrev != begin());
            }

            *pInsertPosition = const_cast<CModuleTreeEntry*>(pScan);
            Assert (*pInsertPosition >= begin());
            Assert (*pInsertPosition <= end());
        }
    }
    else if (pInsertPosition)
    {
        // Empty collection.  Insert position is at the beginning.
        //
        *pInsertPosition = const_cast<CModuleTreeEntry*>(begin());
    }

    return const_cast<CModuleTreeEntry*>(end());
}

CModuleTreeEntry*
CModuleTree::PFindFirstEntryWithModule (
    IN const CModule* pMod) const
{
    CModuleTreeEntry* pEntry;

    Assert (pMod);

    pEntry = PBinarySearchEntryByModule (pMod, NULL);

    if (pEntry != end())
    {
        Assert (pEntry);

        if (pEntry != begin())
        {
            CModuleTreeEntry* pPrev;

            Assert (pMod == pEntry->m_pModule);

            while (1)
            {
                pPrev = pEntry - 1;

                if (pPrev->m_pModule == pMod)
                {
                    pEntry = pPrev;
                }
                else
                {
                    break;
                }

                if (pPrev == begin())
                {
                    break;
                }
            }
        }
    }

    return pEntry;
}

CModuleTreeEntry*
CModuleTree::PFindFirstEntryAfterModuleGroup (
    IN const CModule* pMod) const
{
    CModuleTreeEntry* pEntry;

    Assert (pMod);

    pEntry = PBinarySearchEntryByModule (pMod, NULL);

    if (pEntry != end())
    {
        Assert (pEntry);
        Assert (pMod == pEntry->m_pModule);

        while (pEntry != end() && pEntry->m_pModule == pMod)
        {
            pEntry++;
        }
    }

    return pEntry;
}

CModuleTreeEntry*
CModuleTree::PBinarySearchEntry (
    IN const CModule* pMod,
    IN const CModule* pImport,
    OUT CModuleTreeEntry** pInsertPosition OPTIONAL) const
{
    CModuleTreeEntry* pEntry;

    Assert (this);
    Assert (pMod);
    Assert (pImport);

    pEntry = PBinarySearchEntryByModule (pMod, pInsertPosition);

    if (pEntry != end())
    {
        Assert (pEntry);

        const CModuleTreeEntry* pScan;

        // Found an entry with a matching module.  Need to scan backwards
        // in the module group looking for a match.  If not found,
        // Scan to the end looking for a match and if we reach the
        // end of the group, that will be the insert position (if specified).
        //
        pScan = pEntry;
        while (pScan != begin())
        {
            pScan--;

            if (pScan->m_pModule != pMod)
            {
                // Left the group without finding a dupliate.
                //
                break;
            }

            if (pScan->m_pImportModule == pImport)
            {
                Assert (pScan->m_pModule == pMod);
                return const_cast<CModuleTreeEntry*>(pScan);
            }
        }

        pScan = pEntry;
        Assert (pMod == pScan->m_pModule);
        while (pScan != end() && pScan->m_pModule == pMod)
        {
            if (pScan->m_pImportModule == pImport)
            {
                Assert (pScan->m_pModule == pMod);
                return const_cast<CModuleTreeEntry*>(pScan);
            }

            pScan++;
        }

        if (pInsertPosition)
        {
            *pInsertPosition = const_cast<CModuleTreeEntry*>(pScan);
        }

        // No match.
        pEntry = const_cast<CModuleTreeEntry*>(end());
    }

    return pEntry;
}

#if DBG
VOID
CModuleTree::DbgVerifySorted ()
{
    CModuleTreeEntry* pScan;
    CModuleTreeEntry* pPrev = NULL;

    if (size() > 1)
    {
        for (pPrev = begin(), pScan = begin() + 1; pScan != end(); pScan++)
        {
            Assert (strcmp(pPrev->m_pModule->m_pszFileName,
                           pScan->m_pModule->m_pszFileName) <= 0);

            pPrev = pScan;
        }
    }
}
#endif