/******************************************************************************
 *    dh2.cpp
 *
 *    This file is part of Dark Hook 2
 *    Copyright (C) 2005-2009 Tom N Harris <telliamed@whoopdedo.org>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *****************************************************************************/
#define INITGUID 1
#include "dh2.h"

#include <functional>
#include <algorithm>

#include <lg/interface.h>

using namespace std;


static cDH2ScriptService* g_pDH2 = NULL;

sDispatchListenerDesc cDH2ScriptService::sm_simlistenerdesc = {
	&IID_IDarkHookScriptService,
	0xF,
	SimListener,
	NULL
};

bool cDH2ScriptService::sm_initialized = false;
map<int,PropListenerHandle> cDH2ScriptService::sm_prophookhandles;
map<int,bool> cDH2ScriptService::sm_relhookactive;
int cDH2ScriptService::sm_objhookhandle = -1;
bool cDH2ScriptService::sm_traithookactive = false;

// Hooks

void __stdcall cDH2ScriptService::PropertyListener(sPropertyListenMsg* pPropMsg, PropListenerData pData)
{
	if (g_pDH2 == NULL)
		return;

	if (pPropMsg->event > 4)
		return;

	g_pDH2->HandleProperty(pPropMsg, pData);
}

void cDH2ScriptService::HandleProperty(sPropertyListenMsg* pPropMsg, PropListenerData pData)
{
	//if (!(m_pSimMan->LastMsg() & 9))
	//	return;

	static const char __event[] = { 0,1,2,2,3,3,3,3,3,3,3,3,3,3,3,3 };
	int event = __event[pPropMsg->event & 0xF];
	
	const sPropertyDesc* pPropDesc = reinterpret_cast<IProperty*>(pData)->Describe();

	map<objprop,objlist>::const_iterator i = m_proptree.find(objprop(pPropDesc->szName,pPropMsg->iObjId));
	if (i != m_proptree.end())
	{
		objlist::const_iterator l = i->second.begin();
		for (; l != i->second.end(); ++l)
		{
			DoPropertyNotify(l->first, l->second, event, pPropDesc->szName, pPropMsg->iObjId, pPropMsg->pData);
		}
	}
	i = m_proptree.find(objprop(pPropDesc->szName,0));
	if (i != m_proptree.end())
	{
		objlist::const_iterator l = i->second.begin();
		for (; l != i->second.end(); ++l)
		{
			DoPropertyNotify(l->first, l->second, event, pPropDesc->szName, pPropMsg->iObjId, pPropMsg->pData);
		}
	}
	/* Have to find a way to register a listener for every property
	i = m_proptree.find(objprop("",pPropMsg->iObjId));
	if (i != m_proptree.end())
	{
		objlist::const_iterator l = i->second.begin();
		for (; l != i->second.end(); ++l)
		{
			DoPropertyNotify(l->first, l->second, event, pPropDesc->szName, pPropMsg->iObjId, pPropMsg->pData);
		}
	}
	*/

}

void __stdcall cDH2ScriptService::RelationListener(sRelationListenMsg* pRelMsg, void* pData)
{
	if (g_pDH2 == NULL)
		return;

	if (!(pRelMsg->event & 0xF))
		return;

	g_pDH2->HandleRelation(pRelMsg, pData);
}

void cDH2ScriptService::HandleRelation(sRelationListenMsg* pRelMsg, void* pData)
{
	//if (!(m_pSimMan->LastMsg() & 9))
	//	return;

	static const char __event[] = { 0,1,2,2,3,3,3,3,3,3,3,3,3,3,3,3 };
	int event = __event[pRelMsg->event & 0xF];

	IRelation* pRel = m_pLinkMan->GetRelation(pRelMsg->flavor);
	const sRelationDesc* pRelDesc = pRel->Describe();

	map<objprop,objlist>::const_iterator i = m_reltree.find(objprop(pRelDesc->szName,pRelMsg->source));
	if (i != m_reltree.end())
	{
		objlist::const_iterator l = i->second.begin();
		for (; l != i->second.end(); ++l)
		{
			DoRelationNotify(l->first, l->second, event, pRelDesc->szName, pRelMsg->lLink, pRelMsg->source, pRelMsg->dest, pRel);
		}
	}
	m_reltree.find(objprop(pRelDesc->szName,0));
	if (i != m_reltree.end())
	{
		objlist::const_iterator l = i->second.begin();
		for (; l != i->second.end(); ++l)
		{
			DoRelationNotify(l->first, l->second, event, pRelDesc->szName, pRelMsg->lLink, pRelMsg->source, pRelMsg->dest, pRel);
		}
	}
	/* Have to find a way to register a listener for every link
	m_reltree.find(objprop("",pRelMsg->source));
	if (i != m_reltree.end())
	{
		objlist::const_iterator l = i->second.begin();
		for (; l != i->second.end(); ++l)
		{
			DoRelationNotify(l->first, l->second, event, pRelDesc->szName, pRelMsg->lLink, pRelMsg->source, pRelMsg->dest, pRel);
		}
	}
	*/

	pRel->Release();

#ifdef __GNUC__
	pData = pData;
#endif
#ifdef __BORLANDC__
#pragma argsused(pResMsg)
#endif
}

void __cdecl cDH2ScriptService::ObjectListener(int iObjId, unsigned long uEvent, void* pData)
{
	if (g_pDH2 == NULL)
		return;

	// PostLoad and BeginCreate events are ignored
	if (uEvent > 2)
		return;

	g_pDH2->HandleObject(iObjId, uEvent, pData);
}

void cDH2ScriptService::HandleObject(int iObjId, unsigned long uEvent, void* pData)
{
	//if (!(m_pSimMan->LastMsg() & 9))
	//	return;

	map<int,objlist>::const_iterator i = m_objtree.find(iObjId);
	if (i != m_objtree.end())
	{
		objlist::const_iterator l = i->second.begin();
		for (; l != i->second.end(); ++l)
		{
			DoObjectNotify(l->first, l->second, uEvent, iObjId);
		}
	}

	i = m_objtree.find(0);
	if (i != m_objtree.end())
	{
		objlist::const_iterator l = i->second.begin();
		for (; l != i->second.end(); ++l)
		{
			DoObjectNotify(l->first, l->second, uEvent, iObjId);
		}
	}

#ifdef __GNUC__
	pData = pData;
#endif
#ifdef __BORLANDC__
#pragma argsused(iObjId,uEvent)
#endif
}

void __stdcall cDH2ScriptService::HierarchyListener(const sHierarchyMsg* pTraitMsg, void* pData)
{
	if (g_pDH2 == NULL)
		return;

	g_pDH2->HandleHierarchy(pTraitMsg, pData);
}

void cDH2ScriptService::HandleHierarchy(const sHierarchyMsg* pTraitMsg, void* pData)
{
	//if (!(m_pSimMan->LastMsg() & 9))
	//	return;

	map<int,objlist>::const_iterator i = m_traittree.find(pTraitMsg->iSubjId);
	if (i != m_traittree.end())
	{
		objlist::const_iterator l = i->second.begin();
		for (; l != i->second.end(); ++l)
		{
			DoHierarchyNotify(l->first, l->second, pTraitMsg->event, pTraitMsg->iSubjId, pTraitMsg->iObjId);
		}
	}
	i = m_traittree.find(0);
	if (i != m_traittree.end())
	{
		objlist::const_iterator l = i->second.begin();
		for (; l != i->second.end(); ++l)
		{
			DoHierarchyNotify(l->first, l->second, pTraitMsg->event, pTraitMsg->iSubjId, pTraitMsg->iObjId);
		}
	}
	// And again with the inverse context
	i = m_traittree.find(pTraitMsg->iObjId);
	if (i != m_traittree.end())
	{
		objlist::const_iterator l = i->second.begin();
		for (; l != i->second.end(); ++l)
		{
			DoHierarchyNotify(l->first, l->second, pTraitMsg->event+2, pTraitMsg->iObjId, pTraitMsg->iSubjId);
		}
	}
	i = m_traittree.find(0);
	if (i != m_traittree.end())
	{
		objlist::const_iterator l = i->second.begin();
		for (; l != i->second.end(); ++l)
		{
			DoHierarchyNotify(l->first, l->second, pTraitMsg->event+2, pTraitMsg->iObjId, pTraitMsg->iSubjId);
		}
	}

#ifdef __GNUC__
	pData = pData;
#endif
#ifdef __BORLANDC__
#pragma argsused(pTraitMsg)
#endif
}

int __cdecl cDH2ScriptService::SimListener(const sDispatchMsg* pSimMsg, const sDispatchListenerDesc* pDesc)
{
	if (pSimMsg->dwEventId == kSimStop)
		reinterpret_cast<cDH2ScriptService*>(pDesc->pData)->Reset();
	return 0;
}


// DH2 class

HRESULT __stdcall cDH2ScriptService::QueryInterface(REFIID riid, void** ppv)
{
	if (riid == IID_IDarkHookScriptService)
	{
		*ppv = this;
	}
	else if (riid == IID_IUnknown)
		*ppv = static_cast<IUnknown*>(this);
	else {
		*ppv = NULL;
		return E_NOINTERFACE;
	}
	return S_OK;
}
ULONG __stdcall cDH2ScriptService::AddRef()
{
	return InterlockedIncrement((LONG*)&m_iRef);
}
ULONG __stdcall cDH2ScriptService::Release()
{
	ULONG uRefCnt = InterlockedDecrement((LONG*)&m_iRef);
	if (uRefCnt == 0)
		delete this;
	return uRefCnt;
}

cDH2ScriptService::~cDH2ScriptService()
{
	sm_initialized = false;
	g_pDH2 = NULL;

	Reset();

	m_pSimMan->Unlisten(&IID_IDarkHookScriptService);
	m_pSimMan->Release();
	m_pTraitMan->Release();
	m_pObjMan->Release();
	m_pLinkMan->Release();
	m_pPropMan->Release();
	m_pScriptMan->Release();
}

cDH2ScriptService::cDH2ScriptService(IUnknown* pIFace)
	: m_iRef(1)
{
	if (E_NOINTERFACE == 
	  pIFace->QueryInterface(IID_IScriptMan, reinterpret_cast<void**>(&m_pScriptMan)))
		throw no_interface("IID_IScriptMan");
	if (E_NOINTERFACE == 
	  pIFace->QueryInterface(IID_IPropertyManager, reinterpret_cast<void**>(&m_pPropMan)))
		throw no_interface("IID_IPropertyManager");
	if (E_NOINTERFACE == 
	  pIFace->QueryInterface(IID_ILinkManager, reinterpret_cast<void**>(&m_pLinkMan)))
		throw no_interface("IID_ILinkManager");
	if (E_NOINTERFACE == 
	  pIFace->QueryInterface(IID_IObjectSystem, reinterpret_cast<void**>(&m_pObjMan)))
		throw no_interface("IID_IObjectSystem");
	if (E_NOINTERFACE == 
	  pIFace->QueryInterface(IID_ITraitManager, reinterpret_cast<void**>(&m_pTraitMan)))
		throw no_interface("IID_ITraitManager");
	if (E_NOINTERFACE == 
	  pIFace->QueryInterface(IID_ISimManager, reinterpret_cast<void**>(&m_pSimMan)))
		throw no_interface("IID_ISimManager");

	sm_simlistenerdesc.pData = reinterpret_cast<void*>(this);
	m_pSimMan->Listen(&sm_simlistenerdesc);

	g_pDH2 = this;
	sm_initialized = true;
}

// BaseScriptService functions 
void __stdcall cDH2ScriptService::Init(void)
{
}

void __stdcall cDH2ScriptService::End(void)
{
}

void cDH2ScriptService::Reset(void)
{
	if (sm_objhookhandle != -1)
	{
		m_pObjMan->Unlisten(sm_objhookhandle);
		sm_objhookhandle = -1;
	}

	map<int,PropListenerHandle>::iterator i = sm_prophookhandles.begin();
	for (; i != sm_prophookhandles.end(); ++i)
	{
		IProperty* pProp = m_pPropMan->GetProperty(i->first);
		pProp->Unlisten(i->second);
		pProp->Release();
	}
	sm_prophookhandles.clear();

	m_proptree.clear();
	m_reltree.clear();
	m_objtree.clear();
	m_traittree.clear();
}

template <class _Tp, class _Pr>
struct _Select1st_equal_to : public binary_function<_Tp,_Pr,bool>
{
	bool operator()(const _Tp& __x, const _Pr& __y) const 
	{ return __x == __y.first; }
};
typedef _Select1st_equal_to<int,pair<int,eDHRegisterFlags> >  objpair_equal_to;

// DarkHookScriptService
BOOL __stdcall cDH2ScriptService::InstallPropHook(int iAgent, eDHRegisterFlags eFlags, const char * pszProp, int iObj)
{
	if (! pszProp || ! *pszProp)
		return FALSE;

	IProperty* pProp = m_pPropMan->GetPropertyNamed(pszProp);
	if (!pProp)
		return FALSE;
	map<int,PropListenerHandle>::iterator k = sm_prophookhandles.find(pProp->GetID());
	if (k == sm_prophookhandles.end())
	{
		// We don't increment the reference count on pProp to avoid the hassle
		// of having to release it later... Shouldn't be a problem since the 
		// listener will be called by the same instance anyway (we hope).
		PropListenerHandle h = pProp->Listen(kPropertyFull, PropertyListener, reinterpret_cast<PropListenerData>(pProp));
		sm_prophookhandles.insert(make_pair(pProp->GetID(),h));
	}
	pProp->Release();

	objprop proptag(pszProp,iObj);
	objlist& l = m_proptree[proptag];
	objlist::iterator i = find_if(l.begin(), l.end(), bind1st(objpair_equal_to(),iAgent));
	if (i == l.end())
		l.push_back(make_pair(iAgent,eFlags));

	return TRUE;
}

void __stdcall cDH2ScriptService::UninstallPropHook(int iAgent, const char * pszProp, int iObj)
{
	if (! pszProp || ! *pszProp)
		return;

	IProperty* pProp = m_pPropMan->GetPropertyNamed(pszProp);
	if (!pProp)
		return;

	map<objprop,objlist>::iterator i = m_proptree.find(objprop(pszProp,iObj));
	if (i != m_proptree.end())
	{
		i->second.remove_if(bind1st(objpair_equal_to(),iAgent));
		if (i->second.empty())
		{
			m_proptree.erase(i);

			map<int,PropListenerHandle>::iterator k = sm_prophookhandles.find(pProp->GetID());
			if (k != sm_prophookhandles.end())
			{
				pProp->Unlisten(k->second);
				sm_prophookhandles.erase(k);
			}
		}
	}

	pProp->Release();
}

BOOL __stdcall cDH2ScriptService::InstallRelHook(int iAgent, eDHRegisterFlags eFlags, const char * pszRel, int iObj)
{
	if (! pszRel || ! *pszRel)
		return FALSE;

	IRelation* pRel = m_pLinkMan->GetRelationNamed(pszRel);
	if (!pRel)
		return FALSE;
	map<int,bool>::iterator k = sm_relhookactive.find(pRel->GetID());
	if (k == sm_relhookactive.end())
	{
		pRel->Listen(kRelationFull, RelationListener, NULL);
		sm_relhookactive.insert(make_pair(pRel->GetID(),true));
	}
	pRel->Release();

	objprop reltag(pszRel,iObj);
	objlist& l = m_reltree[reltag];
	objlist::iterator i = find_if(l.begin(), l.end(), bind1st(objpair_equal_to(),iAgent));
	if (i == l.end())
		l.push_back(make_pair(iAgent,eFlags));

	return TRUE;
}

void __stdcall cDH2ScriptService::UninstallRelHook(int iAgent, const char * pszRel, int iObj)
{
	if (! pszRel || ! *pszRel)
		return;

	IRelation* pRel = m_pLinkMan->GetRelationNamed(pszRel);
	if (!pRel)
		return;

	map<objprop,objlist>::iterator i = m_reltree.find(objprop(pszRel,iObj));
	if (i != m_reltree.end())
	{
		i->second.remove_if(bind1st(objpair_equal_to(),iAgent));
		if (i->second.empty())
		{
			m_reltree.erase(i);
			// Can't unregister link listeners...
			// lets just hope the relation IDs don't change behind our back
		}
	}

	pRel->Release();
}

BOOL __stdcall cDH2ScriptService::InstallObjHook(int iAgent, eDHRegisterFlags eFlags, int iObj)
{
	static const sObjListenerDesc _desc = { ObjectListener, NULL };

	objlist& l = m_objtree[iObj];
	objlist::iterator i = find_if(l.begin(), l.end(), bind1st(objpair_equal_to(),iAgent));
	if (i == l.end())
		l.push_back(make_pair(iAgent,eFlags));
	if (sm_objhookhandle == -1)
		m_pObjMan->Listen(const_cast<sObjListenerDesc*>(&_desc));
	return TRUE;
}

void __stdcall cDH2ScriptService::UninstallObjHook(int iAgent, int iObj)
{
	map<int,objlist>::iterator i = m_objtree.find(iObj);
	if (i != m_objtree.end())
	{
		i->second.remove_if(bind1st(objpair_equal_to(),iAgent));
		if (i->second.empty())
			m_objtree.erase(i);
	}
}

BOOL __stdcall cDH2ScriptService::InstallHierarchyHook(int iAgent, eDHRegisterFlags eFlags, int iObj)
{
	objlist& l = m_traittree[iObj];
	objlist::iterator i = find_if(l.begin(), l.end(), bind1st(objpair_equal_to(),iAgent));
	if (i == l.end())
		l.push_back(make_pair(iAgent,eFlags));
	if (! sm_traithookactive)
	{
		m_pTraitMan->Listen(HierarchyListener, NULL);
		sm_traithookactive = true;
	}
	return TRUE;
}

void __stdcall cDH2ScriptService::UninstallHierarchyHook(int iAgent, int iObj)
{
	map<int,objlist>::iterator i = m_traittree.find(iObj);
	if (i != m_traittree.end())
	{
		i->second.remove_if(bind1st(objpair_equal_to(),iAgent));
		if (i->second.empty())
			m_traittree.erase(i);
	}
}

// Notifiers

void cDH2ScriptService::DoPropertyNotify(int iAgent, eDHRegisterFlags eFlags, unsigned int uEvent, const char* pszName, int iObjId, void* pData)
{
	if (eFlags & kDHNotifyAsync)
	{
		char szData[64];
		sprintf(szData, "%d,%s", iObjId, pszName);
		m_pScriptMan->PostMessage2(0,iAgent,"DHNotifyAsync",int(kDH_Property),int(uEvent),szData);
	}
	else
	{
		cMultiParm mpReply;
		sDHNotifyMsg* pMsg = new sDHNotifyMsg();
		pMsg->to = iAgent;
		pMsg->message = "DHNotify";
		pMsg->typeDH = kDH_Property;
		pMsg->sProp.event = ePropEvent(uEvent);
		pMsg->sProp.idObj = iObjId;
		pMsg->sProp.pszPropName = pszName;
		pMsg->sProp.pvPropData = pData;
		m_pScriptMan->SendMessage(pMsg,&mpReply);
		pMsg->Release();
	}
}

void cDH2ScriptService::DoRelationNotify(int iAgent, eDHRegisterFlags eFlags, unsigned int uEvent, const char* pszName, long lLinkId, int iLinkSource, int iLinkDest, IRelation* pRel)
{
	if (eFlags & kDHNotifyAsync)
	{
		m_pScriptMan->PostMessage2(0,iAgent,"DHNotifyAsync",int(kDH_Relation),int(uEvent),int(lLinkId));
	}
	else
	{
		cMultiParm mpReply;
		sDHNotifyMsg* pMsg = new sDHNotifyMsg();
		pMsg->to = iAgent;
		pMsg->message = "DHNotify";
		pMsg->typeDH = kDH_Relation;
		pMsg->sRel.event = eLinkEvent(uEvent);
		pMsg->sRel.pszRelName = pszName;
		pRel->AddRef();
		pMsg->sRel.pRel = pRel;
		pMsg->sRel.lLinkId = lLinkId;
		pMsg->sRel.iLinkSource = iLinkSource;
		pMsg->sRel.iLinkDest = iLinkDest;
		m_pScriptMan->SendMessage(pMsg,&mpReply);
		pMsg->Release();
	}
}

void cDH2ScriptService::DoObjectNotify(int iAgent, eDHRegisterFlags eFlags, unsigned int uEvent, int iObjId)
{
	if (eFlags & kDHNotifyAsync)
	{
		m_pScriptMan->PostMessage2(0,iAgent,"DHNotifyAsync",int(kDH_Object),int(uEvent),iObjId);
	}
	else
	{
		cMultiParm mpReply;
		sDHNotifyMsg* pMsg = new sDHNotifyMsg();
		pMsg->to = iAgent;
		pMsg->message = "DHNotify";
		pMsg->typeDH = kDH_Object;
		pMsg->sObj.event = eObjEvent(uEvent);
		pMsg->sObj.idObj = iObjId;
		m_pScriptMan->SendMessage(pMsg,&mpReply);
		pMsg->Release();
	}
}

void cDH2ScriptService::DoHierarchyNotify(int iAgent, eDHRegisterFlags eFlags, unsigned int uEvent, int iObjId, int iSubjId)
{
	if (eFlags & kDHNotifyAsync)
	{
		char szData[32];
		sprintf(szData, "%d,%d", iObjId, iSubjId);
		m_pScriptMan->PostMessage2(0,iAgent,"DHNotifyAsync",int(kDH_Trait),int(uEvent),szData);
	}
	else
	{
		cMultiParm mpReply;
		sDHNotifyMsg* pMsg = new sDHNotifyMsg();
		pMsg->to = iAgent;
		pMsg->message = "DHNotify";
		pMsg->typeDH = kDH_Trait;
		pMsg->sTrait.event = eTraitEvent(uEvent);
		pMsg->sTrait.idObj = iObjId;
		pMsg->sTrait.idSubj = iSubjId;
		m_pScriptMan->SendMessage(pMsg,&mpReply);
		pMsg->Release();
	}
}

