/****************************************************************************
*                                                                           *
*  OpenNI 1.x Alpha                                                         *
*  Copyright (C) 2011 PrimeSense Ltd.                                       *
*                                                                           *
*  This file is part of OpenNI.                                             *
*                                                                           *
*  OpenNI is free software: you can redistribute it and/or modify           *
*  it under the terms of the GNU Lesser General Public License as published *
*  by the Free Software Foundation, either version 3 of the License, or     *
*  (at your option) any later version.                                      *
*                                                                           *
*  OpenNI 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 Lesser General Public License for more details.                      *
*                                                                           *
*  You should have received a copy of the GNU Lesser General Public License *
*  along with OpenNI. If not, see <http://www.gnu.org/licenses/>.           *
*                                                                           *
****************************************************************************/
#ifndef _XN_EVENT_T_H_
#define _XN_EVENT_T_H_ 

//---------------------------------------------------------------------------
// Includes
//---------------------------------------------------------------------------
#include "XnOSCpp.h"
#include "XnListT.h"
#include "XnTypes.h"

//---------------------------------------------------------------------------
// Types
//---------------------------------------------------------------------------

/** 
* A struct for holding a callback function with its cookie
*
* @tparam	FuncPtr		The callback signature
*/
template<typename FuncPtr>
struct XnCallbackT
{
	XnCallbackT(FuncPtr func, void* cookie) : pFunc(func), pCookie(cookie) {}

	FuncPtr pFunc;
	void* pCookie;
};

/** 
* A class that contains the interface of an event (i.e. Register and Unregister)
*
* @tparam	FuncPtr		The callback signature
*/
template<typename FuncPtr>
class XnEventInterfaceT
{
public:
	typedef FuncPtr HandlerPtr;
	typedef XnCallbackT<FuncPtr> TCallback;
	typedef XnEventInterfaceT TInterface;

	~XnEventInterfaceT()
	{
		Clear();
		xnOSCloseCriticalSection(&m_hLock);
	}

	XnStatus Register(FuncPtr pFunc, void* pCookie, XnCallbackHandle& hCallback)
	{
		XnStatus nRetVal = XN_STATUS_OK;

		XN_VALIDATE_INPUT_PTR(pFunc);

		TCallback* pCallback = NULL;
		XN_VALIDATE_NEW(pCallback, TCallback, pFunc, pCookie);

		// always add to list of added (actual list will be updated in Raise method, to allow registering 
		// from a callback).
		{
			XnAutoCSLocker locker(m_hLock);
			nRetVal = m_toAdd.AddLast(pCallback);
		}

		if (nRetVal != XN_STATUS_OK)
		{
			XN_DELETE(pCallback);
			return nRetVal;
		}

		// return handle
		hCallback = (XnCallbackHandle)pCallback;

		return XN_STATUS_OK;
	}

	XnStatus Unregister(XnCallbackHandle hCallback)
	{
		XnStatus nRetVal = XN_STATUS_OK;

		TCallback* pCallback = (TCallback*)hCallback;

		// add it to a temp list, to allow unregistering from a callback (actual list will be updated in raise
		// function).
		{
			XnAutoCSLocker locker(m_hLock);

			// try to remove it from the ToBeAdded list.
			if (!RemoveCallback(m_toAdd, pCallback))
			{
				// it's not in this list, so it's probably in the main list
				nRetVal = m_toRemove.AddLast(pCallback);
			}
		}
		XN_IS_STATUS_OK(nRetVal);

		return XN_STATUS_OK;
	}

protected:
	typedef XnListT<TCallback*> CallbackPtrList;

	// Constructors are protected, so that this class cannot be instantiated directly.
	XnEventInterfaceT()
	{
		Init();
	}

	XnEventInterfaceT(const XnEventInterfaceT& other)
	{
		Init();
		*this = other;
	}

	XnEventInterfaceT& operator=(const XnEventInterfaceT& other)
	{
		Clear();

		// lock other one (so it won't change while being copied)
		XnAutoCSLocker otherLocker(other.m_hLock);
		// lock this one (we're making changes)
		XnAutoCSLocker locker(m_hLock);

		m_callbacks = other.m_callbacks;
		m_toAdd = other.m_toAdd;
		m_toRemove = other.m_toRemove;

		ApplyListChanges();

		return *this;
	}

	XnStatus Clear()
	{
		XnAutoCSLocker locker(m_hLock);
		ApplyListChanges();

		for (typename CallbackPtrList::ConstIterator it = m_callbacks.Begin(); it != m_callbacks.End(); ++it)
		{
			TCallback* pCallback = *it;
			XN_DELETE(pCallback);
		}

		m_callbacks.Clear();
		m_toRemove.Clear();
		m_toAdd.Clear();
		return (XN_STATUS_OK);
	}

	XnStatus ApplyListChanges()
	{
		XnAutoCSLocker locker(m_hLock);

		// first add all
		for (typename CallbackPtrList::ConstIterator it = m_toAdd.Begin(); it != m_toAdd.End(); ++it)
		{
			m_callbacks.AddLast(*it);
		}
		m_toAdd.Clear();

		// and now remove
		for (typename CallbackPtrList::ConstIterator it = m_toRemove.Begin(); it != m_toRemove.End(); ++it)
		{
			TCallback* pCallback = *it;
			RemoveCallback(m_callbacks, pCallback);
		}
		m_toRemove.Clear();

		return (XN_STATUS_OK);
	}

	XnBool RemoveCallback(CallbackPtrList& list, TCallback* pCallback)
	{
		typename CallbackPtrList::Iterator it = list.Find(pCallback);
		if (it != list.End())
		{
			list.Remove(it);
			XN_DELETE(pCallback);
			return TRUE;
		}

		return FALSE;
	}

	XN_CRITICAL_SECTION_HANDLE m_hLock;
	CallbackPtrList m_callbacks;
	CallbackPtrList m_toAdd;
	CallbackPtrList m_toRemove;

private:
	void Init()
	{
		m_hLock = NULL;
		XnStatus nRetVal = xnOSCreateCriticalSection(&m_hLock);
		if (nRetVal != XN_STATUS_OK)
		{
			XN_ASSERT(FALSE);
		}
	}
};

// Handlers
struct XnHandlerFuncNoArgs
{
	typedef void (XN_CALLBACK_TYPE* FuncPtr)(void* pCookie);
};

template<class TArg1>
struct XnHandlerFunc1Arg
{
	typedef void (XN_CALLBACK_TYPE* FuncPtr)(TArg1 arg1, void* pCookie);
};

template<class TArg1, class TArg2>
struct XnHandlerFunc2Args
{
	typedef void (XN_CALLBACK_TYPE* FuncPtr)(TArg1 arg1, TArg2 arg2, void* pCookie);
};

template<class TArg1, class TArg2, class TArg3>
struct XnHandlerFunc3Args
{
	typedef void (XN_CALLBACK_TYPE* FuncPtr)(TArg1 arg1, TArg2 arg2, TArg3 arg3, void* pCookie);
};

template<class TArg1, class TArg2, class TArg3, class TArg4>
struct XnHandlerFunc4Args
{
	typedef void (XN_CALLBACK_TYPE* FuncPtr)(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, void* pCookie);
};

template<class TArg1, class TArg2, class TArg3, class TArg4, class TArg5>
struct XnHandlerFunc5Args
{
	typedef void (XN_CALLBACK_TYPE* FuncPtr)(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, void* pCookie);
};

// Event classes (there's a class per number of arguments)
class XnEventNoArgs : public XnEventInterfaceT<XnHandlerFuncNoArgs::FuncPtr>
{
public:
	XnStatus Raise()
	{
		XnAutoCSLocker locker(this->m_hLock);
		ApplyListChanges();

		for (CallbackPtrList::ConstIterator it = m_callbacks.Begin(); it != m_callbacks.End(); ++it)
		{
			TCallback* pCallback = *it;
			pCallback->pFunc(pCallback->pCookie);
		}

		ApplyListChanges();
		return (XN_STATUS_OK);
	}
};


template<class TArg1>
class XnEvent1Arg : public XnEventInterfaceT<typename XnHandlerFunc1Arg<TArg1>::FuncPtr>
{
	typedef XnEventInterfaceT<typename XnHandlerFunc1Arg<TArg1>::FuncPtr> Base;

public:
	XnStatus Raise(TArg1 arg)
	{
		XnAutoCSLocker locker(this->m_hLock);
		this->ApplyListChanges();

		for (typename Base::CallbackPtrList::ConstIterator it = this->m_callbacks.Begin(); it != this->m_callbacks.End(); ++it)
		{
			typename Base::TCallback* pCallback = *it;
			pCallback->pFunc(arg, pCallback->pCookie);
		}

		this->ApplyListChanges();
		return (XN_STATUS_OK);
	}
};

template<class TEventArgs>
class XnEventT : public XnEvent1Arg<const TEventArgs&>
{};

template<class TArg1, class TArg2>
class XnEvent2Args : public XnEventInterfaceT<typename XnHandlerFunc2Args<TArg1, TArg2>::FuncPtr>
{
	typedef XnEventInterfaceT<typename XnHandlerFunc2Args<TArg1, TArg2>::FuncPtr> Base;

public:
	XnStatus Raise(TArg1 arg1, TArg2 arg2)
	{
		XnAutoCSLocker locker(this->m_hLock);
		this->ApplyListChanges();

		for (typename Base::CallbackPtrList::ConstIterator it = this->m_callbacks.Begin(); it != this->m_callbacks.End(); ++it)
		{
			typename Base::TCallback* pCallback = *it;
			pCallback->pFunc(arg1, arg2, pCallback->pCookie);
		}

		this->ApplyListChanges();
		return (XN_STATUS_OK);
	}
};

template<class TArg1, class TArg2, class TArg3>
class XnEvent3Args : public XnEventInterfaceT<typename XnHandlerFunc3Args<TArg1, TArg2, TArg3>::FuncPtr>
{
	typedef XnEventInterfaceT<typename XnHandlerFunc3Args<TArg1, TArg2, TArg3>::FuncPtr> Base;
	
public:
	XnStatus Raise(TArg1 arg1, TArg2 arg2, TArg3 arg3)
	{
		XnAutoCSLocker locker(this->m_hLock);
		this->ApplyListChanges();

		for (typename Base::CallbackPtrList::ConstIterator it = this->m_callbacks.Begin(); it != this->m_callbacks.End(); ++it)
		{
			typename Base::TCallback* pCallback = *it;
			pCallback->pFunc(arg1, arg2, arg3, pCallback->pCookie);
		}

		this->ApplyListChanges();
		return (XN_STATUS_OK);
	}
};

template<class TArg1, class TArg2, class TArg3, class TArg4>
class XnEvent4Args : public XnEventInterfaceT<typename XnHandlerFunc4Args<TArg1, TArg2, TArg3, TArg4>::FuncPtr>
{
	typedef XnEventInterfaceT<typename XnHandlerFunc4Args<TArg1, TArg2, TArg3, TArg4>::FuncPtr> Base;
	
public:
	XnStatus Raise(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4)
	{
		XnAutoCSLocker locker(this->m_hLock);
		this->ApplyListChanges();

		for (typename Base::CallbackPtrList::ConstIterator it = this->m_callbacks.Begin(); it != this->m_callbacks.End(); ++it)
		{
			typename Base::TCallback* pCallback = *it;
			pCallback->pFunc(arg1, arg2, arg3, arg4, pCallback->pCookie);
		}

		this->ApplyListChanges();
		return (XN_STATUS_OK);
	}
};

template<class TArg1, class TArg2, class TArg3, class TArg4, class TArg5>
class XnEvent5Args : public XnEventInterfaceT<typename XnHandlerFunc5Args<TArg1, TArg2, TArg3, TArg4, TArg5>::FuncPtr>
{
	typedef XnEventInterfaceT<typename XnHandlerFunc5Args<TArg1, TArg2, TArg3, TArg4, TArg5>::FuncPtr> Base;
	
public:
	XnStatus Raise(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5)
	{
		XnAutoCSLocker locker(this->m_hLock);
		this->ApplyListChanges();

		for (typename Base::CallbackPtrList::ConstIterator it = this->m_callbacks.Begin(); it != this->m_callbacks.End(); ++it)
		{
			typename Base::TCallback* pCallback = *it;
			pCallback->pFunc(arg1, arg2, arg3, arg4, arg5, pCallback->pCookie);
		}

		this->ApplyListChanges();
		return (XN_STATUS_OK);
	}
};

#endif // _XN_EVENT_T_H_