#ifndef _xbHOTPLUG_
#define _xbHOTPLUG_
/*<<<***************************************************************************
The MIT License (MIT)

Copyright(C) 2013 - 2015 Marc Mirti Mancinelli, Christophe Lardau and Philippe Pamart (xdcube.org)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
////////////////////////////////////////////////////////////////////////////////
#Title: xbHotPlug
#Project: xdcBase
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<12/02/2014: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/

#ifndef _xbCONFIG_
#	include "xbConfig.h"
#endif

#ifndef _xbHOTPLUGITF_
#	include "xbHotPlugItf.h"
#endif

#ifndef _xbENGINE_
#	include "xbEngine.h"
#endif

#ifndef _xbMSG_
#	include "xbMsg.h"
#endif

#ifndef _xbLIBTOOLS_
#	include "xbLibTools.h"
#endif

/***********************************************************************************
registry structure to list plugins to use :
	ex: \\HKEY_LOCAL_MACHINE\Software\xdcBase\PLUGTYPENAME Plugins.1\ :
		- key module guid = 'label'
		-	subkey 'objectname' ou 'default',
		-	priority = 

	priority = 0 : lower than priority = 1

************************************************************************************/
class xbHotPlugFactory;
struct xbEXPORT xbPlugObjInfo
{
	xbString	m_plugId;
	xbString	m_plugPath;
	xbString	m_name;
	long		m_priority;
	void		*m_pData;
	xbHotPlugFactory *m_pEmbeddedFact;
};


typedef	void (*xbPluginCreateObject)(IxbHotPlugin *pPluginContainer,const xbString &name,xbRefPtr<IxbObject> *ppObj,void *pCtorData);
typedef bool (*xbPluginUnload)(IxbHotPlugin *pPluginContainer,bool bForce);
typedef void (*xbPluginLoad)(IxbHotPlugin *pPluginContainer);

enum xbPlugRet
{
	xbPlugOk, //Nothing to download, plugins/ocx/... ready
		xbPlugUpdating // download of plugins/ocx in progress
};

class xbEXPORT  __declspec(uuid("{72B5D83C-ED16-43e7-BDA0-915460D80CB1}"))
	xbHotPlugType:public xbEngine,public sIxbHotPlugType
{
public:
	xbHotPlugType(const char *pPlugTypeName,const char *pPlugTypeVer,const char *pLabel,const xbEngines &engUsed = xbEngines());
	virtual ~xbHotPlugType()
	{
		Destroy();
	}
	virtual bool GetInterface(REFIID riid, void ** ppInterf)
	{
		xbImplInterface(xbHotPlugType);
		xbImplInterface(sIxbHotPlugType);
		return xbEngine::GetInterface(riid,ppInterf);
	}


	bool	CreatePluggedObject(sIxbPlugPtr *pPtr,const xbString &name,void *pCtorData=NULL);
	bool	CreatePluggedObject(const xbString &name,xbRefPtr<IxbObject> *pprObj,xbRefPtr<IxbHotPlugin> *pprPlugin = NULL,void *pCtorData=NULL);

	void	AddPlugTypeCb(IxbHotPlugTypeCb *pCb);


	bool	CanWorkWith(IxbHotPlugin *pObj,const xbString &name);

	// sIxbHotPlugType methods
	virtual xbString	GetPlugTypeName() const {return m_pName;}
	virtual xbString	GetPlugTypeVer() const {return m_pVer;}
	virtual xbString	GetPlugTypeLabel() const {return Label;}
	virtual xbString	GetConfigPath() const;
	virtual xbCfgKey	GetConfigKey() const;

	void AskConfigUpdate() const;

	void ReloadConfig(bool bNow);

	virtual bool isLockedByMe() const {return m_sect.IsLockedByMe();}

	virtual bool IsInUpdate() const {return m_nLockCbCall > 0;}
protected:
	void	loadConfig();

	virtual xbPlugUpdateCaps	onGetPluginCaps() {return xbPlugUpdateNormal;}
	virtual xbPlugUpdateCaps	onErrorGetPluginCaps() {return xbPlugUpdateNormal;}
	virtual void	onReloadConfig();

	virtual bool onLoadConfigForPluggedObject(const xbString &plugid,const xbString &name,long priority,xbCfgKey &keyobj,void **ppData) {return true;}
	virtual void onUnloadConfigForPluggedObject(const xbString &plugid,const xbString &name,void *pData) {xbAssert(pData == NULL);}
	
	virtual void onStart();
	virtual bool onStop(bool bForce);

	xbCfgChangedCb	m_cfgChangedCb;
	void receiveConfigChanged(xbCfgChangedCb &cbObj,xbCfgRoot root,xbCfgKeyChangesInfo *pInfo,const xbString &cfgKeyPath);

	class UpdateCallLock;
	class xbEXPORT __declspec(uuid("{2B2174C2-98CB-46ca-8122-D377BA71B8BB}"))
		Plugin:public xbObject,public IxbHotPlugin,public sIxbHotPlugType
	{
	public:
		Plugin(xbHotPlugType *pPlugType,const xbString &plugid,const xbString &label,const xbString &plugPath);
		void setEmbeddedFact(xbHotPlugFactory *m_pEmbeddedFact);
		virtual ~Plugin();

		xbImplObject();

		virtual bool GetInterface(REFIID riid, void ** ppInterf)
		{
			xbImplInterface(IxbHotPlugin);
			xbImplInterface(Plugin);
			xbImplInterface(sIxbHotPlugType);
			return xbObject::GetInterface(riid,ppInterf);
		}

		void resetUsed() {m_bUsed = false;}
		bool setUsed(xbPlugObjInfo *pInfo);
		bool isUsed() {return m_bUsed;}
		bool isToCheck();

		void check();
		void createPluggedObject(sIxbPlugPtr *pPtr,const xbString &name,void *pCtorData);
		void createPluggedObject(const xbString &name,xbRefPtr<IxbObject> *pprObj,xbRefPtr<IxbHotPlugin> *pprPlugin,void *pCtorData);
		bool canWorkWith(IxbHotPlugin *pPtr,const xbString &name);
		void createObject(const xbString &name,xbRefPtr<IxbObject> *ppObj,void *pCtorData);
		void loadPlugin();
		void unloadPlugin(bool bForce = false);

		void unloadPluggedObj();

		xbPlugUpdateCaps onError(xbEbase *pE);
		xbPlugUpdateCaps onError(bool bCanRetry, HRESULT hr, const xbString &errorText);
		xbPlugRet onLoadPluginAsked();
		void onProgress( double progress, xbPlugStatus status, const xbString &statusText);

		xbPlugUpdateCaps	getPluginCaps();

		// sIxbHotPlugType methods
		virtual xbString	GetPlugTypeName() const {return m_pPlugType->m_pName;}
		virtual xbString	GetPlugTypeVer() const {return m_pPlugType->m_pVer;}
		virtual xbString	GetPlugTypeLabel() const {return m_pPlugType->Label;}
		virtual xbString	GetConfigPath() const;
		virtual xbCfgKey	GetConfigKey() const;

		// IxbHotPlugin methods
		virtual void		_forceUnload()
		{
			Destroy();
		}

		virtual xbString	GetPluginId() const {return m_plugId;}
		virtual xbString	GetPluginLabel() const {return m_label;}
	protected:
		virtual void onBeforeDestruction(bool bInDestroy);
		virtual void onDestruction(bool bInDestroy);

		void lockUpdateCall() const;
		void unlockUpdateCall() const;

		xbHotPlugType						*m_pPlugType;
		xbString							m_plugId;
		xbString							m_label;
		bool								m_bUsed;
		xbString							m_plugPath;

		HINSTANCE							m_hPlugin;
		bool								m_bInPluginLoad,m_bInPluginUnload,m_bPluginUnloadAsked;
		long								m_cInPluginCreate;
		HRESULT								m_hrPlugin;
		xbPluginCreateObject				m_pPluginCreateObject;
		xbPluginUnload						m_pPluginUnload;

		mutable long						m_nLockUpdateCall;
		mutable HANDLE						m_hLockUpdateThread;

		xbHotPlugFactory					*m_pEmbeddedFact;
		bool								m_bIsEmbeddedFact;

		friend UpdateCallLock;
	};
	class UpdateCallLock
	{
	public:
		UpdateCallLock()
		{
			m_pObj = NULL;
		}
		UpdateCallLock(const Plugin *pObj)
		{
			m_pObj = NULL;
			if (pObj) Lock(pObj);
		}
		~UpdateCallLock()
		{
			eProtect(Unlock());
		}
		void Lock(const Plugin *pObj)
		{
			if (m_pObj) Unlock();
			pObj->lockUpdateCall();
			m_pObj = pObj;
		}
		void Unlock()
		{
			if (!m_pObj) return;
			const Plugin	*pObj = m_pObj;
			m_pObj = NULL;
			pObj->unlockUpdateCall();
		}
	protected:
		const Plugin	*m_pObj;
	};

	struct Object
	{
		xbString			m_name;
		xbRefPtr<Plugin>	m_pPlg;
		void				*m_pData;
		long				m_priority;
		Object(const xbString &name,long priority,Plugin *pPlg,void *pData)
			:m_name(name),m_pPlg(pPlg),m_pData(pData),m_priority(priority)
		{
		}
		Object() {}
	};

	const char *m_pName,*m_pVer;

	mutable xbSectShare	m_sect;

	xbArray<Object>				m_arrObj;
	xbArray< xbRefPtr<Plugin> >	m_arrPlg;

	static int		findPlugin(const xbRefPtr<Plugin> &PlgId1,const void *pId);
	static int		findPlugObj(const Object &Obj,const void *pStr);

	bool						m_bCfgLoaded;

	int							m_cPlugin;


	mutable long							m_nLockCbCall,m_nLockCbAdd;
	xbPtrArray< xbObsPtr<IxbHotPlugTypeCb> >	m_arrCb;

	friend Plugin;

	void lockCbCall() const;
	void unlockCbCall() const;

	void lockCbAdd() const;
	void unlockCbAdd() const;

	void addCb(IxbHotPlugTypeCb *pCb);
public:
	class CbCallLock
	{
	public:
		CbCallLock()
		{
			m_pObj = NULL;
		}
		CbCallLock(const xbHotPlugType *pObj)
		{
			m_pObj = NULL;
			if (pObj) Lock(pObj);
		}
		~CbCallLock()
		{
			eProtect(Unlock());
		}
		void Lock(const xbHotPlugType *pObj)
		{
			if (m_pObj) Unlock();
			pObj->lockCbCall();
			m_pObj = pObj;
		}
		void Unlock()
		{
			if (!m_pObj) return;
			const xbHotPlugType	*pObj = m_pObj;
			m_pObj = NULL;
			pObj->unlockCbCall();
		}
	protected:
		const xbHotPlugType	*m_pObj;
	};
	class CbAddLock
	{
	public:
		CbAddLock()
		{
			m_pObj = NULL;
		}
		CbAddLock(const xbHotPlugType *pObj)
		{
			m_pObj = NULL;
			if (pObj) Lock(pObj);
		}
		~CbAddLock()
		{
			eProtect(Unlock());
		}
		void Lock(const xbHotPlugType *pObj)
		{
			if (m_pObj) Unlock();
			pObj->lockCbAdd();
			m_pObj = pObj;
		}
		void Unlock()
		{
			if (!m_pObj) return;
			const xbHotPlugType	*pObj = m_pObj;
			m_pObj = NULL;
			pObj->unlockCbAdd();
		}
	protected:
		const xbHotPlugType	*m_pObj;
	};

	friend CbCallLock;
	friend CbAddLock;
};


class xbHotPlugFactory;
typedef	void (*xbHotPlugCreateObject)(xbHotPlugFactory *pFactory,IxbHotPlugin *pPluginContainer,const char *pName,const void *pCtorData,xbRefPtr<IxbObject> *ppObj);
struct xbEXPORT xbHotPlugFactoryList
{
	const char				*m_pName;	
	xbHotPlugCreateObject	m_pCreateFct;
	long					m_priority;
	const void				*m_pCtorData;
};

enum xbHotPlugLinkType
{
#ifndef XDCBASE_EMBED
	xbHotPlugLinkDynamic = 0,
#	define xbHotPlugLinkType_Default xbHotPlugLinkDynamic
#else
#	define xbHotPlugLinkType_Default xbHotPlugLinkEmbedded
#endif
	xbHotPlugLinkEmbedded = 1
};

class xbEXPORT xbHotPlugFactory
{
public:
	xbHotPlugFactory(
#ifndef XDCBASE_EMBED
		xbXdcLib *pLib,
#endif
		const xbHotPlugType *pPlugType,const char *pId,const char *pLabel,
		const xbHotPlugFactoryList *pFactList,ulong unloadDelayInMs = 0,
		xbHotPlugLinkType linkType = xbHotPlugLinkType_Default);
	virtual ~xbHotPlugFactory();

	void Register();
	void Unregister();

	void CreateObject(IxbHotPlugin *pPluginContainer,const xbString &name,xbRefPtr<IxbObject> *ppObj,void *pCtorData);
	bool Unload(IxbHotPlugin *pPluginContainer,bool bForce = false);
	void Load(IxbHotPlugin *pPluginContainer);
	void DecObjCnt();
	void IncObjCnt();

	bool GetPluginContainer(xbRefPtr<IxbHotPlugin> *ppPlugCont)
	{
		return m_poPluginContainer.GetPtr(ppPlugCont);
	}

	bool ForceUnload();
	bool isUnloadAsked() const {return m_bUnloadAsked;}
	xbString GetLabel() const
	{
		return m_pLabel;
	}
	const xbHotPlugType *GetHotPlugType() const
	{
		return m_pPlugType;
	}
	const xbHotPlugFactoryList *GetHotPlugFactoryList() const
	{
		return m_pFactList;
	}
	const char *GetID()
	{
		return m_pId;
	}
	xbHotPlugFactory *GetEmbeddedNext()
	{
		return m_pEmbeddedFactoryNext;
	}
protected:
	void _embeddedRegister();
	void _embeddedUnregister();

	virtual void onLoad(IxbHotPlugin *pPluginContainer) {}
	virtual void onUnload(IxbHotPlugin *pPluginContainer) {}

	const xbHotPlugType			*m_pPlugType;
	const char					*m_pId;
	const char					*m_pLabel;
	const xbHotPlugFactoryList	*m_pFactList;


	xbSectShare		m_sect;
	bool					m_bUnloadAsked;
	long					m_ObjCnt;
	ulong					m_lastTimeFreeCnt;
	ulong					m_unloadDelayInMs;
	IxbHotPlugin			*m_pDbgPluginContainer;

	xbObsPtrExt<IxbHotPlugin>	m_poPluginContainer;

	xbHotPlugLinkType	m_linkType;
	xbHotPlugFactory	*m_pEmbeddedFactoryNext;
#ifndef XDCBASE_EMBED
	xbXdcLib			*m_pLib;
#endif
};

template<class baseclass> class xbHotPlugObject:public baseclass
{
public:
	xbHotPlugObject()
	{
		m_pFact = NULL;
	}
	virtual ~xbHotPlugObject()
	{
		eProtectBegin();
		if (m_pFact) m_pFact->DecObjCnt();
		eProtectEndTrace();
	}
//protected:
	virtual void onSetFactoryInfo(IxbHotPlugin *pPluginContainer,const char *pName,const void *pData) {}

	void setFactory(xbHotPlugFactory *pFact)
	{
		xbHotPlugFactory *pOldFact = m_pFact;
		m_pFact = pFact;
		if (m_pFact) m_pFact->IncObjCnt();
		if (pOldFact) pOldFact->DecObjCnt();
	}
protected:
	xbHotPlugFactory *m_pFact;
};

#define xbHotPlugCreateImpl(obj,interf) \
	{ \
		obj *plg = xbDnew obj(); \
		try \
		{ \
			plg->setFactory(pFactory); \
			plg->Create(); \
			plg->onSetFactoryInfo(pPluginContainer,pName,pCtorData); \
		 \
			(*ppObj) = (interf*)plg; \
		 \
			plg->Release(); \
		} \
		catch(...) \
		{ \
			plg->Release(); \
			throw; \
		} \
	}

#define xbHotPlugCreateDcl(obj,interf) \
	static void CreateObject(xbHotPlugFactory *pFactory,IxbHotPlugin *pPluginContainer,const char *pName,const void *pCtorData,xbRefPtr<IxbObject> *ppObj) \
	xbHotPlugCreateImpl(obj,interf)

#endif
