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

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

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

#ifndef _xbSHAREDQUEUE_
#	include "xbSharedQueue.h"
#endif

#ifndef _xbWINBASE_
#	include "xbWinBase.h"
#endif

//#define xbXDCMSGHEAP_SZMAX		1048576
#define xbXDCMSGHEAP_SZMAX			262144

#define xbPOSTMSG_TRYCNT			50
#define xbPOSTMSG_TRYSLEEP			100

#define xbXDCTIMER_STEP			200
#define xbXDCMSG_PING				(3*60000)

//Tools
bool xbEXPORT xbDoOneEvent (bool *pbQuit);
bool xbEXPORT xbDoOneEvent (xbCtxGet &ctx,bool *pbQuit);
bool xbEXPORT xbDoEvents (bool *pbQuit,int cMsgMax=5);
bool xbEXPORT xbDispatchMsg(xbCtxGet &ctx,MSG &msg,bool *pbQuit);
void xbEXPORT xbDispatchMsgLow(MSG &msg);

void xbEXPORT xbPostThreadMessage(
		DWORD idThread, // thread identifier
		UINT Msg,       // message to post
		WPARAM wParam = 0,  // first message parameter
		LPARAM lParam = 0  // second message parameter
		);
void xbEXPORT xbPostThreadMessage(
		HANDLE hThread, // thread identifier
		DWORD idThread, // thread identifier
		UINT Msg,       // message to post
		WPARAM wParam = 0,  // first message parameter
		LPARAM lParam = 0  // second message parameter
		);
void xbEXPORT xbPostMessage(
		xbHWND hWnd, // Window handle
		UINT Msg,       // message to post
		WPARAM wParam = 0,  // first message parameter
		LPARAM lParam = 0   // second message parameter
		);
void xbEXPORT xbPostExternalMessage(
		xbHWND hWnd, // Window handle
		UINT Msg,       // message to post
		WPARAM wParam = 0,  // first message parameter
		LPARAM lParam = 0   // second message parameter
		);

// messages 
//	1 - intra xdcBase based application - inter components : xbMsg & xbMsgEngine
//	2 - extra application or inter components from all applications : xbExtraMsg
//	3 - extra xdcBase based application : xbXdcMsg  & xbXdcMsgEngine



/**************************************************************************************/
/*	1 - intra xdcBase based application - inter components : xbMsg & xbMsgEngine	  */	
/**************************************************************************************/
class xbMsg;
class xbEXPORT __declspec(uuid("{FCFC22D9-3ABF-44c2-A70E-9F58453B3FEB}")) 
	xbMsgEngine:public xbEngine
{
public:
	xbMsgEngine(const char *pLabel,const xbEngines &engUsed = xbEngines())
		:xbEngine(xbEngineStart_OnUse,pLabel,engUsed)
	{
	}
	virtual ~xbMsgEngine()
	{
		Destroy();
	}
	virtual bool GetInterface(REFIID riid, void ** ppInterf)
	{
		xbImplInterface(xbMsgEngine);
		return xbEngine::GetInterface(riid,ppInterf);
	}

	int Add(xbMsg *pMsg);
	void Remove(int id);

	virtual bool isLockedByMe() const {return m_sectData.IsLockedByMe();}
	mutable xbSectShare	m_sectData;
protected:
	virtual void onStart() {}
	virtual bool onStop(bool) {m_arrMsg.Clear();return true;}

	xbArray<xbMsg*>	m_arrMsg;
};

extern xbMsgEngine	xbEXPORT g_xbMsgEngine;

class xbEXPORT __declspec(uuid("{2B1BF2EF-D4C1-44da-A4FE-FE41D4E9D33D}")) 
	xbMsg:public xbEngine
{
public:
	xbMsg(const char *pLabel,const xbEngines &engUsed = xbEngines())
		:xbEngine(xbEngineStart_OnUse,pLabel,xbEngines(&g_xbMsgEngine,engUsed))
	{
		m_id = 0;
	}
	virtual ~xbMsg()
	{
		Destroy();
	}
	virtual bool GetInterface(REFIID riid, void ** ppInterf)
	{
		xbImplInterface(xbMsg);
		return xbEngine::GetInterface(riid,ppInterf);
	}
	operator DWORD() const
	{
		xbLock lckStart(this);
		return DWORD(m_id)+WM_APP-1;
	};
	DWORD	GetMsg() const
	{
		xbSectLock lck(&m_sect);
		return DWORD(m_id)+WM_APP-1;
	}
	virtual bool isLockedByMe() const {return m_sect.IsLockedByMe();}
protected:
	virtual void onStart() 
	{
		int id = g_xbMsgEngine.Add(this);
		xbSectLock lck(&m_sect);
		m_id = id;
	}
	virtual bool onStop(bool) 
	{
		{
			xbSectLock lck(&m_sect);
			if (!m_id) 
				return true;
		}
		g_xbMsgEngine.Remove(m_id);
		{
			xbSectLock lck(&m_sect);
			m_id = 0;
		}
		return true;
	}

	mutable xbSectShare	m_sect;
	int							m_id;
};

/**************************************************************************************/
/*	2 - extra application or inter components from all applications : xbExtraMsg	  */	
/**************************************************************************************/
class xbEXPORT __declspec(uuid("{3D67C01F-CCCD-450a-AB54-233D4C56C58B}")) 
	xbExtraMsg:public xbEngine
{
public:
	xbExtraMsg(const char *pLabel,const xbEngines &engUsed = xbEngines())
		:xbEngine(xbEngineStart_OnUse,pLabel,engUsed)
	{
		m_id = 0;
	}
	virtual ~xbExtraMsg()
	{
		Destroy();
	}
	virtual bool GetInterface(REFIID riid, void ** ppInterf)
	{
		xbImplInterface(xbExtraMsg);
		return xbEngine::GetInterface(riid,ppInterf);
	}
	operator DWORD() const
	{
		xbLock lckStart(this);
		return DWORD(m_id);
	};
	virtual bool isLockedByMe() const {return false;}
protected:
	virtual void onStart() 
	{
		xbStringW strw(Label);
		m_id = ::RegisterWindowMessageW(strw);
		if (m_id == 0) 
			xbThrowEsys(xbHR_LAST,"xbExtraMsg("+xbString(Label)+")::onStart() : RegisterWindowMessage failure");
		if (m_id < 0xC000) 
			xbThrowEsys(xbHR_LAST,"xbExtraMsg("+xbString(Label)+")::onStart() : RegisterWindowMessage failure (id < 0xC000)");
	}
	virtual bool onStop(bool) 
	{
		m_id = 0;
		return true;
	}

	int			m_id;
};

class xbEXPORT __declspec(uuid("{5C0027FE-A73C-4fca-A8B2-5795B20CFB64}"))
	xbExtraDataMsg:public xbEngine
{
public:
	xbExtraDataMsg(const char *pLabel,const xbEngines &engUsed = xbEngines())
		:xbEngine(xbEngineStart_OnUse,pLabel,engUsed)
	{
		m_id = 0;
	}
	virtual ~xbExtraDataMsg()
	{
		Destroy();
	}
	virtual bool GetInterface(REFIID riid, void ** ppInterf)
	{
		xbImplInterface(xbExtraDataMsg);
		return xbEngine::GetInterface(riid,ppInterf);
	}
	operator DWORD() const
	{
		xbLock lckStart(this);
		return DWORD(m_id);
	};
	virtual bool isLockedByMe() const {return false;}
protected:
	virtual void onStart() 
	{
		xbStringW strw(Label);
		m_id = ::RegisterWindowMessageW(strw);
		if (m_id == 0) 
			xbThrowEsys(xbHR_LAST,"xbExtraDataMsg("+xbString(Label)+")::onStart() : RegisterWindowMessage failure");
		if (m_id < 0xC000) 
			xbThrowEsys(xbHR_LAST,"xbExtraDataMsg("+xbString(Label)+")::onStart() : RegisterWindowMessage failure (id < 0xC000)");
	}
	virtual bool onStop(bool) 
	{
		m_id = 0;
		return true;
	}

	int			m_id;
};

/**************************************************************************************/
/*	3 - extra xdcBase based application : xbXdcMsg  & xbXdcMsgEngine	              */	
/**************************************************************************************/
//class xbXdcMsg;
class xbXdcTimerCb;
class xbXdcCb;
class xbEXPORT __declspec(uuid("{8BAD78E3-C27E-4a73-B4BA-86E6CA2F255E}"))
	xbXdcMsgEngine:public xbEngine
{
public:
	xbXdcMsgEngine(const char *pLabel,const char *pHeapLabel,const xbEngines &engUsed = xbEngines());
	virtual ~xbXdcMsgEngine()
	{
		Destroy();
	}
	virtual bool GetInterface(REFIID riid, void ** ppInterf)
	{
		xbImplInterface(xbXdcMsgEngine);
		return xbEngine::GetInterface(riid,ppInterf);
	}

	bool Alloc(xbRefPtr<xbSharedMem> *pMem,ulong cByte,xbOpMode opm =xbOp_Required);
	bool Load(xbRefPtr<xbSharedMem> *pMem,const xbSharedHandle &handle,ulong cByte = 0,xbOpMode opm =xbOp_Required);

	int Post(LPCSTR target,const xbExtraDataMsg &msg,const void *pData,int cByte,ulong wParam, ulong lParam)
	{
		eProtectBegin();
		xbLock lckStart(this);
		xbDbgOutput("xbXdcMsgEngine::Post(msg="+xbString(msg.Label)+":"+xbStrFromUlong(DWORD(msg),16)
			+", cByte="+xbString(cByte)
			+", wParam="+xbString(wParam,xbStringNum_hexa)+", lParam="+xbString(lParam,xbStringNum_hexa)+")...");
		return m_sharedQ.Post(target,msg,pData,cByte,wParam,lParam);
		eProtectEndTrace();
		return -1;
	}
	int Post(LPCSTR target,const xbExtraDataMsg &msg,xbSharedMem *pMem,ulong wParam, ulong lParam)
	{
		eProtectBegin();
		xbLock lckStart(this);
		xbDbgOutput("xbXdcMsgEngine::Post(msg="+xbString(msg.Label)+":"+xbStrFromUlong(DWORD(msg),16)
			+", pData="+xbString(pMem?pMem->GetHandle().m_handle:0,xbStringNum_hexa)
			+", cByte="+xbString(pMem?pMem->GetLength():0)
			+", wParam="+xbString(wParam,xbStringNum_hexa)+", lParam="+xbString(lParam,xbStringNum_hexa)+") ...");
		return m_sharedQ.Post(target,msg,pMem,wParam,lParam);
		eProtectEndTrace();
		return -1;
	}
	int Post(LPCSTR target,const xbExtraMsg &msg,ulong wParam, ulong lParam)
	{
		eProtectBegin();
		xbLock lckStart(this);
		xbDbgOutput("xbXdcMsgEngine::Post(msg="+xbString(msg.Label)+":"+xbStrFromUlong(DWORD(msg),16)
			+", wParam="+xbString(wParam,xbStringNum_hexa)+", lParam="+xbString(lParam,xbStringNum_hexa)+") ...");
		return m_sharedQ.Post(target,msg,NULL,wParam,lParam);
		eProtectEndTrace();
		return -1;
	}
	void SetQueueName(LPCSTR pQname)
	{
		m_sharedQ.SetQueueName(pQname);
	}
	xbString GetQueueName() const
	{
		return m_sharedQ.GetQueueName();
	}
	bool Call(ulong message, xbSharedMem *pMem, ulong wParam, ulong lParam);
	virtual bool isLockedByMe() const {return m_sect.IsLockedByMe();}
protected:
	void Add(xbXdcCb *pCb);
	void Remove(xbXdcCb *pCb);
	void Add(xbXdcTimerCb *pCb);
	void Remove(xbXdcTimerCb *pCb);
	virtual void onStart();
	virtual bool onStop(bool);

	void TimerUpdate();
	void TimerDo();

	mutable xbSectShare	m_sect;
	struct MsgCb
	{
		DWORD		m_msg;
		bool		m_bDataMsg;
		xbXdcCb	*m_pFirstCb;
		MsgCb()
		{
			m_bDataMsg = false;
			m_msg = 0;
			m_pFirstCb = NULL;
		}
		MsgCb(DWORD msg,bool bDataMsg)
		{
			m_bDataMsg = bDataMsg;
			m_msg = msg;
			m_pFirstCb = NULL;
		}
		~MsgCb();
	};
	struct TimerCb
	{
		ulong			m_timerElapse;
		ulong			m_timerId;
		ulong			m_nextCall;
		xbXdcTimerCb	*m_pFirstCb;
		bool			m_bStarted;
		TimerCb()
		{
			m_timerId = 0;
			m_timerElapse = 0;
			m_nextCall = 0;
			m_pFirstCb = NULL;
			m_bStarted = false;
		}
		~TimerCb();
		void Start(xbXdcMsgEngine *pEngine);
		void Stop();
	};
	friend TimerCb;

	MsgCb	*getMsgCb(DWORD msg,bool bDataMsg,bool bCreate);
	static int	findMsgCb(const MsgCb &cb,const void *id);
	xbArray<MsgCb>	m_arrMsgCb;

	TimerCb	*createTimerCb(DWORD timerEllapse);
	xbArray<TimerCb>	m_arrTimerCb;
	ulong				m_cTimerChg,m_cTimerDone;
	bool				m_bTimerToDo;

	friend xbXdcCb;
	friend xbXdcTimerCb;

	class xbEXPORT  Thread:public xbTThreadBase<xbThreadCb>
	{
	public:
		Thread(xbXdcMsgEngine *pParent)
			:xbTThreadBase<xbThreadCb>(&pParent->m_sect,xbString(pParent->Label)+" Thread")
		{
			m_pParent = pParent;
		}
		virtual DWORD	onThreadProc(sIxbThread *pTh);
		virtual void	onCloseAsked(sIxbThread *pTh,LPCSTR pReason);
	protected:
		xbXdcMsgEngine	*m_pParent;
	};
	friend Thread;

	Thread m_th;

public:
	xbSharedQueue	m_sharedQ;
};

extern xbXdcMsgEngine	xbEXPORT g_xbXdcMsgEngine;

class xbEXPORT xbXdcCb
{
public:
	xbXdcCb(bool bCreate = false)
	{
		m_msg = 0;
		m_pPrevCb = m_pNextCb = NULL;

		if (bCreate) Create();
	}
	virtual ~xbXdcCb()
	{
		eProtectTrace(Destroy());
	}
	
	virtual DWORD	GetMsgId() = 0;
	virtual bool	IsDataMsg() = 0;
	virtual void Call(xbSharedMem *pData,ulong wParam, ulong lParam) = 0;

	void Create()
	{
		g_xbXdcMsgEngine.Add(this);
	}
	void Destroy()
	{
		g_xbXdcMsgEngine.Remove(this);
	}
protected:
	DWORD		m_msg;
	xbXdcCb	*m_pPrevCb	,*m_pNextCb;
	friend xbXdcMsgEngine;
	friend xbXdcMsgEngine::MsgCb;
};

class xbXdcDataCb;

xbDclCb(xbEXPORT,xbXDCDATA,(xbXdcDataCb &cbObj,xbSharedMem *pData,ulong wParam,ulong lParam),(cbObj,pData,wParam,lParam));

class xbEXPORT xbXdcDataCb :public xbXdcCb
{
public:
	xbXdcDataCb(int cByteMin,const xbExtraDataMsg *pDataMsg,const xbXDCDATACB &cb,bool bCreate = false)
		:xbXdcCb()
	{
		if (cByteMin > 0)
		{
			m_bDataOptional=false;
			m_cByteMin = cByteMin;
		}
		else
		{
			m_bDataOptional=true;
			m_cByteMin = -cByteMin;
		}
		m_pDataMsg = pDataMsg;
		m_cb = cb;

		if (bCreate) Create();
	}
	virtual DWORD	GetMsgId()
	{
		return *m_pDataMsg;
	}
	virtual bool	IsDataMsg()
	{
		return true;
	}
	virtual bool	IsDataOptional()
	{
		return m_bDataOptional;
	}
	virtual int		GetDataLengthMin()
	{
		return m_cByteMin;
	}
	virtual void Call(WPARAM, LPARAM)
	{
		xbThrowEsys("xbXdcDataCb::Call(WPARAM wParam, LPARAM lParam) : forbidden call, bad msg type");
	}
	virtual void Call(xbSharedMem *pData,ulong wParam, ulong lParam)
	{
		if (!pData)
		{
			if (!m_bDataOptional)
				xbThrowEsys("xbXdcDataCb::Call() : message "+xbString(GetMsgId(),xbStringNum_hexa)+" ("+m_pDataMsg->Label+") : received without data");
		}
		else
		{
			if (m_cByteMin > 0 && pData->GetLength() < ulong(m_cByteMin))
				xbThrowEsys("xbXdcDataCb::Call() : message "+xbString(GetMsgId(),xbStringNum_hexa)+" ("+m_pDataMsg->Label+") : data size too little (sz="+xbString(pData->GetLength())+", szmin="+xbString(m_cByteMin)+")");
		}
		m_cb.Call(*this,pData,wParam,lParam);
	}
	int Post(LPCSTR target,const void *pData,int cByte,ulong wParam,ulong lParam) const
	{
		return g_xbXdcMsgEngine.Post(target,*m_pDataMsg,pData,cByte,wParam,lParam);
	}
	int Post(LPCSTR target,xbSharedMem *pData,ulong wParam,ulong lParam) const
	{
		return g_xbXdcMsgEngine.Post(target,*m_pDataMsg,pData,wParam,lParam);
	}
protected:
	xbXDCDATACB			m_cb;
	const xbExtraDataMsg	*m_pDataMsg;
	bool					m_bDataOptional;
	int						m_cByteMin;
};

class xbXdcMsgCb;
xbDclCb(xbEXPORT,xbXDCMSG,(xbXdcMsgCb &cbObj,ulong wParam, ulong lParam),(cbObj,wParam,lParam));

class xbEXPORT xbXdcMsgCb :public xbXdcCb
{
public:
	xbXdcMsgCb(const xbExtraMsg *pMsg,const xbXDCMSGCB &cb,bool bCreate = false)
		:xbXdcCb()
	{
		m_pMsg = pMsg;
		m_cb = cb;

		if (bCreate) Create();
	}
	virtual DWORD	GetMsgId()
	{
		return *m_pMsg;
	}
	virtual bool	IsDataMsg()
	{
		return false;
	}
	virtual void Call(xbSharedMem *pData,ulong wParam, ulong lParam)
	{
		if (pData)
			xbThrowEsys("xbXdcMsgCb::Call() : message "+xbString(GetMsgId(),xbStringNum_hexa)
			+" ("+m_pMsg->Label+") : received with unexpected data (sz="+xbString(pData->GetLength())+")");
		m_cb.Call(*this,wParam,lParam);
	}
	int Post(LPCSTR target,ulong wParam, ulong lParam) const
	{
		return g_xbXdcMsgEngine.Post(target,*m_pMsg,wParam,lParam);
	}
protected:
	xbXDCMSGCB			m_cb;
	const xbExtraMsg	*m_pMsg;
};

class xbXdcTimerCb;
xbDclCb(xbEXPORT,xbXDCTIMER,(xbXdcTimerCb &cbObj),(cbObj));

class xbEXPORT xbXdcTimerCb
{
public:
	xbXdcTimerCb(DWORD timerElapse,const xbXDCTIMERCB &cb,bool bCreate = false)
	{
		m_timerId = 0;
		m_timerElapse = timerElapse;
		m_pPrevCb = m_pNextCb = NULL;
		m_cb = cb;

		if (bCreate) Create();
	}
	virtual ~xbXdcTimerCb()
	{
		eProtectTrace(Destroy());
	}
	
	virtual DWORD	GetTimerElapse() {return m_timerElapse;}
	virtual void Call()
	{
		m_cb.Call(*this);
	}

	void Create()
	{
		g_xbXdcMsgEngine.Add(this);
	}
	void Destroy()
	{
		g_xbXdcMsgEngine.Remove(this);
	}
protected:
	DWORD			m_timerId,m_timerElapse;
	xbXdcTimerCb	*m_pPrevCb,*m_pNextCb;

	xbXDCTIMERCB	m_cb;

	friend xbXdcMsgEngine;
	friend xbXdcMsgEngine::TimerCb;
};


class xbMsgWnd;
xbDclRetCb(xbEXPORT,xbMSGWND,bool,false,(xbMsgWnd &obj, UINT message, WPARAM wParam, LPARAM lParam, LRESULT &res),(obj,message,wParam,lParam,res));

class xbEXPORT __declspec(uuid("{78C2143F-DEEB-47ca-90E6-AB28C1EF3F1C}"))
	xbMsgWnd:public xbWinBase
{
public:
	xbMsgWnd(const xbStringW &caption,const xbMSGWNDCB &msgcb)
		:m_caption(caption),m_msgcb(msgcb)
	{
	}
	virtual ~xbMsgWnd()
	{
		eProtect(Destroy());
	}
	virtual bool GetInterface(REFIID riid, void ** ppInterf)
	{
		xbImplInterface(xbWinBase);
		return xbObjectBase::GetInterface(riid,ppInterf);
	}
	virtual void Create()
	{
		if (m_hWnd)
			return;

		CreateEx(0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL);
	}
protected:
	virtual void PreCreate(CREATESTRUCTW& cs);
	virtual LRESULT WindowProc(UINT msg, WPARAM wParam, LPARAM lParam);

	xbStringW	m_caption;
	xbMSGWNDCB	m_msgcb;
private:
	xbDclNoCopyForObject(xbMsgWnd);
};

class xbEXPORT xbSaveMsgInfo
{
public:
	xbSaveMsgInfo(xbCtorMode ctorMode = xbCtor_CallCreate)
	{
		m_bSave = false;
		if (ctorMode == xbCtor_CallCreate) 
			Save();
	}
	inline void Save()
	{
		if (m_bSave) return;
		m_info = ::GetMessageExtraInfo();
		m_bSave = true;
	}
	inline void Restore()
	{
		if (!m_bSave) return;
		 ::SetMessageExtraInfo(m_info);
		m_bSave = false;
	}
	inline bool IsSaved() const {return m_bSave;}
	inline LPARAM GetInfo() const
	{
		if (!m_bSave) 
			xbThrowEsys("xbSaveMsgInfo::GetInfo() : info not saved");
		return m_info;
	}
	~xbSaveMsgInfo()
	{
		/*eProtectTrace(*/Restore();//);
	}
protected:
	bool	m_bSave;
	LPARAM	m_info;
private:
	xbDclNoCopyForObject(xbSaveMsgInfo);
	xbDclNoNewForObject(xbSaveMsgInfo);
};


#endif
