#pragma once

#include "volumeshop.h"

#include <windows.h>
#include <string>
#include <typeinfo>

#include "Plugin.h"
#include "Environment.h"
#include "Image.h"


#define DECLARE_PLUGIN(type,name) \
	extern "C" __declspec(dllexport) type * CreateInstance(Environment & envEnvironment); \
	extern "C" __declspec(dllexport) const char * GetType(); \
	extern "C" __declspec(dllexport) const char * GetName(); \
	extern "C" __declspec(dllexport) const char * GetDescription(); \
	extern "C" __declspec(dllexport) const unsigned char * GetIcon(); \

#define DEFINE_PLUGIN(type,name,description,icon) \
	extern "C" __declspec(dllexport) type * CreateInstance(Environment & envEnvironment) \
	{ \
		return new name(envEnvironment); \
	} \
	\
	extern "C" __declspec(dllexport) const char * GetType(void) \
	{ \
	return #type; \
	} \
	extern "C" __declspec(dllexport) const char * GetName(void) \
	{ \
		return #name; \
	} \
	\
	extern "C" __declspec(dllexport) const char * GetDescription(void) \
	{ \
	return description; \
	} \
	\
	extern "C" __declspec(dllexport) const unsigned char * GetIcon(void) \
	{ \
	return icon; \
	} \
	\
	BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) \
	{ \
		return TRUE; \
	} \

template <class Type>
class BasicPlugin : public Plugin
{
public:

	class Iterator
	{
	public:
		Iterator(const std::string & strFilter) : m_hHandle(NULL), m_bLast(false), m_bAtEnd(false)
		{
			m_hHandle = FindFirstFile(strFilter.c_str(),&m_fdCurrent);

			if (m_hHandle == INVALID_HANDLE_VALUE)
				m_bAtEnd = true;
			else
			{
				std::string strSearchType(typeid(typename Type).name());
				std::string strFoundType(std::string("class ") + std::string(GetType(m_fdCurrent.cFileName)));

				if (strSearchType != strFoundType)
					++(*this);
			}
		};

		~Iterator()
		{
			if (m_hHandle != INVALID_HANDLE_VALUE)
				FindClose(m_hHandle);
		};

		const Iterator & operator++()
		{
			if (!m_bLast)
			{
				std::string strSearchType(typeid(typename Type).name());
				std::string strFoundType;

				do
				{					
					m_bLast = (FindNextFile(m_hHandle,&m_fdCurrent) == 0) ? true : false;
					strFoundType = std::string("class ") + std::string(GetType(m_fdCurrent.cFileName));
				}
				while (!m_bLast && strFoundType != strSearchType);

				if (m_bLast)
				{
					if (strFoundType != strSearchType)
						m_bAtEnd = true;
				}
			}			
			else
				m_bAtEnd = true;

			return *this;
		};

		const std::string operator*() const
		{
			return m_fdCurrent.cFileName;
		};

		const std::string GetName() const
		{
			return BasicPlugin<Type>::GetName(m_fdCurrent.cFileName);
		};

		const std::string GetDescription() const
		{
			return BasicPlugin<Type>::GetDescription(m_fdCurrent.cFileName);
		};

		const Image GetIcon() const
		{
			return BasicPlugin<Type>::GetIcon(m_fdCurrent.cFileName);
		};

		const bool IsAtEnd() const
		{
			return m_bAtEnd;
		};

	private:

		HANDLE m_hHandle;
		WIN32_FIND_DATA m_fdCurrent;
		bool m_bLast;
		bool m_bAtEnd;
	};

	BasicPlugin(Environment & envEnvironment, const std::string & strFilename) : Plugin(envEnvironment), m_strFilename(strFilename), m_hLibrary(NULL), m_pInstance(NULL)
	{
	};

	virtual ~BasicPlugin()
	{
		uninitialize();
	};

	virtual void initialize()
	{
		if (!IsInitialized())
		{
			typedef Type* (*CreateInstanceType)(Environment &);

			//Load the dll and keep the handle to it
			m_hLibrary = LoadLibrary(m_strFilename.c_str());

			// If the handle is valid, try to get the function address. 
			if (m_hLibrary != NULL) 
			{ 
				// Get pointer to our function using GetProcAddress
				CreateInstanceType CreateInstancePtr = (CreateInstanceType) GetProcAddress(m_hLibrary,"CreateInstance");

				// If the function address is valid, call the function. 
				if (CreateInstancePtr != NULL)
				{
					try
					{
						m_pInstance = CreateInstancePtr(GetEnvironment());
					}
					catch (Exception e)
					{
						if (m_pInstance)
						{
							try
							{
								delete m_pInstance;
							}
							catch (...)
							{
							}
						}

						m_pInstance = NULL;
						std::cerr << "Unable to initialize " << m_strFilename.c_str() << " (" << e << ")." << std::endl << std::endl;
					}
					catch (...)
					{
						if (m_pInstance)
						{
							try
							{
								delete m_pInstance;
							}
							catch (...)
							{
							}
						}

						m_pInstance = NULL;
						std::cerr << "Unable to initialize " << m_strFilename.c_str() << "." << std::endl << std::endl;
					}

					if (!m_pInstance)
					{
						FreeLibrary(m_hLibrary);
						m_pInstance = NULL;
					}
				}
				else
				{
					FreeLibrary(m_hLibrary);
					m_hLibrary = NULL;
				}
			}
		}
	};

	virtual void uninitialize()
	{
		if (IsInitialized())
		{
			if (m_hLibrary != NULL)
			{
				delete m_pInstance;
				m_pInstance = NULL;

				FreeLibrary(m_hLibrary);
				m_hLibrary = NULL;
			}
		}
	};

	virtual const bool IsInitialized() const
	{
		return (m_hLibrary != NULL && m_pInstance != NULL);
	};

	virtual const std::string GetType() const
	{
		if (IsInitialized())
		{
			std::string strResult;
			typedef const char* (*GetTypeType)();

			// If the handle is valid, try to get the function address. 
			if (m_hLibrary != NULL) 
			{ 
				// Get pointer to our function using GetProcAddress
				GetTypeType GetTypePtr = (GetTypeType) GetProcAddress(m_hLibrary,"GetType");

				// If the function address is valid, call the function. 
				if (GetTypePtr != NULL)
				{
					strResult = GetTypePtr();
				}
			}

			return strResult;
		}

		return GetType(m_strFilename);
	};

	virtual const std::string GetName() const
	{
		if (IsInitialized())
		{
			std::string strResult;
			typedef const char* (*GetNameType)();

			// If the handle is valid, try to get the function address. 
			if (m_hLibrary != NULL) 
			{ 
				// Get pointer to our function using GetProcAddress
				GetNameType GetNamePtr = (GetNameType) GetProcAddress(m_hLibrary,"GetName");

				// If the function address is valid, call the function. 
				if (GetNamePtr != NULL)
				{
					strResult = GetNamePtr();
				}
			}

			return strResult;
		}

		return GetName(m_strFilename);
	};

	virtual const std::string GetDescription() const
	{
		if (IsInitialized())
		{
			std::string strResult;
			typedef const char* (*GetDescriptionType)();

			// If the handle is valid, try to get the function address. 
			if (m_hLibrary != NULL) 
			{ 
				// Get pointer to our function using GetProcAddress
				GetDescriptionType GetDescriptionPtr = (GetDescriptionType) GetProcAddress(m_hLibrary,"GetDescription");

				// If the function address is valid, call the function. 
				if (GetDescriptionPtr != NULL)
				{
					strResult = GetDescriptionPtr();
				}
			}

			return strResult;
		}

		return GetDescription(m_strFilename);
	};

	virtual const Image GetIcon() const
	{
		if (IsInitialized())
		{
			Image imgResult;
			typedef const unsigned char* (*GetIconType)();

			// If the handle is valid, try to get the function address. 
			if (m_hLibrary != NULL) 
			{ 
				// Get pointer to our function using GetProcAddress
				GetIconType GetIconPtr = (GetIconType) GetProcAddress(m_hLibrary,"GetIcon");

				// If the function address is valid, call the function. 
				if (GetIconPtr != NULL)
				{
					const unsigned char *pIcon = GetIconPtr();

					if (pIcon)
					{
						const unsigned int uWidth(pIcon[0]);
						const unsigned int uHeight(pIcon[1]);
						const Color *pData = (Color *) (pIcon+2);
						imgResult = Image(uWidth,uHeight,pData);
					}
				}
			}

			return imgResult;
		}

		return GetIcon(m_strFilename);
	};

	static const std::string GetType(const std::string & strFilename)
	{
		std::string strResult;
		typedef const char* (*GetTypeType)();

		//Load the dll and keep the handle to it
		HINSTANCE hLibrary = LoadLibrary(strFilename.c_str());

		// If the handle is valid, try to get the function address. 
		if (hLibrary != NULL) 
		{ 
			// Get pointer to our function using GetProcAddress
			GetTypeType GetTypePtr = (GetTypeType) GetProcAddress(hLibrary,"GetType");

			// If the function address is valid, call the function. 
			if (GetTypePtr != NULL)
			{
				strResult = GetTypePtr();
			}

			FreeLibrary(hLibrary);
		}

		return strResult;
	};

	static const std::string GetName(const std::string & strFilename)
	{
		std::string strResult;
		typedef const char* (*GetNameType)();

		//Load the dll and keep the handle to it
		HINSTANCE hLibrary = LoadLibrary(strFilename.c_str());

		// If the handle is valid, try to get the function address. 
		if (hLibrary != NULL) 
		{ 
			// Get pointer to our function using GetProcAddress
			GetNameType GetNamePtr = (GetNameType) GetProcAddress(hLibrary,"GetName");

			// If the function address is valid, call the function. 
			if (GetNamePtr != NULL)
			{
				strResult = GetNamePtr();
			}

			FreeLibrary(hLibrary);
		}

		return strResult;
	};

	static const std::string GetDescription(const std::string & strFilename)
	{
		std::string strResult;
		typedef const char* (*GetDescriptionType)();

		//Load the dll and keep the handle to it
		HINSTANCE hLibrary = LoadLibrary(strFilename.c_str());

		// If the handle is valid, try to get the function address. 
		if (hLibrary != NULL) 
		{ 
			// Get pointer to our function using GetProcAddress
			GetDescriptionType GetDescriptionPtr = (GetDescriptionType) GetProcAddress(hLibrary,"GetDescription");

			// If the function address is valid, call the function. 
			if (GetDescriptionPtr != NULL)
			{
				strResult = GetDescriptionPtr();
			}

			FreeLibrary(hLibrary);
		}

		return strResult;
	};

	static const Image GetIcon(const std::string & strFilename)
	{
		Image imgResult;
		typedef const unsigned char* (*GetIconType)();

		//Load the dll and keep the handle to it
		HINSTANCE hLibrary = LoadLibrary(strFilename.c_str());

		// If the handle is valid, try to get the function address. 
		if (hLibrary != NULL) 
		{ 
			// Get pointer to our function using GetProcAddress
			GetIconType GetIconPtr = (GetIconType) GetProcAddress(hLibrary,"GetIcon");

			// If the function address is valid, call the function. 
			if (GetIconPtr != NULL)
			{
				const unsigned char *pIcon = GetIconPtr();

				if (pIcon)
				{
					const unsigned int uWidth(pIcon[0]);
					const unsigned int uHeight(pIcon[1]);
					const Color *pData = (Color *) (pIcon+2);
					imgResult = Image(uWidth,uHeight,pData);
				}
			}

			FreeLibrary(hLibrary);
		}

		return imgResult;
	};

protected:

	Type * GetInstance()
	{
		return m_pInstance;
	};

private:

	const std::string m_strFilename;
	HINSTANCE m_hLibrary;
	Type *m_pInstance;
};
