/******************************************************************************
 *                                                                         
 *               (C) COPYRIGHT Pacific MindWorks, Inc. 2001-2005
 *                          All rights reserved.
 *
 *****************************************************************************/

#ifndef __NTLTRACE_INCLUDED
#define __NTLTRACE_INCLUDED

#include <atlpath.h>
#include <atltime.h>
#include <atlfile.h>

#import "msxml4.dll"
using namespace MSXML2;

///////////////////////////////////////////////////////////////////////////////
//	CTracer

typedef enum
{
	TraceStateRunning,
	TraceStateStopped,
	TraceStateDataFull
} TraceState;

class CTracer
{
public:
	CTracer();
	~CTracer();

	virtual TraceState GetTraceState() const;
	virtual void SetTraceState(TraceState eTraceState);

	virtual bool GetTraceOn() const;

	virtual bool GetTraceEnabled() const;
	virtual HRESULT SetTraceEnabled(bool bTraceEnabled) = 0;

	virtual int GetTraceSizeMax() const; 
	virtual HRESULT SetTraceSizeMax(int nTraceSizeMax); 

	virtual bool GetTraceArray() const; 
	virtual HRESULT SetTraceArray(bool bTraceArray); 

	virtual int GetTraceArraySizeMax() const;
	virtual HRESULT SetTraceArraySizeMax(int nTraceArraySizeMax);

	virtual int GetTraceSaveInterval() const;
	virtual HRESULT SetTraceSaveInterval(int nTraceSaveInterval);

	virtual MSXML2::IXMLDOMDocument2Ptr GetTraceDocument();
	virtual void SetTraceDocument(MSXML2::IXMLDOMDocument2Ptr spXmlDoc);

protected:
	TraceState m_eTraceState;
	bool m_bTraceEnabled;
	int m_nTraceSizeMax;
	bool m_bTraceArray;
	int m_nTraceArraySizeMax;
	int m_nTraceSaveInterval;
	
	MSXML2::IXMLDOMDocument2Ptr m_spXmlDoc;
};


inline CTracer::CTracer()
{
	m_eTraceState = TraceStateStopped;
	m_bTraceEnabled = false ;
	m_nTraceSizeMax = 1000000;
	m_bTraceArray = false;
	m_nTraceArraySizeMax = 10;
	m_nTraceSaveInterval = 1;
}

inline CTracer::~CTracer()
{
}

inline TraceState CTracer::GetTraceState() const
{
	return m_eTraceState;
}

inline void CTracer::SetTraceState(TraceState eTraceState)
{
	m_eTraceState = eTraceState;
}

inline bool CTracer::GetTraceOn() const
{
	return (m_eTraceState == TraceStateRunning);
}

inline bool CTracer::GetTraceEnabled() const
{ 
	return m_bTraceEnabled; 
}

inline int CTracer::GetTraceSizeMax() const 
{ 
	return m_nTraceSizeMax; 
}

inline HRESULT CTracer::SetTraceSizeMax(int nTraceSizeMax) 
{ 
	m_nTraceSizeMax = nTraceSizeMax;
	
	return S_OK; 
}

inline bool CTracer::GetTraceArray() const 
{ 
	return m_bTraceArray; 
}

inline HRESULT CTracer::SetTraceArray(bool bTraceArray) 
{ 
	m_bTraceArray = bTraceArray; 
	
	return S_OK;
}

inline int CTracer::GetTraceArraySizeMax() const 
{ 
	return m_nTraceArraySizeMax; 
}

inline HRESULT CTracer::SetTraceArraySizeMax(int nTraceArraySizeMax) 
{ 
	m_nTraceArraySizeMax = nTraceArraySizeMax;
	
	return S_OK;
}

inline int CTracer::GetTraceSaveInterval() const
{
	return m_nTraceSaveInterval;
}

inline HRESULT CTracer::SetTraceSaveInterval(int nTraceSaveInterval)
{
	m_nTraceSaveInterval = nTraceSaveInterval;

	return S_OK;
}

inline 	MSXML2::IXMLDOMDocument2Ptr CTracer::GetTraceDocument()
{
	return m_spXmlDoc;
}

inline void CTracer::SetTraceDocument(MSXML2::IXMLDOMDocument2Ptr spXmlDoc)
{
	m_spXmlDoc = spXmlDoc;
}

///////////////////////////////////////////////////////////////////////////////
//	CTraceOwner

class CTraceOwner
	: public CTracer
{
public:
	CTraceOwner();
	virtual ~CTraceOwner();

	virtual HRESULT SetTraceEnabled(bool bTraceEnabled);

	const CString& GetTraceName() const;
	HRESULT SetTraceName(const CString& strTraceName);

	const CPath& GetTraceTransformPath();
	HRESULT SetTraceTransformPath(const CPath& pathTransform);

	HRESULT StartTrace(const CString& strDriverIdentifier, const CString& strTraceName);
	HRESULT StopTrace(TraceState eTraceState = TraceStateStopped);

	HRESULT InitializeTraceData();
	HRESULT CloseTraceData();

	HRESULT CommitTraceData();
	HRESULT SaveTraceData();

	MSXML2::IXMLDOMElementPtr GetCurrentElement() const;
	HRESULT SetCurrentElement(MSXML2::IXMLDOMElementPtr spElement);

protected:
	CPath GetTraceLogPathFromTraceName(const CString& strTraceName) const;
	CPath GetTraceLogPathFromTraceName(const CString& strTraceName, const CTime& timeStamp) const;
	CPath GetCurrentTraceDirectory() const;
	HRESULT LoadOrCreateTraceLog(const CPath& pathTraceLog);
	bool TraceNameChanged(const CString& strNewTraceName);
	CPath GetTraceTransformPathFromFileName(const CString& strFileName) const;
	CPath GetStandardTraceTransformDirectory() const;

protected:
	CString m_strTraceName;
	CPath m_pathTraceLog;
	CPath m_pathTraceTransform;

	CTime m_startTime;
	CTime m_stopTime;
	CTime m_timeLastSave;

	MSXML2::IXMLDOMElementPtr m_spCurrentElement;
};

inline CTraceOwner::CTraceOwner()
{
}

inline CTraceOwner::~CTraceOwner()
{
	if (m_eTraceState == TraceStateRunning)
	{
		StopTrace();
	}
}

inline HRESULT CTraceOwner::SetTraceEnabled(bool bTraceEnabled)	
{ 
	HRESULT hr = S_OK;
	try
	{
		if (bTraceEnabled && !m_bTraceEnabled)			// Don't enable it if it's already enabled
		{
			ATLASSERT(m_spXmlDoc == NULL);
			if (m_spXmlDoc == NULL)
			{
				hr = InitializeTraceData();
				ATLASSERT(SUCCEEDED(hr));
			}
		}
		else if (!bTraceEnabled && m_bTraceEnabled)		// Don't disable it if it's already disabled
		{
			// Make sure the tracer is stopped first
			hr = StopTrace();
			ATLASSERT(SUCCEEDED(hr));
			if (SUCCEEDED(hr))
			{
				hr = CloseTraceData();
				ATLASSERT(SUCCEEDED(hr));
			}

			m_spXmlDoc = NULL;
		}

		if (SUCCEEDED(hr))
		{
			m_bTraceEnabled = bTraceEnabled;
		}
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}

	return hr;
}

inline const CString& CTraceOwner::GetTraceName() const 
{ 
	return m_strTraceName; 
}

inline HRESULT CTraceOwner::SetTraceName(const CString& strTraceName) 
{ 
	m_strTraceName = strTraceName;
	
	return S_OK; 
}

inline const CPath& CTraceOwner::GetTraceTransformPath()
{
	return m_pathTraceTransform;
}

inline HRESULT CTraceOwner::SetTraceTransformPath(const CPath& pathTransform)
{
	HRESULT hr = S_OK;

	m_pathTraceTransform = pathTransform;

	return hr;
}

inline HRESULT CTraceOwner::StartTrace(const CString& strDriverIdentifier, const CString& strTraceName)	
{ 
	ATLASSERT(m_eTraceState != TraceStateRunning);
	if (m_eTraceState == TraceStateRunning)
		return E_UNEXPECTED;

	HRESULT hr = S_OK;
	try
	{
		if (m_eTraceState != TraceStateDataFull)
		{
			CPath pathTraceLog;
			CTime startTime = CTime::GetCurrentTime();

			bool bLogFileActive = !CString(m_pathTraceLog).IsEmpty();

			if (strTraceName.IsEmpty())
			{
				// A trace name wasn't specified, so generate a name using the driver identifier and the current time
				pathTraceLog = GetTraceLogPathFromTraceName(strDriverIdentifier, startTime);
			}
			else
			{
				// A trace name was specified, so use the trace name directly
				if (TraceNameChanged(strTraceName) || !bLogFileActive)
				{
					// The trace name changed OR a log file doesn't exist, so we need to create a new log file
					pathTraceLog = GetTraceLogPathFromTraceName(strTraceName);
				}
				else
				{
					// The trace name is the same AND a log file is active, so we only need to load the existing log file
				}
			}

			hr = LoadOrCreateTraceLog(pathTraceLog);
			if (SUCCEEDED(hr))
			{
				m_pathTraceLog = pathTraceLog;
				m_startTime = startTime;
				m_eTraceState = TraceStateRunning;
				m_spCurrentElement = m_spXmlDoc->documentElement;
			}
		}
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}

	return hr;
}

inline bool CTraceOwner::TraceNameChanged(const CString& strNewTraceName)
{
	CPath pathNewTrace = strNewTraceName;
	CPath pathOldTrace = m_strTraceName;
	pathNewTrace.RemoveExtension();
	pathOldTrace.RemoveExtension();

	CString strTempNewTraceName = CString(pathNewTrace);
	strTempNewTraceName = strTempNewTraceName.Trim();
	CString strTempOldTraceName = CString(pathOldTrace);
	strTempOldTraceName = strTempOldTraceName.Trim();

	return strTempNewTraceName.CompareNoCase(strTempOldTraceName) != 0;
}

inline HRESULT CTraceOwner::StopTrace(TraceState eTraceState)
{
	if (!m_bTraceEnabled)
		return E_UNEXPECTED;			

	if (m_eTraceState == TraceStateStopped)
		return E_UNEXPECTED;

	HRESULT hr = S_OK;
	if (SUCCEEDED(hr))
	{
		m_eTraceState = eTraceState;
	}

	return hr;
}

inline HRESULT CTraceOwner::InitializeTraceData()
{
	HRESULT hr = S_OK;

	hr = m_spXmlDoc.CreateInstance(__uuidof(DOMDocument40));

	return hr;
}

inline HRESULT CTraceOwner::CloseTraceData()
{
	m_spXmlDoc = NULL;

	return S_OK;
}

inline HRESULT CTraceOwner::CommitTraceData()
{
	HRESULT hr = S_OK;
	CTimeSpan timeElapsedSinceLastSave = CTime::GetCurrentTime() - m_timeLastSave;
	if (timeElapsedSinceLastSave.GetMinutes() >= m_nTraceSaveInterval)
	{
		hr = SaveTraceData();
	}

	return hr;
}

inline HRESULT CTraceOwner::SaveTraceData()
{
	HRESULT hr = S_OK;
	try
	{
		// Save the XML trace data
		m_spXmlDoc->save(LPCTSTR(m_pathTraceLog));

		CAtlFile fileTraceData;
		hr = fileTraceData.Create(m_pathTraceLog, FILE_READ_DATA, FILE_SHARE_READ, OPEN_EXISTING);
		ATLASSERT(SUCCEEDED(hr));
		if (SUCCEEDED(hr))
		{
			ULONGLONG ulSize;
			hr = fileTraceData.GetSize(ulSize);
			ATLASSERT(SUCCEEDED(hr));
			if (ulSize > m_nTraceSizeMax)
			{
				hr = StopTrace(TraceStateDataFull);
				ATLASSERT(SUCCEEDED(hr));
			}
		}

		m_timeLastSave = CTime::GetCurrentTime();
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}

	return hr;
}

inline CPath CTraceOwner::GetTraceLogPathFromTraceName(const CString& strTraceName) const
{
	// The input trace name may or may not have the .xml extension already
	CPath pathTraceLog = strTraceName;
	CString strExtension = pathTraceLog.GetExtension();
	if (strExtension.CompareNoCase(_T(".xml")) != 0)
	{
		pathTraceLog.AddExtension(_T(".xml"));
	}

	// If the trace name is actually a fully-qualified path, then use the path as-is
	if (pathTraceLog.IsRelative() || pathTraceLog.IsFileSpec())
	{
		// The resulting path should be relative to the current directory
		CPath pathTemp;
		pathTemp.Combine(GetCurrentTraceDirectory(), pathTraceLog);
		pathTraceLog = pathTemp;
	}

	return pathTraceLog;
}

inline CPath CTraceOwner::GetTraceLogPathFromTraceName(const CString& strTraceName, const CTime& timeStamp) const
{
	// Timestamp format is: "yyyy-mm-dd_hr-min-sec"
	CString strTimeStamp = timeStamp.Format(_T("%Y-%m-%d_%H-%M-%S"));

	// Trace log filename format is: <baseName>_<timestamp>.xml
	CString strFileName = strTraceName + "_" + strTimeStamp + ".xml";

	CPath pathTraceLog;
	pathTraceLog.Combine(GetCurrentTraceDirectory(), strFileName);
	
	return pathTraceLog;
}

inline CPath CTraceOwner::GetCurrentTraceDirectory() const
{
	CPath pathCurDir;
	TCHAR szCurrentDir[MAX_PATH];
	DWORD dRet = ::GetCurrentDirectory(MAX_PATH, szCurrentDir);
	ATLASSERT(dRet != 0);
	if (dRet != 0)
	{
		pathCurDir = szCurrentDir;
	}

	return pathCurDir;
}

inline HRESULT CTraceOwner::LoadOrCreateTraceLog(const CPath& pathTraceLog)
{
	HRESULT hr = S_OK;
	try
	{
		if (pathTraceLog.FileExists())
		{
			if (m_spXmlDoc->load(LPCTSTR(pathTraceLog)) != VARIANT_TRUE)
			{
				// Load failed
				hr = E_FAIL;
			}
		}
		else
		{
			// Create the XML version processing instruction
			CString strTarget = _T("version=\"1.0\"");
			_bstr_t bstrTarget = CT2OLE(strTarget);
			MSXML2::IXMLDOMProcessingInstructionPtr spPI = m_spXmlDoc->createProcessingInstruction("xml", bstrTarget);
			m_spXmlDoc->appendChild(spPI);

			// Create the XSLT processing instruction
			CPath pathTraceTransform = m_pathTraceTransform;
			CString strTraceTransformPath = CString(m_pathTraceTransform);
			if (strTraceTransformPath.IsEmpty())
			{
				// No transform was specified, so use the default trace name
				pathTraceTransform = GetTraceTransformPathFromFileName(_T("trace.xslt"));
				m_pathTraceTransform = pathTraceTransform;
			}
			else
			{
				// A transform was specified, so determine if it's an absolute path or simply a filename
				if (m_pathTraceTransform.IsFileSpec())
				{
					pathTraceTransform = GetTraceTransformPathFromFileName(CString(m_pathTraceTransform));
					m_pathTraceTransform = pathTraceTransform;
				}
			}

			if (m_pathTraceTransform.FileExists())
			{
				strTarget.Format(_T("type=\"text/xsl\" href=\"file:\\\\%s\""), CString(m_pathTraceTransform));
				bstrTarget = CT2OLE(strTarget);
				spPI = m_spXmlDoc->createProcessingInstruction("xml-stylesheet", bstrTarget);
				m_spXmlDoc->appendChild(spPI);
			}

			// Create the root element
			MSXML2::IXMLDOMElementPtr spRootElement = m_spXmlDoc->createElement(_T("TraceRecords"));
			m_spXmlDoc->documentElement = spRootElement;

			m_spXmlDoc->save(LPCTSTR(pathTraceLog));
		}
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}

	return hr;
}

inline CPath CTraceOwner::GetTraceTransformPathFromFileName(const CString& strFileName) const
{
	// The input transform name may or may not have the .xslt extension already
	CPath pathTemp = strFileName;
	CString strExtension = pathTemp.GetExtension();
	if (strExtension.CompareNoCase(_T(".xslt")) != 0)
	{
		pathTemp.AddExtension(_T(".xslt"));
	}

	CPath pathTraceTransform = GetStandardTraceTransformDirectory();
	pathTraceTransform.Combine(pathTraceTransform, pathTemp);

	return pathTraceTransform;
}

inline CPath CTraceOwner::GetStandardTraceTransformDirectory() const
{
	// Drivers running on a Nimbus development machine may have the transform in the following directory:
	// <IviStandardRootDir>\Components\Pacific MindWorks
	//
	// Drivers running on an end user machine will have the transform in the following directory
	// <IviStandardRootDir>\Components\<Vendor>

	// <IviStandardRootDir>\Components
	CPath pathIVI;
	pathIVI.Combine(GetIVIStandardRootDirectory(), _T("Components"));

	// <IviStandardRootDir>\Components\<Vendor>
	CPath pathTransformDir;
	pathTransformDir.Combine(pathIVI, GetDriverVendor());

	if (!pathTransformDir.FileExists())
	{
		// <IviStandardRootDir>\Components\Pacific MindWorks
		pathTransformDir = _T("");
		pathTransformDir.Combine(pathIVI, _T("Pacific MindWorks"));
	}

	return pathTransformDir;
}

inline MSXML2::IXMLDOMElementPtr CTraceOwner::GetCurrentElement() const
{
	return m_spCurrentElement;
}

inline HRESULT CTraceOwner::SetCurrentElement(MSXML2::IXMLDOMElementPtr spElement)
{
	HRESULT hr = S_OK;

	m_spCurrentElement = spElement;

	return hr;
}

///////////////////////////////////////////////////////////////////////////////
//	CTraceUser

class CTraceUser
	: public CTracer
{
public:
	virtual HRESULT SetTraceEnabled(bool bTraceEnabled);
};

inline HRESULT CTraceUser::SetTraceEnabled(bool bTraceEnabled)
{
	m_bTraceEnabled = bTraceEnabled;

	return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//	CTraceManager

class CTraceManager
{
public:
	virtual CTracer* GetTracer() = 0;
	virtual MSXML2::IXMLDOMElementPtr GetCurrentTraceElement() = 0;
	virtual void SetCurrentTraceElement(MSXML2::IXMLDOMElementPtr spCurrentElement) = 0;
	virtual HRESULT CommitTraceData() = 0;
};

///////////////////////////////////////////////////////////////////////////////
//	CParamTrace

typedef enum
{
	ParamDirectionIn,
	ParamDirectionOut,
	ParamDirectionInOut
} ParamDirection;

class CParamTrace 
{
public:
	template <typename T>
	CParamTrace(const CString& strName, T val, ParamDirection eDirection, long lArraySize = -1);

	ParamDirection GetDirection() const;

	HRESULT Serialize(CTracer* pTracer, MSXML2::IXMLDOMElementPtr spElement);

private:
	HRESULT SerializeParamValue(CTracer* pTracer, MSXML2::IXMLDOMElementPtr spElement, const CComVariant& varValue, long lArraySize);
	HRESULT SerializeSafeArrayParamValue(CTracer* pTracer, MSXML2::IXMLDOMElementPtr spElement, SAFEARRAY* psa);
	HRESULT SerializeCArrayParamValue(CTracer* pTracer, MSXML2::IXMLDOMElementPtr spElement, const CComVariant& varValue, long lArraySize);
	HRESULT SerializeScalarParamValue(CTracer* pTracer, MSXML2::IXMLDOMElementPtr spElement, const CComVariant& varValue);

protected:
	CString m_strName;
	CComVariant m_varValue;
	ParamDirection m_eDirection;
	long m_lArraySize;				// size of C-style array parameter
};

template <typename T>
inline CParamTrace::CParamTrace(const CString& strName, T val, ParamDirection eDirection, long lArraySize)
	: m_strName(strName), m_eDirection(eDirection), m_lArraySize(lArraySize)
{
	// The following line of code can cause an exception if the parameters is an [out] or [in,out] parameter
	// and has not been initialized.  The CComVariant assignment operator will try to dereference a bogus
	// or unitialized pointer and cause an exception to be thrown.  The forwarding shim code initializes [out]
	// parameters BEFORE this class is instantiated, but users could still make the mistake of not initializing
	// an [in,out] parameter.  However, the compiler should issue a warning and/or they should receive a 
	// run-time check failure.

	m_varValue = val;
}

inline ParamDirection CParamTrace::GetDirection() const
{
	return m_eDirection;
}

inline HRESULT CParamTrace::Serialize(CTracer* pTracer, MSXML2::IXMLDOMElementPtr spElement)
{
	HRESULT hr = S_OK;

	try
	{
		spElement->setAttribute(_T("Name"), LPCTSTR(m_strName));

		CString strDirection;
		if (m_eDirection == ParamDirectionIn)
		{
			strDirection = "In";
		}
		else if (m_eDirection == ParamDirectionOut)
		{
			strDirection = "Out";
		}
		else if (m_eDirection == ParamDirectionInOut)
		{
			strDirection = "InOut";
		}
		else
		{
			ATLASSERT(!"Unexpected parameter direction.");
			hr = E_UNEXPECTED;
		}

		spElement->setAttribute("Direction", LPCTSTR(strDirection));

		SerializeParamValue(pTracer, spElement, m_varValue, m_lArraySize);
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}
	
	return hr;
}

inline HRESULT CParamTrace::SerializeParamValue(CTracer* pTracer, MSXML2::IXMLDOMElementPtr spElement, const CComVariant& varValue, long lArraySize)
{
	HRESULT hr = S_OK;

	try
	{
		if (varValue.vt & VT_ARRAY)
		{
			if (varValue.vt & VT_BYREF)
			{
				hr = SerializeSafeArrayParamValue(pTracer, spElement, *varValue.pparray);
			}
			else
			{
				hr = SerializeSafeArrayParamValue(pTracer, spElement, varValue.parray);
			}
		}
		else if (lArraySize > 0)
		{
			hr = SerializeCArrayParamValue(pTracer, spElement, varValue, lArraySize);
		}
		else
		{
			hr = SerializeScalarParamValue(pTracer, spElement, varValue);
		}
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}
	
	return hr;
}

inline HRESULT CParamTrace::SerializeScalarParamValue(CTracer* pTracer, MSXML2::IXMLDOMElementPtr spElement, const CComVariant& varValue)
{
	NTL_UNUSED(pTracer);

	HRESULT hr = S_OK;

	try
	{
		CString strValue;
		switch (varValue.vt)
		{
			case VT_I1 | VT_BYREF:
				strValue.Format(_T("%d"), *varValue.pbVal);
				break;
			case VT_I1:
				strValue.Format(_T("%d"), varValue.bVal);
				break;
			case VT_I2 | VT_BYREF:
				strValue.Format(_T("%d"), *varValue.piVal);
				break;
			case VT_I2:
				strValue.Format(_T("%d"), varValue.iVal);
				break;
			case VT_I4 | VT_BYREF:
				strValue.Format(_T("%d"), *varValue.plVal);
				break;
			case VT_I4:
				strValue.Format(_T("%d"), varValue.lVal);
				break;
			case VT_I8 | VT_BYREF:
				strValue.Format(_T("%d"), *varValue.pllVal);
				break;
			case VT_I8:
				strValue.Format(_T("%d"), varValue.llVal);
				break;
			case VT_UI1 | VT_BYREF:
				strValue.Format(_T("%d"), *varValue.pbVal);
				break;
			case VT_UI1:
				strValue.Format(_T("%d"), varValue.bVal);
				break;
			case VT_UI2 | VT_BYREF:
				strValue.Format(_T("%d"), *varValue.puiVal);
				break;
			case VT_UI2:
				strValue.Format(_T("%d"), varValue.uiVal);
				break;
			case VT_UI4 | VT_BYREF:
				strValue.Format(_T("%d"), *varValue.pulVal);
				break;
			case VT_UI4:
				strValue.Format(_T("%d"), varValue.ulVal);
				break;
			case VT_UI8 | VT_BYREF:
				strValue.Format(_T("%d"), *varValue.pullVal);
				break;
			case VT_UI8:
				strValue.Format(_T("%d"), varValue.ullVal);
				break;
			case VT_INT | VT_BYREF:
				strValue.Format(_T("%d"), *varValue.pintVal);
				break;
			case VT_INT:
				strValue.Format(_T("%d"), varValue.intVal);
				break;
			case VT_R4 | VT_BYREF:
				strValue.Format(_T("%.15g"), *varValue.pfltVal);
				break;
			case VT_R4:
				strValue.Format(_T("%.15g"), varValue.fltVal);
				break;
			case VT_R8 | VT_BYREF:
				strValue.Format(_T("%.15g"), *varValue.pdblVal);
				break;
			case VT_R8:
				strValue.Format(_T("%.15g"), varValue.dblVal);
				break;
			case VT_BOOL | VT_BYREF:
				strValue.Format(_T("%s"), *varValue.pboolVal ? "true" : "false");
				break;
			case VT_BOOL:
				strValue.Format(_T("%s"), varValue.boolVal ? "true" : "false");
				break;
			case VT_BSTR | VT_BYREF:
				strValue = *varValue.pbstrVal;
				break;
			case VT_BSTR:
				strValue = varValue.bstrVal;
				break;
			case VT_VARIANT | VT_BYREF:
				strValue.Format(_T("%s"), _T("<variant>"));
				break;
			case VT_VARIANT:
				strValue.Format(_T("%s"), _T("<variant>"));
				break;
			case VT_DISPATCH | VT_BYREF:
			case VT_DISPATCH:
			case VT_UNKNOWN | VT_BYREF:
			case VT_UNKNOWN:
				strValue = _T("<interface>");
				break;
			case VT_NULL:
				strValue = _T("<null>");
				break;
			case VT_EMPTY:
				strValue = _T("<empty>");
				break;
			default:
				strValue = _T("<unknown>");
				break;
		}

		spElement->setAttribute(_T("Value"), LPCTSTR(strValue));
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}
	
	return hr;
}

inline	HRESULT CParamTrace::SerializeSafeArrayParamValue(CTracer* pTracer, MSXML2::IXMLDOMElementPtr spElement, SAFEARRAY* psa)
{
	HRESULT hr = S_OK;
	try
	{
		// Print out the number of dimensions
		UINT nNumDims = ::SafeArrayGetDim(psa);
		spElement->setAttribute("NumDims", nNumDims);

		// Print out the dimension sizes
		CString strDimSizes;
		long* pDimSizes = (long*)_alloca(nNumDims * sizeof(long));
		for (UINT i = 0; i < nNumDims; i++)
		{
			long lDimSize = psa->rgsabound[i].cElements;
			pDimSizes[i-1] = lDimSize;
			CString strDimSize;
			strDimSize.Format(_T("%d"), lDimSize);
			strDimSizes += strDimSize;
			if (i < nNumDims - 1)
			{
				strDimSizes += _T(",");
			}
		}

		if (SUCCEEDED(hr))
		{
			spElement->setAttribute(_T("DimSizes"), LPCTSTR(strDimSizes));

			// Only print out the elements for 1-dimensional arrays if option selected
			if (nNumDims == 1 && pTracer->GetTraceArray())
			{
				VARTYPE vt;
				hr = ::SafeArrayGetVartype(psa, &vt);
				ATLASSERT(SUCCEEDED(hr));
				if (SUCCEEDED(hr))
				{
					long lFirstDimSize = psa->rgsabound[0].cElements;
					for (long i = 0; i < lFirstDimSize && i < pTracer->GetTraceArraySizeMax() && SUCCEEDED(hr); i++)
					{
						VARIANT varElement;
						if (vt == VT_VARIANT)
						{
							hr = ::SafeArrayGetElement(psa, &i, &varElement);		// copy into the entire VARIANT
						}
						else
						{
							varElement.vt = vt;
							hr = ::SafeArrayGetElement(psa, &i, &varElement.bVal);	// copy only the data portion of the VARIANT
						}
						ATLASSERT(SUCCEEDED(hr));
						if (SUCCEEDED(hr))
						{
							MSXML2::IXMLDOMElementPtr spArrayElement = spElement->ownerDocument->createElement("Element");
							SerializeScalarParamValue(pTracer, spArrayElement, varElement);
							spElement->appendChild(spArrayElement);
						}
					}
				}
			}
		}
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}

	return hr;
}

inline	HRESULT CParamTrace::SerializeCArrayParamValue(CTracer* pTracer, MSXML2::IXMLDOMElementPtr spElement, const CComVariant& varValue, long lArraySize)
{
	HRESULT hr = S_OK;
	try
	{
		// Print out the number of dimensions => always 1 dim for C-style array
		spElement->setAttribute("NumDims", 1);

		spElement->setAttribute("DimSizes", lArraySize);

		// Only print out the elements if option selected
		if (pTracer->GetTraceArray())
		{
			VARTYPE vtElement = (VARTYPE)(varValue.vt ^ VT_BYREF);
			long lElementSize = -1;
			switch (vtElement)
			{
				case VT_I1:
				case VT_UI1:
					lElementSize = 1;
					break;
				case VT_I2:
				case VT_UI2:
					lElementSize = 2;
					break;
				case VT_INT:
				case VT_I4:
				case VT_UI4:
				case VT_R4:
					lElementSize = 4;
					break;
				case VT_I8:
				case VT_UI8:
				case VT_R8:
					lElementSize = 8;
					break;
				case VT_BOOL:
					lElementSize = 1;
					break;
				case VT_BSTR:
					lElementSize = 4;
					break;
				default:
					ATLASSERT(!"Unexpected C-style array element type.");
					break;
			}

			CComVariant varElement = varValue;
			for (long i = 0; i < lArraySize && i < pTracer->GetTraceArraySizeMax(); i++)
			{
				MSXML2::IXMLDOMElementPtr spArrayElement = spElement->ownerDocument->createElement("Element");
				SerializeScalarParamValue(pTracer, spArrayElement, varElement);
				spElement->appendChild(spArrayElement);

				varElement.pbVal += lElementSize;
			}
		}
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}

	return hr;
}

///////////////////////////////////////////////////////////////////////////////
//	CInParamTrace

class CInParamTrace : public CParamTrace
{
public:
	template <typename T>
	CInParamTrace(const CString& strName, T val, long lArraySize = -1)
		: CParamTrace(strName, val, ParamDirectionIn, lArraySize)
	{
	}
};

#define IN_PARAM(x)					new CInParamTrace(_T(#x), x)
#define SIZED_IN_PARAM(x, size)		new CInParamTrace(_T(#x), x, size)
#define IN_CSTRING_PARAM(x)			new CInParamTrace(_T(#x), CComBSTR(x))

///////////////////////////////////////////////////////////////////////////////
//	COutParamTrace

class COutParamTrace : public CParamTrace
{
public:
	template <typename T>
	COutParamTrace(const CString& strName, void* pValue, T val, long lArraySize = -1)
		: CParamTrace(strName, val, ParamDirectionOut, lArraySize)
	{
		m_varValue.pbVal = (BYTE*)pValue;
		m_varValue.vt |= VT_BYREF;
		//m_varValue.vt = VT_R8 | VT_BYREF;
	}
};

#define OUT_PARAM(x)				new COutParamTrace(_T(#x), x, *x)
#define SIZED_OUT_PARAM(x, size)	new COutParamTrace(_T(#x), x, *x, size)

///////////////////////////////////////////////////////////////////////////////
//	CInOutParamTrace

class CInOutParamTrace : public CParamTrace
{
public:
	template <typename T>
	CInOutParamTrace(const CString& strName, void* pValue, T val, long lArraySize = -1)
		: CParamTrace(strName, val, ParamDirectionInOut, lArraySize)
	{
		m_varValue.pbVal = (BYTE*)pValue;
		m_varValue.vt |= VT_BYREF;
	}
};

#define INOUT_PARAM(x)				new CInOutParamTrace(_T(#x), x, *x)
#define SIZED_INOUT_PARAM(x, size)	new CInOutParamTrace(_T(#x), x, *x, size)


///////////////////////////////////////////////////////////////////////////////
//	CMethodTrace

#define MAX_PARAMS	25

class CMethodTrace
{
public:
	CMethodTrace(CTraceManager* pTraceManager, LPCTSTR pszIntfName, LPCTSTR pszMethodName, ...);
	~CMethodTrace();

	virtual HRESULT BeginSerialize();
	virtual HRESULT EndSerialize();

private:
	bool m_bTraceOn;
	CTraceManager* m_pTraceManager;
	CTracer* m_pTracer;
	MSXML2::IXMLDOMElementPtr m_spElement;
	MSXML2::IXMLDOMElementPtr m_spParentElement;
	CString m_strIntfName;
	CString m_strMethodName;
	CParamTrace* m_rgpParams[MAX_PARAMS];
	int m_nNumParams;
	CTime m_startTime;
};

inline CMethodTrace::CMethodTrace(CTraceManager* pTraceManager, LPCTSTR pszIntfName, LPCTSTR pszMethodName, ...)
	: m_pTraceManager(pTraceManager), 
	  m_pTracer(m_pTraceManager->GetTracer()), 
	  m_strIntfName(pszIntfName), 
	  m_strMethodName(pszMethodName), 
	  m_bTraceOn(false), 
	  m_spElement(NULL), 
	  m_nNumParams(0)
{
	// It's important to keep track of all parameters, even if tracing is turned off, 
	// because they are allocated with new and must be deleted in the destructor
	va_list argPtr;
	va_start(argPtr, pszMethodName);

	CParamTrace* pParam = va_arg(argPtr, CParamTrace*);
	for (int i = 0; i < MAX_PARAMS && pParam != NULL; i++)
	{
		m_rgpParams[i] = pParam;
		m_nNumParams++;
		pParam = va_arg(argPtr, CParamTrace*);
	}

	va_end(argPtr);

	// Don't trace if tracer is NULL => usually means we're not initialized yet
	if (m_pTracer != NULL)
	{
		if (m_pTracer->GetTraceOn())
		{
			m_bTraceOn = true;

			HRESULT hr = BeginSerialize();
			ATLASSERT(SUCCEEDED(hr));
			NTL_UNUSED(hr);
		}
	}
}

inline CMethodTrace::~CMethodTrace()
{
	if (m_bTraceOn)
	{
		ATLASSERT(m_pTracer != NULL);
		if (m_pTracer != NULL)
		{
			HRESULT hr = EndSerialize();
			ATLASSERT(SUCCEEDED(hr));
			if (SUCCEEDED(hr))
			{
				hr = m_pTraceManager->CommitTraceData();
			}
		}
	}

	for (int i = 0; i < m_nNumParams; i++)
	{
		delete m_rgpParams[i];
		m_rgpParams[i] = NULL;
	}
}

inline HRESULT CMethodTrace::BeginSerialize()
{
	HRESULT hr = S_OK;
	try
	{
		MSXML2::IXMLDOMDocument2Ptr spDoc = m_pTracer->GetTraceDocument();
		m_spElement = spDoc->createElement(_T("Method"));

		m_spElement->setAttribute(_T("Name"), LPCTSTR(m_strMethodName));
		m_spElement->setAttribute(_T("Interface"), LPCTSTR(m_strIntfName));

		m_startTime = CTime::GetCurrentTime();
		CString strStartTime = m_startTime.Format(_T("%H:%M:%S"));
		m_spElement->setAttribute(_T("StartTime"), LPCTSTR(strStartTime));

		CString strThread;
		strThread.Format(_T("%d"), ::GetCurrentThreadId());
		m_spElement->setAttribute(_T("Thread"), LPCTSTR(strThread));

		// Serialize only the [in] and [in,out] parameters as children of the method element
		for (int i = 0; i < m_nNumParams; i++)
		{
			CParamTrace* pParam = m_rgpParams[i];
			if ((pParam->GetDirection() == ParamDirectionIn) || (pParam->GetDirection() == ParamDirectionInOut))
			{
				MSXML2::IXMLDOMElementPtr spParamElement = spDoc->createElement("Parameter");
				pParam->Serialize(m_pTracer, spParamElement);
				m_spElement->appendChild(spParamElement);
			}
		}

		m_spParentElement = m_pTraceManager->GetCurrentTraceElement();
		m_spParentElement->appendChild(m_spElement);
		m_pTraceManager->SetCurrentTraceElement(m_spElement);
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}
	
	return hr;
}

inline HRESULT CMethodTrace::EndSerialize()
{
	HRESULT hr = S_OK;
	try
	{
		MSXML2::IXMLDOMDocument2Ptr spDoc = m_pTracer->GetTraceDocument();

		CTime endTime = CTime::GetCurrentTime();
		CString strEndTime = endTime.Format(_T("%H:%M:%S"));
		m_spElement->setAttribute(_T("EndTime"), LPCTSTR(strEndTime));

		CTimeSpan executionTime = endTime - m_startTime;
		CString strExecutionTime = executionTime.Format(_T("%S"));
		m_spElement->setAttribute(_T("ExecutionTime"), LPCTSTR(strExecutionTime));

		// Serialize only the [out] and [in,out] parameters as children of the method element
		for (int i = 0; i < m_nNumParams; i++)
		{
			CParamTrace* pParam = m_rgpParams[i];
			if ((pParam->GetDirection() == ParamDirectionOut) || (pParam->GetDirection() == ParamDirectionInOut))
			{
				MSXML2::IXMLDOMElementPtr spParamElement = spDoc->createElement("Parameter");
				pParam->Serialize(m_pTracer, spParamElement);
				m_spElement->appendChild(spParamElement);
			}
		}

		m_pTraceManager->SetCurrentTraceElement(m_spParentElement);
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}
	
	return hr;
}

#endif // __NTLTRACE_INCLUDED