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

#ifndef __NTLERROR_INCLUDED
#define __NTLERROR_INCLUDED

#include <stdarg.h>
#include <atlstr.h>

///////////////////////////////////////////////////////////////////////////////
//
//	CDriverErrorInfo
//

class CDriverErrorInfo
{
public:
	CDriverErrorInfo(UINT uResid, HRESULT hr, DWORD dwHelpContextID)
		: m_uResid(uResid), m_hr(hr), m_dwHelpContextID(dwHelpContextID)
	{}

	UINT GetResourceID() const { return m_uResid; }
	HRESULT GetHRESULT() const { return m_hr; }
	DWORD GetHelpContextID() const { return m_dwHelpContextID; }

private:
	UINT m_uResid;
	HRESULT m_hr;
	DWORD m_dwHelpContextID;
};


///////////////////////////////////////////////////////////////////////////////
//
//	CErrorMap
//

typedef CAtlMap<UINT, const CDriverErrorInfo*> ErrorMap;			// key = resid, value = CDriverErrorInfo

class CErrorMap
{
public:
	CErrorMap(const CDriverErrorInfo* pErrorInfo, ...);
	virtual ~CErrorMap();

	const ErrorMap* GetErrorMap() const;

private:
	ErrorMap m_mapErrors;
};

inline CErrorMap::CErrorMap(const CDriverErrorInfo* pFirstErrorInfo, ...)
{
	va_list argPtr;
	va_start(argPtr, pFirstErrorInfo);

	const CDriverErrorInfo* pErrorInfo = pFirstErrorInfo;
	while (pErrorInfo != NULL)
	{
		m_mapErrors[pErrorInfo->GetResourceID()] = pErrorInfo;

		pErrorInfo = va_arg(argPtr, CDriverErrorInfo*);
	}

	va_end(argPtr);
}

inline CErrorMap::~CErrorMap()
{
	POSITION pos = m_mapErrors.GetStartPosition();
	while (pos != NULL)
	{
		delete m_mapErrors.GetNextValue(pos);
	}
}

inline const ErrorMap* CErrorMap::GetErrorMap() const
{
	return &m_mapErrors;
}

const CErrorMap* GetDriverErrorMap();							// Implemented in generated code
extern const CString& GetHelpFileName();						// Implemented in generated code
extern UINT GetDriverErrorResourceId(UINT uNtlResid);			// Implemented in generated code

inline const CDriverErrorInfo* GetDriverErrorInfo(UINT uResid)
{
	const CDriverErrorInfo* pErrorInfo = NULL;
	bool bFound = GetDriverErrorMap()->GetErrorMap()->Lookup(uResid, pErrorInfo);

	return bFound ? pErrorInfo : NULL;
}

///////////////////////////////////////////////////////////////////////////////
//
//	CErrorManagerBase
//

class CErrorManagerBase
{
public:
	CErrorManagerBase()
		: m_iid(GUID_NULL)
	{
	}

	virtual HRESULT ReportError(UINT uResid, ...) = 0;
	virtual HRESULT ReportErrorEx(UINT uResid, LPCTSTR szErrorDetails, ...) = 0;
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, BSTR bstrValue) = 0;
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, LPCTSTR pszValue) = 0;
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, double dValue) = 0;
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, float Value) = 0;
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, long lValue) = 0;
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, short sValue) = 0;
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, BYTE cValue) = 0;
	virtual HRESULT ReportIOError() = 0;
	virtual HRESULT ReportIOTimeoutError() = 0;

	IID SetInterface(IID iid)
	{
		IID iidPrevious = m_iid;
		m_iid = iid;
		return iidPrevious;
	}

protected:
	IID m_iid; // iid of the most recently executed method/property
};


///////////////////////////////////////////////////////////////////////////////
//
//	CErrorManager
//

template <class ObjectClass>
class CErrorManager :
	public CErrorManagerBase
{
public:
	virtual HRESULT ReportError(UINT uResid, ...);
	virtual HRESULT ReportErrorEx(UINT uResid, LPCTSTR szErrorDetails, ...);
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, BSTR bstrValue);
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, LPCTSTR pszValue);
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, double dValue);
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, float Value);
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, long lValue);
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, short sValue);
	virtual HRESULT ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, BYTE cValue);
	virtual HRESULT ReportIOError();
	virtual HRESULT ReportIOTimeoutError();
#ifdef NTL_MANAGED_CPP_EXTENSIONS
	// The compiler requires that these be non-virtual
	HRESULT ReportException(UINT uResid, System::Exception* e);
	HRESULT ReportException(System::Exception* e);
#endif // NTL_MANAGED_CPP_EXTENSIONS

private:
	HRESULT _FormatError(UINT uResid, CString& strError, va_list ArgList);
	HRESULT _ReportError(UINT uResid, const CString& strError);
};

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, BSTR bstrValue)
{
	HRESULT hr;

	// Important: Can't use COLE2T here for a variable argument
	CString strValue(bstrValue);
	hr = ReportError(IDS_E_IVI_INVALID_VALUE, LPCTSTR(strValue), pszMethod, pszParam);

	return hr;
}

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, LPCTSTR pszValue)
{
	HRESULT hr;

	hr = ReportError(IDS_E_IVI_INVALID_VALUE, pszValue, pszMethod, pszParam);

	return hr;
}

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, double dValue)
{
	HRESULT hr;

	TCHAR pszValue[100];

	_stprintf(pszValue, _T("%.15g"), dValue);

	hr = ReportError(IDS_E_IVI_INVALID_VALUE, pszValue, pszMethod, pszParam);

	return hr;
}

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, float fValue)
{
	HRESULT hr;

	TCHAR pszValue[100];

	_stprintf(pszValue, _T("%.15g"), fValue);

	hr = ReportError(IDS_E_IVI_INVALID_VALUE, pszValue, pszMethod, pszParam);

	return hr;
}

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, long lValue)
{
	HRESULT hr;

	TCHAR pszValue[100];

	_stprintf(pszValue, _T("%d"), lValue);

	hr = ReportError(IDS_E_IVI_INVALID_VALUE, pszValue, pszMethod, pszParam);

	return hr;
}

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, short sValue)
{
	HRESULT hr;

	TCHAR pszValue[100];

	_stprintf(pszValue, _T("%d"), sValue);

	hr = ReportError(IDS_E_IVI_INVALID_VALUE, pszValue, pszMethod, pszParam);

	return hr;
}

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportInvalidValueError(LPCTSTR pszMethod, LPCTSTR pszParam, BYTE cValue)
{
	HRESULT hr;

	TCHAR pszValue[100];

	_stprintf(pszValue, _T("%d"), cValue);

	hr = ReportError(IDS_E_IVI_INVALID_VALUE, pszValue, pszMethod, pszParam);

	return hr;
}

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportIOError()
{
	HRESULT hr;

	IErrorInfo* pErrorInfo = NULL;
	hr = GetErrorInfo(NULL, &pErrorInfo);
	if (hr == S_OK)
	{
		CComBSTR bstrDescription;
		hr = pErrorInfo->GetDescription(&bstrDescription);
		if (SUCCEEDED(hr))
		{
			CString strDescription(bstrDescription);
			hr = ReportError(GetDriverErrorResourceId(NTL_IDS_IO_GENERAL), LPCTSTR(strDescription));
		}
		else
		{
			hr = ReportError(GetDriverErrorResourceId(NTL_IDS_IO_GENERAL), _T(""));
		}

		pErrorInfo->Release();
	}
	else
	{
		hr = ReportError(GetDriverErrorResourceId(NTL_IDS_IO_GENERAL), _T(""));
	}

	return hr;
}

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportIOTimeoutError()
{
	HRESULT hr;

	IErrorInfo* pErrorInfo = NULL;
	hr = GetErrorInfo(NULL, &pErrorInfo);
	if (hr == S_OK)
	{
		CComBSTR bstrDescription;
		hr = pErrorInfo->GetDescription(&bstrDescription);
		if (SUCCEEDED(hr))
		{
			CString strDescription(bstrDescription);
			hr = ReportError(GetDriverErrorResourceId(NTL_IDS_IO_TIMEOUT), LPCTSTR(strDescription));
		}
		else
		{
			hr = ReportError(GetDriverErrorResourceId(NTL_IDS_IO_TIMEOUT), _T(""));
		}

		pErrorInfo->Release();
	}
	else
	{
		hr = ReportError(GetDriverErrorResourceId(NTL_IDS_IO_TIMEOUT), _T(""));
	}

	return hr;
}

#ifdef NTL_MANAGED_CPP_EXTENSIONS
template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportException(UINT uResid, System::Exception* e)
{
	HRESULT hr = S_OK;

	// Important: This assumes that the resource string takes one parameter
	// That parameter is replaced with the exception message
	IntPtr ptr = Runtime::InteropServices::Marshal::StringToBSTR(e->Message).ToPointer();
	hr = ReportError(uResid, LPCTSTR(COLE2T((BSTR)ptr.ToPointer())));
	Runtime::InteropServices::Marshal::FreeBSTR(ptr);
	
	return hr;
}

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportException(System::Exception* e)
{
	return ReportException(GetDriverErrorResourceId(NTL_IDS_WRAPPED_DRIVER_ERROR), e);
}
#endif // NTL_MANAGED_CPP_EXTENSIONS

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportError(UINT uResid, ...)
{
	// Setup the variable argument list.
	va_list ArgList;
	va_start(ArgList, uResid);

	CString strError;
	HRESULT hr = _FormatError(uResid, strError, ArgList);
	if (SUCCEEDED(hr))
	{
		hr = _ReportError(uResid, strError);
	}

	va_end(ArgList);

	return hr;
}

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::ReportErrorEx(UINT uResid, LPCTSTR szErrorDetails, ...)
{
	// Setup the variable argument list.
	va_list ArgList;
	va_start(ArgList, szErrorDetails);

	CString strError;
	HRESULT hr = _FormatError(uResid, strError, ArgList);
	if (SUCCEEDED(hr))
	{
		// Append the error details to the base error message
		strError += "\r\n";
		strError += szErrorDetails;
		hr = _ReportError(uResid, strError);
	}

	va_end(ArgList);

	return hr;
}

template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::_FormatError(UINT uResid, CString& strError, va_list ArgList)
{
	ObjectClass* pT = static_cast<ObjectClass*>(this);

	// Retrieve desired message in the correct language from the message 
	// table and format it using the variable argument list provided.

	HRESULT hr = S_OK;

	LANGID langid = 0;    
	// LANGID = 0 instructs FormatMessage to look for a message for LANGIDs
	// in the following order:
	//	1) Language neutral
	//  2) Thread LANGID
	//  3) User default LANGID
	//  4) System default LANGID
	//  5) US English
	LPVOID lpMsgBuf;										
	if (::FormatMessage(
			FORMAT_MESSAGE_FROM_HMODULE |
			FORMAT_MESSAGE_ALLOCATE_BUFFER,
			_AtlBaseModule.GetModuleInstance(), 
			uResid, 
			langid,
			(LPTSTR) &lpMsgBuf, 
			0, 
			&ArgList) == 0)
	{
		// Failed to load resource and format resource
		::FormatMessage(
			FORMAT_MESSAGE_FROM_SYSTEM |
			FORMAT_MESSAGE_ALLOCATE_BUFFER |
			FORMAT_MESSAGE_IGNORE_INSERTS,
			NULL, 
			GetLastError(), 
			langid,
			(LPTSTR) &lpMsgBuf, 
			0, 
			NULL);
		char szBuf[500];
		sprintf(szBuf, "Internal Error: Unable to retrieve and format error message from message table.\nResource ID = %d\nLanguage ID = %d\nReason = '%s'", uResid, langid, (LPTSTR) lpMsgBuf);

		return pT->GetRoot()->Error(szBuf);
	}

	strError = (LPTSTR)lpMsgBuf;
	strError.Trim();

	LocalFree(lpMsgBuf);

	return hr;
}


template <class ObjectClass>
HRESULT CErrorManager<ObjectClass>::_ReportError(UINT uResid, const CString& strError)
{
	ObjectClass* pT = static_cast<ObjectClass*>(this);

	HRESULT hrDriverError = E_FAIL;
	DWORD dwDriverHelpContextID = 0;
	const CDriverErrorInfo* pErrorInfo = GetDriverErrorInfo(uResid);
	if (pErrorInfo != NULL)
	{
		hrDriverError = pErrorInfo->GetHRESULT();
		dwDriverHelpContextID = pErrorInfo->GetHelpContextID();
	}

	HRESULT hr = pT->GetRoot()->Error(CT2OLE(strError), dwDriverHelpContextID, CT2OLE(GetHelpFileName()), m_iid, hrDriverError);

	return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
//	CErrorHelper
//

class CErrorHelper
{
public:
	CErrorHelper(CErrorManagerBase* pObj, IID iid)
	{
		m_iidPrevious = pObj->SetInterface(iid);
		m_pObj = pObj;
	}

	~CErrorHelper()
	{
		m_pObj->SetInterface(m_iidPrevious);
	}

private:
	IID m_iidPrevious;
	CErrorManagerBase* m_pObj;
};


#endif // __NTLERROR_INCLUDED
