/*<<<***************************************************************************
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:
<10/02/2014: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/
#include "stdafx.h"

#include "xbMsg.h"
#include "_xbVer.h"

#pragma warning(push)
#pragma warning(disable: 4355) //'this' : utilisé dans la liste des initialiseurs membre de base
#pragma warning(disable: 4312) //'cast de type' : la conversion de 'DWORD' en 'void *' d'une taille supérieure
#pragma warning(disable: 4311) //'<function-style-cast>' : troncation de pointeur de 'const void *' à 'DWORD'

//////////////////////////////////////////////////////////////////////////////////////////
// xbMsgEngine
bool xbDoEvents(bool *pbQuit,int cMsgMax)
{
	xbCtxGet ctx;
	int cnt=0;
	while(::xbDoOneEvent(ctx,pbQuit) && cnt < cMsgMax) 
		cnt++;
	return cnt>0;
}

bool xbDoOneEvent (bool *pbQuit)
{
	xbCtxGet ctx;
	return xbDoOneEvent(ctx,pbQuit);
}

bool xbDoOneEvent(xbCtxGet &ctx,bool *pbQuit)
{
	MSG msg;
	if (!::PeekMessage (&msg, NULL, 0, 0,PM_REMOVE)) 
		return false;
	bool bRet = true;
	eProtect(bRet = xbDispatchMsg(ctx,msg,pbQuit));
	return bRet;
}

bool xbDispatchMsg(xbCtxGet &ctx,MSG &msg,bool *pbQuit)
{
	if (ctx.onThreadMsg(&msg,pbQuit))
	{
		if (pbQuit && *pbQuit)
			return false;
		return true;
	}
	if (msg.message == WM_QUIT)
	{
		::PostQuitMessage(int(msg.wParam));
		if (pbQuit) 
			*pbQuit = true;
		return false;
	}

	xbDispatchMsgLow(msg);
	return true;
}

void xbDispatchMsgLow(MSG &msg)
{
	::TranslateMessage(&msg);
	::DispatchMessage (&msg);
}

/*bool xbDoCmdEvent()
{
	MSG msg;
	if (!::PeekMessage (&m, NULL, WM_COMMAND, WM_COMMAND,PM_REMOVE)) 
		return false;
	::DispatchMessage (&msg);
	return true;
}
*/
void xbPostThreadMessage(
		DWORD idThread, // thread identifier
		UINT Msg,       // message to post
		WPARAM wParam,  // first message parameter
		LPARAM lParam   // second message parameter
		)
{
	int cnt = 0;
	while(cnt < xbPOSTMSG_TRYCNT)
	{
		BOOL ret = ::PostThreadMessage(idThread,Msg,wParam,lParam);
		if (ret) return;
		::xbSleep(xbPOSTMSG_TRYSLEEP);
		cnt++;
	}
	xbThrowEsys(xbHR_LAST,"xbPostThreadMessage() failure");
}

void xbPostThreadMessage(
		HANDLE hThread, // thread handle
		DWORD idThread, // thread identifier
		UINT Msg,       // message to post
		WPARAM wParam,  // first message parameter
		LPARAM lParam   // second message parameter
		)
{
	int cnt = 0;
	while(cnt < xbPOSTMSG_TRYCNT)
	{
		BOOL ret = ::PostThreadMessage(idThread,Msg,wParam,lParam);
		if (ret) return;
		if (::GetLastError() == ERROR_INVALID_THREAD_ID)
		{
			DWORD exitcode = 0;
			if (!::GetExitCodeThread(hThread,&exitcode)) return;
			if (exitcode != STILL_ACTIVE) return;
		}
		::xbSleep(xbPOSTMSG_TRYSLEEP);
		cnt++;
	}
	xbThrowEsys(xbHR_LAST,"xbPostThreadMessage2() failure");
}

void xbPostMessage(
		HWND hWnd, // Window handle
		UINT Msg,       // message to post
		WPARAM wParam,  // first message parameter
		LPARAM lParam   // second message parameter
		)
{
	BOOL ret = ::PostMessage(hWnd,Msg,wParam,lParam);
	if (ret) return;
	if (!hWnd)
		xbThrowEsys(xbHR_LAST,"xbPostMessage() failure");
	DWORD thId = ::xbThreadId();
	DWORD winThId = ::GetWindowThreadProcessId(hWnd,NULL);
	if (thId == winThId)
		xbThrowEsys(xbHR_LAST,"xbPostMessage() failure");
	::xbPostExternalMessage(hWnd,Msg,wParam,lParam);
}

void xbPostExternalMessage(
		HWND hWnd, // Window handle
		UINT Msg,       // message to post
		WPARAM wParam,  // first message parameter
		LPARAM lParam   // second message parameter
		)
{
	int cnt = 0;
	while(cnt < xbPOSTMSG_TRYCNT)
	{
		if (!::IsWindow(hWnd))
		{
			xbRdbgOutput("xbPostExternalMessage(), hWnd="+xbStrFromPtr(hWnd)+" isn't a window !");
			return;
		}
		//	xbThrowEsys("xbPostExternalMessage(), hWnd="+xbString(int(hWnd),true)+" isn't a window !");
		BOOL ret = ::PostMessage(hWnd,Msg,wParam,lParam);
		if (ret) return;
		::xbSleep(xbPOSTMSG_TRYSLEEP);
		cnt++;
	}
	xbThrowEsys(xbHR_LAST,"xbPostExternalMessage() failure");
}

//////////////////////////////////////////////////////////////////////////////////////////
// xbMsgEngine
xbMsgEngine		g_xbMsgEngine("Base Intra application Message Center");
xbXdcMsgEngine	g_xbXdcMsgEngine("Base Extra application Message Center","!EproMsgInterAppli4");

int xbMsgEngine::Add(xbMsg *pMsg)
{
	xbLock lckStart(this);
	xbSectLock lck(&m_sectData);

	int no = 0;
	while(no < m_arrMsg.Length)
	{
		if (!m_arrMsg.GetAt(no))
		{
			m_arrMsg.SetAt(no,pMsg);
			return no+1;
		}
		no++;
	}
	int id = m_arrMsg.Add(pMsg) + 1;
	if (id + WM_APP - 1 > 0xBFFF)
	{
		Remove(id);
		xbThrowEsys("xbMsgEngine::Add() : too many messages");
	}
	return id;
}

void xbMsgEngine::Remove(int id)
{
//	xbLock lckStart(this);
	if (!IsReady())
		return;
	xbSectLock lck(&m_sectData);
	m_arrMsg.SetAt(id-1,NULL);
}


//////////////////////////////////////////////////////////////////////////////////////////
// xbXdcMsgEngine
xbXdcMsgEngine::xbXdcMsgEngine(const char *pLabel,const char *pHeapLabel,const xbEngines &engUsed)
	:xbEngine(xbEngineStart_OnUse,pLabel,xbEngines(&g_xbThreadCenter,engUsed)),
	m_th(this),m_sharedQ(xbXDCMSGHEAP_SZMAX,pHeapLabel)
{
	m_cTimerChg = m_cTimerDone = 0;
	m_bTimerToDo = false;
}

void xbXdcMsgEngine::Add(xbXdcCb *pCb)
{
	xbLock lckStart(this);
	DWORD msg = pCb->GetMsgId();
	xbSectLock lck(&m_sect);
	if (pCb->m_msg) return;
	MsgCb *pMsgCb = getMsgCb(msg,pCb->IsDataMsg(),true);
	pCb->m_pNextCb = pMsgCb->m_pFirstCb;
	pCb->m_pPrevCb = NULL;
	if (pMsgCb->m_pFirstCb)
	{
		xbAssert(pMsgCb->m_pFirstCb->m_pPrevCb == NULL);
		pMsgCb->m_pFirstCb->m_pPrevCb = pCb;
	}
	pMsgCb->m_pFirstCb = pCb;
	pCb->m_msg = msg;
}

void xbXdcMsgEngine::Remove(xbXdcCb *pCb)
{
	if (!IsReady())
		return;
	xbSectLock lck(&m_sect);
	if (!pCb->m_msg) return;
	if (pCb->m_pPrevCb) pCb->m_pPrevCb->m_pNextCb = pCb->m_pNextCb;
	else
	{
		MsgCb *pMsgCb = getMsgCb(pCb->m_msg,pCb->IsDataMsg(),false);
		xbAssert(pMsgCb->m_pFirstCb == pCb);
		pMsgCb->m_pFirstCb = pCb->m_pNextCb;
	}
	if (pCb->m_pNextCb) pCb->m_pNextCb->m_pPrevCb = pCb->m_pPrevCb;
	pCb->m_msg = 0;
}

xbXdcMsgEngine::MsgCb	*xbXdcMsgEngine::getMsgCb(DWORD msg,bool bDataMsg,bool bCreate)
{
	int pos;
	MsgCb *pMsgCb = m_arrMsgCb.SortedFind((void*)msg,findMsgCb,&pos);
	if (pMsgCb) 
	{
		if (pMsgCb->m_bDataMsg != bDataMsg)
			xbThrowEsys("xbXdcMsgEngine::getMsgCb() : bad type message found");
		return pMsgCb;
	}
	if (!bCreate) 
		xbThrowEsys("xbXdcMsgEngine::getMsgCb() : can't find message");
	m_arrMsgCb.Insert(pos,MsgCb(msg,bDataMsg));
	return &m_arrMsgCb.GetAt(pos);
}

int		xbXdcMsgEngine::findMsgCb(const MsgCb &cb,const void *_id)
{
	DWORD id = DWORD(_id);
	if (cb.m_msg > id) return 1;
	if (cb.m_msg < id) return -1;
	return 0;
}

void xbXdcMsgEngine::Add(xbXdcTimerCb *pCb)
{
	xbLock lckStart(this);
	xbSectLock lck(&m_sect);
	if (pCb->m_timerId) 
		return;
	TimerCb *pTimerCb = createTimerCb(pCb->m_timerElapse);
	pCb->m_pNextCb = pTimerCb->m_pFirstCb;
	pCb->m_pPrevCb = NULL;
	if (pTimerCb->m_pFirstCb)
	{
		xbAssert(pTimerCb->m_pFirstCb->m_pPrevCb == NULL);
		pTimerCb->m_pFirstCb->m_pPrevCb = pCb;
	}
	pTimerCb->m_pFirstCb = pCb;
	pCb->m_timerId = pTimerCb->m_timerId;
}

void xbXdcMsgEngine::Remove(xbXdcTimerCb *pCb)
{
	if (!IsReady())
		return;
	xbSectLock lck(&m_sect);
	if (!pCb->m_timerId) 
		return;
	if (pCb->m_pPrevCb) 
		pCb->m_pPrevCb->m_pNextCb = pCb->m_pNextCb;
	else
	{
		TimerCb *pTimerCb = &m_arrTimerCb.GetAt(pCb->m_timerId-1);
		xbAssert(pTimerCb->m_pFirstCb == pCb);
		if ((pTimerCb->m_pFirstCb = pCb->m_pNextCb) == NULL)
			pTimerCb->Stop();
	}
	if (pCb->m_pNextCb) 
		pCb->m_pNextCb->m_pPrevCb = pCb->m_pPrevCb;
	pCb->m_timerId = 0;
}

xbXdcMsgEngine::TimerCb	*xbXdcMsgEngine::createTimerCb(DWORD elapse)
{
	if (elapse < 1) 
		xbThrowEsys("xbXdcMsgEngine::createTimerCb() : elapse time is null");
	elapse = (elapse + xbXDCTIMER_STEP - 1)/xbXDCTIMER_STEP;
	elapse *= xbXDCTIMER_STEP;

	TimerCb *ptr = m_arrTimerCb.Begin();
	TimerCb *pend = m_arrTimerCb.End();
	while( ptr < pend)
	{
		if (ptr->m_timerElapse == elapse)
		{
			ptr->Start(this);
			return ptr;
		}
		ptr++;
	}


	int pos = m_arrTimerCb.Length;
	m_arrTimerCb.Length = pos + 1;
	ptr = &m_arrTimerCb.GetAt(pos);
	ptr->m_timerId = pos + 1;
	ptr->m_timerElapse = elapse;
	ptr->Start(this);
	return ptr;
}

void xbXdcMsgEngine::TimerUpdate()
{
	{
		xbSectLock lck(&m_sect);
		m_cTimerChg++;
	}
	m_sharedQ.m_qEvent.Set();
}

void xbXdcMsgEngine::TimerDo()
{
	xbSectLock lck(&m_sect);
	if (m_cTimerChg == m_cTimerDone && !m_bTimerToDo)
	{
		lck.Unlock();
		m_sharedQ.SetTimeOut(xbXDCMSG_PING);
		return;
	}
	m_cTimerDone = m_cTimerChg;
	m_bTimerToDo = false;

	ulong ti = ::GetTickCount();
	TimerCb	*ptr = m_arrTimerCb.Begin();
	TimerCb	*pend = m_arrTimerCb.End();
	ulong diffMin = xbXDCMSG_PING;
	while(ptr < pend)
	{
		if (ptr->m_bStarted)
		{
			ulong diff;
//			ulong diff = xbCmpTime(ptr->m_nextCall,ti);
			if (ti >= ptr->m_nextCall)
			{
				xbXdcTimerCb	*pCb = ptr->m_pFirstCb;
				while(pCb)
				{
					xbXdcTimerCb	*pNextCb = pCb->m_pNextCb;
					eProtectTrace(pCb->Call());
					pCb = pNextCb;
				}

				ti = ::GetTickCount();
				ptr->m_nextCall = ti + ptr->m_timerElapse;
				diff = ptr->m_timerElapse;
//				xbAssert(xbCmpTime(ptr->m_nextCall,ti) == int(ptr->m_timerElapse));
			}
			else
				diff = ptr->m_nextCall - ti;
			if (diff < diffMin)
				diffMin = diff;

			m_bTimerToDo = true;
		}
		ptr++;
	}
	xbAssert(diffMin > 0);
	if (diffMin  < xbXDCTIMER_STEP)
		diffMin = xbXDCTIMER_STEP;
	m_sharedQ.SetTimeOut(diffMin);
}

bool xbXdcMsgEngine::Call(ulong message, xbSharedMem *pMem, ulong wParam, ulong lParam)
{
	xbDbgOutput("xbXdcMsgEngine::Call("+m_sharedQ.GetQueueName()
		+", msg="+xbString(message,xbStringNum_hexa)
		+", data="+xbString(pMem != NULL?pMem->GetLength():0)
		+", wParam="+xbString(wParam,xbStringNum_hexa)+", lParam="+xbString(lParam,xbStringNum_hexa)+") ...");
	xbSectLock lck(&m_sect);
	MsgCb *pMsgCb = m_arrMsgCb.SortedFind((void*)message,findMsgCb);
	if (!pMsgCb)
	{
		xbDbgOutput("xbXdcMsgEngine::Call("+m_sharedQ.GetQueueName()
			+", msg="+xbString(message,xbStringNum_hexa)
			+", data="+xbString(pMem != NULL?pMem->GetLength():0)
			+", wParam="+xbString(wParam,xbStringNum_hexa)
			+", lParam="+xbString(lParam,xbStringNum_hexa)+") : message received not intercepted !");
		return false;
	}

	xbXdcCb	*pCb = pMsgCb->m_pFirstCb;
	while(pCb)
	{
		xbXdcCb	*pNextCb = pCb->m_pNextCb;
		eProtectTrace(pCb->Call(pMem,wParam,lParam));
		pCb = pNextCb;
	}

	return true;
}

void xbXdcMsgEngine::onStart()
{
	m_sharedQ.Create();
	m_th.Create();
}

bool xbXdcMsgEngine::onStop(bool bForce)
{
	{
		xbSectLock lck(&m_sect);
		m_arrTimerCb.Clear();
	}
	
	bool bRet = m_th.AskDestroy("xbXdcMsgEngine stopping");
	
	{
		xbSectLock lck(&m_sect);
		m_arrMsgCb.Clear();
	}

	if (!bForce && !m_sharedQ.EndMemLocks()) 
		bRet = false;
	else
		m_sharedQ.Destroy();
	return bRet;
}

bool xbXdcMsgEngine::Alloc(xbRefPtr<xbSharedMem> *pMem,ulong cByte,xbOpMode opm)
{
	xbLock lckStart(this);
	return m_sharedQ.Alloc(pMem,cByte,opm);
}

bool xbXdcMsgEngine::Load(xbRefPtr<xbSharedMem> *pMem,const xbSharedHandle &handle,ulong cByte,xbOpMode opm)
{
	xbLock lckStart(this);
	return m_sharedQ.Load(pMem,handle,cByte,opm);
}

//////////////////////////////////////////////////////////////////////////////////////////
// xbXdcMsgEngine::MsgCb
xbXdcMsgEngine::MsgCb::~MsgCb()
{
	eProtectBegin();
	xbXdcCb	*pCb = m_pFirstCb;
	int			cnt=0;
	while(pCb)
	{
		xbXdcCb	*pNextCb = pCb->m_pNextCb;
		eProtectBegin();
		pCb->Destroy();
		eProtectEndTrace();
		pCb = pNextCb;
		cnt++;
	}
	if (cnt > 0)
		xbRelOutput("[PB] xbXdcMsgEngine::MsgCb::~MsgCb() : staying "+xbString(cnt)+ " xbXdcMsgEngine::MsgCb(s)");
	eProtectEndTrace();
}

//////////////////////////////////////////////////////////////////////////////////////////
// xbXdcMsgEngine::TimerCb
xbXdcMsgEngine::TimerCb::~TimerCb()
{
	eProtectBegin();
	Stop();
	xbXdcTimerCb	*pCb = m_pFirstCb;
	while(pCb)
	{
		xbXdcTimerCb	*pNextCb = pCb->m_pNextCb;
		eProtectBegin();
		pCb->Destroy();
		eProtectEndTrace();
		pCb = pNextCb;
	}
	xbAssert(m_pFirstCb == NULL);
	eProtectEndTrace();
}

void xbXdcMsgEngine::TimerCb::Start(xbXdcMsgEngine *pEngine)
{
	if (m_bStarted) return;
	if (m_timerElapse < xbXDCTIMER_STEP || m_timerId < 1) 
		xbThrowEsys("xbXdcMsgEngine::TimerCb::Start() : internal error");

	m_nextCall = ::GetTickCount() + m_timerElapse;
	m_bStarted = true;

	pEngine->TimerUpdate();
}

void xbXdcMsgEngine::TimerCb::Stop()
{
	if (!m_bStarted) return;
	m_bStarted = false;
	m_nextCall = 0;
}

//////////////////////////////////////////////////////////////////////////////////////////
// xbXdcMsgEngine::Thread
/*
void	xbXdcMsgEngine::Thread::onStart(sIxbThread *pTh)
{
	pTh->StartCoInit();
	xbTThreadBase<xbThreadCb>::onStart(pTh);
}

DWORD	xbXdcMsgEngine::Thread::onStop(sIxbThread *pTh,DWORD ret)
{
	return xbTThreadBase<xbThreadCb>::onStop(pTh,ret);
}
*/

DWORD	xbXdcMsgEngine::Thread::onThreadProc(sIxbThread *pTh)
{
	bool bWait = true,bEnd = false;
	xbSharedQueue &shQ = m_pParent->m_sharedQ;
	for(;;)
	{
		bool bTimeOut = false;
		if (bWait && (!shQ.Wait(*m_eventPtr,&bTimeOut) || m_eventPtr.ProcessToStop()))
		{
			if (bTimeOut)
			{
				//On time out, check timers
				m_pParent->TimerDo();
			}
			else
			{
				if (bEnd)
					break;
				bEnd = true;
			}
		}

		bWait = false;
		ulong msgId;
		xbRefPtr<xbSharedMem> prMem;
		ulong wParam,lParam;
		if (!shQ.Peek(&msgId,&prMem,&wParam,&lParam))
		{
			//On no more messages, check timers
			if (!bTimeOut)
				m_pParent->TimerDo();
			bWait = true;
			continue;
		}
		
		m_pParent->Call(msgId,prMem,wParam,lParam);
	}
	return 0;
}

void	xbXdcMsgEngine::Thread::onCloseAsked(sIxbThread *pTh,LPCSTR pReason)
{
	setCloseReason(pReason);

	m_pParent->m_sharedQ.Close();
}

//////////////////////////////////////////////////////////////////////////////////////////
// xbMsgWnd

//Defined in winuser.h
#if !defined(HWND_MESSAGE)
#	define HWND_MESSAGE     ((HWND)-3)
#	ifndef xbNOCWARNINGS
#		pragma message("XDCBASE: WARNING : Bad standard C includes (HWND_MESSAGE not defined)")
#	endif
#endif /* WINVER >= 0x0500 */

void xbMsgWnd::PreCreate(CREATESTRUCTW& cs) 
{
	cs.style = WS_POPUP;
	cs.dwExStyle = WS_EX_TOPMOST | WS_EX_TOOLWINDOW;
	if (xbIsNTos() && xbGetOSver() >= 5)
		cs.hwndParent = HWND_MESSAGE;
	cs.lpszName = m_caption;
	
	xbWinBase::PreCreate(cs);
}

LRESULT xbMsgWnd::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
	LRESULT res = 0;
	if (m_msgcb.Call(*this,msg,wParam,lParam,res))
		return res;

	return xbWinBase::WindowProc(msg, wParam, lParam);
}

#pragma warning(pop)
