#include "SCardLoader.h"
#include "PCSCException.h"

#ifndef TC_WINDOWS
#include <dlfcn.h>
#define LoadLibrary(x) dlopen(x, RTLD_NOW | RTLD_LOCAL)
#define FreeLibrary(x) dlclose(x)
#define GetProcAddress(x, y) dlsym(x, y)
typedef void* HMODULE;
#ifdef TC_MACOSX
#if !defined(USE_SCARD_CONTROL_112)
#define SCardControlName "SCardControl132"
#else
#define SCardControlName "SCardControl"
#endif
#else
#define SCardControlName "SCardControl"
#endif
#define SCardConnectName "SCardConnect"
#define SCardStatusName "SCardStatus"
#define SCardGetStatusChangeName "SCardGetStatusChange"
#define SCardListReaderGroupsName "SCardListReaderGroups"
#define SCardListReadersName "SCardListReaders"
#else
#define SCardControlName "SCardControl"
#define SCardConnectName "SCardConnectW"
#define SCardStatusName "SCardStatusW"
#define SCardGetStatusChangeName "SCardGetStatusChangeW"
#define SCardListReaderGroupsName "SCardListReaderGroupsW"
#define SCardListReadersName "SCardListReadersW"
#endif

using namespace std;

namespace VeraCrypt
{
	HMODULE SCardLoader::hScardModule = NULL;
	SCARDCONTEXT SCardLoader::hScardContext = 0;
	SCardEstablishContextPtr SCardLoader::scardEstablishContext = NULL;
	SCardReleaseContextPtr SCardLoader::scardReleaseContext = NULL;
	SCardIsValidContextPtr SCardLoader::scardIsValidContext = NULL;
#ifndef TC_MACOSX
	SCardFreeMemoryPtr SCardLoader::scardFreeMemory = NULL;
#endif
	SCardConnectPtr SCardLoader::scardConnect = NULL;
	SCardReconnectPtr SCardLoader::scardReconnect = NULL;
	SCardDisconnectPtr SCardLoader::scardDisconnect = NULL;     
	SCardBeginTransactionPtr SCardLoader::scardBeginTransaction = NULL;
	SCardEndTransactionPtr SCardLoader::scardEndTransaction = NULL;
	SCardStatusPtr SCardLoader::scardStatus = NULL;
	SCardGetStatusChangePtr SCardLoader::scardGetStatusChange = NULL;
	SCardControlPtr SCardLoader::scardControl = NULL;
	SCardTransmitPtr SCardLoader::scardTransmit = NULL;
	SCardListReaderGroupsPtr SCardLoader::scardListReaderGroups = NULL;
	SCardListReadersPtr SCardLoader::scardListReaders = NULL;
	SCardCancelPtr SCardLoader::scardCancel = NULL;
	SCardGetAttribPtr SCardLoader::scardGetAttrib = NULL;
	SCardSetAttribPtr SCardLoader::scardSetAttrib = NULL;
	SCARD_IO_REQUEST* SCardLoader::scardT0Pci = NULL;
	SCARD_IO_REQUEST* SCardLoader::scardT1Pci = NULL;
	SCARD_IO_REQUEST* SCardLoader::scardRawPci = NULL;
	bool SCardLoader::bInitialized = false;

#ifdef TC_WINDOWS
	wstring SCardLoader::GetSCardPath()
#else
	string SCardLoader::GetSCardPath()
#endif
	{
#ifdef TC_WINDOWS
		wchar_t winscardPath[TC_MAX_PATH];
		if (GetSystemDirectory(winscardPath, TC_MAX_PATH))
		{
			StringCbCat(winscardPath, sizeof(winscardPath), L"\\Winscard.dll");
		}
		else
			StringCbCopy(winscardPath, sizeof(winscardPath), L"C:\\Windows\\System32\\Winscard.dll");
		return winscardPath;
#elif TC_MACOSX
		return "/System/Library/Frameworks/PCSC.framework/PCSC";
#else
		string pcscPath = "";
		FILE* pipe = 
#ifdef TC_LINUX
			popen("ldconfig -p", "r");
#else
			popen("ldconfig -r", "r"); // FreeBSD
#endif
		if (pipe)
		{
			char buffer[128];
			while (!feof(pipe))
			{
				if (fgets(buffer, 128, pipe) != NULL)
				{
					string line(buffer);
					if (line.find("libpcsclite.so") != string::npos)
					{
						size_t pos = line.find("=>");
						if (pos != string::npos)
						{
							pcscPath = line.substr(pos + 3);
							pos = pcscPath.find_first_of(" \t\r\n");
							if (pos != string::npos)
								pcscPath = pcscPath.substr(0, pos);
							break;
						}
					}
				}
			}
			pclose(pipe);
		}

		if (pcscPath == "")
		{
			pcscPath = "libpcsclite.so";
		}
		
		return pcscPath;
#endif
	}

	void SCardLoader::Initialize()
	{
		if (bInitialized)
			return;

		hScardModule = LoadLibrary(GetSCardPath().c_str());
		if (hScardModule)
		{
			scardEstablishContext = (SCardEstablishContextPtr)GetProcAddress(hScardModule, "SCardEstablishContext");
			scardReleaseContext = (SCardReleaseContextPtr)GetProcAddress(hScardModule, "SCardReleaseContext");
			scardIsValidContext = (SCardIsValidContextPtr)GetProcAddress(hScardModule, "SCardIsValidContext");
#ifndef TC_MACOSX
			scardFreeMemory = (SCardFreeMemoryPtr)GetProcAddress(hScardModule, "SCardFreeMemory");
#endif
			scardConnect = (SCardConnectPtr)GetProcAddress(hScardModule, SCardConnectName);
			scardReconnect = (SCardReconnectPtr)GetProcAddress(hScardModule, "SCardReconnect");
			scardDisconnect = (SCardDisconnectPtr)GetProcAddress(hScardModule, "SCardDisconnect");
			scardBeginTransaction = (SCardBeginTransactionPtr)GetProcAddress(hScardModule, "SCardBeginTransaction");
			scardEndTransaction = (SCardEndTransactionPtr)GetProcAddress(hScardModule, "SCardEndTransaction");
			scardStatus = (SCardStatusPtr)GetProcAddress(hScardModule, SCardStatusName);
			scardGetStatusChange = (SCardGetStatusChangePtr)GetProcAddress(hScardModule, SCardGetStatusChangeName);
			scardControl = (SCardControlPtr)GetProcAddress(hScardModule, SCardControlName);
			scardTransmit = (SCardTransmitPtr)GetProcAddress(hScardModule, "SCardTransmit");
			scardListReaderGroups = (SCardListReaderGroupsPtr)GetProcAddress(hScardModule, SCardListReaderGroupsName);
			scardListReaders = (SCardListReadersPtr)GetProcAddress(hScardModule, SCardListReadersName);
			scardCancel = (SCardCancelPtr)GetProcAddress(hScardModule, "SCardCancel");
			scardGetAttrib = (SCardGetAttribPtr)GetProcAddress(hScardModule, "SCardGetAttrib");
			scardSetAttrib = (SCardSetAttribPtr)GetProcAddress(hScardModule, "SCardSetAttrib");
			scardT0Pci = (SCARD_IO_REQUEST*)GetProcAddress(hScardModule, "g_rgSCardT0Pci");
			scardT1Pci = (SCARD_IO_REQUEST*)GetProcAddress(hScardModule, "g_rgSCardT1Pci");
			scardRawPci = (SCARD_IO_REQUEST*)GetProcAddress(hScardModule, "g_rgSCardRawPci");
			if (
#ifndef TC_MACOSX
				scardFreeMemory &&
#endif
				scardEstablishContext && scardReleaseContext && scardIsValidContext && scardConnect && scardReconnect && scardDisconnect &&
				scardBeginTransaction && scardEndTransaction && scardStatus && scardGetStatusChange && scardControl && scardTransmit &&
				scardListReaderGroups && scardListReaders && scardCancel && scardGetAttrib && scardSetAttrib && scardT0Pci && scardT1Pci && scardRawPci)
			{
				if (SCARD_S_SUCCESS == scardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hScardContext))
				{
					bInitialized = true;
				}
			}
		}

		if (!bInitialized)
		{
			Finalize();
		}
	}

	void SCardLoader::Finalize()
	{
		if (hScardContext)
		{
			scardReleaseContext(hScardContext);
			hScardContext = 0;
		}

		if (hScardModule)
		{
			FreeLibrary(hScardModule);
			hScardModule = NULL;
		}

		scardEstablishContext = NULL;
		scardReleaseContext = NULL;
		scardIsValidContext = NULL;
#ifndef TC_MACOSX
		scardFreeMemory = NULL;
#endif
		scardConnect = NULL;
		scardReconnect = NULL;
		scardDisconnect = NULL;
		scardBeginTransaction = NULL;
		scardEndTransaction = NULL;
		scardStatus = NULL;
		scardGetStatusChange = NULL;
		scardControl = NULL;
		scardTransmit = NULL;
		scardListReaderGroups = NULL;
		scardListReaders = NULL;
		scardCancel = NULL;
		scardGetAttrib = NULL;
		scardSetAttrib = NULL;
		scardT0Pci = NULL;
		scardT1Pci = NULL;
		scardRawPci = NULL;

		bInitialized = false;
	}

	SCARDCONTEXT SCardLoader::GetSCardContext()
	{
		return hScardContext;
	}

	LONG SCardLoader::SCardEstablishContext(DWORD dwScope, LPCVOID pvReserved1, LPCVOID pvReserved2, LPSCARDCONTEXT phContext)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardEstablishContext(dwScope, pvReserved1, pvReserved2, phContext);
	}

	LONG SCardLoader::SCardReleaseContext(SCARDCONTEXT hContext)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardReleaseContext(hContext);
	}

	LONG SCardLoader::SCardIsValidContext(SCARDCONTEXT hContext)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardIsValidContext(hContext);
	}

#ifndef TC_MACOSX
	LONG SCardLoader::SCardFreeMemory(SCARDCONTEXT hContext, LPCVOID pvMem)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardFreeMemory(hContext, pvMem);
	}
#endif

	LONG SCardLoader::SCardConnect(SCARDCONTEXT hContext, LPCTSTR szReader, DWORD dwShareMode, DWORD dwPreferredProtocols, LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardConnect(hContext, szReader, dwShareMode, dwPreferredProtocols, phCard, pdwActiveProtocol);
	}
	
	LONG SCardLoader::SCardReconnect(SCARDHANDLE hCard, DWORD dwShareMode, DWORD dwPreferredProtocols, DWORD dwInitialization, LPDWORD pdwActiveProtocol)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardReconnect(hCard, dwShareMode, dwPreferredProtocols, dwInitialization, pdwActiveProtocol);
	}
	
	LONG SCardLoader::SCardDisconnect(SCARDHANDLE hCard, DWORD dwDisposition)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardDisconnect(hCard, dwDisposition);
	}
	
	LONG SCardLoader::SCardBeginTransaction(SCARDHANDLE hCard)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardBeginTransaction(hCard);
	}
	
	LONG SCardLoader::SCardEndTransaction(SCARDHANDLE hCard, DWORD dwDisposition)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardEndTransaction(hCard, dwDisposition);
	}
	
	LONG SCardLoader::SCardStatus(SCARDHANDLE hCard, LPTSTR mszReaderNames, LPDWORD pcchReaderLen, LPDWORD pdwState, LPDWORD pdwProtocol, BYTE* pbAtr, LPDWORD pcbAtrLen)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardStatus(hCard, mszReaderNames, pcchReaderLen, pdwState, pdwProtocol, pbAtr, pcbAtrLen);
	}
	
	LONG SCardLoader::SCardGetStatusChange(SCARDCONTEXT hContext, DWORD dwTimeout, LPSCARD_READERSTATE rgReaderStates, DWORD cReaders)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardGetStatusChange(hContext, dwTimeout, rgReaderStates, cReaders);
	}
	
	LONG SCardLoader::SCardControl(SCARDHANDLE hCard, DWORD dwControlCode, LPCVOID pbSendBuffer, DWORD cbSendLength, LPVOID pbRecvBuffer, DWORD cbRecvLength, LPDWORD lpBytesReturned)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardControl(hCard, dwControlCode, pbSendBuffer, cbSendLength, pbRecvBuffer, cbRecvLength, lpBytesReturned);
	}
	
	LONG SCardLoader::SCardTransmit(SCARDHANDLE hCard, LPCSCARD_IO_REQUEST pioSendPci, const BYTE* pbSendBuffer, DWORD cbSendLength, LPSCARD_IO_REQUEST pioRecvPci, BYTE* pbRecvBuffer, LPDWORD pcbRecvLength)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardTransmit(hCard, pioSendPci, pbSendBuffer, cbSendLength, pioRecvPci, pbRecvBuffer, pcbRecvLength);
	}
	
	LONG SCardLoader::SCardListReaderGroups(SCARDCONTEXT hContext, LPTSTR mszGroups, LPDWORD pcchGroups)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardListReaderGroups(hContext, mszGroups, pcchGroups);
	}
	
	LONG SCardLoader::SCardListReaders(SCARDCONTEXT hContext, LPCTSTR mszGroups, LPTSTR mszReaders, LPDWORD pcchReaders)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardListReaders(hContext, mszGroups, mszReaders, pcchReaders);
	}
	
	LONG SCardLoader::SCardCancel(SCARDCONTEXT hContext)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardCancel(hContext);
	}
	
	LONG SCardLoader::SCardGetAttrib(SCARDHANDLE hCard, DWORD dwAttrId, BYTE* pbAttr, LPDWORD pcbAttrLen)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardGetAttrib(hCard, dwAttrId, pbAttr, pcbAttrLen);
	}
	
	LONG SCardLoader::SCardSetAttrib(SCARDHANDLE hCard, DWORD dwAttrId, const BYTE* pbAttr, DWORD cbAttrLen)
	{
		Initialize();

		if (!bInitialized)
			throw ScardLibraryInitializationFailed();

		return scardSetAttrib(hCard, dwAttrId, pbAttr, cbAttrLen);
	}
}