/** @file
 *	i created my own dispatcher classes to emit signals with parameters to the 
 *	gtk-gui.
 *	see http://www.gtkmm.org/gtkmm2/docs/reference/html/classGlib_1_1Dispatcher.html
 *	for more information on the dispatcher facility
 */
#ifndef _DISPATCHER_H_
#define _DISPATCHER_H_

#include <sigc++/sigc++.h>
#include <glibmm/thread.h>
#include <glibmm/dispatcher.h>


// traits to ensure correct reference handling
template <typename T>
struct disp_traits
{
	typedef T value_type;
	typedef T type;
	typedef T& reference;
};

// partial template instantiation
template <typename T>
struct disp_traits <T&>
{
	typedef T value_type;
	typedef T& type;
	typedef T& reference;
};


class Dispatcher0: public SigC::Object, protected Glib::Dispatcher
{
public:
	typedef SigC::Signal0 <void> InSignalType;
	typedef InSignalType OutSignalType;
	typedef SigC::Slot0 <void> InSlotType;
	typedef InSlotType OutSlotType;

	
private:
	Glib::Mutex m_mutex;
	Glib::Cond m_cond;
	bool m_bSync; // (a)synchronous signal delivery
	
protected:
	OutSignalType m_signalIntern;
	InSignalType m_signalClient;


public:
	Dispatcher0()
		: SigC::Object()
		, Glib::Dispatcher()
		, m_bSync(false)
	{
		Glib::Dispatcher::connect(SigC::slot(*this, &Dispatcher0::on_dispatch));
	}
	

private:
	void on_sender_emit(bool bSync)
	{
		emit(bSync);
	}
	
protected:
	void on_dispatch()
	{
		if (!m_bSync)
			m_signalClient.emit();
		else
		{
			Glib::Mutex::Lock lock(m_mutex);
			m_cond.signal();
		}
	}
	
public:
	SigC::Connection connect(const InSlotType& slot)
	{
		return m_signalClient.connect(slot);
	}
	
	OutSlotType slot(bool bSync = false)
	{
		m_signalIntern.connect(SigC::bind <bool> (SigC::slot(*this, &Dispatcher0::on_sender_emit), bSync));
		return m_signalIntern.slot();
	}
	

	/**	(a)synchronous emission and communication.
	 */
	void emit(bool bSync = false)
	{
		if (bSync)
		{	//synchronous
			Glib::Mutex::Lock lock(m_mutex);
			m_bSync = true;
			Glib::Dispatcher::emit();
			m_cond.wait(m_mutex);
			m_signalClient.emit();
		}
		else
		{	//asynchronous
			m_bSync = false;
			Glib::Dispatcher::emit();
		}
	}
	
	void operator ()(bool bSync = false)
	{
		emit(bSync);
	}
};


template <typename P1>
class Dispatcher1: public SigC::Object, protected Glib::Dispatcher
{
public:
	typedef SigC::Signal1 <void, typename disp_traits <P1>::type> InSignalType;
	typedef SigC::Signal1 <void, typename disp_traits <P1>::type> OutSignalType;
	typedef SigC::Slot1 <void, typename disp_traits <P1>::type> InSlotType;
	typedef SigC::Slot1 <void, typename disp_traits <P1>::type> OutSlotType;

	
private:
	Glib::Mutex m_mutex;
	Glib::Cond m_cond;
	bool m_bSync; // (a)synchronous signal delivery
	
protected:
	OutSignalType m_signalIntern;
	InSignalType m_signalClient;
	typename disp_traits <P1>::value_type m_s1; // copy of the signal-parameters
												// if asynchronous


public:
	Dispatcher1()
		: SigC::Object()
		, Glib::Dispatcher()
		, m_bSync(false)
	{
		Glib::Dispatcher::connect(SigC::slot(*this, &Dispatcher1::on_dispatch));
	}
	

private:
	void on_sender_emit(typename disp_traits <P1>::type p1, bool bSync)
	{
		emit(p1, bSync);
	}
	
protected:
	void on_dispatch()
	{
		if (!m_bSync)
			m_signalClient.emit(m_s1);
		else
		{
			Glib::Mutex::Lock lock(m_mutex);
			m_cond.signal();
		}
	}
	
public:
	SigC::Connection connect(const InSlotType& slot)
	{
		return m_signalClient.connect(slot);
	}
	
	OutSlotType slot(bool bSync = false)
	{
		m_signalIntern.connect(SigC::bind <bool> (SigC::slot(*this, &Dispatcher1::on_sender_emit), bSync));
		return m_signalIntern.slot();
	}
	

	/**	(a)synchronous emission and communication.
	 */
	void emit(typename disp_traits <P1>::reference p1, bool bSync = false)
	{
		if (bSync)
		{	//synchronous
			Glib::Mutex::Lock lock(m_mutex);
			m_bSync = true;
			Glib::Dispatcher::emit();
			m_cond.wait(m_mutex);
			m_signalClient.emit(p1);
		}
		else
		{	//asynchronous
			m_bSync = false;
			m_s1 = p1; // store the data for sending it if the receiver is ready
			Glib::Dispatcher::emit();
		}
	}
	
	void operator ()(typename disp_traits <P1>::reference p1, bool bSync = false)
	{
		emit(p1, bSync);
	}
};


template <typename P1, typename P2>
class Dispatcher2: public SigC::Object, protected Glib::Dispatcher
{
public:
	typedef SigC::Signal2 <void, P1, P2> InSignalType;
	typedef SigC::Signal2 <void, P1, P2> OutSignalType;
	typedef SigC::Slot2 <void, P1, P2> InSlotType;
	typedef SigC::Slot2 <void, P1, P2> OutSlotType;

	
private:
	Glib::Mutex m_mutex;
	Glib::Cond m_cond;
	bool m_bSync; // (a)synchronous signal delivery
	
protected:
	OutSignalType m_signalIntern;
	InSignalType m_signalClient;
	typename disp_traits <P1>::value_type m_s1; // copy of the signal-parameters
	typename disp_traits <P2>::value_type m_s2; // if asynchronous


public:
	Dispatcher2()
		: SigC::Object()
		, Glib::Dispatcher()
		, m_bSync(false)
	{
		Glib::Dispatcher::connect(SigC::slot(*this, &Dispatcher2::on_dispatch));
	}
	

private:
	void on_sender_emit(typename disp_traits <P1>::type p1, typename disp_traits <P2>::type p2, bool bSync)
	{
		emit(p1, p2, bSync);
	}
	
protected:
	void on_dispatch()
	{
		if (!m_bSync)
			m_signalClient.emit(m_s1, m_s2);
		else
		{
			Glib::Mutex::Lock lock(m_mutex);
			m_cond.signal();
		}
	}
	
public:
	SigC::Connection connect(const InSlotType& slot)
	{
		return m_signalClient.connect(slot);
	}
	
	OutSlotType slot(bool bSync = false)
	{
		//return create_slot((SigC::FuncPtr)(&emit));
		m_signalIntern.connect(SigC::bind <bool> (SigC::slot(*this, &Dispatcher2::on_sender_emit), bSync));
		return m_signalIntern.slot();
	}
	

	/**	(a)synchronous emission and communication.
	 */
	void emit(typename disp_traits <P1>::reference p1, typename disp_traits <P2>::reference p2, bool bSync = false)
	{
		if (bSync)
		{	//synchronous
			Glib::Mutex::Lock lock(m_mutex);
			m_bSync = true;
			Glib::Dispatcher::emit();
			m_cond.wait(m_mutex);
			m_signalClient.emit(p1, p2);
		}
		else
		{	//asynchronous
			m_bSync = false;
			m_s1 = p1; // store the data for sending it if the receiver is ready
			m_s2 = p2;
			Glib::Dispatcher::emit();
		}
	}
	
	void operator ()(typename disp_traits <P1>::reference p1, typename disp_traits <P2>::reference p2, bool bSync = false)
	{
		emit(p1, p2, bSync);
	}
};

#endif
