#ifndef _xbENGINE_
#define _xbENGINE_
/*<<<***************************************************************************
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: xbEngine
#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 _xbLOCK_
#	include "xbLock.h"
#endif
#ifndef _xbEXCEPTIONLOW_
#	include "xbExceptionLow.h"
#endif
#ifndef _xbARRAY_
#	include "xbArray.h"
#endif

class xbEngine;

#define xbENGINE_TRYCNT		50
#define xbENGINE_TRYSLEEP	100
typedef xbEXPORT xbSArray<xbEngine*> xbEngines;

enum xbEXPORT xbEngineStartMode
{
	xbEngineStart_Auto = 0,	//Started when xbEngineCenter::Start() is called
	xbEngineStart_Manual = 1, //To start manually, you must call xbEngine::Start() explicitly
	xbEngineStart_OnUse = 2, // Started on first use (started when xbEngine.Lock() is called)
	xbEngineStart_Disabled = 3 // Not startable
};

struct xbEngineStartStopStack;
class xbMTuseEngineCenter;
class xbUseEngineCenterBase;
class xbEXPORT xbEngineCenter:public xbBase
{
public:
	xbEngineCenter();
	virtual ~xbEngineCenter();
	void Start();
	void Stop();
	bool IsReady() const {return m_pThis == this;}
	inline void CheckReady() const
	{
		if (m_pThis != this) 
			xbThrowEsysLow("xbEngineCenter : not ready");
	}
	mutable xbSectShare			m_sectStartStop;
	mutable xbSectShare			m_sectData;
	bool	isInUse() const {return m_cUse > 0;} //CAUTION: lock m_sect before use this function

	const OSVERSIONINFOEX &GetOsInfo();
	xbString	GetOSlabel();
	bool		IsNTos();
	bool		Is95os();
	ulong		GetOSver();
	ulong		GetOSminorVer();

	const SYSTEM_INFO &GetSysInfo();
	ulong GetSysCoreCount();
	ulong GetSysPageGranularity();

	xbEngine	*GetEngine(int &idx,const CLSID *pInterfId,xbLock *pLckStart);

	xbSegHeap	m_heap;

protected:
	void useLock(bool bStart,xbSectLock *pLck);
	void useUnlock(xbSectLock *pLck);

	xbEngineCenter		*m_pThis;
	xbEngine			*m_pFirst;

	long				m_cUse;

	OSVERSIONINFOEX		m_osInfo;
	SYSTEM_INFO			m_sysInfo;
	bool				m_bOsInfoOk,m_bSysInfoOk;

	bool				m_bAllStarted;

	friend xbEngine;
	friend xbUseEngineCenterBase;
	friend xbMTuseEngineCenter;

	xbDclNoNewForObject(xbEngineCenter);
};

extern xbEXPORT xbEngineCenter g_xbEngineCenter;
//Cf xbLock.h pour xbUseEngineCenter

class xbEXPORT __declspec(uuid("{5B6F3237-5A39-46e5-A8A7-A88CA0F9FE0F}"))
	xbEngine:public xbBase,public sIxbLock
{
public:
	xbEngine(xbEngineCenter *pCenter,xbEngineStartMode startMode,const char *pLabel,const xbEngines &engUsed = xbEngines());
	xbEngine(xbEngineStartMode startMode,const char *pLabel,const xbEngines &engUsed = xbEngines());
	virtual ~xbEngine() = 0;
protected:
	virtual void Destroy(); //to call in all dtors of inherited classes
public:
//sIxbBase methods
	virtual bool GetInterface(REFIID riid, void ** ppInterf)
	{
		xbImplInterface(sIxbLock);
		xbImplInterface(xbEngine);
		return xbBase::GetInterface(riid,ppInterf);
	}

//self methods
	void Start();
	void Stop();
	bool TryStop();

	inline bool IsReady() const {return m_pThis == this && m_bLinked;}
	
	inline bool isNowStarted() const //not thread safe, lock center
	{
		if (m_pThis != this || !m_bLinked 
			|| !m_bStarted || !m_pCenter->isInUse()) return false;
		return true;
	}
	inline bool IsNowStarted(xbLock *pLock) const 
	{
		if (!IsReady()) return false;
//		xbSectLock lck(&m_pCenter->m_sectStartStop);
		if (!m_bStarted || !m_pCenter->isInUse()) return false;
		pLock->Lock(this);
		return true;
	}
	inline void CheckReady() const
	{
		if (m_pThis != this) 
			xbThrowEsysLow("xbEngine : not ready");
		if (!m_bLinked) 
			xbThrowEsysFmt("xbEngine(%s) : not ready",m_pLabel);
	}
	virtual void				Lock() const
	{
		CheckReady();
		::xbInterlockIncrement32((LONG*)&m_cStartLock);
		try
		{
//			if (!m_pCenter->isInUse()) 
//				xbThrowEsysFmt("xbEngine(%s) : center not started",m_pLabel);
			if (!m_bStarted && m_mode == xbEngineStart_OnUse)
				((xbEngine*)this)->Start();
			if (!m_bStarted) 
				xbThrowEsysFmt("xbEngine(%s) : not started",m_pLabel);
		}
		catch(...)
		{
			::xbInterlockDecrement32((LONG*)&m_cStartLock);

			throw;
		}
	}
	virtual void				Unlock() const
	{
		::xbInterlockDecrement32((LONG*)&m_cStartLock);
		xbRdbgAssert(m_cStartLock>=0 /*&& m_bStarted*/);
	}
	inline bool IsStarted(xbLock *pLock) const
	{
		if (!IsReady()) return false;
//		xbSectLock lck(&m_pCenter->m_sectStartStop);
		if (!m_bStarted && m_mode != xbEngineStart_OnUse) return false;
		if (pLock) pLock->Lock(this);
		return true;
	}
	void	SetStartMode(xbEngineStartMode mode)
	{
		CheckReady();
		{
			xbSectLock lck(&m_pCenter->m_sectStartStop);
			m_mode = mode;		
		}
		if (mode == xbEngineStart_Disabled) Stop();
	}
	xbEngineStartMode	GetStartMode() const
	{
		CheckReady();
		//xbSectLock lck(&m_pCenter->m_sectStartStop);
		return m_mode;
	}
	xbPropertyRW(xbEngineStartMode,StartMode);

	LPCSTR	GetLabel() const {return m_pLabel;}
	xbPropertyR(LPCSTR,Label);

	bool	IsUsed(xbEngine *pEng) const;
	bool	CanStop() const
	{
		CheckReady();
		if (m_cStartLock > 0) return false;
		xbSectLock lck(&m_pCenter->m_sectStartStop);
		if (m_cStartLock > 0) return false;
		return !m_bStarted || onCanStop();
	}

	virtual bool isLockedByMe() const = 0;
protected:
	void start(xbEngineStartStopStack *pStack, int level);
	bool stop(xbEngineStartStopStack *pStack, int level, bool &bForce);

	virtual void onStart() = 0;
	virtual bool onStop(bool bForce) = 0;
	virtual bool onCanStop() const {return true;}

	void centerLink();
	void centerUnlink();

	xbEngine					*m_pNext,*m_pPrev;
	xbEngineCenter				*m_pCenter;
	volatile bool				m_bStarted,m_bStopped,m_bLinked;
	volatile mutable int		m_cStartLock;
	xbEngine					*m_pThis;
	xbEngineStartMode			m_mode;

	xbEngines					m_engUsed;
	const char					*m_pLabel;

	friend xbEngineCenter;

//	xbDclNoNewForObject(xbEngine);
};

class xbEXPORT xbUseEngineCenterBase:public xbBase
{
public:
	xbUseEngineCenterBase()
	{
		m_pCenter = NULL;
	}
	xbUseEngineCenterBase(xbEngineCenter *pCenter,bool bStart,LPCSTR pThreadName = NULL)
	{
		m_pCenter = NULL;
		StartUse(pCenter,bStart,pThreadName);
	}
	~xbUseEngineCenterBase()
	{
		eProtectTrace(StopUse());
	}
	void StartUse(bool bStart = true,LPCSTR pThreadName = NULL)
	{
		StartUse(&g_xbEngineCenter,bStart,pThreadName);
	}
	virtual void StartUse(xbEngineCenter *pCenter,bool bStart,LPCSTR pThreadName = NULL)
	{
		m_useCtx.Start(pThreadName);
		if (pCenter == m_pCenter) 
		{
			if (bStart) pCenter->Start();
			return;
		}
		if (m_pCenter) 
			StopUse();
		pCenter->useLock(bStart,NULL);
		m_pCenter = pCenter;
	}
	virtual void StopUse()
	{
		if (!m_pCenter) return;
		xbEngineCenter	*pCenter = m_pCenter;
		m_pCenter = NULL;
		pCenter->useUnlock(NULL);
		m_useCtx.Stop();
	}
protected:
	xbUseThreadContext	m_useCtx;
	xbEngineCenter		*m_pCenter;

	xbDclNoNewForObject(xbUseEngineCenterBase);
	xbDclNoCopyForObject(xbUseEngineCenterBase);
};

class xbEXPORT xbUseEngineCenterOcx:public xbUseEngineCenterBase
{
public:
	xbUseEngineCenterOcx()
	{
	}
	xbUseEngineCenterOcx(xbEngineCenter *pCenter,bool bStart,LPCSTR pThreadName = NULL)
		:xbUseEngineCenterBase(pCenter,bStart,pThreadName)
	{
	}
protected:
	xbDclNoNewForObject(xbUseEngineCenterOcx);
	xbDclNoCopyForObject(xbUseEngineCenterOcx);
};

class xbEXPORT xbMTuseEngineCenter //:public xbBase
{
public:
	xbMTuseEngineCenter()
	{
		m_sect.DeadLockOrderBeforePriorAll();
		m_pCenter = NULL;
		m_bIgnoreStop = false;
	}
	~xbMTuseEngineCenter()
	{
		m_bIgnoreStop = false;
		eProtectBegin();
		xbSectLock lck(&m_sect);
		if (!m_pCenter) 
			return;
		xbEngineCenter	*pCenter = m_pCenter;
		m_pCenter = NULL;
		pCenter->useUnlock(&lck);
		eProtectEndTrace();
	}
	void StartUse(bool bStart = true)
	{
		StartUse(&g_xbEngineCenter,bStart);
	}
	void StartUse(xbEngineCenter *pCenter,bool bStart)
	{
		xbSectLock lck(&m_sect);
		if (pCenter == m_pCenter) 
		{
			if (bStart) pCenter->Start();
			return;
		}
		if (m_pCenter) StopUse();
		pCenter->useLock(bStart,&lck);
		m_pCenter = pCenter;
	}
	bool StopUse()
	{
		xbSectLock lck(&m_sect);
		if (m_bIgnoreStop)
		{
			m_bIgnoreStop = false;
			return false;
		}
		if (!m_pCenter) return true;
		xbEngineCenter	*pCenter = m_pCenter;
		m_pCenter = NULL;
		pCenter->useUnlock(&lck);
		return true;
	}
	void IgnoreStopUse(bool bVal)
	{
		xbSectLock lck(&m_sect);
		m_bIgnoreStop = bVal;
	}
protected:
	xbSectShare			m_sect;
	xbEngineCenter		*m_pCenter;
	bool				m_bIgnoreStop;

	xbDclNoNewForObject(xbMTuseEngineCenter);
	xbDclNoCopyForObject(xbMTuseEngineCenter);
};

#define xbDefaultHeapDnewImpl(clsname) \
		xbHeapDnewImpl(clsname,g_xbEngineCenter.m_heap)

class xbEXPORT __declspec(uuid("{169F7CE0-36D9-42bf-8D2E-A160EDDD93C7}"))
	xbEsys:public xbEbase
{
public:
	static xbEinfoSt s_info;
	virtual const xbEinfoSt *GetInfo() const {return &s_info;}

	virtual LPCSTR GetFullEname() const {return "xbEsys";}
	virtual bool IsToWriteOnLogFile() const {return true;}
	virtual xbMBiconType GetMBoxIcon() const {return xbMBicon_Stop;}
	virtual const char *GetMBoxTitle() const {return "System error";} 
	virtual bool GetInterface(REFIID riid, void ** ppInterf)
	{
		xbImplInterface(xbEsys);
		return xbEbase::GetInterface(riid,ppInterf);
	}

	xbEsys(const char *pszMsg,xbEctorMode ctorMode = xbEctor_final)
		:xbEbase()
	{
		m_pszMsg = g_xbEngineCenter.m_heap.xbAllocString(pszMsg);

		traceExcept(ctorMode);
	}
	xbEsys(HRESULT hr,const char *pszMsg,xbEctorMode ctorMode = xbEctor_final)
		:xbEbase(hr)
	{
		m_pszMsg = g_xbEngineCenter.m_heap.xbAllocString(pszMsg);

		traceExcept(ctorMode);
	}
	xbEsys(HRESULT hr,const char *pszMsg,va_list ap,xbEctorMode ctorMode = xbEctor_final)
		:xbEbase(hr)
	{
		msgFmt(pszMsg,ap);

		traceExcept(ctorMode);
	}

	virtual const char *GetMsg() const {return m_pszMsg;}

	virtual ~xbEsys()
	{
		eProtectBegin();
		if (m_pszMsg != NULL)
			g_xbEngineCenter.m_heap.Free(m_pszMsg);
		eProtectEndRdbgTrace();
	}

protected:
	xbEsys(HRESULT hr)
		:xbEbase(hr)
	{
		m_pszMsg = NULL;
	}

	void msgFmt(const char *pszMsg,va_list ap)
	{
		char buf[xbEsysFmt_SIZEMAX];
		xbVsnprintf(buf,sizeof(buf)-1,pszMsg,ap);
		buf[sizeof(buf)-1] = 0;

		m_pszMsg = g_xbEngineCenter.m_heap.xbAllocString(buf);
	}
	char	*m_pszMsg;
private:
	xbDclNoCopyForObject(xbEsys);
public:
	xbDefaultHeapDnewImpl(xbEsys);
};

#endif
