/////////////////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 1998 Active Voice Corporation. All Rights Reserved. 
//
// Active Agent(r) and Unified Communications(tm) are trademarks of Active Voice Corporation.
//
// Other brand and product names used herein are trademarks of their respective owners.
//
// The entire program and user interface including the structure, sequence, selection, 
// and arrangement of the dialog, the exclusively "yes" and "no" choices represented 
// by "1" and "2," and each dialog message are protected by copyrights registered in 
// the United States and by international treaties.
//
// Protected by one or more of the following United States patents: 5,070,526, 5,488,650, 
// 5,434,906, 5,581,604, 5,533,102, 5,568,540, 5,625,676, 5,651,054.
//
// Active Voice Corporation
// Seattle, Washington
// USA
//
/////////////////////////////////////////////////////////////////////////////////////////

////
// trace.c - debug trace functions
////

#ifndef NOTRACE

#include "winlocal.h"

#include <stdlib.h>
#include <stdarg.h>

#include "trace.h"
#include "mem.h"
#include "sys.h"
#include "str.h"

////
//	private definitions
////

// wOutputTo values
//
#define TRACE_OUTPUTNONE			0x0000
#define TRACE_OUTPUTDEBUGSTRING		0x0001
#define TRACE_OUTPUTCOMM			0x0002
#if 0 // no longer supported
#define TRACE_OUTPUTFILE			0x0004
#endif
#ifdef _WIN32
#define TRACE_OUTPUTCONSOLE			0x0008
#endif

// trace control struct
//
typedef struct TRACE
{
	DWORD dwVersion;
	HINSTANCE hInst;
	HTASK hTask;
	int nLevel;
	int wOutputTo;
#ifdef TRACE_OUTPUTFILE
	HFIL hFile;
#endif
#ifdef _WIN32
	HANDLE hConsole;
#endif
	int hComm;
	LPTSTR lpszTemp;
} TRACE, FAR *LPTRACE;

// shared trace engine handle
//
static LPTRACE lpTraceShare = NULL;
static int cShareUsage = 0;

#define TRACE_SECTION TEXT("TRACE")
#define TRACE_PROFILE TraceGetProfile()

// helper functions
//
static LPTRACE TraceGetPtr(HTRACE hTrace);
static HTRACE TraceGetHandle(LPTRACE lpTrace);
static int TraceError(LPCTSTR lpszFormat, ...);
static LPTSTR TraceGetProfile(void);

////
//	public functions
////

// TraceInit - initialize trace engine
//		<dwVersion>			(i) must be TRACE_VERSION
//		<hInst>				(i) instance of calling module
// return handle to trace engine (NULL if error)
//
// NOTE: The level and destination of trace output is determined
// by values found in the file TRACE.INI in the Windows directory.
// TRACE.INI is expected to have the following format:
//
//		[TRACE]
//		Level=0						{TRACE_MINLEVEL...TRACE_MAXLEVEL}
//		OutputTo=					OutputDebugString()
//				=COM1				COM1:9600,n,8,1
//				=COM2:2400,n,8,1	specified comm device
//				=filename			specified file
#ifdef _WIN32
//				=console			stdout
#endif
//
HTRACE DLLEXPORT WINAPI TraceInit(DWORD dwVersion, HINSTANCE hInst)
{
	BOOL fSuccess = TRUE;
	LPTRACE lpTrace = NULL;
	TCHAR szOutputTo[_MAX_PATH];
#ifdef _WIN32
	BOOL fShare = TRUE; // FALSE;
#else
	BOOL fShare = TRUE;
#endif
	int nLevel = -1;
	LPTSTR lpszOutputTo = NULL;

	if (dwVersion != TRACE_VERSION)
		fSuccess = FALSE;

	else if (hInst == NULL)
		fSuccess = FALSE;

	else if (nLevel != -1 &&
		(nLevel < TRACE_MINLEVEL || nLevel > TRACE_MAXLEVEL))
		fSuccess = FALSE;

	// if a shared trace engine already exists,
	// use it rather than create another one
	//
	else if (fShare && cShareUsage > 0 && lpTraceShare != NULL)
		lpTrace = lpTraceShare;

#if 0 // can't call mem functions, because they require trace functions
	else if ((lpTrace = (LPTRACE) MemAlloc(NULL, sizeof(TRACE), 0)) == NULL)
#else
#ifdef _WIN32
	else if ((lpTrace = (LPTRACE) HeapAlloc(GetProcessHeap(),
		HEAP_ZERO_MEMORY, sizeof(TRACE))) == NULL)
#else
	else if ((lpTrace = (LPTRACE) GlobalAllocPtr(GMEM_MOVEABLE |
		GMEM_ZEROINIT, sizeof(TRACE))) == NULL)
#endif
#endif
		fSuccess = FALSE;

	else
	{
		lpTrace->dwVersion = dwVersion;
		lpTrace->hInst = hInst;
		lpTrace->hTask = GetCurrentTask();
		lpTrace->nLevel = nLevel != -1 ? nLevel :
			GetPrivateProfileInt(TRACE_SECTION, TEXT("Level"), 0, TRACE_PROFILE);
		lpTrace->wOutputTo = TRACE_OUTPUTNONE;
#ifdef TRACE_OUTPUTFILE
		lpTrace->hFile = NULL;
#endif
#ifdef _WIN32
		lpTrace->hConsole = NULL;
#endif
		lpTrace->hComm = -1;
		lpTrace->lpszTemp = NULL;

		// use the specified destination if possible
		//
		if (lpszOutputTo != NULL)
			StrNCpy(szOutputTo, lpszOutputTo, SIZEOFARRAY(szOutputTo));

		// else use the last known destination
		//
		else
		{
			GetPrivateProfileString(TRACE_SECTION, TEXT("OutputTo"), TEXT(""),
				szOutputTo, SIZEOFARRAY(szOutputTo), TRACE_PROFILE);
		}

		// use OutputDebugString() if destination == ""
		//
		if (*szOutputTo == '\0')
			lpTrace->wOutputTo = TRACE_OUTPUTDEBUGSTRING;

		// use standard output console if specified
		//
		else if (StrICmp(szOutputTo, TEXT("Console")) == 0)
		{
#ifdef _WIN32
			COORD coord;

			lpTrace->wOutputTo = TRACE_OUTPUTCONSOLE;

			AllocConsole();
			lpTrace->hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

			coord.X = 80;
			coord.Y = 1000;
			SetConsoleScreenBufferSize(lpTrace->hConsole, coord);
#else
			lpTrace->wOutputTo = TRACE_OUTPUTDEBUGSTRING;
#endif
		}

		// use serial comm device if destination starts with "COMx"
		//
		else if (StrNICmp(szOutputTo, TEXT("COM"), 3) == 0 &&
			ChrIsDigit(*(szOutputTo + 3)))
		{
// Comm functions not available under WIN32
//
#ifdef _WIN32
			lpTrace->wOutputTo = TRACE_OUTPUTDEBUGSTRING;
#else
			TCHAR szComX[16];
			DCB dcb;
			int iError;

			StrNCpy(szComX, szOutputTo, 5);
			*(szComX + 5) = '\0';

			// convert "COM1" to "COM1:"
			//
			if (*(szOutputTo + 4) == '\0')
				StrCat(szOutputTo, TEXT(":"));

			// convert "COM1:" to "COM1:9600,n,8,1"
			//
			if (*(szOutputTo + 5) == '\0')
				StrCat(szOutputTo, TEXT("9600,n,8,1"));

			// [From the WinSDK KnowledgeBase PSS ID Number: Q102642]
			// The cbInQueue and cbOutQueue parameters of OpenComm() are
			// both type UINT and should be valid up to 64K. However,
			// values greater than or equal to 32K cause strange behavior.
			//
			if ((lpTrace->hComm = OpenComm(szComX, 1024, 32767)) < 0)
			{
				TraceError(TEXT("OpenComm error (%d)\n"), lpTrace->hComm);
				lpTrace->hComm = -1;
				fSuccess = FALSE;
			}

			else if ((iError = BuildCommDCB(szOutputTo, &dcb)) != 0)
			{
				TraceError(TEXT("BuildCommDCB error (%d)\n"), iError);
				fSuccess = FALSE;
			}

			else if ((iError = SetCommState(&dcb)) != 0)
			{
				TraceError(TEXT("SetCommState error (%d)\n"), iError);
				fSuccess = FALSE;
			}

			else
				lpTrace->wOutputTo = TRACE_OUTPUTCOMM;
#endif
		}

#ifdef TRACE_OUTPUTFILE
		// else assume the string must be a file name
		//
		else
		{
			if ((lpTrace->hFile = FileCreate(szOutputTo, 0, !fShare)) == NULL)
			{
				TraceError(TEXT("FileCreate error (%s)\n"), (LPTSTR) szOutputTo);
				fSuccess = FALSE;
			}

			else
				lpTrace->wOutputTo = TRACE_OUTPUTFILE;
		}
#else
		else
		{
			TraceError(TEXT("Unknown trace OutputTo (%s)\n"), (LPTSTR) szOutputTo);
			fSuccess = FALSE;
		}
#endif

		if (fSuccess &&
#if 0 // can't call mem functions, because they require trace functions
			(lpTrace->lpszTemp = (LPTSTR) MemAlloc(NULL,
			1024 * sizeof(TCHAR), 0)) == NULL)
#else
#ifdef _WIN32
			(lpTrace->lpszTemp = (LPTSTR) HeapAlloc(GetProcessHeap(),
			HEAP_ZERO_MEMORY, 1024 * sizeof(TCHAR))) == NULL)
#else
			(lpTrace->lpszTemp = (LPTSTR) GlobalAllocPtr(
			GMEM_MOVEABLE | GMEM_ZEROINIT, 1024 * sizeof(TCHAR))) == NULL)
#endif
#endif
			fSuccess = FALSE;
	}

	if (!fSuccess)
	{
		TraceTerm(TraceGetHandle(lpTrace));
		lpTrace = NULL;
	}

	// keep track of total modules sharing a task engine handle
	//
	if (fSuccess && fShare)
	{
		if (++cShareUsage == 1)
			lpTraceShare = lpTrace;
	}

	return fSuccess ? TraceGetHandle(lpTrace) : NULL;
}

// TraceTerm - shut down trace engine
//		<hTrace>			(i) handle returned from TraceInit or NULL
// return 0 if success
//
int DLLEXPORT WINAPI TraceTerm(HTRACE hTrace)
{
	BOOL fSuccess = TRUE;
	LPTRACE lpTrace;

	if ((lpTrace = TraceGetPtr(hTrace)) == NULL)
		fSuccess = FALSE;

	// only shut down trace engine if handle
	// is not shared (or is no longer being shared)
	//
	else if (lpTrace != lpTraceShare || --cShareUsage <= 0)
	{
#ifndef _WIN32
		int iError;
#endif
		// shared trace engine handle no longer valid
		//
		if (cShareUsage <= 0)
			lpTraceShare = NULL;

// Comm functions not available under WIN32
//
#ifndef _WIN32
		if (lpTrace->hComm != -1 &&
			(iError = CloseComm(lpTrace->hComm)) != 0)
		{
			TraceError(TEXT("CloseComm error (%d)\n"), iError);
			fSuccess = FALSE;
		}
		else
			lpTrace->hComm = -1;
#endif

#ifdef _WIN32
		if (lpTrace->hConsole != NULL)
		{
			FreeConsole();
			lpTrace->hConsole = NULL;
		}
#endif

#ifdef TRACE_OUTPUTFILE
		if (lpTrace->hFile != NULL &&
			FileClose(lpTrace->hFile) != 0)
		{
			TraceError(TEXT("FileClose error\n"));
			fSuccess = FALSE;
		}
		else
			lpTrace->hFile = NULL;
#endif

		if (lpTrace->lpszTemp != NULL &&
#if 0 // can't call mem functions, because they require trace functions
			(lpTrace->lpszTemp = MemFree(NULL, lpTrace->lpszTemp)) != NULL)
#else
#ifdef _WIN32
			(!HeapFree(GetProcessHeap(), 0, lpTrace->lpszTemp)))
#else
			(GlobalFreePtr(lpTrace->lpszTemp) != 0))
#endif
#endif
			fSuccess = FALSE;

		lpTrace->wOutputTo = TRACE_OUTPUTNONE;

#if 0 // can't call mem functions, because they require trace functions
		if ((lpTrace = MemFree(NULL, lpTrace)) != NULL)
#else
#ifdef _WIN32
		if (!HeapFree(GetProcessHeap(), 0, lpTrace->lpszTemp))
#else
		if (GlobalFreePtr(lpTrace->lpszTemp) != 0)
#endif
#endif
			fSuccess = FALSE;
	}

	return fSuccess ? 0 : -1;
}

// TraceGetLevel - get current trace level
//		<hTrace>			(i) handle returned from TraceInit or NULL
// return trace level (-1 if error)
//
int DLLEXPORT WINAPI TraceGetLevel(HTRACE hTrace)
{
	BOOL fSuccess = TRUE;
	LPTRACE lpTrace;

	if ((lpTrace = TraceGetPtr(hTrace)) == NULL)
		fSuccess = FALSE;

	return fSuccess ? lpTrace->nLevel : -1;
}

// TraceSetLevel - set new trace level (-1 if error)
//		<hTrace>			(i) handle returned from TraceInit or NULL
//		<nLevel>			(i) new trace level {TRACE_MINLEVEL...TRACE_MAXLEVEL}
// return 0 if success
//
int DLLEXPORT WINAPI TraceSetLevel(HTRACE hTrace, int nLevel)
{
	BOOL fSuccess = TRUE;
	LPTRACE lpTrace;

	if ((lpTrace = TraceGetPtr(hTrace)) == NULL)
		fSuccess = FALSE;

	else if (nLevel < TRACE_MINLEVEL || nLevel > TRACE_MAXLEVEL)
		fSuccess = FALSE;

	else
	{
		TCHAR szLevel[17];

		lpTrace->nLevel = nLevel;

		// save the level for next time
		//
		StrItoA(lpTrace->nLevel, szLevel, 10);
		WritePrivateProfileString(TRACE_SECTION, TEXT("Level"), szLevel, TRACE_PROFILE);

		// display new trace level whenever it changes
		//
		TracePrintf_1(hTrace, 1, TEXT("TraceLevel=%d\n"),
			(int) lpTrace->nLevel);
	}

	return fSuccess ? 0 : -1;
}

// TraceOutput - output debug string
//		<hTrace>			(i) handle returned from TraceInit or NULL
//		<nLevel>			(i) output only if current trace level is >= nLevel
//		<lpszText>			(i) string to output
// return 0 if success
//
int DLLEXPORT WINAPI TraceOutput(HTRACE hTrace, int nLevel, LPCTSTR lpszText)
{
	BOOL fSuccess = TRUE;
	LPTRACE lpTrace;

	if ((lpTrace = TraceGetPtr(hTrace)) == NULL)
		fSuccess = FALSE;

	else if (lpszText == NULL)
		fSuccess = FALSE;

	else if (nLevel > 0 && nLevel <= lpTrace->nLevel)
	{
		switch (lpTrace->wOutputTo)
		{
			case TRACE_OUTPUTNONE:
				break;

			case TRACE_OUTPUTDEBUGSTRING:
				OutputDebugString(lpszText);
				break;
#ifdef _WIN32
			case TRACE_OUTPUTCONSOLE:
				if (lpTrace->hConsole != NULL)
				{
					DWORD dwBytes;
					WriteFile(lpTrace->hConsole, lpszText, StrLen(lpszText), &dwBytes, NULL);
				}
				break;
#endif

			case TRACE_OUTPUTCOMM:
// Comm functions not available under WIN32
//
#ifdef _WIN32
				OutputDebugString(lpszText);
#else
				if (lpTrace->hComm != -1)
				{
					LPCTSTR lpsz;
					TCHAR chReturn = '\r';

					for (lpsz = lpszText; *lpsz != '\0'; lpsz = StrNextChr(lpsz))
					{
						if ((*lpsz == '\n' &&
							WriteComm(lpTrace->hComm, &chReturn, 1) < 0) ||
							WriteComm(lpTrace->hComm, lpsz, 1) <= 0)
						{
							COMSTAT comstat;
							GetCommError(lpTrace->hComm, &comstat);
							TraceError(TEXT("WriteComm error (%u, %u, %u) %s\n"),
								(UINT) comstat.status,
								(UINT) comstat.cbInQue,
								(UINT) comstat.cbOutQue,
								(LPTSTR) lpszText);
							fSuccess = FALSE;
							break;
						}
					}
				}
#endif
				break;

#ifdef TRACE_OUTPUTFILE
			case TRACE_OUTPUTFILE:
				if (lpTrace->hFile != NULL)
				{
					LPCTSTR lpsz;
					TCHAR chReturn = '\r';

					for (lpsz = lpszText; *lpsz != '\0'; lpsz = StrNextChr(lpsz))
					{
						if ((*lpsz == '\n' &&
							FileWrite(lpTrace->hFile, &chReturn, 1) == -1) ||
							FileWrite(lpTrace->hFile, lpsz, 1) == -1)
						{
							TraceError(TEXT("FileWrite error: %s\n"),
								(LPTSTR) lpszText);
							fSuccess = FALSE;
							break;
						}
					}
				}
				break;
#endif

			default:
				break;
		}
	}

	return fSuccess ? 0 : -1;
}

// TracePrintf - output formatted debug string
//		<hTrace>			(i) handle returned from TraceInit or NULL
//		<nLevel>			(i) output only if current trace level is >= nLevel
//		<lpszFormat,...>	(i) format string and arguments to output
// return 0 if success
//
int DLLEXPORT FAR CDECL TracePrintf(HTRACE hTrace, int nLevel, LPCTSTR lpszFormat, ...)
{
	BOOL fSuccess = TRUE;
	LPTRACE lpTrace;

	if ((lpTrace = TraceGetPtr(hTrace)) == NULL)
		fSuccess = FALSE;

	else if (nLevel <= lpTrace->nLevel)
	{	                    
	    va_list args;

	    va_start(args, lpszFormat);
	   	wvsprintf(lpTrace->lpszTemp, lpszFormat, args);
	    va_end(args);

		if (TraceOutput(hTrace, nLevel, lpTrace->lpszTemp) != 0)
			fSuccess = FALSE;
	}

	return fSuccess ? 0 : -1;
}

////
//	private functions
////

// TraceGetPtr - convert trace handle to trace pointer
//		<hTrace>			(i) handle returned from TraceInit or NULL
// return trace pointer (NULL if error)
//
static LPTRACE TraceGetPtr(HTRACE hTrace)
{
	BOOL fSuccess = TRUE;
	LPTRACE lpTrace;

	// use shared trace handle if no other supplied
	//
	if (hTrace == NULL && lpTraceShare != NULL)
		lpTrace = lpTraceShare;

	// create shared trace handle if no other supplied
	//
	else if (hTrace == NULL && lpTraceShare == NULL &&
		(hTrace = TraceInit(TRACE_VERSION, SysGetTaskInstance(NULL))) == NULL)
		fSuccess = FALSE;

	else if ((lpTrace = (LPTRACE) hTrace) == NULL)
		fSuccess = FALSE;

	// note: check for good pointer made only if not using lpTraceShare
	//
	else if (lpTrace != lpTraceShare &&
		IsBadWritePtr(lpTrace, sizeof(TRACE)))
		fSuccess = FALSE;

#ifdef CHECKTASK
	// make sure current task owns the trace handle
	// except when shared trace handle is used
	//
	if (fSuccess && lpTrace != lpTraceShare &&
		lpTrace->hTask != GetCurrentTask())
		fSuccess = FALSE;
#endif

	return fSuccess ? lpTrace : NULL;
}

// TraceGetHandle - convert trace pointer to trace handle
//		<lpTrace>			(i) pointer to TRACE struct
// return trace handle (NULL if error)
//
static HTRACE TraceGetHandle(LPTRACE lpTrace)
{
	BOOL fSuccess = TRUE;
	HTRACE hTrace;

	if ((hTrace = (HTRACE) lpTrace) == NULL)
		fSuccess = FALSE;

	return fSuccess ? hTrace : NULL;
}

// TraceError - display formatted trace error string
//		<lpszFormat...>		(i) format string and arguments to output
// return 0 if success
//
static int TraceError(LPCTSTR lpszFormat, ...)
{
	BOOL fSuccess = TRUE;
    va_list args;
	TCHAR lpszTemp[256];

    va_start(args, lpszFormat);
   	wvsprintf(lpszTemp, lpszFormat, args);
    va_end(args);

	OutputDebugString(lpszTemp);

	return fSuccess ? 0 : -1;
}

// TraceGetProfile - get trace ini file name
// return pointer to file name
//
static LPTSTR TraceGetProfile(void)
{
	return TEXT("trace.ini");
}

#endif // #ifndef NOTRACE