#ifndef _LOADPLUGIN_H_
#define _LOADPLUGIN_H_


#include<map>
//#include<helpers_s.h>

#ifdef _UNICODE
#define _tstring wstring
#else
#define _tstring string
#endif

typedef HRESULT (STDAPICALLTYPE *PDLLGETCLASSOBJECT)(const CLSID& clsid, const IID& iid, void** ppv);

class cLoadPlugin
{
public:
	cLoadPlugin()
	{
		m_plugins.clear();
		InitializeCriticalSection( &m_cs );
	}
	~cLoadPlugin();

	bool LoadPlugin( const TCHAR* szDllPath , IID IInterface , void** pInterface );




private:
	HMODULE  GetModule( const TCHAR* szDllPath );
	_tstring& toLower( _tstring& sin );
	HMODULE isInList( _tstring& path );
	map<_tstring , HMODULE> m_plugins;
	CRITICAL_SECTION m_cs;
	
	void ecs()
	{
		EnterCriticalSection( &m_cs );
	}
	void lcs()
	{
		LeaveCriticalSection( &m_cs );
	}
};



cLoadPlugin::~cLoadPlugin()
{	
	map<_tstring , HMODULE>::iterator it;

	DeleteCriticalSection( &m_cs );
	
	for ( it=m_plugins.begin() ; it != m_plugins.end(); it++ )
	{
		FreeLibrary( (HMODULE)it->second );
	}


}


_tstring& cLoadPlugin::toLower( _tstring& sin )
{
	TCHAR cStart = L'A', cEnd=L'Z';
	for( unsigned int i=0; i < sin.length(); i++ )
	{
		if( sin[i] >= cStart && sin[i] <= cEnd )
		{
			sin[i] = sin[i]+32;
		}

	}

	return( sin );
}

HMODULE cLoadPlugin::isInList( _tstring& path )
{
	map<_tstring , HMODULE>::iterator it;
	
	it = m_plugins.find( path );
	if( it == m_plugins.end() )
		return( NULL );
	return( it->second );
}

bool cLoadPlugin::LoadPlugin( const TCHAR* szDllPath , IID IInterface , void** pInterface )
{
	ecs();

	HRESULT hr;
	HMODULE hMod = NULL;
	PDLLGETCLASSOBJECT proc = NULL;
	IClassFactory* pFactory = NULL;

	//Get the module from list or load it
	hMod = GetModule( szDllPath );
	if( !hMod )
	{
		lcs();
		return( false );
	}

	//Get Proc address
	proc = (PDLLGETCLASSOBJECT) GetProcAddress( hMod , "DllGetClassObject" );
	if( !proc )
	{
		lcs();
		return( false );
	}

	hr = proc( IInterface , IID_IClassFactory , (void**)&pFactory );
	if( hr || !pFactory )
	{
		lcs();
		return( false );
	}

	hr = pFactory->CreateInstance( NULL, IInterface , pInterface );
	if( hr || !pInterface )
	{
		lcs();
		return( false );
	}

	pFactory->Release();
	
	lcs();
	return( true );
}


HMODULE  cLoadPlugin::GetModule( const TCHAR* szDllPath )
{
	_tstring spath;
	_tstring mname;
	const TCHAR *pName = NULL, c = L'\\';
	HMODULE hMod = NULL;
	PDLLGETCLASSOBJECT proc = NULL;

	
	if( !szDllPath )
		return( NULL );

	//convet path to lowercase and check if file exist
	//If not a file
	spath = szDllPath;
	toLower( spath );

	//if( !IsFile( toLower( spath ).c_str() ) )
	//	return( false );

	//Is alread on list
	pName = _tcsrchr( spath.c_str() , c );
	if( !pName )
		pName = spath.c_str();
	else
		pName++;
	mname = pName;

	//Check if module already loaded
	hMod = isInList( mname );

	//if not on list load module and store it to list
	if( !hMod )
	{
		if( ( hMod = LoadLibrary( spath.c_str() ) ) == NULL )
			return( NULL );

		 proc = (PDLLGETCLASSOBJECT) GetProcAddress( hMod , "DllGetClassObject" );
		 if( !proc )
		 {
			 FreeLibrary( hMod );
			 return( NULL );
		 }

		 //insert plugin
		 m_plugins[mname] = hMod;
	}

	return( hMod );
}

#endif //_LOADPLUGIN_H_