//------------------------------------------------------------------------------------------------------------
// <copyright file="LqrSMACThreadSafeEvent.h" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// 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.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
//  Declaration of ThreadSafeEvent class and related macros.  This is intended to
//	improve upon the existing mechanism by:
//
//		- bundling thread-safety (i.e. a mutex) into to event declaration so that
//		  classes that advertise events don't have to separately define a mutex
//		  that serializing firing events and clients don't have to explicitly contact
//		  the advertising class and lock a mutex when unsubscribing
//
//		- making it easier to subscribe to event callbacks in the default format
//		  (the caller doesn't have to explicitly invoke bind, but this use case is
//		  still reported)
//
//	Example server:
//
//		class MyServer {
//		public:
//			DECLARE_TS_EVENT_2(ExampleEvent,void (MyServer& sender, int someParameter))
//			MyClass()
//			: CONSTRUCT_TS_EVENT(ExampleEvent)
//			{ }
//			void SomeMethod()
//			{ FIRE_TS_EVENT(ExampleEvent)(*this, 17); }
//		};
//
// Example client:
//
//		class MyClient {
//		public:
//			void Initialize() { subscription = server.ExampleEvent.Subscribe(&MyClient::ExampleEventCallback,this); }
//			void Shutdown() { subscription.Unsubscribe(); }  // also unsubscribes when subscription goes out of scope
//		private:
//			void ExampleEventCallback(MyServer& sender, int someParameter) { /* handle event */ }
//			MyServer::ExampleEvent::Subscription subscription;
//		};
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------------------------------------

#ifndef __LqrSMACThreadSafeEvent_h__
#define __LqrSMACThreadSafeEvent_h__

#include "LqrSMACLockableConcept.h"
#include "LqrSMACSharedLib.h"
#include <boost/signals2.hpp>

#define TS_EVENT_NAMESPACE 						LiquidRobotics::SMACFramework
#define CONSTRUCT_TS_EVENT(eventName,sigp)		eventName##(eventName##_private)
#define DECLARE_TS_EVENT_0(eventName,sigp)		private: TS_EVENT_NAMESPACE##::ThreadSafeEventOwner eventName##_private; public: TS_EVENT_NAMESPACE##::ThreadSafeEventClientAccessible eventName##(eventName##_private);
#define DECLARE_TS_EVENT_1(eventName,sigp)
#define DECLARE_TS_EVENT_2(eventName,sigp)
#define DECLARE_TS_EVENT_3(eventName,sigp)
#define DECLARE_TS_EVENT_4(eventName,sigp)
#define DECLARE_TS_EVENT_5(eventName,sigp)
#define DECLARE_TS_EVENT_6(eventName,sigp)
#define DECLARE_TS_EVENT_7(eventName,sigp)
#define DECLARE_TS_EVENT_8(eventName,sigp)
#define DECLARE_TS_EVENT_9(eventName,sigp)
#define FIRE_TS_EVENT(eventName)				(eventName#_private.signal)

namespace LiquidRobotics {
namespace SMACFramework {

template<typename SignalParamT>
class SHARED_LIB_PUBLIC ThreadSafeEventOwner
{
public:

	typedef SignalParamT SignalParamType;
	typedef boost::signals2::signal<SignalParamType> SignalType;

	ThreadSafeEventOwner<SignalParamT>() {}

	inline void Fire()
	{
		if (firingMutexPtr)
		{
			ILockableConcept::Lock lock(*firingMutexPtr);
			signal();
		}
	}

	template<typename ReturnT>
	void Fire(ReturnT& result)
	{
		if (firingMutexPtr)
		{
			ILockableConcept::Lock lock(*firingMutexPtr);
			result = signal();
		}
	}

	SignalType signal;
	LockableConceptMutex::Ptr firingMutexPtr;
};

class SHARED_LIB_PUBLIC ThreadSafeEventClientSubscription
{
public:

	typedef boost::signals2::scoped_connection ScopedConnection;
	typedef boost::shared_ptr<ScopedConnection> ScopedConnectionPtr;

	ThreadSafeEventClientSubscription(ScopedConnectionPtr _connPtr, LockableConceptMutex::WeakPtr _firingMutexWeakPtr)
	: connPtr(_connPtr)
	, firingMutexWeakPtr(_firingMutexWeakPtr)
	{
	}

	~ThreadSafeEventClientSubscription()
	{
		Unsubscribe();
	}

	bool IsSubscribed() const
	{
		return !!connPtr;
	}

	void Unsubscribe()
	{
		LockableConceptMutex::Ptr firingMutex = firingMutexWeakPtr.lock();
		if (firingMutex)
		{
			ILockableConcept::Lock lock(*firingMutex);
			connPtr.reset();
		}
		else
			connPtr.reset();
	}

	operator bool() const
	{
		return IsSubscribed();
	}

	void reset()
	{
		Unsubscribe();
	}

private:

	ScopedConnectionPtr connPtr;
	LockableConceptMutex::WeakPtr firingMutexWeakPtr;
};

template<typename SignalParamT>
class SHARED_LIB_PUBLIC ThreadSafeEventClientAccessible
{
public:

	typedef ThreadSafeEventOwner<SignalParamT> OwnerType;

	ThreadSafeEventClientAccessible<SignalParamT>(OwnerType& _owner)
	: owner(_owner)
	{
	}

	template<typename ClientT, typename ReturnT>
	ThreadSafeEventClientSubscription Subscribe(ClientT* pClient, ReturnT (ClientT::*memberPtr)())
	{
		ThreadSafeEventClientSubscription::ScopedConnectionPtr connPtr;
		connPtr.reset(new ThreadSafeEventClientSubscription::ScopedConnection(owner.signal.connect(boost::bind(memberPtr, pClient))));
		return ThreadSafeEventClientSubscription(connPtr, LockableConceptMutex::WeakPtr(owner.firingMutexPtr));
	}

	template<typename ClientT, typename ReturnT, typename P1T>
	ThreadSafeEventClientSubscription Subscribe(ClientT* pClient, ReturnT (ClientT::*memberPtr)(P1T p1))
	{
		ThreadSafeEventClientSubscription::ScopedConnectionPtr connPtr;
		connPtr.reset(new ThreadSafeEventClientSubscription::ScopedConnection(owner.signal.connect(boost::bind(memberPtr, pClient, _1))));
		return ThreadSafeEventClientSubscription(connPtr, LockableConceptMutex::WeakPtr(owner.firingMutexPtr));
	}

	template<typename ClientT, typename ReturnT, typename P1T, typename P2T>
	ThreadSafeEventClientSubscription Subscribe(ClientT* pClient, ReturnT (ClientT::*memberPtr)(P1T p1, P2T p2))
	{
		ThreadSafeEventClientSubscription::ScopedConnectionPtr connPtr;
		connPtr.reset(new ThreadSafeEventClientSubscription::ScopedConnection(owner.signal.connect(boost::bind(memberPtr, pClient, _1, _2))));
		return ThreadSafeEventClientSubscription(connPtr, LockableConceptMutex::WeakPtr(owner.firingMutexPtr));
	}

private:

	OwnerType& owner;
};

}
}
#endif
