#include "DeviceRegistry.h"

#include <windows.h>

#include <map>
#include <string>
#include <sstream>


typedef const wchar_t* (*GetDeviceName)();
typedef Device*		   (*AcquireDevice)();
typedef void           (*ReleaseDevice)(Device* device);



/*****************************************************
*	_DeviceRegistry (inner class) implementation
*****************************************************/

class DeviceRegistry::_DeviceRegistry {
public:
	_DeviceRegistry();
	~_DeviceRegistry();

	Device* acquireDevice(const wchar_t* deviceName);
    void    releaseDevice(Device** device);

	const wchar_t* getDeviceList();

private:
	void scanLibraries();
	void releaseLibraries();
    
	std::map<std::wstring, void*>			_libraryHandleMap;
    std::map<std::wstring, AcquireDevice>	_deviceAllocatorMap;
    std::map<std::wstring, ReleaseDevice>	_deviceDeallocatorMap;
};

DeviceRegistry::_DeviceRegistry::_DeviceRegistry() 
{
	scanLibraries();
}

DeviceRegistry::_DeviceRegistry::~_DeviceRegistry() 
{
	releaseLibraries();
}


void DeviceRegistry::_DeviceRegistry::scanLibraries()
{
	WIN32_FIND_DATA findFileData;

	HANDLE hFind;
	HMODULE hLibrary = NULL;

	GetDeviceName getDeviceName = NULL;
	AcquireDevice acquireDevice = NULL;
    ReleaseDevice releaseDevice = NULL;

    std::wstring deviceName = L"";



	hFind = FindFirstFile(L"*.dll", &findFileData);

	if (hFind != INVALID_HANDLE_VALUE) {
		hLibrary = LoadLibrary(findFileData.cFileName);
		
		if (hLibrary != NULL) {
			getDeviceName = (GetDeviceName) GetProcAddress(hLibrary, "getDeviceName");
			acquireDevice = (AcquireDevice) GetProcAddress(hLibrary, "acquireDevice");
            releaseDevice = (ReleaseDevice) GetProcAddress(hLibrary, "releaseDevice");

			if	((getDeviceName != NULL) && (acquireDevice != NULL) && (releaseDevice != NULL)) {
				deviceName = getDeviceName();

				_libraryHandleMap.insert(std::pair<std::wstring, void*>(deviceName, hLibrary));
                _deviceAllocatorMap.insert(std::pair<std::wstring, AcquireDevice>(deviceName, acquireDevice));
                _deviceDeallocatorMap.insert(std::pair<std::wstring, ReleaseDevice>(deviceName, releaseDevice));
			}
			else {
				FreeLibrary(hLibrary);
			}
		}

		while(FindNextFile(hFind, &findFileData)) {
			hLibrary = LoadLibrary(findFileData.cFileName);
			
			if (hLibrary != NULL) {
				getDeviceName = (GetDeviceName) GetProcAddress(hLibrary, "getDeviceName");
				acquireDevice = (AcquireDevice) GetProcAddress(hLibrary, "acquireDevice");
                releaseDevice = (ReleaseDevice) GetProcAddress(hLibrary, "releaseDevice");

				if	((getDeviceName != NULL) && (acquireDevice != NULL)) {
					deviceName = getDeviceName();

					_libraryHandleMap.insert(std::pair<std::wstring, void*>(deviceName, hLibrary));
                    _deviceAllocatorMap.insert(std::pair<std::wstring, AcquireDevice>(deviceName, acquireDevice));
                    _deviceDeallocatorMap.insert(std::pair<std::wstring, ReleaseDevice>(deviceName, releaseDevice));
				}
				else {
					FreeLibrary(hLibrary);
				}
			}
		}

		FindClose(hFind);
	}

}

Device* DeviceRegistry::_DeviceRegistry::acquireDevice(const wchar_t* deviceName)
{
	std::map<std::wstring, AcquireDevice>::iterator iDeviceAllocator = _deviceAllocatorMap.find(deviceName);

	if (iDeviceAllocator != _deviceAllocatorMap.end()) {
		if (iDeviceAllocator->second != NULL)  {
			return iDeviceAllocator->second();
		}
	}

	return NULL;
}

void DeviceRegistry::_DeviceRegistry::releaseDevice(Device** device)
{
    if (*device != NULL) {
        std::map<std::wstring, ReleaseDevice>::iterator iDeviceDeallocator = _deviceDeallocatorMap.find((*device)->getDeviceName());

	    if (iDeviceDeallocator != _deviceDeallocatorMap.end()) {
		    if (iDeviceDeallocator->second != NULL)  {
			    iDeviceDeallocator->second(*device);
                *device = NULL;
		    }
	    }  
    }
}



void DeviceRegistry::_DeviceRegistry::releaseLibraries()
{
	for (std::map<std::wstring, void*>::iterator iLibraryHandle = _libraryHandleMap.begin(); iLibraryHandle != _libraryHandleMap.end(); iLibraryHandle++) {
		FreeLibrary((HMODULE)iLibraryHandle->second);
	}

	_libraryHandleMap.clear();
	_deviceAllocatorMap.clear();
    _deviceDeallocatorMap.clear();
}



const wchar_t* DeviceRegistry::_DeviceRegistry::getDeviceList()
{
	if (_deviceAllocatorMap.empty())
		return NULL;

	std::wstringstream stream;

	for (std::map<std::wstring, AcquireDevice>::iterator iDevice = _deviceAllocatorMap.begin(); iDevice != _deviceAllocatorMap.end(); ++iDevice) {
        stream << iDevice->first << std::endl;
	}

	return stream.str().c_str();
}




/*****************************************************
*	DeviceRegistry (outer class) implementation
*****************************************************/

DeviceRegistry::DeviceRegistry()
{
	_deviceRegistry = new _DeviceRegistry();
}

DeviceRegistry::~DeviceRegistry()
{
	delete _deviceRegistry;
}

DeviceRegistry& DeviceRegistry::instance()
{
	static DeviceRegistry deviceRegistry;
	
	return deviceRegistry;
}

Device* DeviceRegistry::acquireDevice(const wchar_t* deviceName)
{
	return _deviceRegistry->acquireDevice(deviceName);
}

void DeviceRegistry::releaseDevice(Device** device)
{
	_deviceRegistry->releaseDevice(device);
}

const wchar_t* DeviceRegistry::getDeviceList()
{
	return _deviceRegistry->getDeviceList();
}