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

#ifndef __NTL_IVICDRIVER_INCLUDED
#define __NTL_IVICDRIVER_INCLUDED

#include <ivicshared.h>
#include <stdarg.h>
#include <atlstr.h>
#include "ntlvisatypeconversion.h"

// IVI-C driver export macros

#define ADD_EXPORT(x)		// depracated

//	Usage 1:	ADD_FUNCTION_EXPORT("\System\System Write\SystemWrite")
//				ViStatus _VI_FUNC  prefix_SystemWrite( ViSession Vi, ViConstString CommandString )
#define ADD_FUNCTION_EXPORT(x)		

//  Usage 1:	ADD_ATTRIBUTE_EXPORT("\System\Direct IO\Integration Time\INTEGRATION_TIME", 13)
//				ViStatus _VI_FUNC  prefix_get_INTEGRATION_TIME(  ViSession Vi, ViConstString RepCapIdentifier, ViAttr AttributeID, ViReal64* AttributeValue  )
#define ADD_ATTRIBUTE_EXPORT(x, y)		

//	Usage 1: IMPLEMENT_EXPORT()
#define IMPLEMENT_EXPORT()

#define IN
#define OUT

#if ! defined (IVIC_DRIVER_TRACE)
#define IVIC_TRACE //
#elif defined (IVIC_DRIVER_TRACE) // IVIC_DRIVER_TRACE is ON
#define IVIC_TRACE	ATLTRACE
#endif // IVIC_DRIVER_TRACE

///////////////////////////////////////////////////////////////////////////////
//
//	CIviCDriverErrorInfo
//

class CIviCDriverErrorInfo
{
public:
	CIviCDriverErrorInfo(HRESULT hr, ViStatus status)
		: m_hr(hr), m_status(status)
	{}

	HRESULT GetHRESULT() const { return m_hr; }
	DWORD GetStatus() const { return m_status; }

private:
	HRESULT m_hr;
	ViStatus m_status;
};


///////////////////////////////////////////////////////////////////////////////
//
//	CIviCErrorMap
//

typedef CAtlMap<HRESULT, const CIviCDriverErrorInfo*> IviCErrorMap;			// key = hr, value = CIviCDriverErrorInfo

class CIviCErrorMap
{
public:
	CIviCErrorMap(const CIviCDriverErrorInfo* pErrorInfo, ...);
	virtual ~CIviCErrorMap();

	const IviCErrorMap* GetErrorMap() const;

private:
	IviCErrorMap m_mapErrors;
};

inline CIviCErrorMap::CIviCErrorMap(const CIviCDriverErrorInfo* pFirstErrorInfo, ...)
{
	va_list argPtr;
	va_start(argPtr, pFirstErrorInfo);

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

		pErrorInfo = va_arg(argPtr, CIviCDriverErrorInfo*);
	}

	va_end(argPtr);
}

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

inline const IviCErrorMap* CIviCErrorMap::GetErrorMap() const
{
	return &m_mapErrors;
}

inline ViStatus SetErrorUsingComErrorInfoDescription(ViSession session, ViStatus error)
{
	IErrorInfo* pErrorInfo = NULL;
	if (::GetErrorInfo(0, &pErrorInfo) == S_OK)
	{
		CComBSTR bstrDesc;
		if (SUCCEEDED(pErrorInfo->GetDescription(&bstrDesc)))
		{
			USES_CONVERSION;
			ViStatus status = IviSession_SetError(session, error, OLE2A(bstrDesc));
			NTL_UNUSED(status);
		}
		else
		{
			ViStatus status = IviSession_SetError(session, error, "");
			NTL_UNUSED(status);
		}

		pErrorInfo->Release();
	}

	return error;
}

ViStatus TranslateHRESULT(ViSession session, HRESULT hr); // Implemented in generated code


///////////////////////////////////////////////////////////////////////////////
//
//	CIviCDriverErrorDescriptionInfo
//

class CIviCDriverErrorDescriptionInfo
{
public:
	CIviCDriverErrorDescriptionInfo(ViStatus errorCode, const CString& strDescription)
		: m_errorCode(errorCode), m_strDescription(strDescription)
	{}

	ViStatus GetErrorCode() const { return m_errorCode; }
	CString GetDescription() const { return m_strDescription; }

private:
	ViStatus m_errorCode;
	CString m_strDescription;
};


///////////////////////////////////////////////////////////////////////////////
//
//	CIviCErrorDescriptionMap
//

typedef CAtlMap<ViStatus, const CIviCDriverErrorDescriptionInfo*> IviCErrorDescriptionMap;			// key = hr, value = CIviCDriverErrorDescriptionInfo

class CIviCErrorDescriptionMap
{
public:
	CIviCErrorDescriptionMap(const CIviCDriverErrorDescriptionInfo* pErrorDescInfo, ...);
	virtual ~CIviCErrorDescriptionMap();

	const IviCErrorDescriptionMap* GetErrorDescriptionMap() const;

private:
	IviCErrorDescriptionMap m_mapErrors;
};

inline CIviCErrorDescriptionMap::CIviCErrorDescriptionMap(const CIviCDriverErrorDescriptionInfo* pFirstErrorInfo, ...)
{
	va_list argPtr;
	va_start(argPtr, pFirstErrorInfo);

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

		pErrorInfo = va_arg(argPtr, CIviCDriverErrorDescriptionInfo*);
	}

	va_end(argPtr);
}

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

inline const IviCErrorDescriptionMap* CIviCErrorDescriptionMap::GetErrorDescriptionMap() const
{
	return &m_mapErrors;
}

///////////////////////////////////////////////////////////////////////////////
//
//	CRepCapSelector
//

class CRepCapSelector
{
public:
	CRepCapSelector(ViConstString RepCapIdentifier)
	{
		m_strIdentifier = RepCapIdentifier;
		m_parsed = false;
	}

	~CRepCapSelector()
	{
		for (UINT n = 0; n < m_arSelectors.GetCount(); n++)
		{
			delete m_arSelectors[n];
		}
	}

public:
	ViStatus GetSelectors(ViSession session, bool bAcceptRange, int nLevel, CAtlArray<CString>& arSelectors)
	{
		arSelectors.RemoveAll();

		ViStatus status = ParseSelectors(session, bAcceptRange);
		if (status == VI_SUCCESS)
		{
			// nLevel is 0 based
			ATLASSERT(nLevel >= 0);

			if (nLevel < (int)m_arSelectors.GetCount())
			{
				arSelectors.Append(*m_arSelectors[nLevel]);
			}
			else
			{
				// BADLY_FORMED_SELECTOR
				status = IviSession_SetError(session, 0xBFFA0000L + 0x66, "The repeated capability selector is badly-formed.");
				if (status == VI_SUCCESS)
				{
					status = 0xBFFA0000L + 0x66;
				}
			}
		}

		return status;
	}

private:
	ViStatus ParseSelectors(ViSession session, bool bAcceptRange)
	{
		NTL_UNUSED(bAcceptRange);

		ViStatus status = VI_SUCCESS;

		// We only need to do it once for this instance of the class
		// We don't do it in constructor so that we can return an error
		if (!m_parsed)
		{
			int pos = 0;
			CString s = m_strIdentifier.Tokenize(_T(":"), pos);
			while (s != _T("")) 
			{
				CSelectorArray* parSelectors = new CSelectorArray;
				parSelectors->Add(s);
				m_arSelectors.Add(parSelectors);

				s = m_strIdentifier.Tokenize(_T(":"), pos);
			}

			m_parsed = true;
		}

		return status;
	}

private:
	typedef CAtlArray<CString> CSelectorArray;
	CAtlArray<CSelectorArray*> m_arSelectors;
	CString m_strIdentifier;
	bool m_parsed;
};


///////////////////////////////////////////////////////////////////////////////
//
//	IVI-C Helper methods
//

inline HRESULT CreateDriver(REFCLSID rclsid, IUnknown** ppUnk)
{
	HRESULT hr;

	*ppUnk = NULL;

	hr = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	if (FAILED(hr))
	{
		// Maybe it's because it has already been initialized using a different model
		hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
	}

	if (SUCCEEDED(hr))
	{
		hr = ::CoCreateInstance(rclsid, NULL, CLSCTX_ALL, __uuidof(IUnknown), (void**)ppUnk);
		ATLASSERT(SUCCEEDED(hr));
		ATLASSERT(*ppUnk != NULL);
	}

	return hr;
}

template <class Q>
inline ViStatus GetDriverInterface(ViSession session, Q** pp)
{
	IUnknown* pDriver = NULL;
	ViStatus status = GetDriver(session, &pDriver);
	if (status == VI_SUCCESS)
	{
		HRESULT hr = pDriver->QueryInterface(&pp);
		status = TranslateHRESULT(session, hr);

		pDriver->Release();
	}

	return status;
}

inline ViStatus GetDriver(ViSession session, IUnknown** ppUnk)
{
	ViAddr sessionData = VI_NULL;
	ViStatus status = IviSession_GetDataPtr(session, &sessionData);
	if (status == VI_SUCCESS)
	{
		IUnknown* pDriver = reinterpret_cast<IUnknown*>(sessionData);
		ATLASSERT(pDriver != NULL);
		pDriver->AddRef();

		*ppUnk = pDriver;
	}
	return status;
}

inline ViStatus AddDriver(IUnknown* pDriver, ViSession* session)
{
	*session = VI_NULL;

	ViStatus status = IviSession_New(session);
	ATLASSERT(status == VI_SUCCESS);
	if (status == VI_SUCCESS)
	{
		status = IviSession_SetDataPtr(*session, reinterpret_cast<ViAddr>(pDriver));
		ATLASSERT(status == VI_SUCCESS);
		if (status == VI_SUCCESS)
		{
			pDriver->AddRef();
		}
	}

	return status;
}

inline ViStatus RemoveDriver(ViSession session)
{
	ViAddr sessionData = VI_NULL;
	ViStatus status = IviSession_GetDataPtr(session, &sessionData);
	if (status == VI_SUCCESS)
	{
		IUnknown* pDriver = reinterpret_cast<IUnknown*>(sessionData);
		ATLASSERT(pDriver != NULL);
		pDriver->Release();

		status = IviSession_Dispose(session);
		ATLASSERT(status == VI_SUCCESS);
	}

	return status;
}


///////////////////////////////////////////////////////////////////////////////
//
//	IVI Inherent methods implementation
//

inline ViStatus ntl_InitWithOptions(REFCLSID rclsid, ViRsrc ResourceName, ViBoolean IdQuery, ViBoolean Reset, ViConstString OptionsString, ViSession* Vi)
{
	ViStatus status = VI_SUCCESS;

	*(Vi) = 0;

	IUnknown* pDriver = NULL;
	HRESULT hr = CreateDriver(rclsid, &pDriver);
	ATLASSERT(SUCCEEDED(hr));
	ATLASSERT(pDriver != NULL);
	if (SUCCEEDED(hr) && (pDriver != NULL))
	{
		IIviDriver* pIviDriver = NULL;
		hr = pDriver->QueryInterface(&pIviDriver);
		status = TranslateHRESULT(VI_NULL, hr);
		ATLASSERT(SUCCEEDED(hr));
		if (SUCCEEDED(hr))
		{
			CBSTR _resourceName(ResourceName);
			CVariantBool _idQuery(IdQuery);
			CVariantBool _reset(Reset);
			CBSTR _optionsString(OptionsString);
			
			hr = pIviDriver->Initialize(_resourceName.GetVal(), _idQuery.GetVal(), _reset.GetVal(), _optionsString.GetVal());
			if (SUCCEEDED(hr))
			{
				status = AddDriver(pIviDriver, Vi);
				if (status == VI_SUCCESS)
				{
					// Need to do this so that the user gets the warnings (reset, idquery not supported)
					status = TranslateHRESULT(*Vi, hr);
				}
			}
			else
			{
				status = TranslateHRESULT(VI_NULL, hr);
			}
			
			pIviDriver->Release();
		}
		pDriver->Release();
	}
	else
	{
		// Internal error (Could not create COM instance, class not registered?)
		status = _VI_ERROR;
	}

	return status;
}

inline ViStatus ntl_close(ViSession Vi)
{
	ViStatus status = VI_SUCCESS;

	IUnknown* pDriver = NULL;
	status = GetDriver(Vi, &pDriver);
	if (status == VI_SUCCESS)
	{
		IIviDriver* pIviDriver = NULL;
		HRESULT hr = pDriver->QueryInterface(&pIviDriver);
		status = TranslateHRESULT(Vi, hr);
		ATLASSERT(SUCCEEDED(hr));
		if (SUCCEEDED(hr))
		{
			hr = pIviDriver->Close();
			status = TranslateHRESULT(Vi, hr);

			// We always want to call RemoveDriver, even if Close fails
			// RemoveDriver should never fail, (because GetDriver succeeded above)
			// but we still look at the return value and return it
			// (only if Close didn't fail, because the Close error code has precedence).
			ViStatus status2 = RemoveDriver(Vi);
			if (status == VI_SUCCESS)
			{
				status = status2;
			}

			pIviDriver->Release();
		}
		pDriver->Release();

		// Note: Here we are certain that CoInitialize was called
		//       because they passed in a valid session.
		//       It's the only safe place to CoUninitialize, as it's
		//       after the last COM call (Release).
		::CoUninitialize();
	}
	
	return status;
}

inline ViStatus ntl_GetNativeIUnknownPtr(ViSession Vi, IUnknown** NativeIUnknownPtr)
{
	ViStatus status = VI_SUCCESS;

	*NativeIUnknownPtr = NULL;

	IUnknown* pDriver = NULL;
	status = GetDriver(Vi, &pDriver);
	if (status == VI_SUCCESS)
	{	
		*NativeIUnknownPtr = pDriver;
	}

	return status;
}

inline ViStatus ntl_AttachToExistingCOMSession(IUnknown* ExistingIUnknownPtr, ViSession* Vi)
{
	ViStatus status = VI_SUCCESS;

	status = AddDriver(ExistingIUnknownPtr, Vi);

	return status;
}

inline ViStatus ntl_revision_query(ViSession Vi, ViChar DriverRev[], ViChar InstrRev[])
{
	ViStatus status = VI_SUCCESS;

	IUnknown* pDriver = NULL;
	status = GetDriver(Vi, &pDriver);
	if (status == VI_SUCCESS)
	{
		IIviDriverIdentity* pIIviDriverIdentity = NULL;
		HRESULT hr = pDriver->QueryInterface(&pIIviDriverIdentity);
		status = TranslateHRESULT(Vi, hr);
		ATLASSERT(SUCCEEDED(hr));
		if (SUCCEEDED(hr))
		{
			CBSTR _driverRev;
			hr = pIIviDriverIdentity->get_Revision(_driverRev.GetPtr());
			status = TranslateHRESULT(Vi, hr);
			if (SUCCEEDED(hr))
			{
				status = _driverRev.ToViDataType(DriverRev, 256);
			}

			if (SUCCEEDED(hr))
			{
				CBSTR _instrRev;
				hr = pIIviDriverIdentity->get_InstrumentFirmwareRevision(_instrRev.GetPtr());
				status = TranslateHRESULT(Vi, hr);
				if (SUCCEEDED(hr))
				{
					status = _instrRev.ToViDataType(InstrRev, 256);
				}
			}
			pIIviDriverIdentity->Release();
		}
		pDriver->Release();
	}

	return status;
}

inline ViStatus ntl_GetError(ViSession Vi, ViStatus* ErrorCode, ViInt32 ErrorDescriptionBufferSize, ViChar ErrorDescription[])
{
	ViStatus status = VI_SUCCESS;

	status = IviSession_GetError(Vi, ErrorDescriptionBufferSize, ErrorCode, ErrorDescription);

	return status;
}


inline ViStatus ntl_ClearError(ViSession Vi)
{
	ViStatus status = VI_SUCCESS;

	status = IviSession_ClearError(Vi);

	return status;
}

inline ViStatus ntl_UnlockSession(ViSession Vi, ViBoolean* CallerHasLock)
{
	ViStatus status = VI_SUCCESS;

	status = IviSession_Unlock(Vi, CallerHasLock);

	return status;
}


inline ViStatus ntl_LockSession(ViSession Vi, ViBoolean* CallerHasLock)
{
	ViStatus status = VI_SUCCESS;

	status = IviSession_Lock(Vi, CallerHasLock);
	
	return status;
}


#endif // __NTL_IVICDRIVER_INCLUDED