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

#ifndef _NTLUTIL_INCLUDED
#define _NTLUTIL_INCLUDED

#include <atlstr.h>
#include <atlrx.h>
#include <atlcoll.h>
#include <atlpath.h>

///////////////////////////////////////////////////////////////////////////////
//	CSafeArray

template <typename DataType>
class CSafeArray
{
public:
	static HRESULT CreateFromArray(DataType* pBuf, long lLength, VARTYPE VariantType, SAFEARRAY** ppsa)
	{
		if (ppsa == NULL)
			return E_POINTER;

		*ppsa = NULL;

		SAFEARRAY* psa = ::SafeArrayCreateVector(VariantType, 0, lLength);
		if (psa == NULL)
			return E_OUTOFMEMORY;

		HRESULT hr = Read(psa, pBuf, lLength);
		if (SUCCEEDED(hr))
		{
			*ppsa = psa;
		}
		else
		{
			::SafeArrayDestroy(psa);
		}

		return hr;
	}

	static HRESULT Read(SAFEARRAY* psa, DataType* pBuf, long lLength)
	{
		if (psa == NULL)
			return E_POINTER;

		if (pBuf == NULL)
			return E_POINTER;

		DataType* prguch;
		HRESULT hr = ::SafeArrayAccessData(psa, (void**) &prguch);
		if (SUCCEEDED(hr))
		{
			memcpy(prguch, pBuf, lLength * sizeof(DataType));

			::SafeArrayUnaccessData(psa);
		}

		return hr;
	}

	static HRESULT Write(SAFEARRAY* psa, DataType* pBuf, long lMaxLength, long* plActualLength)
	{
		if (psa == NULL)
			return E_POINTER;

		if (pBuf == NULL)
			return E_POINTER;

		long lLBound, lUBound;
		HRESULT hr = ::SafeArrayGetLBound(psa, 1, &lLBound);
		if (SUCCEEDED(hr))
		{
			hr = ::SafeArrayGetUBound(psa, 1, &lUBound);
			if (SUCCEEDED(hr))
			{
				long lLength = lUBound - lLBound + 1;

				if (plActualLength != NULL)
				{
					*plActualLength = lLength;
				}

				if (lLength <= lMaxLength)
				{
					DataType* prguch;
					HRESULT hr = ::SafeArrayAccessData(psa, (void**) &prguch);
					if (SUCCEEDED(hr))
					{
						memcpy(pBuf, prguch, lLength * sizeof(DataType));

						::SafeArrayUnaccessData(psa);
					}
				}
				else
				{
					// Buffer is too small to fit values
					hr = E_FAIL;
				}
			}
		}

		return hr;
	}

	static HRESULT Write(VARIANT v, DataType* pBuf, long lMaxLength, long* plActualLength)
	{
		ATLASSERT((v.vt & VT_ARRAY) != 0);

		SAFEARRAY* psa;
		
		if (v.vt & VT_BYREF)
			psa = *(v.pparray);
		else
			psa = v.parray;

		ATLASSERT(psa != NULL);

		HRESULT hr = Write(psa, pBuf, lMaxLength, plActualLength);

		return hr;
	}

	static HRESULT WriteString(SAFEARRAY* psa, CComBSTR* pBuf, long lMaxLength, long* plActualLength)
	{
		if (psa == NULL)
			return E_POINTER;

		if (pBuf == NULL)
			return E_POINTER;

		long lLBound, lUBound;
		HRESULT hr = ::SafeArrayGetLBound(psa, 1, &lLBound);
		if (SUCCEEDED(hr))
		{
			hr = ::SafeArrayGetUBound(psa, 1, &lUBound);
			if (SUCCEEDED(hr))
			{
				long lLength = lUBound - lLBound + 1;

				if (plActualLength != NULL)
				{
					*plActualLength = lLength;
				}

				if (lLength <= lMaxLength)
				{
					for (long i = lLBound; i <= lUBound; i++)
					{
						hr = ::SafeArrayGetElement(psa, &i, &(pBuf[i]));
					}
				}
				else
				{
					// Buffer is too small to fit values
					hr = E_FAIL;
				}
			}
		}

		return hr;
	}

	static HRESULT WriteString(VARIANT v, CComBSTR* pBuf, long lMaxLength, long* plActualLength)
	{
		ATLASSERT((v.vt & VT_ARRAY) != 0);

		SAFEARRAY* psa;
		
		if (v.vt & VT_BYREF)
			psa = *(v.pparray);
		else
			psa = v.parray;

		ATLASSERT(psa != NULL);

		HRESULT hr = WriteString(psa, pBuf, lMaxLength, plActualLength);

		return hr;
	}

	static HRESULT Destroy(SAFEARRAY* psa)
	{
		if (psa == NULL)
			return E_POINTER;

		HRESULT hr = ::SafeArrayDestroy(psa);

		return hr;
	}
};

inline bool IsSafeArrayElementOfType(SAFEARRAY* psa, VARTYPE vtMatch)
{
	bool bIsElementOfType = false;

	VARTYPE vtActual;
	HRESULT hr = ::SafeArrayGetVartype(psa, &vtActual);
	if (SUCCEEDED(hr) && ((vtActual & vtMatch) != 0))
	{
		bIsElementOfType = true;
	}

	return bIsElementOfType;
}

inline long GetSafeArrayLength(SAFEARRAY* psa)
{
	long length = 0;

	long lLowerBound = 0;
	long lUpperBound = 0;

	HRESULT hr = ::SafeArrayGetLBound(psa, 1, &lLowerBound);
	if (SUCCEEDED(hr))
	{
		hr = ::SafeArrayGetUBound(psa, 1, &lUpperBound);
		if (SUCCEEDED(hr))
		{
			length = lUpperBound - lLowerBound + 1;
		}
	}

	return length;
}

inline HRESULT GetSafeArrayVectorSizeInfo(SAFEARRAY* psa, int* pnLowerBound, int* pnCount)
{
	HRESULT hr = S_OK;

	*pnLowerBound = 0;
	*pnCount = 0;

	long nLowerBound = 0;
	hr = ::SafeArrayGetLBound(psa, 1, &nLowerBound);
	if (SUCCEEDED(hr))
	{
		long nUpperBound = 0;
		hr = ::SafeArrayGetUBound(psa, 1, &nUpperBound);
		if (SUCCEEDED(hr))
		{
			*pnLowerBound = nLowerBound;
			*pnCount = nUpperBound - nLowerBound + 1;
		}
	}

	return hr;
}

///////////////////////////////////////////////////////////////////////////////
//	String parsing helpers

inline HRESULT ParseNameValuePairs(const CString& strSource, CAtlMap<CString, CString>& mapPairs)
{
	HRESULT hr = S_OK;

	CString str = strSource;
	CT2W szTemp(str);
	LPWSTR psz = szTemp.m_psz;
	LPCWSTR pszNext = psz;

	CAtlRegExp<CAtlRECharTraitsW> re;
	REParseError pe = re.Parse(L"{[^,=]+=[^,=]+}");
	if (pe == REPARSE_ERROR_OK)
	{
		CAtlREMatchContext<CAtlRECharTraitsW> mc;
		while (re.Match(pszNext, &mc, &pszNext) && SUCCEEDED(hr))
		{	
			LPCWSTR pszStart = NULL;
			LPCWSTR pszEnd = NULL;
			mc.GetMatch(0, &pszStart, &pszEnd);
			if ((pszStart != NULL) && (pszEnd != NULL))
			{
				CStringW strPairW;
				while (pszStart != pszEnd)
				{
					strPairW += *pszStart;
					pszStart++;
				}
				CString strPair = CW2T(strPairW);
				int nPos = 0;
				CString strName = strPair.Tokenize(_T("="), nPos);
				CString strValue = strPair.Tokenize(_T("="), nPos);
				strName = strName.Trim();
				strValue = strValue.Trim();
				mapPairs[strName] = strValue;
			}
		}
	}

	return hr;
}

///////////////////////////////////////////////////////////////////////////////
//	File info helper class

#pragma comment(lib, "version")

class CFileInfo
{
public:
	CFileInfo(CString strPath)
		: m_strPath(strPath)
	{}

	CString GetComments() { return GetFileInfoString(_T("Comments")); }
	CString GetCompanyName() { return GetFileInfoString(_T("CompanyName")); }
	CString GetFileVersion() { return GetFileInfoString(_T("FileVersion")); }
	CString GetFileDescription() { return GetFileInfoString(_T("FileDescription")); }
	CString GetInternalName() { return GetFileInfoString(_T("InternalName")); }
	CString GetLegalCopyright() { return GetFileInfoString(_T("LegalCopyright")); }
	CString GetLegalTrademarks() { return GetFileInfoString(_T("LegalTrademarks")); }
	CString GetOriginalFilename() { return GetFileInfoString(_T("OriginalFilename")); }
	CString GetProductName() { return GetFileInfoString(_T("ProductName")); }
	CString GetProductVersion() { return GetFileInfoString(_T("ProductVersion")); }
	CString GetPrivateBuild() { return GetFileInfoString(_T("PrivateBuild")); }
	CString GetSpecialBuild() { return GetFileInfoString(_T("SpecialBuild")); }

protected:
	CString GetFileInfoString(const CString& strInfoName)
	{
		CString strFileInfoString;
		DWORD dwHandle;
		DWORD dwSize = ::GetFileVersionInfoSize(LPCTSTR(m_strPath), &dwHandle);
		if (dwSize > 0)
		{
			BYTE* pbData = new BYTE[dwSize];
			BOOL bRet = ::GetFileVersionInfo(LPCTSTR(m_strPath), 0, dwSize, pbData);
			if (bRet)
			{
				DWORD* pBuffer;
				UINT uVerLen;
				bRet = ::VerQueryValue(pbData, _T("\\VarFileInfo\\Translation"), (void**)&pBuffer, &uVerLen);  
				if (bRet && uVerLen > 0)
				{
					UINT uSize;
					CString strInfoPath;
					strInfoPath.Format(_T("\\StringFileInfo\\%04hX%04hX\\%s"), LOWORD(*pBuffer), HIWORD(*pBuffer), strInfoName);

					LPTSTR pszInfoPath = strInfoPath.GetBuffer();
					BOOL bRet = ::VerQueryValue(pbData, pszInfoPath, (void**)&pBuffer, &uSize);
					if (bRet)
					{
						strFileInfoString = (LPTSTR)pBuffer;
					}
				}
			}

			if (pbData != NULL)
				delete[] pbData;
		}

		return strFileInfoString;
	}

private:
	CString m_strPath;
};


///////////////////////////////////////////////////////////////////////////////
//	IVI install helpers

inline CPath GetIVIStandardRootDirectory()
{
	CPath pathIVI;
	CRegKey regKey;
	LONG lRet = regKey.Open(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\IVI"), KEY_READ);
	ATLASSERT(lRet == ERROR_SUCCESS);
	if (lRet == ERROR_SUCCESS)
	{
		TCHAR szPath[MAX_PATH];
		ULONG ulCnt = MAX_PATH;
		lRet = regKey.QueryStringValue(_T("IviStandardRootDir"), szPath, &ulCnt);
		ATLASSERT(lRet == ERROR_SUCCESS);
		if (lRet == ERROR_SUCCESS)
		{
			pathIVI = szPath;
		}
	}

	return pathIVI;
}

inline CString GetIVISharedComponentVersion()
{
	CPath pathIVIBinDirectory;
	pathIVIBinDirectory.Combine(GetIVIStandardRootDirectory(), _T("Bin"));

	CPath pathSharedComponentVersionDLL;
	pathSharedComponentVersionDLL.Combine(pathIVIBinDirectory, _T("IVISharedComponentVersion.dll"));

	CFileInfo fileInfo(pathSharedComponentVersionDLL);

	return fileInfo.GetFileVersion();
}

inline CString GetDriverVendor()
{
	CString strDriverVendor;

	TCHAR szModulePath[MAX_PATH];
	DWORD dwFLen = ::GetModuleFileName(_AtlBaseModule.GetModuleInstance(), szModulePath, MAX_PATH);
	if (dwFLen > 0 && dwFLen < MAX_PATH)
	{
		CFileInfo fileInfo(szModulePath);
		strDriverVendor = fileInfo.GetCompanyName();
	}

	return strDriverVendor;
}


///////////////////////////////////////////////////////////////////////////////
//	Resource helpers

#define RESOURCE_STRING_MAX		1024

inline HRESULT LoadDriverResourceString(UINT nID, BSTR* pbstr)
{
	*pbstr = NULL;

	HRESULT hr = S_OK;
	TCHAR szBuf[RESOURCE_STRING_MAX];
	if (::LoadString(_AtlBaseModule.GetModuleInstance(), nID, szBuf, RESOURCE_STRING_MAX))
	{
		*pbstr = ::SysAllocString(CT2OLE(szBuf));
	}

	return hr;
}

///////////////////////////////////////////////////////////////////////////////
//	Miscellaneous helpers

class CVersion
{
public:
	CVersion(int nMajor, int nMinor, int nBuild, int nRev)
		: m_nMajor(nMajor), m_nMinor(nMinor), m_nBuild(nBuild), m_nRev(nRev)
	{
	}

	int GetMajor() const { return m_nMajor; }
	int GetMinor() const { return m_nMinor; }
	int GetBuild() const { return m_nBuild; }
	int GetRevision() const { return m_nRev; }

	CString ToString(bool bIncludeRevision)
	{
		CString str;
		if (bIncludeRevision)
		{
			str.Format(_T("%d.%d.%d.%d"), m_nMajor, m_nMinor, m_nBuild, m_nRev);
		}
		else
		{
			str.Format(_T("%d.%d.%d"), m_nMajor, m_nMinor, m_nBuild);
		}

		return str;
	}

protected:
	int m_nMajor;
	int m_nMinor;
	int m_nBuild;
	int m_nRev;
};

inline CVersion GetNimbusVersion()
{
	static CVersion _version(0, 7, 0, 0);

	return _version;
}

#endif // _NTLUTIL_INCLUDED
