/***
*DbgRpt.c - Debug Cluster Reporting Functions
*
*       Copyright (c) 1988-1998, Microsoft Corporation. All rights reserved.
*
*Purpose:
*
*******************************************************************************/

#include <malloc.h>
#include <mbstring.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>

#include <windows.h>

#define CLRTL_INCLUDE_DEBUG_REPORTING
#include "ClRtlDbg.h"
#include <strsafe.h>

#define _ClRtlInterlockedIncrement InterlockedIncrement
#define _ClRtlInterlockedDecrement InterlockedDecrement

/*---------------------------------------------------------------------------
 *
 * Debug Reporting
 *
 --------------------------------------------------------------------------*/

static int ClRtlMessageWindow(
    int,
    const char *,
    const char *,
    const char *,
    const char *
    );

static int __clrtlMessageBoxA(
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT uType
    );

extern "C"
{
_CLRTL_REPORT_HOOK _pfnReportHook;

long _clrtlAssertBusy = -1;

int _ClRtlDbgMode[_CLRTLDBG_ERRCNT] = {
    _CLRTLDBG_MODE_DEBUG,
    _CLRTLDBG_MODE_DEBUG | _CLRTLDBG_MODE_WNDW,
    _CLRTLDBG_MODE_DEBUG | _CLRTLDBG_MODE_WNDW
    };

_HFILE _ClRtlDbgFile[_CLRTLDBG_ERRCNT] = {
    _CLRTLDBG_INVALID_HFILE,
    _CLRTLDBG_INVALID_HFILE,
    _CLRTLDBG_INVALID_HFILE
    };
}

static const char * _ClRtlDbgModeMsg[_CLRTLDBG_ERRCNT] = { "Warning", "Error", "Assertion Failed" };

/***
*void _ClRtlDebugBreak - call OS-specific debug function
*
*Purpose:
*       call OS-specific debug function
*
*Entry:
*
*Exit:
*
*Exceptions:
*
*******************************************************************************/

#undef _ClRtlDbgBreak

extern "C" void _cdecl _ClRtlDbgBreak(
    void
    )
{
    DebugBreak();

} //*** _ClRtlDbgBreak()

/***
*int _ClRtlSetReportMode - set the reporting mode for a given report type
*
*Purpose:
*       set the reporting mode for a given report type
*
*Entry:
*       int nRptType    - the report type
*       int fMode       - new mode for given report type
*
*Exit:
*       previous mode for given report type
*
*Exceptions:
*
*******************************************************************************/
extern "C" int __cdecl _ClRtlSetReportMode(
    int nRptType,
    int fMode
    )
{
    int oldMode;

    if (nRptType < 0 || nRptType >= _CLRTLDBG_ERRCNT)
        return -1;

    if (fMode == _CLRTLDBG_REPORT_MODE)
        return _ClRtlDbgMode[nRptType];

    /* verify flags values */
    if (fMode & ~(_CLRTLDBG_MODE_FILE | _CLRTLDBG_MODE_DEBUG | _CLRTLDBG_MODE_WNDW))
        return -1;

    oldMode = _ClRtlDbgMode[nRptType];

    _ClRtlDbgMode[nRptType] = fMode;

    return oldMode;

} //*** _ClRtlSetReportMode()

/***
*int _ClRtlSetReportFile - set the reporting file for a given report type
*
*Purpose:
*       set the reporting file for a given report type
*
*Entry:
*       int nRptType    - the report type
*       _HFILE hFile    - new file for given report type
*
*Exit:
*       previous file for given report type
*
*Exceptions:
*
*******************************************************************************/
extern "C" _HFILE __cdecl _ClRtlSetReportFile(
    int nRptType,
    _HFILE hFile
    )
{
    _HFILE oldFile;

    if (nRptType < 0 || nRptType >= _CLRTLDBG_ERRCNT)
        return _CLRTLDBG_HFILE_ERROR;

    if (hFile == _CLRTLDBG_REPORT_FILE)
        return _ClRtlDbgFile[nRptType];

    oldFile = _ClRtlDbgFile[nRptType];

    if (_CLRTLDBG_FILE_STDOUT == hFile)
        _ClRtlDbgFile[nRptType] = GetStdHandle(STD_OUTPUT_HANDLE);

    else if (_CLRTLDBG_FILE_STDERR == hFile)
        _ClRtlDbgFile[nRptType] = GetStdHandle(STD_ERROR_HANDLE);
    else
        _ClRtlDbgFile[nRptType] = hFile;

    return oldFile;

} //*** _ClRtlSetReportFile()


/***
*_CLRTL_REPORT_HOOK _ClRtlSetReportHook() - set client report hook
*
*Purpose:
*       set client report hook
*
*Entry:
*       _CLRTL_REPORT_HOOK pfnNewHook - new report hook
*
*Exit:
*       return previous hook
*
*Exceptions:
*
*******************************************************************************/
extern "C" _CLRTL_REPORT_HOOK __cdecl _ClRtlSetReportHook(
    _CLRTL_REPORT_HOOK pfnNewHook
    )
{
    _CLRTL_REPORT_HOOK pfnOldHook = _pfnReportHook;
    _pfnReportHook = pfnNewHook;
    return pfnOldHook;

} //*** _ClRtlSetReportHook()


#define MAXLINELEN 64
#define MAX_MSG 512
#define TOOLONGMSG "_ClRtlDbgReport: String too long or IO Error"


/***
*int _ClRtlDbgReport() - primary reporting function
*
*Purpose:
*       Display a message window with the following format.
*
*       ================= Microsft Visual C++ Debug Library ================
*
*       {Warning! | Error! | Assertion Failed!}
*
*       Program: c:\test\mytest\foo.exe
*       [Module: c:\test\mytest\bar.dll]
*       [File: c:\test\mytest\bar.c]
*       [Line: 69]
*
*       {<warning or error message> | Expression: <expression>}
*
*       [For information on how your program can cause an assertion
*        failure, see the Visual C++ documentation on asserts]
*
*       (Press Retry to debug the application)
*
*       ===================================================================
*
*Entry:
*       int             nRptType    - report type
*       const char *    szFile      - file name
*       int             nLine       - line number
*       const char *    szModule    - module name
*       const char *    szFormat    - format string
*       ...                         - var args
*
*Exit:
*       if (MessageBox)
*       {
*           Abort -> aborts
*           Retry -> return TRUE
*           Ignore-> return FALSE
*       }
*       else
*           return FALSE
*
*Exceptions:
*
*******************************************************************************/
extern "C" int __cdecl _ClRtlDbgReport(
    int nRptType,
    const char * szFile,
    int nLine,
    const char * szModule,
    const char * szFormat,
    ...
    )
{
    int retval;
    va_list arglist;
    char szLineMessage[MAX_MSG] = {0};
    char szOutMessage[MAX_MSG] = {0};
    char szUserMessage[MAX_MSG] = {0};
    #define ASSERTINTRO1 "Assertion failed: "
    #define ASSERTINTRO2 "Assertion failed!"

    va_start(arglist, szFormat);

    if (nRptType < 0 || nRptType >= _CLRTLDBG_ERRCNT)
        return -1;

    /*
     * handle the (hopefully rare) case of
     *
     * 1) ASSERT while already dealing with an ASSERT
     *      or
     * 2) two threads asserting at the same time
     */
    if (_CLRTLDBG_ASSERT == nRptType && _ClRtlInterlockedIncrement(&_clrtlAssertBusy) > 0)
    {
        /* use only 'safe' functions -- must not assert in here! */
        static int (APIENTRY *pfnwsprintfA)(LPSTR, LPCSTR, ...) = NULL;

        if (NULL == pfnwsprintfA)
        {
            HINSTANCE hlib = LoadLibraryA("user32.dll");

            if (NULL == hlib || NULL == (pfnwsprintfA =
                        (int (APIENTRY *)(LPSTR, LPCSTR, ...))
                        GetProcAddress(hlib, "wsprintfA")))
                return -1;
        }

        (*pfnwsprintfA)( szOutMessage,
            "Second Chance Assertion Failed: File %s, Line %d\n",
            szFile, nLine);

        OutputDebugStringA(szOutMessage);

        _ClRtlInterlockedDecrement(&_clrtlAssertBusy);

        _ClRtlDbgBreak();
        return -1;
    }

    szUserMessage[ MAX_MSG - 1 ] = 0;
    if (szFormat && StringCbVPrintfA( szUserMessage,
                   MAX_MSG-max(sizeof(ASSERTINTRO1),sizeof(ASSERTINTRO2))-1,
                   szFormat,
                   arglist) != S_OK ) {
        PCHAR dot;
        dot = szUserMessage + MAX_MSG - 4;
        StringCbCopyA( dot, 4, "..." );
    }
    if (_CLRTLDBG_ASSERT == nRptType) {
        if ( szFormat == 0 ) {
            StringCbCopyA( szLineMessage, MAX_MSG, ASSERTINTRO1 );
        }
        else {
            StringCbCopyA( szLineMessage, MAX_MSG, ASSERTINTRO2 );
        }
    }
    StringCbCatA( szLineMessage, MAX_MSG, szUserMessage );

    if (_CLRTLDBG_ASSERT == nRptType)
    {
        if (_ClRtlDbgMode[nRptType] & _CLRTLDBG_MODE_FILE) {
            StringCbCatA( szLineMessage, MAX_MSG, "\r" );
        }
        StringCbCatA( szLineMessage, MAX_MSG, "\n" );
    }

    if (szFile)
    {
        if ( StringCbPrintfA(szOutMessage, MAX_MSG, "%s(%d) : %s",
            szFile, nLine, szLineMessage) != S_OK )
        StringCbCopyA( szOutMessage, MAX_MSG, TOOLONGMSG );
    }
    else {
        StringCbCopyA( szOutMessage, MAX_MSG, szLineMessage );
    }

    /* user hook may handle report */
    if (_pfnReportHook && (*_pfnReportHook)(nRptType, szOutMessage, &retval))
    {
        if (_CLRTLDBG_ASSERT == nRptType)
            _ClRtlInterlockedDecrement(&_clrtlAssertBusy);
        return retval;
    }

    if (_ClRtlDbgMode[nRptType] & _CLRTLDBG_MODE_FILE)
    {
        if (_ClRtlDbgFile[nRptType] != _CLRTLDBG_INVALID_HFILE)
        {
            DWORD written;
            WriteFile(_ClRtlDbgFile[nRptType], szOutMessage, strlen(szOutMessage), &written, NULL);
        }
    }

    if (_ClRtlDbgMode[nRptType] & _CLRTLDBG_MODE_DEBUG)
    {
        OutputDebugStringA(szOutMessage);
    }

    if (_ClRtlDbgMode[nRptType] & _CLRTLDBG_MODE_WNDW)
    {
        char szLine[20];

        retval = ClRtlMessageWindow(nRptType, szFile, nLine ? _itoa(nLine, szLine, 10) : NULL, szModule, szUserMessage);
        if (_CLRTLDBG_ASSERT == nRptType)
            _ClRtlInterlockedDecrement(&_clrtlAssertBusy);
        return retval;
    }

    if (_CLRTLDBG_ASSERT == nRptType)
        _ClRtlInterlockedDecrement(&_clrtlAssertBusy);
    /* ignore */
    return FALSE;

} //*** _ClRtlDbgReport()


/***
*static int ClRtlMessageWindow() - report to a message window
*
*Purpose:
*       put report into message window, allow user to choose action to take
*
*Entry:
*       int             nRptType      - report type
*       const char *    szFile        - file name
*       const char *    szLine        - line number
*       const char *    szModule      - module name
*       const char *    szUserMessage - user message
*
*Exit:
*       if (MessageBox)
*       {
*           Abort -> aborts
*           Retry -> return TRUE
*           Ignore-> return FALSE
*       }
*       else
*           return FALSE
*
*Exceptions:
*
*******************************************************************************/
static int ClRtlMessageWindow(
        int nRptType,
        const char * szFile,
        const char * szLine,
        const char * szModule,
        const char * szUserMessage
        )
{
    int nCode;
    char *szShortProgName;
    char *szShortModuleName;
    char szExeName[MAX_PATH];
    char szOutMessage[MAX_MSG];

    _CLRTL_ASSERTE(szUserMessage != NULL);

    /* Shorten program name */
    if (!GetModuleFileNameA(NULL, szExeName, MAX_PATH))
        StringCbCopyA(szExeName, MAX_PATH, "<program name unknown>");

    szShortProgName = szExeName;

    if (strlen(szShortProgName) > MAXLINELEN)
    {
        szShortProgName += MAXLINELEN - 4;
        StringCbCopyA( szShortProgName, 4, "..." );
    }

    /* Shorten module name */
    szShortModuleName = (char *) szModule;

    if (szShortModuleName && strlen(szShortModuleName) > MAXLINELEN)
    {
        szShortModuleName += MAXLINELEN - 4;
        StringCbCopyA( szShortModuleName, 4, "..." );
    }

    if ( StringCbPrintfA( szOutMessage, MAX_MSG,  
            "Debug %s!\n\nProgram: %s%s%s%s%s%s%s%s%s%s%s"
            "\n\n(Press Retry to debug the application)",
            _ClRtlDbgModeMsg[nRptType],
            szShortProgName,
            szShortModuleName ? "\nModule: " : "",
            szShortModuleName ? szShortModuleName : "",
            szFile ? "\nFile: " : "",
            szFile ? szFile : "",
            szLine ? "\nLine: " : "",
            szLine ? szLine : "",
            szUserMessage[0] ? "\n\n" : "",
            szUserMessage[0] && _CLRTLDBG_ASSERT == nRptType ? "Expression: " : "",
            szUserMessage[0] ? szUserMessage : "",
            0 /*_CLRTLDBG_ASSERT == nRptType*/ ? // Don't display this text, it's superfluous
            "\n\nFor information on how your program can cause an assertion"
            "\nfailure, see the Visual C++ documentation on asserts."
            : "") != S_OK )
        StringCbCopyA( szOutMessage, MAX_MSG, TOOLONGMSG );

    /* Report the warning/error */
    nCode = __clrtlMessageBoxA(
                        szOutMessage,
                        "Microsoft Visual C++ Debug Library",
                        MB_TASKMODAL|MB_ICONHAND|MB_ABORTRETRYIGNORE|MB_SETFOREGROUND);

    /* Abort: abort the program */
    if (IDABORT == nCode)
    {
        /* raise abort signal */
        raise(SIGABRT);

        /* We usually won't get here, but it's possible that
           SIGABRT was ignored.  So exit the program anyway. */

        _exit(3);
    }

    /* Retry: return 1 to call the debugger */
    if (IDRETRY == nCode)
        return 1;

    /* Ignore: continue execution */
    return 0;

} //*** ClRtlMessageWindow()


/***
*__clrtlMessageBoxA - call MessageBoxA dynamically.
*
*Purpose:
*       Avoid static link with user32.dll. Only load it when actually needed.
*
*Entry:
*       see MessageBoxA docs.
*
*Exit:
*       see MessageBoxA docs.
*
*Exceptions:
*
*******************************************************************************/
static int __clrtlMessageBoxA(
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT uType
    )
{
    static int (APIENTRY *pfnMessageBoxA)(HWND, LPCSTR, LPCSTR, UINT) = NULL;
    static HWND (APIENTRY *pfnGetActiveWindow)(void) = NULL;
    static HWND (APIENTRY *pfnGetLastActivePopup)(HWND) = NULL;

    HWND hWndParent = NULL;

    if (NULL == pfnMessageBoxA)
    {
        HINSTANCE hlib = LoadLibraryA("user32.dll");

        if (NULL == hlib || NULL == (pfnMessageBoxA =
                    (int (APIENTRY *)(HWND, LPCSTR, LPCSTR, UINT))
                    GetProcAddress(hlib, "MessageBoxA")))
            return 0;

        pfnGetActiveWindow = (HWND (APIENTRY *)(void))
                    GetProcAddress(hlib, "GetActiveWindow");

        pfnGetLastActivePopup = (HWND (APIENTRY *)(HWND))
                    GetProcAddress(hlib, "GetLastActivePopup");
    }

    if (pfnGetActiveWindow)
        hWndParent = (*pfnGetActiveWindow)();

    if (hWndParent != NULL && pfnGetLastActivePopup)
        hWndParent = (*pfnGetLastActivePopup)(hWndParent);

    return (*pfnMessageBoxA)(hWndParent, lpText, lpCaption, uType);

} //*** __clrtlMessageBoxA()