#ifndef _xbDNCENTER_
#define _xbDNCENTER_
/*<<<***************************************************************************
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: xbDnCenter
#Project: xdcBase
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<04/09/2013: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#ifndef _xbDNITF_
#	include "xbDnItf.h"
#endif

#ifndef _xbTHREAD_
#	include "xbThread.h"
#endif

#ifndef _xbSYNC_
#	include "xbSync.h"
#endif

#ifndef _xbDNPLUGIN_
#	include "xbDnPlugin.h"
#endif

#define xbDNCENTER_CBYTECACHEMAX		0x40000		//256Ko
#define xbDNCENTER_THREADMAX			4
#define xbDNCENTER_THREADMAXWITHTIMEOUT	0			// inf

#define xbDNCENTER_CANCELTIMEOUT		30000		//30 secs

#define xbDNCENTER_CACHECLEANTIMEOUT	60000		//1 min

#define xbDNCENTER_OBJNOTUSEDTIMEOUT	600000		//10 min

#define xbDNCENTER_THREADTIMEOUT		600000		//10 min
#define xbDNCENTER_SESSIONTIMEOUT		300000		//5 min


class xbEXPORT __declspec(uuid("{74C1C7F0-EE3E-4695-AAA4-2408DE7006DA}"))
	xbDnCenter:public xbEngine,public sIxbDnCenter,public sIxbHotPlugTypeCb
{
public:
	xbDnCenter(const char *pLabel,int cThreadMax,const xbEngines &engUsed = xbEngines());
	virtual ~xbDnCenter()
	{
		Destroy();
	}
//self methods
	bool AskOp(sIxbDnPtr *pPtr,xbDnOp op,xbDnPriority priority = xbDnLow,bool bWaitProcessMsg = false,bool bCacheableOnly = true);
	bool AskOp(IxbDnObject *pObj,xbDnOp op,LPCSTR pszUrl,xbDnPriority priority = xbDnLow,bool bWaitProcessMsg = false);
	bool CancelOp(IxbDnObject *pObj,LPCSTR pReason,xbDnOp op,bool bWaitProcessMsg = false);
	bool WaitOp(IxbDnObject *pObj,xbDnOp op,int timeOut = xbDNWAIT_DFLTTIMEOUT,bool bWaitProcessMsg = false);
	bool UpdateCacheInfo(IxbDnObject *pObj,bool bAddIfNotInCache);
	bool RemoveFromCache(IxbDnObject *pObj);
	bool RetrieveFromCache(const CLSID &clsid,const xbString &url,int64 urlParseType,xbRefPtr<IxbDnObject> *ppObj,bool bCacheableOnly = true);
	int RetrieveFromCache(const CLSID &clsid,LPCSTR pszUrl,
		int64 urlParseType,xbArray< xbRefPtr<IxbDnObject> > *pArrObj,
		bool bCacheableOnly = true);
	int RemoveFromCache(const CLSID &clsid,LPCSTR pszUrl = NULL,
		int64 urlParseType = -1,bool bCacheableOnly = false);

	int	GetOpWaitingCount() const;
	int	GetOpWaitingLockedCount() const;
	int	GetOpWaitingNotReadyCount() const;

	void SetThreadMax(int cThread);
	int	GetThreadMax() const;
	void SetThreadMin(int cThread);
	int	GetThreadMin() const;
	void SetThreadMaxWithTimeOut(int cThread);
	int	GetThreadMaxWithTimeOut() const;
	void SetThreadTimeOut(int timeout); // in ms
	int	GetThreadTimeOut() const;
	void SetSessionTimeOut(int timeout); // in ms
	int	GetSessionTimeOut() const;
	void SetCacheCleanTimeOut(int timeout); // in ms
	int	GetCacheCleanTimeOut() const;
	void SetObjNotUsedTimeOut(int timeout); // in ms
	int	GetObjNotUsedTimeOut() const;
	void SetCacheSizeMax(int timeout); // in ms
	int	GetCacheSizeMax() const;

	void ResetPerfStats()
	{
		xbSectLock lck(&m_sect);
		m_cThreadPeak = 0;
		m_cThreadDone = 0;
		m_cTaskDone = 0;
		m_cCacheBytePeak = 0;
		m_cCacheByteDone = 0;
		m_cCacheObjPeak = 0;
		m_cCacheObjDone = 0;
	}
	uint GetOpDoneCount() const
	{
//		xbSectLock lck(&m_sect);
		return m_cTaskDone;
	}
	int GetThreadCount() const
	{
//		xbSectLock lck(&m_sect);
		return m_cThread;
	}
	int GetThreadPeakCount() const
	{
//		xbSectLock lck(&m_sect);
		return m_cThreadPeak;
	}
	uint GetThreadDoneCount() const
	{
//		xbSectLock lck(&m_sect);
		return m_cThreadDone;
	}
	int GetThreadWaitingCount() const
	{
//		xbSectLock lck(&m_sect);
		return m_cThreadWaiting;
	}
	int GetCacheSize() const
	{
//		xbSectLock lck(&m_sect);
		return m_cCacheByte;
	}
	int GetCacheSizePeak() const
	{
//		xbSectLock lck(&m_sect);
		return m_cCacheBytePeak;
	}
	uint GetCacheSizeDone() const
	{
//		xbSectLock lck(&m_sect);
		return m_cCacheByteDone;
	}
	int GetCacheObjCount() const
	{
//		xbSectLock lck(&m_sect);
		return m_cCacheObj;
	}
	int GetCacheObjPeakCount() const
	{
//		xbSectLock lck(&m_sect);
		return m_cCacheObjPeak;
	}
	uint GetCacheObjDoneCount() const
	{
//		xbSectLock lck(&m_sect);
		return m_cCacheObjDone;
	}

	HANDLE GetEvTaskDone() {return m_evTaskDone.GetHandle();}
	bool				IsDlgDispLocked() const;
	bool				IsDlgUseLocked() const;

//sIxbBase methods
	virtual bool GetInterface(REFIID riid, void ** ppInterf)
	{
		xbImplInterface(sIxbDnCenter);
		xbImplInterface(xbDnCenter);
		return xbEngine::GetInterface(riid,ppInterf);
	}

//engine methods overrides
protected:
	virtual void onStart();
	virtual bool onStop(bool); 
//self protected methods and classes
protected:
	bool askOp(sIxbDnPtr *pPtr,xbDnOp op,xbDnPriority priority = xbDnLow,bool bWaitProcessMsg = false,bool bCacheableOnly = true);
	class Thread:public xbThread
	{
	public:
		Thread(xbDnCenter *pDnCenter,int thNo)
			:xbThread("dnCenter Thread n°"+xbString(thNo)),
			m_session(m_eventPtr)
		{
			m_pDnCenter = pDnCenter;
			m_thNo = thNo;
			m_tiWork = -1;
		}

		virtual ~Thread()
		{
			eProtect(destroy("Thread object destruction"));
		}
		
		//sIxbThread methods
		virtual void	onCloseAsked(sIxbThread *pTh,LPCSTR pReason);
		virtual DWORD	onThreadProc(sIxbThread *pTh);
		virtual void	onStart(sIxbThread *pTh);
		virtual DWORD	onStop(sIxbThread *pTh,DWORD ret);

		//self methods
		void onWait();
		HANDLE GetEventHandle() const {return m_eventPtr->GetHandle();}
		bool shouldStop()
		{
			ResumeTaskProcess();
			return IsCloseAskedInt();
		}

		//members
		int					m_thNo;
		xbDnCenter			*m_pDnCenter;
		int64				m_tiWork;

		xbDnSession			m_session;

	};
	friend Thread;

	struct ThreadInfo
	{
		ThreadInfo()
		{
			m_pTh = NULL;
			m_bInCreate = false;
			m_bDestroyed = false;
		}
		~ThreadInfo()
		{
			xbAssert((!m_pTh || m_bDestroyed) && !m_bInCreate);
			eProtectBegin();
			delete m_pTh;
			eProtectEndTrace();
		}
		Thread	*m_pTh;
		bool	m_bInCreate,m_bDestroyed;
	};

	void	taskNew(xbDnPriority priority,xbSectLock &lck);
	void	taskAdd(IxbDnObject *pObj,xbDnPriority priority,xbSectLock &lck);
	void	taskRemove(IxbDnObject *pObj,bool *pbToAdd = NULL,xbDnPriority priority = xbDnLow);
	void	taskClear();
	int		taskRetryNotReady();

	bool	cacheClean(bool bForce);
	bool	cacheFind(const CLSID &id,const xbString &url,int64 urlParseType,
				xbRefPtr<IxbDnObject> *ppObj,bool bCacheableOnly = true);
	int		cacheFind(const CLSID &clsid,LPCSTR pszUrl,
				int64 urlParseType,xbArray< xbRefPtr<IxbDnObject> > *pArrObj,
				bool bCacheable);
	void	cacheAdd(IxbDnObject *pObj,xbSectLock *pLck,bool bUrlChg = false);
	void	cacheClear();

	bool	threadWaitForNewTask(Thread *pTh,xbRefPtr<IxbDnObject> *pptr,xbDnPriority *ppriority,xbDnOp *pop);
	void	threadClear(LPCSTR pReason);
public:
	mutable xbSectShare	m_sect;
	virtual bool isLockedByMe() const {return m_sect.IsLockedByMe()||m_sectInfo.IsLockedByMe();}
protected:
//Config	
	int							m_cThreadMax; //xbDNCENTER_THREADMAX -> dflt = 4 threads, 0 -> inf;
	int							m_cThreadMin; //dflt = 1, can't be less than 1 thread;
	int							m_cThreadMaxWithTimeOut;//xbDNCENTER_THREADMAXWITHTIMEOUT -> dflt = 8 threads;
	ulong						m_ThreadTimeOut; //xbDNCENTER_THREADTIMEOUT -> dflt = 10 min;
	ulong						m_SessionTimeOut; //xbDNCENTER_THREADTIMEOUT -> dflt = 5 min;
	ulong						m_CacheCleanTimeOut; //xbDNCENTER_CACHECLEANTIMEOUT -> dflt = 1 min
													//CAUTION: protected by m_sectInfo
	ulong						m_ObjNotUsedTimeOut; //xbDNCENTER_OBJNOTUSEDTIMEOUT -> dflt = 1 min
	int							m_cCacheByteMax; //xbDNCENTER_CBYTECACHEMAX -> dflt = 265Ko;

//Threads info
	int							m_cThread,m_cThreadWaiting,m_cThreadPeak;
	uint						m_cThreadDone;
	bool						m_bThreadCntValid;
	int64						m_cThreadClosed;

	xbArray<ThreadInfo>	m_arrTh;

//Tasks list
	uint						m_cTaskDone;
public:
	xbEvent						m_evTaskNew,m_evTaskDone;
protected:

	xbPtrArray< xbObsPtr<IxbDnObject> >		m_arrNotReady;
	xbPtrArray< xbObsRefPtr<IxbDnObject> >	m_arrNotReadyLocked;
	xbPtrArray< xbObsPtr<IxbDnObject> >		m_arrHigh;
	xbPtrArray< xbObsRefPtr<IxbDnObject> >	m_arrHighLocked;
	xbPtrArray< xbObsPtr<IxbDnObject> >		m_arrLow;
	xbPtrArray< xbObsRefPtr<IxbDnObject> >	m_arrLowLocked;

//Cache
public:
	struct	CacheInfo
	{
		CacheInfo(xbDnCenter *pParent)
		{
			m_id = 0;
			m_pObj = NULL;
			m_pNext = m_pPrev = NULL;
			m_pParent = pParent;
			m_bLinked = false;
			m_cByteObj = 0;
			m_tiAccess = -1;
			m_bCacheable = false;
			m_pParent->m_cCacheObj++;
			if (m_pParent->m_cCacheObj > m_pParent->m_cCacheObjPeak)
				m_pParent->m_cCacheObjPeak = m_pParent->m_cCacheObj;
		}
		~CacheInfo()
		{
			eProtectBegin();
			m_pParent->m_cCacheObj--;
			m_pParent->m_cCacheObjDone++;
			xbAssert(m_pParent->m_cCacheObj >= 0);
			if (m_pObj)
			{
				if (!m_pObj->dnCenter_tryDel(m_pParent,m_id))
				{
					m_pObj->dnCenter_cache(NULL,0);
				}
				m_pObj = NULL;
			}
			Unlink();
			m_pParent->m_cCacheByte -= m_cByteObj;
			xbAssert(m_pParent->m_cCacheByte >= 0);
			m_pParent->m_cCacheByteDone += m_cByteObj;
			eProtectEndTrace();
		}
		void	Link();
		void	Unlink();
		void	Access();

		int				m_id;
		IxbDnObject		*m_pObj;
		CacheInfo		*m_pNext,*m_pPrev;
		xbDnCenter		*m_pParent;
		bool			m_bLinked;

		int				m_cByteObj;
		int64			m_tiAccess;

		bool			m_bCacheable;
		const CLSID		*m_pClsId;
		xbString		m_url;
		int64			m_urlParseType;
		xbHeapNewImpl(CacheInfo);
	};
protected:
	friend CacheInfo;

	xbPtrArray< CacheInfo >			m_arrCache;
	xbPtrArray< CacheInfo, false >	m_arrCacheSort;
	bool							m_bCacheSorted;
	CacheInfo						*m_pCacheFirst,*m_pCacheLast;
	int								m_cCacheByte,m_cCacheObj;
	int								m_cCacheBytePeak,m_cCacheObjPeak;
	uint							m_cCacheByteDone,m_cCacheObjDone;

public:
	mutable xbSectShare	m_sectInfo;
protected:
	int64						m_tiCacheClean;
//	mutable int					m_cDlgUseLock,m_cDlgDispLock;
//	sIxbDlgCtx*					m_pDlgCtx;

	static int cacheFindCb(CacheInfo* const &pInfo,const void *pFind);
	static int cacheFindNotCacheableCb(CacheInfo* const &pInfo,const void *pFind);
	static int cacheSortCb(CacheInfo* const &pInfo1,CacheInfo* const &pInfo2);

	static int cacheFindExtCb(CacheInfo* const &pInfo,const void *pFind);
	static int cacheFindNotCacheableExtCb(CacheInfo* const &pInfo,const void *pFind);

public:
	xbHotPlugCb					m_hotPlugCb;
protected:
	uint						m_retryTaskChgCnt,m_retryTaskDoneCnt,m_retryTaskDoneTi;

// sIxbHotPlugTypeCb
	virtual xbPlugUpdateCaps onGetPluginCaps(xbHotPlugCb *pCb,IxbHotPlugin*,xbSectLock &lck)
	{
		return xbPlugUpdateNormal;
	}

	virtual void onUnloadPluginAsked(xbHotPlugCb *pCb,IxbHotPlugin*,bool bForce,xbSectLock &lck)
	{
	}

	virtual void onLoadPluginAsked(xbHotPlugCb *pCb,IxbHotPlugin*,xbSectLock &lck)
	{
		xbHotPlugTrace(1,"xbDnCenter::onLoadPluginAsked() called");
		eProtectBegin();
		{
			xbSectLock lck2(&m_sect);
			m_retryTaskChgCnt++;
		}
		m_evTaskNew.Set();
		eProtectEndTrace();
	}

	virtual xbPlugUpdateCaps onError(xbHotPlugCb *pCb,IxbHotPlugin*,bool bCanRetry, HRESULT hr, const xbString &errorText,xbSectLock &lck)
	{
		xbHotPlugTrace(1,"xbDnCenter::onError() called");
		eProtectBegin();
		{
			xbSectLock lck2(&m_sect);
			m_retryTaskChgCnt++;
		}
		m_evTaskNew.Set();
		eProtectEndTrace();
		return xbPlugUpdateNormal;
	}
	virtual void onProgress(xbHotPlugCb *pCb,IxbHotPlugin*,double progress,
		xbPlugStatus status, const xbString &statusText,xbSectLock &lck)
	{
	}
	virtual void onReloadConfig(xbHotPlugCb *pCb,sIxbHotPlugType *pPlugType,xbSectLock &lck)
	{
		xbHotPlugTrace(1,"xbDnCenter::onReloadConfig() called");
		eProtectBegin();
		{
			xbSectLock lck2(&m_sect);
			m_retryTaskChgCnt++;
		}
		m_evTaskNew.Set();
		eProtectEndTrace();
	}

/*
// sIxbHotPlugTypeCb
	virtual xbPlugUpdateCaps	onGetPluginCaps(xbHotPlugCb *pCb,IxbHotPlugin*,IxbHotPlugger*,xbSectLock &lck)
	{
		return xbPlugUpdateNormal;
	}

	virtual void				onUnloadPluginAsked(xbHotPlugCb *pCb,IxbHotPlugin*,IxbHotPlugger*,bool bAddingCb,xbSectLock &lck)
	{
	}
	
	virtual void				onLoadPluginAsked(xbHotPlugCb *pCb,IxbHotPlugin*,IxbHotPlugger*,bool bAddingCb,xbSectLock &lck)
	{
		xbHotPlugTrace(1,"xbDnCenter::onLoadPluginAsked() called");
		eProtectBegin();
		{
			xbSectLock lck2(&m_sect);
			m_retryTaskChgCnt++;
		}
		m_evTaskNew.Set();
		eProtectEndTrace();
	}

	virtual xbPlugUpdateCaps	onError(xbHotPlugCb *pCb,IxbHotPlugin*,IxbHotPlugger*, bool bCanRetry, HRESULT hr, const xbString &errorText,xbSectLock &lck)
	{
		xbHotPlugTrace(1,"xbDnCenter::onError() called");
		eProtectBegin();
		{
			xbSectLock lck2(&m_sect);
			m_retryTaskChgCnt++;
		}
		m_evTaskNew.Set();
		eProtectEndTrace();
		return xbPlugUpdateNormal;
	}

	virtual void onProgress(xbHotPlugCb *pCb,IxbHotPlugin *pPlugin,IxbHotPlugger *pPlugger, ULONG ulProgress, ULONG ulProgressMax,
		xbPlugStatus status, const xbString &statusText,xbSectLock &lck)
	{
	}*/

	int	m_cThreadLongTask;
public:
	int GetThreadLongTaskCount() const
	{
		xbSectLock lck(&m_sect);
		return m_cThreadLongTask;
	}
	virtual void LongTaskLock()
	{
		xbSectLock lck(&m_sect);
		xbAssert(m_cThreadLongTask <= 0xFFFF);
		m_cThreadLongTask++;
	}
	virtual void LongTaskUnlock()
	{
		xbSectLock lck(&m_sect);
		xbAssert(m_cThreadLongTask > 0);
		m_cThreadLongTask--;
	}
};

extern xbEXPORT xbDnCenter		g_xbDnCenter;

#endif
