//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1997 - 1999
//
//  File:       debug.cpp
//
//  Provides printf style debug output
//
//--------------------------------------------------------------------------

#include "pch.h"
#include <stdio.h>
#include <comctrlp.h>
#pragma hdrstop


#ifdef DEBUG

DWORD g_dwTraceMask = 0;
DWORD g_tlsDebug = 0xffffffffL;

#define MAX_CALL_DEPTH  64
#define BUFFER_SIZE     2048


class CDebugStack
{
private:
    DWORD m_dwThreadID;
    LONG m_cDepth;
    struct
    {
        BOOL    fTracedYet;
        LPCTSTR pszFunctionName;
        DWORD   dwMask;
    }
    m_CallStack[MAX_CALL_DEPTH];
    TCHAR m_szStringBuffer[BUFFER_SIZE];

public:
    CDebugStack() : m_dwThreadID(GetCurrentThreadId()), m_cDepth(-1)
    { ZeroMemory(&m_CallStack, SIZEOF(m_CallStack)); }

public:
    void _Indent(LONG iDepth, LPCTSTR pszFormat, ...);
    void _vIndent(LONG iDepth, LPCTSTR pszFormat, va_list va);
    BOOL _TraceProlog(LONG iDepth, BOOL fForce);
    void _TraceEnter(DWORD dwMask, LPCTSTR pName);
    void _TraceLeave(void);
    void _Trace(BOOL bForce, LPCTSTR pszFormat, ...);
    void _vTrace(BOOL bForce, LPCTSTR pszFormat, va_list va);
    void _TraceGUID(LPCTSTR pPrefix, REFGUID rGUID);
    void _TraceAssert(int iLine, LPTSTR pFilename);
};
typedef CDebugStack *PDEBUGSTACK;

class CDebugStackHolder
{
private:
    HDPA m_hDebugStackList;
    CRITICAL_SECTION m_csStackList;

public:
    CDebugStackHolder() : m_hDebugStackList(NULL) { ExceptionPropagatingInitializeCriticalSection(&m_csStackList); }
    ~CDebugStackHolder();

public:
    void Add(PDEBUGSTACK pDebugStack);
    void Remove(PDEBUGSTACK pDebugStack);
};
typedef CDebugStackHolder *PDEBUGSTACKHOLDER;

PDEBUGSTACKHOLDER g_pStackHolder = NULL;


/*-----------------------------------------------------------------------------
/ _Indent
/ -------
/   Output to the debug stream indented by n columns.
/
/ In:
/   i = column to indent to.
/   pszFormat -> string to be indented
/
/ Out:
/   -
/----------------------------------------------------------------------------*/

void CDebugStack::_Indent(LONG iDepth, LPCTSTR pszFormat, ...)
{
    va_list va;

    va_start(va, pszFormat);
    _vIndent(iDepth, pszFormat, va);
    va_end(va);
}


void CDebugStack::_vIndent(LONG iDepth, LPCTSTR pszFormat, va_list va)
{
    m_szStringBuffer[0] = TEXT('\0');

    wsprintf(m_szStringBuffer, TEXT("%08x "), m_dwThreadID);

    iDepth = min(iDepth, MAX_CALL_DEPTH - 1);
    for ( ; iDepth > 0 ; iDepth-- )
        lstrcat(m_szStringBuffer, TEXT("  "));

    wvsprintf(m_szStringBuffer + lstrlen(m_szStringBuffer), pszFormat, va);
    lstrcat(m_szStringBuffer, TEXT("\n"));

    OutputDebugString(m_szStringBuffer);
}


/*-----------------------------------------------------------------------------
/ _TraceProlog
/ -------------
/   Handle the prolog to a prefix string, including outputting the
/   function name if we haven't already.
/
/ In:
/   iDepth = depth in the call stack
/   fForce = ignore flags
/
/ Out:
/   BOOL if trace output should be made
/----------------------------------------------------------------------------*/
BOOL CDebugStack::_TraceProlog(LONG iDepth, BOOL fForce)
{
    if ( iDepth < 0 || iDepth >= MAX_CALL_DEPTH )
        return FALSE;

    if  ( (g_dwTraceMask & m_CallStack[iDepth].dwMask) || fForce )
    {
        if ( !m_CallStack[iDepth].fTracedYet )
        {
            if ( iDepth > 0 )
                _TraceProlog(iDepth-1, TRUE);

            _Indent(iDepth, m_CallStack[iDepth].pszFunctionName);
            m_CallStack[iDepth].fTracedYet = TRUE;
        }

        return TRUE;
    }

    return FALSE;
}


/*-----------------------------------------------------------------------------
/ _TraceEnter
/ ------------
/   Set the debugging call stack up to indicate which function we are in.
/
/ In:
/   pName -> function name to be displayed in subsequent trace output.
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void CDebugStack::_TraceEnter(DWORD dwMask, LPCTSTR pName)
{
    m_cDepth++;

    if ( m_cDepth < MAX_CALL_DEPTH )
    {
        if ( !pName )    
            pName = TEXT("<no name>");         // no function name given

        m_CallStack[m_cDepth].fTracedYet = FALSE;
        m_CallStack[m_cDepth].pszFunctionName = pName;
        m_CallStack[m_cDepth].dwMask = dwMask;

        //if ( m_cDepth > 0 )
        //    _TraceProlog(m_cDepth-1, FALSE);
    }
}


/*-----------------------------------------------------------------------------
/ _TraceLeave
/ ------------
/   On exit from a function this will adjust the function stack pointer to 
/   point to our previous function.  If no trace output has been made then 
/   we will output the function name on a single line (to indicate we went somewhere).
/
/ In:
/    -
/ Out:
/   -
/----------------------------------------------------------------------------*/
void CDebugStack::_TraceLeave(void)
{
    //_TraceProlog(m_cDepth, FALSE);

    //if ( !m_cDepth && m_CallStack[0].fTracedYet )
    //    OutputDebugString(TEXT("\n"));
    
    m_cDepth = max(m_cDepth-1, -1);         // account for underflow
}


/*-----------------------------------------------------------------------------
/ _Trace
/ -------
/   Perform printf formatting to the debugging stream.  We indent the output
/   and stream the function name as required to give some indication of 
/   call stack depth.
/
/ In:
/   pszFormat -> printf style formatting string
/   ... = arguments as required for the formatting
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void CDebugStack::_Trace(BOOL bForce, LPCTSTR pszFormat, ...)
{
    va_list va;

    va_start(va, pszFormat);
    _vTrace(bForce, pszFormat, va);
    va_end(va);
}


void CDebugStack::_vTrace(BOOL bForce, LPCTSTR pszFormat, va_list va)
{
    if ( _TraceProlog(m_cDepth, bForce) || bForce )
        _vIndent(m_cDepth+1, pszFormat, va);
}


/*-----------------------------------------------------------------------------
/ _TraceGUID
/ -----------
/   Given a GUID output it into the debug string, first we try and map it
/   to a name (ie. IShellFolder), if that didn't work then we convert it
/   to its human readable form.
/
/ In:
/   pszPrefix -> prefix string
/   lpGuid -> guid to be streamed   
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
#ifdef UNICODE
#define MAP_GUID(x)     &x, TEXT(""L#x)
#else
#define MAP_GUID(x)     &x, TEXT(""#x)
#endif

#define MAP_GUID2(x,y)  MAP_GUID(x), MAP_GUID(y)

const struct 
{
    const GUID* m_pGUID;
    LPCTSTR     m_pName;
}
_guid_map[] = 
{
    MAP_GUID(IID_IUnknown),
    MAP_GUID(IID_IClassFactory),
    MAP_GUID(IID_IDropTarget),
    MAP_GUID(IID_IDataObject),
    MAP_GUID(IID_IPersist),
    MAP_GUID(IID_IOleWindow),

    MAP_GUID2(IID_INewShortcutHookA, IID_INewShortcutHookW),
    MAP_GUID(IID_IShellBrowser),
    MAP_GUID(IID_IShellView),
    MAP_GUID(IID_IContextMenu),
    MAP_GUID(IID_IShellIcon),
    MAP_GUID(IID_IShellFolder),
    MAP_GUID(IID_IShellExtInit),
    MAP_GUID(IID_IShellPropSheetExt),
    MAP_GUID(IID_IPersistFolder),  
    MAP_GUID2(IID_IExtractIconA, IID_IExtractIconW),
    MAP_GUID2(IID_IShellLinkA, IID_IShellLinkW),
    MAP_GUID2(IID_IShellCopyHookA, IID_IShellCopyHookW),
    MAP_GUID2(IID_IFileViewerA, IID_IFileViewerW),
    MAP_GUID(IID_ICommDlgBrowser),
    MAP_GUID(IID_IEnumIDList),
    MAP_GUID(IID_IFileViewerSite),
    MAP_GUID(IID_IContextMenu2),
    MAP_GUID2(IID_IShellExecuteHookA, IID_IShellExecuteHookW),
    MAP_GUID(IID_IPropSheetPage),
    MAP_GUID(IID_IShellView2),
    MAP_GUID(IID_IUniformResourceLocator),
};

void CDebugStack::_TraceGUID(LPCTSTR pPrefix, REFGUID rGUID)
{
    TCHAR szGUID[40];
    LPCTSTR pName = NULL;
    int i;
    
    for ( i = 0 ; i < ARRAYSIZE(_guid_map); i++ )
    {
        if ( IsEqualGUID(rGUID, *_guid_map[i].m_pGUID) )
        {
            pName = _guid_map[i].m_pName;
            break;
        }
    }

    if ( !pName )
    {
// StringFromGUID2 only does UNICODE.  SHStringFromGUID goes both ways,
// but requires shlwapip.h and shlwapi.lib.
#ifndef UNICODE
  #error "_TraceGUID needs fixing"
#endif    
        StringFromGUID2(rGUID, szGUID, ARRAYSIZE(szGUID));
        //SHStringFromGUID(rGUID, szGUID, ARRAYSIZE(szGUID));
        pName = szGUID;
    }

    _Trace(FALSE, TEXT("%s %s"), pPrefix, pName);
}


/*-----------------------------------------------------------------------------
/ _TraceAssert
/ -------------
/   Our assert handler, out faults it the trace mask as enabled assert
/   faulting.
/
/ In:
/   iLine = line 
/   pFilename -> filename of the file we asserted in
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void CDebugStack::_TraceAssert(int iLine, LPTSTR pFilename)
{
    // nb: TRUE --> asserts always displayed
    _Trace(TRUE, TEXT("Assert failed in %s, line %d"), pFilename, iLine);

    if ( g_dwTraceMask & TRACE_COMMON_ASSERT )
        DebugBreak();
}


/*-----------------------------------------------------------------------------
/ ~CDebugStackHolder
/ ------------------
/   Free any DebugStack objects that exist
/
/ In:
/   -
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
int CALLBACK
_DeleteCB(LPVOID pVoid, LPVOID /*pData*/)
{
    PDEBUGSTACK pDebugStack = (PDEBUGSTACK)pVoid;
    if (pDebugStack)
    {
        //pDebugStack->_Trace(TRUE, TEXT("~CDebugStackHolder destroying DebugStack"));
        delete pDebugStack;
    }
    return 1;
}

CDebugStackHolder::~CDebugStackHolder()
{
    EnterCriticalSection(&m_csStackList);

    if (NULL != m_hDebugStackList)
    {
        DPA_DestroyCallback(m_hDebugStackList, _DeleteCB, NULL);
        m_hDebugStackList = NULL;
    }

    LeaveCriticalSection(&m_csStackList);
    DeleteCriticalSection(&m_csStackList);
}


/*-----------------------------------------------------------------------------
/ CDebugStackHolder::Add
/ ----------------------
/   Saves the DebugStack object in a list
/
/ In:
/   PDEBUGSTACK pointer to the thread's debug stack object
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void
CDebugStackHolder::Add(PDEBUGSTACK pDebugStack)
{
    EnterCriticalSection(&m_csStackList);

    if (NULL == m_hDebugStackList)
        m_hDebugStackList = DPA_Create(4);

    if (NULL != m_hDebugStackList)
        DPA_AppendPtr(m_hDebugStackList, pDebugStack);

    LeaveCriticalSection(&m_csStackList);
}


/*-----------------------------------------------------------------------------
/ CDebugStackHolder::Remove
/ -------------------------
/   Removes the DebugStack object from the list
/
/ In:
/   PDEBUGSTACK pointer to the thread's debug stack object
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void
CDebugStackHolder::Remove(PDEBUGSTACK pDebugStack)
{
    EnterCriticalSection(&m_csStackList);

    if (NULL != m_hDebugStackList)
    {
        int iStack = DPA_GetPtrIndex(m_hDebugStackList, pDebugStack);

        if (-1 != iStack)
            DPA_DeletePtr(m_hDebugStackList, iStack);
    }

    LeaveCriticalSection(&m_csStackList);
}


/*-----------------------------------------------------------------------------
/ GetThreadStack
/ --------------
/   Create (if necessary) and return the per-thread debug stack object.
/
/ In:
/   -
/
/ Out:
/   PDEBUGSTACK pointer to the thread's debug stack object
/----------------------------------------------------------------------------*/
PDEBUGSTACK GetThreadStack()
{
    PDEBUGSTACK pDebugStack;

    if (0xffffffffL == g_tlsDebug)
        return NULL;

    pDebugStack = (PDEBUGSTACK)TlsGetValue(g_tlsDebug);

    if (!pDebugStack)
    {
        pDebugStack = new CDebugStack;
        TlsSetValue(g_tlsDebug, pDebugStack);

        if (!g_pStackHolder)
            g_pStackHolder = new CDebugStackHolder;

        if (g_pStackHolder)
            g_pStackHolder->Add(pDebugStack);
    }

    return pDebugStack;
}
    

/*-----------------------------------------------------------------------------
/ DoTraceSetMask
/ --------------
/   Adjust the trace mask to reflect the state given.
/
/ In:
/   dwMask = mask for enabling / disable trace output
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void DoTraceSetMask(DWORD dwMask)
{
    g_dwTraceMask = dwMask;
}


/*-----------------------------------------------------------------------------
/ DoTraceSetMaskFromRegKey
/ ------------------------
/   Pick up the TraceMask value from the given registry key and
/   set the trace mask using that.
/
/ In:
/   hkRoot = handle of open key
/   pszSubKey = name of subkey to open
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void DoTraceSetMaskFromRegKey(HKEY hkRoot, LPCTSTR pszSubKey)
{
    HKEY hKey;

    if (ERROR_SUCCESS == RegOpenKey(hkRoot, pszSubKey, &hKey))
    {
        DWORD dwTraceMask = 0;
        DWORD cbTraceMask = SIZEOF(dwTraceMask);

        RegQueryValueEx(hKey,
                        TEXT("TraceMask"),
                        NULL,
                        NULL,
                        (LPBYTE)&dwTraceMask,
                        &cbTraceMask);
        DoTraceSetMask(dwTraceMask);
        RegCloseKey(hKey);
    }
}


/*-----------------------------------------------------------------------------
/ DoTraceSetMaskFromCLSID
/ -----------------------
/   Pick up the TraceMask value from the given CLSID value and
/   set the trace mask using that.
/
/ In:
/   rCLSID = CLSID to query the value from
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void DoTraceSetMaskFromCLSID(REFCLSID rCLSID)
{
    TCHAR szClsidKey[48] = TEXT("CLSID\\");
    int nLength = lstrlen(szClsidKey);

// StringFromGUID2 only does UNICODE.  SHStringFromGUID goes both ways,
// but requires shlwapip.h and shlwapi.lib.
#ifdef UNICODE
    if (0 == StringFromGUID2(rCLSID, szClsidKey + nLength, ARRAYSIZE(szClsidKey) - nLength))
#else
#error "DoTraceSetMaskFromCLSID needs fixing"
    if (0 == SHStringFromGUID(rCLSID, szClsidKey + nLength, ARRAYSIZE(szClsidKey) - nLength))
#endif    
        return;

    DoTraceSetMaskFromRegKey(HKEY_CLASSES_ROOT, szClsidKey);
}


/*-----------------------------------------------------------------------------
/ DoTraceEnter
/ ------------
/   Set the debugging call stack up to indicate which function we are in.
/
/ In:
/   pName -> function name to be displayed in subsequent trace output.
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void DoTraceEnter(DWORD dwMask, LPCTSTR pName)
{
    PDEBUGSTACK pDebugStack = GetThreadStack();

    if (pDebugStack)
        pDebugStack->_TraceEnter(dwMask, pName);
}


/*-----------------------------------------------------------------------------
/ DoTraceLeave
/ ------------
/   On exit from a function this will adjust the function stack pointer to 
/   point to our previous function.  If no trace output has been made then 
/   we will output the function name on a single line (to indicate we went somewhere).
/
/ In:
/    -
/ Out:
/   -
/----------------------------------------------------------------------------*/
void DoTraceLeave(void)
{
    PDEBUGSTACK pDebugStack = GetThreadStack();

    if (pDebugStack)
        pDebugStack->_TraceLeave();
}


/*-----------------------------------------------------------------------------
/ DoTrace
/ -------
/   Perform printf formatting to the debugging stream.  We indent the output
/   and stream the function name as required to give some indication of 
/   call stack depth.
/
/ In:
/   pszFormat -> printf style formatting string
/   ... = arguments as required for the formatting
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void DoTrace(LPCTSTR pszFormat, ...)
{
    PDEBUGSTACK pDebugStack = GetThreadStack();
    va_list va;

    if (pDebugStack)
    {
        va_start(va, pszFormat);
        pDebugStack->_vTrace(FALSE, pszFormat, va);
        va_end(va);
    }
}


/*-----------------------------------------------------------------------------
/ DoTraceGuid
/ -----------
/   Given a GUID output it into the debug string, first we try and map it
/   to a name (ie. IShellFolder), if that didn't work then we convert it
/   to its human readable form.
/
/ In:
/   pszPrefix -> prefix string
/   lpGuid -> guid to be streamed   
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void DoTraceGUID(LPCTSTR pPrefix, REFGUID rGUID)
{
    PDEBUGSTACK pDebugStack = GetThreadStack();

    if (pDebugStack)
        pDebugStack->_TraceGUID(pPrefix, rGUID);
}


/*-----------------------------------------------------------------------------
/ DoTraceAssert
/ -------------
/   Our assert handler, out faults it the trace mask as enabled assert
/   faulting.
/
/ In:
/   iLine = line 
/   pFilename -> filename of the file we asserted in
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void DoTraceAssert(int iLine, LPTSTR pFilename)
{
    PDEBUGSTACK pDebugStack = GetThreadStack();

    if (pDebugStack)
        pDebugStack->_TraceAssert(iLine, pFilename);
}


/*-----------------------------------------------------------------------------
/ DebugThreadDetach
/ DebugProcessAttach
/ DebugProcessDetach
/ -------------
/   These must be called from DllMain
/
/ In:
/   -
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
void DebugThreadDetach(void)
{
    PDEBUGSTACK pDebugStack;

    if (0xffffffffL == g_tlsDebug)
        return;

    pDebugStack = (PDEBUGSTACK)TlsGetValue(g_tlsDebug);

    if (pDebugStack)
    {
        if (g_pStackHolder)
            g_pStackHolder->Remove(pDebugStack);

        delete pDebugStack;
        TlsSetValue(g_tlsDebug, NULL);
    }
}

void DebugProcessAttach(void)
{
    g_tlsDebug = TlsAlloc();
}

void DebugProcessDetach(void)
{
    DebugThreadDetach();

    if (NULL != g_pStackHolder)
    {
        delete g_pStackHolder;
        g_pStackHolder = NULL;
    }

    if (0xffffffffL != g_tlsDebug)
    {
        TlsFree(g_tlsDebug);
        g_tlsDebug = 0xffffffffL;
    }
}


#endif  // DEBUG
