//----------------------------------------------------------------------------
//
// cppdbg.cpp
//
// C++-only debugging support.
//
// Copyright (C) Microsoft Corporation, 1997.
//
//----------------------------------------------------------------------------

#include "pch.cpp"
#pragma hdrstop

#if DBG

#include "cppdbg.hpp"

#ifdef _ALPHA_
// On Alpha va_list is a structure so it's not compatible with NULL.
static va_list NULLVA;
#else
#define NULLVA NULL
#endif

static DebugModuleFlags g_FailureFlags[] =
{
    DBG_DECLARE_MODFLAG(DBG_FAILURE, BREAK),
    DBG_DECLARE_MODFLAG(DBG_FAILURE, OUTPUT),
    DBG_DECLARE_MODFLAG(DBG_FAILURE, PROMPT),
    DBG_DECLARE_MODFLAG(DBG_FAILURE, FILENAME_ONLY),
    0, NULL,
};

static DebugModuleFlags g_OutputFlags[] =
{
    DBG_DECLARE_MODFLAG(DBG_OUTPUT, SUPPRESS),
    DBG_DECLARE_MODFLAG(DBG_OUTPUT, ALL_MATCH),
    0, NULL,
};

static char *g_pFlagNames[] =
{
    "AssertFlags",
    "HrFlags",
    "OutputFlags",
    "OutputMask",
    "UserFlags"
};

//----------------------------------------------------------------------------
//
// DebugModule::DebugModule
//
//----------------------------------------------------------------------------

DebugModule::DebugModule(char *pModule, char *pPrefix,
                         DebugModuleFlags *pOutputMasks, UINT uOutputMask,
                         DebugModuleFlags *pUserFlags, UINT uUserFlags)
{
    m_pModule = pModule;
    m_iModuleStartCol = strlen(m_pModule) + 2;
    m_pPrefix = pPrefix;

    m_pModFlags[DBG_ASSERT_FLAGS] = g_FailureFlags;
    m_pModFlags[DBG_HR_FLAGS] = g_FailureFlags;
    m_pModFlags[DBG_OUTPUT_FLAGS] = g_OutputFlags;
    m_pModFlags[DBG_OUTPUT_MASK] = pOutputMasks;
    m_pModFlags[DBG_USER_FLAGS] = pUserFlags;

    m_uFlags[DBG_ASSERT_FLAGS] = DBG_FAILURE_OUTPUT | DBG_FAILURE_BREAK |
        DBG_FAILURE_FILENAME_ONLY;
    m_uFlags[DBG_HR_FLAGS] = DBG_FAILURE_OUTPUT |
        DBG_FAILURE_FILENAME_ONLY;
    m_uFlags[DBG_OUTPUT_FLAGS] = 0;
    m_uFlags[DBG_OUTPUT_MASK] = uOutputMask;
    m_uFlags[DBG_USER_FLAGS] = uUserFlags;

    ReadReg();
}

//----------------------------------------------------------------------------
//
// DebugModule::OutVa
//
// Base debug output method.
//
//----------------------------------------------------------------------------

void DebugModule::OutVa(UINT uMask, char *pFmt, va_list Args)
{
    if (m_uFlags[DBG_OUTPUT_FLAGS] & DBG_OUTPUT_SUPPRESS)
    {
        return;
    }

    if ((uMask & DBG_MASK_NO_PREFIX) == 0)
    {
        OutputDebugStringA(m_pModule);
        OutputDebugStringA(": ");
    }

    char chMsg[1024];

    _vsnprintf(chMsg, sizeof(chMsg), pFmt, Args);
    OutputDebugStringA(chMsg);
}

//----------------------------------------------------------------------------
//
// DebugModule::Out
//
// Always-output debug output method.
//
//----------------------------------------------------------------------------

void DebugModule::Out(char *pFmt, ...)
{
    va_list Args;

    va_start(Args, pFmt);
    OutVa(0, pFmt, Args);
    va_end(Args);
}

//----------------------------------------------------------------------------
//
// DebugModule::AssertFailedVa
//
// Handles assertion failure output and interface.
//
//----------------------------------------------------------------------------

void DebugModule::AssertFailedVa(char *pFmt, va_list Args, BOOL bNewLine)
{
    if (m_uFlags[DBG_ASSERT_FLAGS] & DBG_FAILURE_OUTPUT)
    {
        if (OutPathFile("Assertion failed", m_uFlags[DBG_ASSERT_FLAGS]))
        {
            OutVa(DBG_MASK_NO_PREFIX, ":\n    ", NULLVA);
        }
        else
        {
            OutVa(DBG_MASK_NO_PREFIX, ": ", NULLVA);
        }

        OutVa(DBG_MASK_NO_PREFIX, pFmt, Args);
        if (bNewLine)
        {
            OutVa(DBG_MASK_NO_PREFIX, "\n", NULLVA);
        }
    }

    if (m_uFlags[DBG_ASSERT_FLAGS] & DBG_FAILURE_BREAK)
    {
        DebugBreak();
    }
    else if (m_uFlags[DBG_ASSERT_FLAGS] & DBG_FAILURE_PROMPT)
    {
        Prompt(NULL);
    }
}

//----------------------------------------------------------------------------
//
// DebugModule::AssertFailed
//
// Handles simple expression assertion failures.
//
//----------------------------------------------------------------------------

void DebugModule::AssertFailed(char *pExp)
{
    AssertFailedVa(pExp, NULLVA, TRUE);
}

//----------------------------------------------------------------------------
//
// DebugModule::AssertFailedMsg
//
// Handles assertion failures with arbitrary debug output.
//
//----------------------------------------------------------------------------

void DebugModule::AssertFailedMsg(char *pFmt, ...)
{
    va_list Args;

    va_start(Args, pFmt);
    AssertFailedVa(pFmt, Args, FALSE);
    va_end(Args);
}

//----------------------------------------------------------------------------
//
// DebugModule::HrFailure
//
// Handles HRESULT failures.
//
//----------------------------------------------------------------------------

void DebugModule::HrFailure(HRESULT hr, char *pPrefix)
{
    if (m_uFlags[DBG_HR_FLAGS] & DBG_FAILURE_OUTPUT)
    {
        OutPathFile(pPrefix, m_uFlags[DBG_HR_FLAGS]);
        OutMask(DBG_MASK_FORCE_CONT, ": %s\n", HrString(hr));
    }

    if (m_uFlags[DBG_HR_FLAGS] & DBG_FAILURE_BREAK)
    {
        DebugBreak();
    }
    else if (m_uFlags[DBG_HR_FLAGS] & DBG_FAILURE_PROMPT)
    {
        Prompt(NULL);
    }
}

//----------------------------------------------------------------------------
//
// DebugModule::HrStmtFailed
//
// Handles statement-style HRESULT failures.
//
//----------------------------------------------------------------------------

void DebugModule::HrStmtFailed(HRESULT hr)
{
    HrFailure(hr, "HR test fail");
}

//----------------------------------------------------------------------------
//
// DebugModule::ReturnHr
//
// Handles expression-style HRESULT failures.
//
//----------------------------------------------------------------------------

HRESULT DebugModule::HrExpFailed(HRESULT hr)
{
    HrFailure(hr, "HR expr fail");
    return hr;
}

//----------------------------------------------------------------------------
//
// DebugModule::Prompt
//
// Allows control over debug options via interactive input.
//
//----------------------------------------------------------------------------

void DebugModule::Prompt(char *pFmt, ...)
{
    va_list Args;

    if (pFmt != NULL)
    {
        va_start(Args, pFmt);
        OutVa(0, pFmt, Args);
        va_end(Args);
    }

#if 0   // ndef WIN95
    // This is DEADCODE, that is can be potentially used on NT ONLY to
    // bring up a debugging prompt. It requires linking with NTDLL.LIB
    char szInput[512];
    char *pIdx;
    int iIdx;
    static char szFlagCommands[] = "ahomu";

    for (;;)
    {
        ULONG uLen;

        uLen = DbgPrompt("[bgaAFhHmMoOrRuU] ", szInput, sizeof(szInput) - 1);
        if (uLen < 2)
        {
            Out("DbgPrompt failed\n");
            DebugBreak();
            return;
        }

        // ATTENTION - Currently DbgPrompt returns a length that is two
        // greater than the actual number of characters.  Presumably this
        // is an artifact of the Unicode/ANSI conversion and should
        // really only be one greater, so attempt to handle both.

        uLen -= 2;
        if (szInput[uLen] != 0)
        {
            uLen++;
            szInput[uLen] = 0;
        }

        if (uLen < 1)
        {
            Out("Empty command ignored\n");
            continue;
        }

        switch(szInput[0])
        {
        case 'b':
            DebugBreak();
            break;
        case 'g':
            return;

        case 'r':
            WriteReg();
            break;
        case 'R':
            ReadReg();
            break;

        case 'a':
        case 'A':
        case 'h':
        case 'H':
        case 'm':
        case 'M':
        case 'o':
        case 'O':
        case 'u':
        case 'U':
            char chLower;

            if (szInput[0] >= 'A' && szInput[0] <= 'Z')
            {
                chLower = szInput[0] - 'A' + 'a';
            }
            else
            {
                chLower = szInput[0];
            }

            pIdx = strchr(szFlagCommands, chLower);
            if (pIdx == NULL)
            {
                // Should never happen.
                break;
            }

            iIdx = (int)((ULONG_PTR)(pIdx - szFlagCommands));
            if (szInput[0] == chLower)
            {
                // Set.
                m_uFlags[iIdx] = ParseUint(szInput + 1, m_pModFlags[iIdx]);
            }

            // Set or Get.
            OutUint(g_pFlagNames[iIdx], m_pModFlags[iIdx], m_uFlags[iIdx]);
            break;

        case 'F':
            if (uLen < 2)
            {
                Out("'F' must be followed by a flag group specifier\n");
                break;
            }

            pIdx = strchr(szFlagCommands, szInput[1]);
            if (pIdx == NULL)
            {
                Out("Unknown flag group '%c'\n", szInput[1]);
            }
            else
            {
                iIdx = (int)((ULONG_PTR)(pIdx - szFlagCommands));
                ShowFlags(g_pFlagNames[iIdx], m_pModFlags[iIdx]);
            }
            break;

        default:
            Out("Unknown command '%c'\n", szInput[0]);
            break;
        }
    }
#else
    OutUint("OutputMask", m_pModFlags[DBG_OUTPUT_MASK],
            m_uFlags[DBG_OUTPUT_MASK]);
    Out("Prompt not available\n");
    DebugBreak();
#endif
}

//----------------------------------------------------------------------------
//
// DebugModule::OpenDebugKey
//
// Opens the Direct3D\Debug\m_pModule key.
//
//----------------------------------------------------------------------------

HKEY DebugModule::OpenDebugKey(void)
{
    HKEY hKey;
    char szKeyName[128];

    strcpy(szKeyName, "Software\\Microsoft\\Direct3D\\Debug\\");
    strcat(szKeyName, m_pModule);
    if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, szKeyName, 0, KEY_ALL_ACCESS,
                      &hKey) != ERROR_SUCCESS)
    {
        return NULL;
    }
    else
    {
        return hKey;
    }
}

//----------------------------------------------------------------------------
//
// DebugModule::GetRegUint
//
// Gets a UINT value from the given key.
//
//----------------------------------------------------------------------------

UINT DebugModule::GetRegUint(HKEY hKey, char *pValue, UINT uDefault)
{
    DWORD dwType, dwSize;
    DWORD dwVal;

    dwSize = sizeof(dwVal);
    if (RegQueryValueExA(hKey, pValue, NULL, &dwType, (BYTE *)&dwVal,
                         &dwSize) != ERROR_SUCCESS ||
        dwType != REG_DWORD)
    {
        return uDefault;
    }
    else
    {
        return (UINT)dwVal;
    }
}

//----------------------------------------------------------------------------
//
// DebugModule::SetRegUint
//
// Sets a UINT value for the given key.
//
//----------------------------------------------------------------------------

BOOL DebugModule::SetRegUint(HKEY hKey, char *pValue, UINT uValue)
{
    return RegSetValueExA(hKey, pValue, NULL, REG_DWORD, (BYTE *)&uValue,
                          sizeof(uValue)) == ERROR_SUCCESS;
}

//----------------------------------------------------------------------------
//
// DebugModule::ReadReg
//
// Reads settings from the registry.
//
//----------------------------------------------------------------------------

void DebugModule::ReadReg(void)
{
    HKEY hKey;

    hKey = OpenDebugKey();
    if (hKey != NULL)
    {
        int iIdx;

        for (iIdx = 0; iIdx < DBG_FLAGS_COUNT; iIdx++)
        {
            m_uFlags[iIdx] = GetRegUint(hKey, g_pFlagNames[iIdx],
                                        m_uFlags[iIdx]);
        }
        RegCloseKey(hKey);
    }
}

//----------------------------------------------------------------------------
//
// DebugModule::WriteReg
//
// Writes values to the registry.
//
//----------------------------------------------------------------------------

void DebugModule::WriteReg(void)
{
    HKEY hKey;

    hKey = OpenDebugKey();
    if (hKey != NULL)
    {
        int iIdx;

        for (iIdx = 0; iIdx < DBG_FLAGS_COUNT; iIdx++)
        {
            if (!SetRegUint(hKey, g_pFlagNames[iIdx], m_uFlags[iIdx]))
            {
                OutputDebugStringA("Error writing registry information\n");
            }
        }
        RegCloseKey(hKey);
    }
}

//----------------------------------------------------------------------------
//
// DebugModule::ParseUint
//
// Parses a string for a numeric value or a set of flag strings.
//
//----------------------------------------------------------------------------

UINT DebugModule::ParseUint(char *pString, DebugModuleFlags *pFlags)
{
    UINT uVal;

    uVal = 0;

    for (;;)
    {
        while (*pString != 0 &&
               (*pString == ' ' || *pString == '\t'))
        {
            pString++;
        }

        if (*pString == 0)
        {
            break;
        }

        char *pEnd;
        int iStepAfter;

        pEnd = pString;
        while (*pEnd != 0 && *pEnd != ' ' && *pEnd != '\t')
        {
            pEnd++;
        }
        iStepAfter = *pEnd != 0 ? 1 : 0;
        *pEnd = 0;

        if (*pString >= '0' && *pString <= '9')
        {
            uVal |= strtoul(pString, &pString, 0);
            if (*pString != 0 && *pString != ' ' && *pString != '\t')
            {
                Out("Unrecognized characters '%s' after number\n", pString);
            }
        }
        else if (pFlags != NULL)
        {
            DebugModuleFlags *pFlag;

            for (pFlag = pFlags; pFlag->uFlag != 0; pFlag++)
            {
                if (!_stricmp(pString, pFlag->pName))
                {
                    break;
                }
            }

            if (pFlag->uFlag == 0)
            {
                Out("Unrecognized flag string '%s'\n", pString);
            }
            else
            {
                uVal |= pFlag->uFlag;
            }
        }
        else
        {
            Out("No flag definitions, unable to convert '%s'\n", pString);
        }

        pString = pEnd + iStepAfter;
    }

    return uVal;
}

//----------------------------------------------------------------------------
//
// DebugModule::OutUint
//
// Displays a UINT as a set of flag strings.
//
//----------------------------------------------------------------------------

void DebugModule::OutUint(char *pName, DebugModuleFlags *pFlags, UINT uValue)
{
    if (pFlags == NULL || uValue == 0)
    {
        Out("%s: 0x%08X\n", pName, uValue);
        return;
    }

    Out("%s:", pName);
    m_iStartCol = m_iModuleStartCol + strlen(pName) + 1;
    m_iCol = m_iStartCol;

    while (uValue != 0)
    {
        DebugModuleFlags *pFlag;

        for (pFlag = pFlags; pFlag->uFlag != 0; pFlag++)
        {
            if ((pFlag->uFlag & uValue) == pFlag->uFlag)
            {
                AdvanceCols(strlen(pFlag->pName) + 1);
                OutMask(DBG_MASK_FORCE_CONT, " %s", pFlag->pName);
                uValue &= ~pFlag->uFlag;
                break;
            }
        }

        if (pFlag->uFlag == 0)
        {
            AdvanceCols(11);
            OutMask(DBG_MASK_FORCE_CONT, " 0x%X", uValue);
            uValue = 0;
        }
    }

    OutVa(DBG_MASK_NO_PREFIX, "\n", NULLVA);
}

//----------------------------------------------------------------------------
//
// DebugModule::AdvanceCols
//
// Determines if there's enough space on the current line for
// the given number of columns.  If not, a new line is started.
//
//----------------------------------------------------------------------------

void DebugModule::AdvanceCols(int iCols)
{
    static char szSpaces[] = "                                ";

    m_iCol += iCols;
    if (m_iCol >= 79)
    {
        int iSpace;

        OutVa(DBG_MASK_NO_PREFIX, "\n", NULLVA);
        // Force a prefix to be printed to start the line.
        Out("");

        m_iCol = m_iModuleStartCol;
        while (m_iCol < m_iStartCol)
        {
            iSpace = (int)min(sizeof(szSpaces) - 1, m_iStartCol - m_iCol);
            OutMask(DBG_MASK_FORCE_CONT, "%.*s", iSpace, szSpaces);
            m_iCol += iSpace;
        }
    }
}

//----------------------------------------------------------------------------
//
// DebugModule::ShowFlags
//
// Shows the given flag set.
//
//----------------------------------------------------------------------------

void DebugModule::ShowFlags(char *pName, DebugModuleFlags *pFlags)
{
    DebugModuleFlags *pFlag;

    Out("%s:\n", pName);
    if (pFlags == NULL)
    {
        Out("    None defined\n");
    }
    else
    {
        for (pFlag = pFlags; pFlag->uFlag != 0; pFlag++)
        {
            Out("    0x%08X - %s\n", pFlag->uFlag, pFlag->pName);
        }
    }
}

//----------------------------------------------------------------------------
//
// DebugModule::PathFile
//
// Returns the trailing filename component or NULL if the path is
// only a filename.
//
//----------------------------------------------------------------------------

char *DebugModule::PathFile(char *pPath)
{
    char *pFile, *pSlash, *pBack, *pColon;

    pBack = strrchr(pPath, '\\');
    pSlash = strrchr(pPath, '/');
    pColon = strrchr(pPath, ':');

    pFile = pBack;
    if (pSlash > pFile)
    {
        pFile = pSlash;
    }
    if (pColon > pFile)
    {
        pFile = pColon;
    }

    return pFile != NULL ? pFile + 1 : NULL;
}

//----------------------------------------------------------------------------
//
// DebugModule::OutPathFile
//
// Outputs the given string plus a path and filename.
// Returns whether the full path was output or not.
//
//----------------------------------------------------------------------------

BOOL DebugModule::OutPathFile(char *pPrefix, UINT uFailureFlags)
{
    char *pFile;

    if (uFailureFlags & DBG_FAILURE_FILENAME_ONLY)
    {
        pFile = PathFile(m_pFile);
    }
    else
    {
        pFile = NULL;
    }

    if (pFile == NULL)
    {
        Out("%s %s(%d)", pPrefix, m_pFile, m_iLine);
        return TRUE;
    }
    else
    {
        Out("%s <>\\%s(%d)", pPrefix, pFile, m_iLine);
        return FALSE;
    }
}

//----------------------------------------------------------------------------
//
// Global debug module.
//
//----------------------------------------------------------------------------

DBG_DECLARE_ONCE(Global, G, NULL, 0, NULL, 0);

#endif // #if DBG
