/*++

Copyright (c) 1998-1999 Microsoft Corporation
All rights reserved.

Module Name:

    dbgmsgp.cxx

Abstract:

    Debug Library

Author:

    Steve Kiraly (SteveKi)  10-Dec-1995

Revision History:

--*/
#include "precomp.hxx"
#pragma hdrstop

TDebugMsg::
TDebugMsg(
    VOID
    ) : m_eLevel(static_cast<EDebugLevel>(0)),
        m_eBreak(static_cast<EDebugLevel>(0)),
        m_pDeviceRoot(NULL),
        m_pstrPrefix(NULL),
        m_pBuiltinDeviceRoot(NULL)
{
}

TDebugMsg::
~TDebugMsg(
    VOID
    )
{
}

BOOL
TDebugMsg::
Valid(
    VOID
    ) const
{
    return !!m_pBuiltinDeviceRoot;
}

VOID
TDebugMsg::
Disable(
    VOID
    )
{
    m_eLevel = static_cast<EDebugLevel>( m_eLevel | kDbgNone );
    m_eBreak = static_cast<EDebugLevel>( m_eBreak | kDbgNone );
}

VOID
TDebugMsg::
Enable(
    VOID
    )
{
    m_eLevel = static_cast<EDebugLevel>( m_eLevel & ~kDbgNone );
    m_eBreak = static_cast<EDebugLevel>( m_eBreak & ~kDbgNone );
}

BOOL
TDebugMsg::
Type(
    IN EDebugLevel eLevel
    ) const
{
    return !(m_eLevel & kDbgNone) && ((m_eLevel & eLevel) || (eLevel & kDbgAlways));
}

BOOL
TDebugMsg::
Break(
    IN EDebugLevel eLevel
    ) const
{
    return m_eBreak & eLevel;
}

/*++

Routine Name:

    Initialize

Routine Description:


Arguments:


Return Value:

    None.

--*/
VOID
TDebugMsg::
Initialize(
    IN LPCTSTR      pszPrefix,
    IN UINT         uDevice,
    IN INT          eLevel,
    IN INT          eBreak
    )
{
    if (!m_pBuiltinDeviceRoot)
    {
        BOOL bRetval = FALSE;

        //
        // Set the debug message and break level.
        //
        m_eLevel = static_cast<EDebugLevel>(eLevel & ~kDbgPrivateMask);
        m_eBreak = static_cast<EDebugLevel>(eBreak & ~kDbgPrivateMask);

        //
        // Set the global device flags.
        //
        if (!Globals.DebugDevices)
        {
            Globals.DebugDevices = uDevice & (kDbgNull | kDbgDebugger | kDbgFile);
        }

        //
        // Set the character type to current compiled type.
        //
        m_eLevel = static_cast<EDebugLevel>(m_eLevel | Globals.CompiledCharType);

        //
        // Set the prefix string.
        //
        m_pstrPrefix = INTERNAL_NEW TDebugString(pszPrefix ? pszPrefix : kstrPrefix);

        //
        // Set the prefix string.
        //
        if (m_pstrPrefix && m_pstrPrefix->bValid() && m_pstrPrefix->bCat( _T(":")))
        {
            //
            // Set the additional format strings.
            //
            m_pstrFileInfoFormat        = INTERNAL_NEW TDebugString(kstrFileInfoFormat);
            m_pstrTimeStampFormatShort  = INTERNAL_NEW TDebugString(kstrTimeStampFormatShort);
            m_pstrTimeStampFormatLong   = INTERNAL_NEW TDebugString(kstrTimeStampFormatLong);
            m_pstrThreadIdFormat        = INTERNAL_NEW TDebugString(kstrThreadIdFormat);

            if( m_pstrFileInfoFormat && m_pstrFileInfoFormat->bValid() &&
                m_pstrTimeStampFormatShort && m_pstrTimeStampFormatShort->bValid() &&
                m_pstrTimeStampFormatLong && m_pstrTimeStampFormatLong->bValid() &&
                m_pstrThreadIdFormat && m_pstrThreadIdFormat->bValid() )
            {
                //
                // Attach the default debug devices.
                //
                if(Attach(NULL, kDbgDebugger,    NULL,                   &m_pBuiltinDeviceRoot) &&
                   Attach(NULL, kDbgFile,        kstrDefaultLogFileName, &m_pBuiltinDeviceRoot) &&
                   Attach(NULL, kDbgNull,        NULL,                   &m_pBuiltinDeviceRoot))
                {
                    bRetval = TRUE;
                }
                else
                {
                    ErrorText( _T("Error: TDebugMsg::Initialize - A default debug device failed to attach!\n") );
                }
            }
            else
            {
                ErrorText( _T("Error: TDebugMsg::Initialize - format string failed construction!\n") );
            }
        }
        else
        {
            ErrorText( _T("Error: TDebugMsg::Initialize - Debug prefix string failed allocation!\n") );
        }

        //
        // If we failed then unregister, cleanup.
        //
        if (!bRetval)
        {
            Destroy();
        }
    }
    else
    {
        ErrorText( _T("Error: TDebugMsg::Initialize already initalized!\n") );
    }
}

/*++

Routine Name:

    Destroy

Routine Description:

    Destroys the internal state of this class, this function
    does the same work the destructor would.

Arguments:

    None.

Return Value:

    None.

--*/
VOID
TDebugMsg::
Destroy(
    VOID
    )
{
    //
    // Release the debug device.
    //
    while( m_pDeviceRoot )
    {
        TDebugNodeDouble *pNode = m_pDeviceRoot;
        pNode->Remove( &m_pDeviceRoot );
        TDebugFactory::Dispose( static_cast<TDebugDevice *>( pNode ) );
    }

    //
    // Release the builtin debug device.
    //
    while( m_pBuiltinDeviceRoot )
    {
        TDebugNodeDouble *pNode = m_pBuiltinDeviceRoot;
        pNode->Remove( &m_pBuiltinDeviceRoot );
        TDebugFactory::Dispose( static_cast<TDebugDevice *>( pNode ) );
    }

    //
    // Release the string objects.
    //
    INTERNAL_DELETE m_pstrPrefix;
    INTERNAL_DELETE m_pstrFileInfoFormat;
    INTERNAL_DELETE m_pstrTimeStampFormatShort;
    INTERNAL_DELETE m_pstrTimeStampFormatLong;
    INTERNAL_DELETE m_pstrThreadIdFormat;

    //
    // Indicate we are not registered.
    //
    m_eLevel                    = static_cast<EDebugLevel>(0);
    m_eBreak                    = static_cast<EDebugLevel>(0);
    m_pDeviceRoot               = NULL;
    m_pBuiltinDeviceRoot        = NULL;
    m_pstrPrefix                = NULL;
    m_pstrFileInfoFormat        = NULL;
    m_pstrTimeStampFormatShort  = NULL;
    m_pstrTimeStampFormatLong   = NULL;
    m_pstrThreadIdFormat        = NULL;
}

/*++

Routine Name:

    Attach

Routine Description:

    Attach debug device to list of output devices.

Arguments:

    uDevice             - Type of debug device to use.
    pszConfiguration    - Pointer to configuration string.

Return Value:

    TRUE debug device attached, FALSE if error occurred.

--*/
BOOL
TDebugMsg::
Attach(
    IN HANDLE           *phDevice,
    IN UINT             uDevice,
    IN LPCTSTR          pszConfiguration,
    IN TDebugNodeDouble **ppDeviceRoot
    )
{
    BOOL bRetval = FALSE;

    //
    // Get access to the debug factory.
    //
    TDebugFactory DebugFactory;

    //
    // If we failed to create the debug factory then exit.
    //
    if (DebugFactory.bValid())
    {
        //
        // Create the specified debug device using the factory.
        //
        TDebugDevice *pDebugDevice = DebugFactory.Produce(uDevice,
                                                          pszConfiguration,
                                                          m_eLevel & kDbgUnicode);

        //
        // Check if the debug device was created ok.
        //
        if (pDebugDevice)
        {
            //
            // Place this device on the debug device list.
            //
            if (ppDeviceRoot)
            {
                pDebugDevice->Insert(ppDeviceRoot);
            }
            else
            {
                pDebugDevice->Insert(&m_pDeviceRoot);
            }

            //
            // Copy back the pointer to the debug device.
            //
            if (phDevice)
            {
                *phDevice = (HANDLE)pDebugDevice;
            }

            //
            // Successfully attached debug device.
            //
            bRetval = TRUE;
        }
        else
        {
            ErrorText( _T("Error: TDebugMsg::bAttach - Debug device creation failed!\n") );
        }
    }
    else
    {
        ErrorText( _T("Error: TDebugMsg::bAttach - Debug factory creation failed!\n") );
    }

    return bRetval;
}

/*++

Routine Name:

    Detach

Routine Description:

    Detach the debug device from the device stream.

Arguments:

    phDevice - Pointer to debug device handle.

Return Value:

    None.

--*/
VOID
TDebugMsg::
Detach(
    IN HANDLE     *phDevice
    )
{
    //
    // We silently ignore non initialized devices, or null pointers.
    //
    if (phDevice && *phDevice)
    {
        //
        // Get a usable pointer.
        //
        TDebugDevice *pDebugDevice = (TDebugDevice *)*phDevice;

        //
        // Remove this device from the debug device list.
        //
        pDebugDevice->Remove( &m_pDeviceRoot );

        //
        // Dispose of the device.
        //
        TDebugFactory::Dispose( pDebugDevice );

        //

        // Mark this device as released.
        //
        *phDevice = NULL;
    }
    else
    {
        ErrorText( _T("Error: TDebugMsg::vDetach - non initialized or null pointer!\n") );
    }
}

/*++

Routine Name:

    Msg

Routine Description:

    This function is public overloaded function for
    sending the message to the output devices.

Arguments:

    eLevel          - requested message level
    pszFile         - pointer to file name where message was called
    uLine           - line number where message was called
    pszModulePrefix - message defined module prefix, used as an override
    pszMessage      - pointer to post formated message string

Return Value:

    None.

--*/
VOID
TDebugMsg::
Msg(
    IN UINT         eLevel,
    IN LPCTSTR      pszFile,
    IN UINT         uLine,
    IN LPCTSTR      pszModulePrefix,
    IN LPSTR        pszMessage
    ) const
{
    if (pszMessage)
    {
        if (Type(static_cast<EDebugLevel>(eLevel)))
        {
            StringTrait StrMessage;

            StrMessage.pszNarrow = pszMessage;

            eLevel = eLevel & ~kDbgUnicode;

            Output(static_cast<EDebugLevel>(eLevel), pszFile, uLine, pszModulePrefix, StrMessage);

            if (Break(static_cast<EDebugLevel>(eLevel)))
            {
                DebugBreak();
            }
        }

        INTERNAL_DELETE [] pszMessage;
    }
}


/*++

Routine Name:

    Msg

Routine Description:

    This function is public overloaded function for
    sending the message to the output devices.

Arguments:

    eLevel          - requested message level
    pszFile         - pointer to file name where message was called
    uLine           - line number where message was called
    pszModulePrefix - message defined module prefix, used as an override
    pszMessage      - pointer to post formated message string

Return Value:

    None.

--*/
VOID
TDebugMsg::
Msg(
    IN UINT         eLevel,
    IN LPCTSTR      pszFile,
    IN UINT         uLine,
    IN LPCTSTR      pszModulePrefix,
    IN LPWSTR       pszMessage
    ) const
{
    if (pszMessage)
    {
        if (Type(static_cast<EDebugLevel>(eLevel)))
        {
            StringTrait StrMessage;

            StrMessage.pszWide = pszMessage;

            eLevel = eLevel | kDbgUnicode;

            Output(static_cast<EDebugLevel>(eLevel), pszFile, uLine, pszModulePrefix, StrMessage);

            if (Break(static_cast<EDebugLevel>(eLevel)))
            {
                DebugBreak();
            }
        }

        INTERNAL_DELETE [] pszMessage;
    }
}

/********************************************************************

 Private member functions.

********************************************************************/

/*++

Routine Name:

    Output

Routine Description:

    Outputs the messages to the list of registred
    debug devices.

Arguments:

    eLevel          - requested message level
    pszFile         - pointer to file name where message was called
    uLine           - line number where message was called
    pszModulePrefix - message defined module prefix, used as an override
    pszMessage      - pointer to post formated message string

Return Value:

    None.

--*/
VOID
TDebugMsg::
Output(
    IN EDebugLevel  eLevel,
    IN LPCTSTR      pszFileName,
    IN UINT         uLine,
    IN LPCTSTR      pszModulePrefix,
    IN StringTrait &strMsg
    ) const
{
    TDebugString strFinal;

    //
    // Build the final output string.
    //
    if (BuildFinalString(strFinal,
                         eLevel,
                         pszModulePrefix,
                         pszFileName,
                         uLine,
                         strMsg))
    {
        //
        // Calculate the byte count (less the null terminator) of the final string.
        //
        UINT uByteCount = (m_eLevel & kDbgUnicode)
                           ? strFinal.uLen() * sizeof(WCHAR)
                           : strFinal.uLen() * sizeof(CHAR);

        LPBYTE pByte = reinterpret_cast<LPBYTE>(const_cast<LPTSTR>(static_cast<LPCTSTR>(strFinal)));

        {
            //
            // Create interator on built in device list.
            //
            TDebugNodeDouble::Iterator Iter(m_pBuiltinDeviceRoot);

            //
            // Output this string to all the built in debug devices.
            //
            for( Iter.First(); !Iter.IsDone(); Iter.Next() )
            {
                if (static_cast<TDebugDevice *>(Iter.Current())->eGetDebugType() & Globals.DebugDevices)
                {
                    static_cast<TDebugDevice *>(Iter.Current())->bOutput(uByteCount, pByte);
                }
            }
        }

        {
            //
            // Create interator on device list.
            //
            TDebugNodeDouble::Iterator Iter(m_pDeviceRoot);

            //
            // Output this string to all the registered debug devices.
            //
            for( Iter.First(); !Iter.IsDone(); Iter.Next() )
            {
                static_cast<TDebugDevice *>(Iter.Current())->bOutput( uByteCount, pByte);
            }
        }
    }
    else
    {
        ErrorText(_T("Error: TDebugMsg::vOutput - failed to build format string.\n"));
    }
}

/*++

Routine Name:

    BuildFinalString

Routine Description:

    This routing build the actual string that will be sent to the
    debug output devices.

Arguments:

    strFinal        - string refrence where to return the finale output string.
    eLevel          - debug message level.
    pszModulePrefix - per message prefix string, can be null.
    pszFileName     - file name were the message was requested.
    uLine           - line number were message was requested.
    StrMsg          - post formated message string.

Return Value:

    TRUE final string was build successfully, FALSE error occurred.

--*/
BOOL
TDebugMsg::
BuildFinalString(
    IN      TDebugString    &strFinal,
    IN      EDebugLevel     eLevel,
    IN      LPCTSTR         pszModulePrefix,
    IN      LPCTSTR         pszFileName,
    IN      UINT            uLine,
    IN      StringTrait     &StrMsg
    ) const
{
    LPCTSTR         pszFormat;
    TDebugString    strArg0;
    TDebugString    strArg1;
    TDebugString    strArg2;
    TDebugString    strArg3;

    UINT eFlags = m_eLevel | eLevel;

    if (!(eFlags & kDbgNoPrefix))
    {
        (VOID)strArg0.bUpdate(pszModulePrefix ? pszModulePrefix : *m_pstrPrefix);
    }

    if (!(eFlags & kDbgNoFileInfo))
    {
        (VOID)GetParameter(eFlags & (kDbgFileInfo | kDbgFileInfoLong), strArg1, pszFileName, uLine);
    }

    (VOID)GetParameter(eFlags & (kDbgFileInfo | kDbgFileInfoLong), strArg1, pszFileName, uLine);

    (VOID)GetParameter(eFlags & (kDbgTimeStamp | kDbgTimeStampLong), strArg2, NULL, 0);

    (VOID)GetParameter(eFlags & kDbgThreadId, strArg3, NULL, 0);

    if ((eLevel & kDbgUnicode) == (m_eLevel & kDbgUnicode))
    {
        pszFormat = _T("%s%s%s%s %s");
    }
    else
    {
        pszFormat = _T("%s%s%s%s %S");
    }

    (VOID)strFinal.bFormat( pszFormat,
                            static_cast<LPCTSTR>(strArg0),
                            static_cast<LPCTSTR>(strArg1),
                            static_cast<LPCTSTR>(strArg2),
                            static_cast<LPCTSTR>(strArg3),
                            StrMsg.pszByte);

    return strFinal.bValid();
}


/*++

Routine Name:

    GetParameter

Routine Description:

    This function get the parameter for the additinal information
    displayed in a format string.  The flags passed to the message
    class and to the message function are used a guide.

Arguments:

    eFlags      - Flags indicating what parameter to get.
    strString   - place were to return resultant string.
    pszFileName - pointer to file name to format if requested.
    uLine       - line number for file name format.

Return Value:

    TRUE parmeter was returned in strString, FALSE error.

--*/
BOOL
TDebugMsg::
GetParameter(
    IN UINT                 eFlags,
    IN OUT  TDebugString    &strString,
    IN      LPCTSTR         pszFileName,
    IN      UINT            uLine
    ) const
{
    BOOL bRetval = TRUE;

    if (eFlags & kDbgFileInfo)
    {
        bRetval = strString.bFormat(*m_pstrFileInfoFormat, StripPathFromFileName(pszFileName), uLine);
    }

    if (eFlags & kDbgFileInfoLong)
    {
        bRetval = strString.bFormat(*m_pstrFileInfoFormat, pszFileName, uLine);
    }

    if (eFlags & kDbgTimeStamp)
    {
        bRetval = strString.bFormat(*m_pstrTimeStampFormatShort, GetTickCount());
    }

    if (eFlags & kDbgTimeStampLong)
    {
        TCHAR       szBuffer[MAX_PATH];
        SYSTEMTIME  Time;

        GetSystemTime(&Time);

        bRetval = SystemTimeToTzSpecificLocalTime(NULL, &Time, &Time) &&
                  GetTimeFormat(LOCALE_USER_DEFAULT, 0, &Time, NULL, szBuffer, COUNTOF(szBuffer)) &&
                  strString.bFormat(*m_pstrTimeStampFormatLong, szBuffer);
    }

    if (eFlags & kDbgThreadId)
    {
        bRetval = strString.bFormat(*m_pstrThreadIdFormat, GetCurrentThreadId());
    }

    return bRetval;
}


/*++

Routine Name:

    SetMessageFieldFormat

Routine Description:

    This routing allows individual parts of the format string
    to have a custom format string specifier.

Arguments:

    Field - specified which format field to change.
    pszFormat - new format string, the caller must know the correct type.

Return Value:

    None.

--*/
VOID
TDebugMsg::
SetMessageFieldFormat(
    IN UINT         eField,
    IN LPTSTR       pszFormat
    )
{
    //
    // Set the new format string.
    //
    if (eField & kDbgFileInfo)
    {
        m_pstrFileInfoFormat->bUpdate(pszFormat);
    }

    if (eField & kDbgFileInfoLong)
    {
        m_pstrFileInfoFormat->bUpdate(pszFormat);
    }

    if (eField & kDbgTimeStamp)
    {
        m_pstrTimeStampFormatShort->bUpdate(pszFormat);
    }

    if (eField & kDbgTimeStampLong)
    {
        m_pstrTimeStampFormatLong->bUpdate(pszFormat);
    }

    if (eField & kDbgThreadId)
    {
        m_pstrThreadIdFormat->bUpdate(pszFormat);
    }

    //
    // If any of the format strings were cleared then
    // reset them back to the default value.
    //
    if (m_pstrFileInfoFormat->bEmpty())
    {
        m_pstrFileInfoFormat->bUpdate(kstrFileInfoFormat);
    }

    if (m_pstrTimeStampFormatShort->bEmpty())
    {
        m_pstrTimeStampFormatShort->bUpdate(kstrTimeStampFormatShort);
    }

    if (m_pstrTimeStampFormatLong->bEmpty())
    {
        m_pstrTimeStampFormatLong->bUpdate(kstrTimeStampFormatLong);
    }

    if (m_pstrThreadIdFormat->bEmpty())
    {
        m_pstrThreadIdFormat->bUpdate(kstrThreadIdFormat);
    }
}