/*
 * debspew.c - Debug spew functions module.
 */


/* Headers
 **********/

#include "project.h"
#pragma hdrstop

#ifdef DEBUG


/* Types
 ********/


/* debug flags */

typedef enum _debugdebugflags
{
   DEBUG_DFL_ENABLE_TRACE_MESSAGES  = 0x0001,

   DEBUG_DFL_LOG_TRACE_MESSAGES     = 0x0002,

   DEBUG_DFL_DUMP_THREAD_ID         = 0x0004,

   ALL_DEBUG_DFLAGS                 = (DEBUG_DFL_ENABLE_TRACE_MESSAGES |
                                       DEBUG_DFL_LOG_TRACE_MESSAGES |
                                       DEBUG_DFL_DUMP_THREAD_ID)
}
DEBUGDEBUGFLAGS;



/* Module Constants
 *******************/


#pragma data_seg(DATA_SEG_READ_ONLY)

/* debug message output log file */

PRIVATE_DATA CCHAR s_cszLogFile[]   = "debug.log";

#pragma data_seg()



/* Global Variables
 *******************/

#pragma data_seg(DATA_SEG_PER_INSTANCE)

/* parameters used by SpewOut() */

PUBLIC_DATA char SrgchSpewLeader[] = "                                                                                ";

PUBLIC_DATA DWORD g_dwSpewFlags = 0;
PUBLIC_DATA UINT g_uSpewSev = 0;
PUBLIC_DATA UINT g_uSpewLine = 0;
PUBLIC_DATA PCSTR g_pcszSpewFile = NULL;

#pragma data_seg()



/* Module Variables
 *******************/


#pragma data_seg(DATA_SEG_PER_INSTANCE)

/* TLS slot used to store stack depth for SpewOut() indentation */

PRIVATE_DATA DWORD s_dwStackDepthSlot = TLS_OUT_OF_INDEXES;

/* hack stack depth counter used until s_dwStackDepthSlot is not available */

PRIVATE_DATA ULONG s_ulcHackStackDepth = 0;

#pragma data_seg(DATA_SEG_SHARED)

/* debug flags */

PRIVATE_DATA DWORD s_dwDebugModuleFlags = 0;

#pragma data_seg(DATA_SEG_READ_ONLY)

/* .ini file switch descriptions */

PRIVATE_DATA CBOOLINISWITCH s_cbisEnableTraceMessages =
{
   IST_BOOL,
   "EnableTraceMessages",
   &s_dwDebugModuleFlags,
   DEBUG_DFL_ENABLE_TRACE_MESSAGES
};

PRIVATE_DATA CBOOLINISWITCH s_cbisLogTraceMessages =
{
   IST_BOOL,
   "LogTraceMessages",
   &s_dwDebugModuleFlags,
   DEBUG_DFL_LOG_TRACE_MESSAGES
};

PRIVATE_DATA CBOOLINISWITCH s_cbisDumpThreadID =
{
   IST_BOOL,
   "DumpThreadID",
   &s_dwDebugModuleFlags,
   DEBUG_DFL_DUMP_THREAD_ID
};

PRIVATE_DATA const PCVOID s_rgcpcvisDebugModule[] =
{
   &s_cbisLogTraceMessages,
   &s_cbisEnableTraceMessages,
   &s_cbisDumpThreadID
};

#pragma data_seg()



/***************************** Private Functions *****************************/

/* Module Prototypes
 ********************/


PRIVATE_CODE BOOL LogOutputDebugString(PCSTR);
PRIVATE_CODE BOOL IsValidSpewSev(UINT);




/*
** LogOutputDebugString()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL LogOutputDebugString(PCSTR pcsz)
{
   BOOL bResult = FALSE;
   UINT ucb;
   char rgchLogFile[MAX_PATH_LEN];

   ASSERT(IS_VALID_STRING_PTR(pcsz, CSTR));

   ucb = GetWindowsDirectory(rgchLogFile, sizeof(rgchLogFile));

   if (ucb > 0 && ucb < sizeof(rgchLogFile))
   {
      HANDLE hfLog;


      lstrcat(rgchLogFile, "\\");
      lstrcat(rgchLogFile, s_cszLogFile);

      hfLog = CreateFile(rgchLogFile, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS,
                         FILE_FLAG_WRITE_THROUGH, NULL);

      if (hfLog != INVALID_HANDLE_VALUE)
      {
         if (SetFilePointer(hfLog, 0, NULL, FILE_END) != INVALID_SEEK_POSITION)
         {
            DWORD dwcbWritten;

            bResult = WriteFile(hfLog, pcsz, lstrlen(pcsz), &dwcbWritten, NULL);

            if (! CloseHandle(hfLog) && bResult)
               bResult = FALSE;
         }
      }
   }

   return(bResult);
}


/*
** IsValidSpewSev()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL IsValidSpewSev(UINT uSpewSev)
{
   BOOL bResult;

   switch (uSpewSev)
   {
      case SPEW_TRACE:
      case SPEW_WARNING:
      case SPEW_ERROR:
      case SPEW_FATAL:
         bResult = TRUE;
         break;

      default:
         ERROR_OUT(("IsValidSpewSev(): Invalid debug spew severity %u.",
                    uSpewSev));
         bResult = FALSE;
         break;
   }

   return(bResult);
}



/****************************** Public Functions *****************************/



/*
** SetDebugModuleIniSwitches()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL SetDebugModuleIniSwitches(void)
{
   BOOL bResult;

   bResult = SetIniSwitches(s_rgcpcvisDebugModule,
                            ARRAY_ELEMENTS(s_rgcpcvisDebugModule));

   ASSERT(FLAGS_ARE_VALID(s_dwDebugModuleFlags, ALL_DEBUG_DFLAGS));

   return(bResult);
}


/*
** InitDebugModule()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL InitDebugModule(void)
{
   ASSERT(s_dwStackDepthSlot == TLS_OUT_OF_INDEXES);

   s_dwStackDepthSlot = TlsAlloc();

   if (s_dwStackDepthSlot != TLS_OUT_OF_INDEXES)
   {
      EVAL(TlsSetValue(s_dwStackDepthSlot, IntToPtr(s_ulcHackStackDepth)));

      TRACE_OUT(("InitDebugModule(): Using thread local storage slot %lu for debug stack depth counter.",
                 s_dwStackDepthSlot));
   }
   else
      WARNING_OUT(("InitDebugModule(): TlsAlloc() failed to allocate thread local storage for debug stack depth counter."));

   return(TRUE);
}


/*
** ExitDebugModule()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE void ExitDebugModule(void)
{
   if (s_dwStackDepthSlot != TLS_OUT_OF_INDEXES)
   {
      s_ulcHackStackDepth = PtrToUlong(TlsGetValue(s_dwStackDepthSlot));

      /* Leave s_ulcHackStackDepth == 0 if TlsGetValue() fails. */

      EVAL(TlsFree(s_dwStackDepthSlot));
      s_dwStackDepthSlot = TLS_OUT_OF_INDEXES;
   }

   return;
}


/*
** StackEnter()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE void StackEnter(void)
{
   if (s_dwStackDepthSlot != TLS_OUT_OF_INDEXES)
   {
      ULONG ulcDepth;

      ulcDepth = PtrToUlong(TlsGetValue(s_dwStackDepthSlot));

      ASSERT(ulcDepth < ULONG_MAX);

      EVAL(TlsSetValue(s_dwStackDepthSlot, IntToPtr(ulcDepth + 1)));
   }
   else
   {
      ASSERT(s_ulcHackStackDepth < ULONG_MAX);
      s_ulcHackStackDepth++;
   }

   return;
}


/*
** StackLeave()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE void StackLeave(void)
{
   if (s_dwStackDepthSlot != TLS_OUT_OF_INDEXES)
   {
      ULONG ulcDepth;

      ulcDepth = PtrToUlong(TlsGetValue(s_dwStackDepthSlot));

      if (EVAL(ulcDepth > 0))
         EVAL(TlsSetValue(s_dwStackDepthSlot, IntToPtr(ulcDepth - 1)));
   }
   else
   {
      if (EVAL(s_ulcHackStackDepth > 0))
         s_ulcHackStackDepth--;
   }

   return;
}


/*
** GetStackDepth()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE ULONG GetStackDepth(void)
{
   ULONG ulcDepth;

   if (s_dwStackDepthSlot != TLS_OUT_OF_INDEXES)
      ulcDepth = PtrToUlong(TlsGetValue(s_dwStackDepthSlot));
   else
      ulcDepth = s_ulcHackStackDepth;

   return(ulcDepth);
}


/*
** SpewOut()
**
** Spews out a formatted message to the debug terminal.
**
** Arguments:     pcszFormat - pointer to wvsprintf() format string
**                ... - formatting arguments ala wvsprintf()
**
** Returns:       void
**
** Side Effects:  none
**
** N.b., this function assumes the global variables g_dwSpewFlags, g_uSpewSev,
** g_pcszSpewModule, g_pcszSpewFile, and g_pcszSpewLine are filled in.
**
** SpewOut() uses global variables to set the message parameters in order to
** permit printf()-style macro expansion.
*/
PUBLIC_CODE void SpewOut(PCSTR pcszFormat, ...)
{
   ASSERT(IS_VALID_STRING_PTR(pcszFormat, CSTR));

   ASSERT(FLAGS_ARE_VALID(g_dwSpewFlags, ALL_SPEW_FLAGS));
   ASSERT(IsValidSpewSev(g_uSpewSev));
   ASSERT(IS_FLAG_CLEAR(g_dwSpewFlags, SPEW_FL_SPEW_LOCATION) ||
          (IS_VALID_STRING_PTR(g_pcszSpewFile, CSTR) &&
           IS_VALID_STRING_PTR(g_pcszSpewModule, CSTR)));

   if (g_uSpewSev != SPEW_TRACE || IS_FLAG_SET(s_dwDebugModuleFlags, DEBUG_DFL_ENABLE_TRACE_MESSAGES))
   {
      int nMsgLen;
      char rgchMsg[1024];

      va_list nextArg;


      if (IS_FLAG_SET(g_dwSpewFlags, SPEW_FL_SPEW_PREFIX))
      {
         ULONG ulcStackDepth;
         char chReplaced;
         PSTR pszSpewLeaderEnd;
         PCSTR pcszSpewPrefix;

         /* Build spew message space leader string. */

         ulcStackDepth = GetStackDepth();

         if (ulcStackDepth < sizeof(SrgchSpewLeader))
            pszSpewLeaderEnd = SrgchSpewLeader + ulcStackDepth;
         else
            pszSpewLeaderEnd = SrgchSpewLeader + sizeof(SrgchSpewLeader) - 1;

         chReplaced = *pszSpewLeaderEnd;
         *pszSpewLeaderEnd = '\0';

         /* Determine spew prefix. */

         switch (g_uSpewSev)
         {
            case SPEW_TRACE:
               pcszSpewPrefix = "t";
               break;

            case SPEW_WARNING:
               pcszSpewPrefix = "w";
               break;

            case SPEW_ERROR:
               pcszSpewPrefix = "e";
               break;

            case SPEW_FATAL:
               pcszSpewPrefix = "f";
               break;

            default:
               pcszSpewPrefix = "u";
               ERROR_OUT(("SpewOut(): Invalid g_uSpewSev %u.",
                          g_uSpewSev));
               break;
         }

         nMsgLen = wsprintf(rgchMsg, "%s%s %s ", SrgchSpewLeader, pcszSpewPrefix, g_pcszSpewModule);

         /* Restore spew leader. */

         *pszSpewLeaderEnd = chReplaced;

         ASSERT(nMsgLen < sizeof(rgchMsg));
      }
      else
         nMsgLen = 0;

      /* Append thread ID. */

      if (IS_FLAG_SET(s_dwDebugModuleFlags, DEBUG_DFL_DUMP_THREAD_ID))
      {
         nMsgLen += wsprintf(rgchMsg + nMsgLen, "%#lx ", GetCurrentThreadId());

         ASSERT(nMsgLen < sizeof(rgchMsg));
      }

      /* Build position string. */

      if (IS_FLAG_SET(g_dwSpewFlags, SPEW_FL_SPEW_LOCATION))
      {
         nMsgLen += wsprintf(rgchMsg + nMsgLen, "(%s line %u): ", g_pcszSpewFile, g_uSpewLine);

         ASSERT(nMsgLen < sizeof(rgchMsg));
      }

      /* Append message string. */


      va_start(nextArg, pcszFormat);
      nMsgLen += wvsprintf(rgchMsg + nMsgLen, pcszFormat, nextArg);
      va_end(nextArg);

      ASSERT(nMsgLen < sizeof(rgchMsg));

      if (g_uSpewSev == SPEW_ERROR ||
          g_uSpewSev == SPEW_FATAL)
      {
         nMsgLen += wsprintf(rgchMsg + nMsgLen, " (GetLastError() == %lu)", GetLastError());

         ASSERT(nMsgLen < sizeof(rgchMsg));
      }

      nMsgLen += wsprintf(rgchMsg + nMsgLen, "\r\n");

      ASSERT(nMsgLen < sizeof(rgchMsg));

      OutputDebugString(rgchMsg);

      if (IS_FLAG_SET(s_dwDebugModuleFlags, DEBUG_DFL_LOG_TRACE_MESSAGES))
         LogOutputDebugString(rgchMsg);
   }

   /* Break here on errors and fatal errors. */
#ifndef MAINWIN
   if (g_uSpewSev == SPEW_ERROR || g_uSpewSev == SPEW_FATAL)
      DebugBreak();
#endif
   return;
}

#endif   /* DEBUG */