/**************************************************************************\
*
* Copyright (c) 1998-1999  Microsoft Corporation
*
* Abstract:
*
*   Debugging routines
*
* Revision History:
*
*   09/07/1999 agodfrey
*       Created it.
*
\**************************************************************************/
#include "global.h"

// Optional callback function for reporting a debug event.

enum GpAltDebugEventLevel
{
    GpAltDebugFatal,
    GpAltDebugWarning
};

#define GpAltDebugEventFunction 0
//DEBUGEVENTFUNCTION GpAltDebugEventFunction = NULL;

#if DBG

// GpDebugLevel is used to control the amount/severity of debugging messages
// that are actually output.

int GpDebugLevel = DBG_TERSE;

/**************************************************************************\
*
* Function Description:
*
*   Removes the path portion of a pathname
*
* Arguments:
*
*   [IN] str - pathname to strip
*
* Return Value:
*
*   A pointer to the filename portion of the pathname
*
* History:
*
*   09/07/1999 agodfrey
*       Moved from Entry\Initialize.cpp 
*
\**************************************************************************/

const char*
StripDirPrefix(
    const char* str
    )

{
    const char* p;

    p = strrchr(str, '\\');
    return p ? p+1 : str;
}

const int maxInputStringSize = 1024;

/**************************************************************************\
*
* Function Description:
*
*   Outputs to the debugger
*
* Arguments:
*
*   [IN] format - printf-like format string and variable arguments
*
* Return Value:
*
*   Zero. This is to conform to NTDLL's definition of DbgPrint.
*
* Notes:
*
*   There will be no output if a debugger is not connected.
*
* History:
*
*   09/07/1999 agodfrey
*       Moved from Entry\Initialize.cpp 
*
\**************************************************************************/

ULONG _cdecl
DbgPrint(
    char* format,
    ...
    )

{
    va_list arglist;
    va_start(arglist, format);
    
    char buf[maxInputStringSize];
    
    _vsnprintf(buf, maxInputStringSize, format, arglist);
    buf[maxInputStringSize-1]=0;
        
    OutputDebugStringA(buf);
    
    va_end(arglist);
    return 0;
}

// If we can't allocate memory for the debug string, we'll use this buffer
// in desperation. It's not thread-safe. I *did* say 'desperation'.

static char desperationBuffer[maxInputStringSize];

/**************************************************************************\
*
* Function Description:
*
*   Creates a new string, and sprintf's to it.
*
* Arguments:
*
*   [IN] format - printf-like format string and variable arguments
*
* Return Value:
*
*   The probably-newly-allocated string result.
*
* Notes:
*
*   This function is not intended for general use. It guards against memory
*   failure by using a global buffer. So, while the caller is responsible
*   for freeing the memory, the caller must also check for that buffer.
*   i.e. we only want DbgEmitMessage to call this.
*
*   It's also only mostly thread-safe, because if we run out of memory,
*   we'll use that global buffer in a non-protected way.
*
*   This is the only solution I could find so that I could move most of the
*   implementation details out of the header file. The root cause is that
*   macros don't handle multiple arguments natively, so we have to pass
*   the printf arguments as a single macro argument (in parentheses).
*   Which means, the function that consumes those arguments can have no
*   other arguments.
*
* History:
*
*   02/01/2000 agodfrey
*       Created it. Finally, I've found a way to get debug implementation
*       details out of the headers.
*
\**************************************************************************/

char * _cdecl
GpParseDebugString(
    char* format,
    ...
    )
{
    va_list arglist;
    va_start(arglist, format);
    
    // Don't use GpMalloc here so that we can use ASSERT and WARNING in 
    // our memory allocation routines.

    char *newBuf = static_cast<char *>(LocalAlloc(LMEM_FIXED, maxInputStringSize));
    if (!newBuf)
    {
        newBuf = desperationBuffer;
    }
    
    _vsnprintf(newBuf, maxInputStringSize, format, arglist);
    
    // Nuke the last byte, because MSDN isn't clear on what _vsnprintf does
    // in that case.
    
    newBuf[maxInputStringSize-1]=0;
        
    va_end(arglist);
    return newBuf;
}

/**************************************************************************\
*
* Function Description:
*
*   Processes a debug event. Frees the message string.
*
* Arguments:
*
* level   - The debug level of the event 
* file    - Should be __FILE__
* line    - Should be __LINE__
* message - The debug message.
*
* Notes:
*
*   You don't want to call this directly. That would be error-prone. 
*   Use ASSERT, WARNING, etc.
*
*   In Office, it will raise an 'assert' dialog if necessary.
*
*   Depending on the debug level, an identifying prefix will be output.
*
*   If the debug level is DBG_RIP, will suspend execution (e.g. by
*   hitting a breakpoint.)
*
* Note on Office interop:
*
*   Under Office, we'll pass WARNINGs and ASSERTs to a reporting function
*   they provide. Their function will breakpoint on ASSERTs.
*   Lesser events will just be sent to the debugger.
*
* History:
*
*   02/01/2000 agodfrey
*       Created it.
*
\**************************************************************************/

void _cdecl 
GpLogDebugEvent(
    int level, 
    char *file, 
    unsigned int line,
    char *message
    )
{
    // We may want to add things to the passed-in message. So we need
    // a temporary buffer
    
    const int maxOutputStringSize = maxInputStringSize + 100;
    char tempBuffer[maxOutputStringSize+1];
    
    // MSDN's _vsnprintf doc isn't clear on this, so just in case:
    tempBuffer[maxOutputStringSize] = 0;
    int altDebugLevel = -1;
    
    char *prefix = "";
    
    if (GpDebugLevel <= (level))
    {
        switch (level)
        {
        case DBG_WARNING:
            prefix = "WRN ";
            if (GpAltDebugEventFunction)
            {
                altDebugLevel = GpAltDebugWarning;
            }    
            break;
            
        case DBG_RIP:
            prefix = "RIP ";
            if (GpAltDebugEventFunction)
            {
                altDebugLevel = GpAltDebugFatal;
            }    
            break;
        }
        
        // The convention is that we append the trailing \n, not the caller.
        // Two reasons:
        // 1) Callers tend to forget it.
        // 2) More importantly, it encourages the caller to think of each
        //    call as a separate event. This is important in some cases - e.g.
        //    when Office produces a popup for each event.

        _snprintf(
            tempBuffer, 
            maxOutputStringSize, 
            "%s%s(%d): %s\n",
            prefix,
            StripDirPrefix(file),
            line,
            message
            );
        
        if (altDebugLevel >= 0)
        {
            //GpAltDebugEventFunction(altDebugLevel, tempBuffer);
        }
        else
        {
            OutputDebugStringA(tempBuffer);
        }
    }
    
    // Free the message buffer
    
    if (message != desperationBuffer)
    {
        LocalFree(message);
    }
    
    // Force a breakpoint, if it's warranted.
    
    if ((GpDebugLevel <= DBG_RIP) && (level == DBG_RIP) && (altDebugLevel < 0))
    {
        DebugBreak();
    }
}
    
#endif // DBG