//------------------------------------------------------------------------------
// <copyright file="EventTransceiver.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>
// 	Declarations for EventTransceiver class, which is used to send and receive
//  events from EventManager.
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#ifndef __EVENTTRANSCEIVER_H__
#define __EVENTTRANSCEIVER_H__

#include "LqrSMACIdl.h"
#include "EventBase.h"
#include "LqrSMACSharedLib.h"
#include <map>
#include <ostream>
#include <set>
#include <stdexcept>
#include <string>
#include <utility>
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
#include "IEventBase.h"
#include "LqrSMACIObjectBroker.h"
#include "LqrSMACDeclareException.h"
#include "LqrSMACLockableConcept.h"
#include "LqrSMACLogger.h"
#include "EventTransceiver_Settings.h"

namespace LiquidRobotics {
namespace EventLib {

class SHARED_LIB_PUBLIC EventTransceiver
{
public:

	typedef LiquidRobotics::SMACFramework::IObjectBroker IObjectBroker;
	typedef LiquidRobotics::SMACFramework::Logger Logger;
	typedef ::CORBA::Object_ptr Object_ptr;
	typedef boost::shared_ptr<EventTransceiver> Ptr;
	typedef std::string string;
	typedef EventTransceiverSettings Settings;

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

	typedef void (*InformSignalSignature)(EventTransceiver& sender, const IEventBase& receivedEvent);
	//typedef boost::signals2::signal<InformSignalSignature> InformSignalType;
	typedef boost::signals2::signal<void (EventTransceiver& sender, const IEventBase& receivedEvent)> InformSignalType;

	DECLARE_EXCEPTION(AlreadyInitialized,std::logic_error);
	DECLARE_EXCEPTION(AlreadyShutDown,std::logic_error);
	DECLARE_EXCEPTION(NotEnabledForReceive,std::logic_error);
	DECLARE_EXCEPTION(NotInitialized,std::logic_error);

	class SHARED_LIB_PUBLIC EventClassFactory
	{
	public:

		typedef boost::shared_ptr<EventClassFactory> Ptr;

		virtual ~EventClassFactory() { }
		virtual IEventBase::Ptr CreateCustomEvent(const IEventBase::CORBA_StructuredEvent& hint, IObjectBroker& broker) = 0; // returns the base class
	};

	template<class EventClass>
	class TEventClassFactory : public EventClassFactory
	{
	public:
		virtual IEventBase::Ptr CreateCustomEvent(const IEventBase::CORBA_StructuredEvent& hint, IObjectBroker& broker)
		{
			IEventBase::Ptr result;
			result.reset(new EventClass(broker));
			return result;
		}
	};

	EventTransceiver(const Settings& _settings, IObjectBroker& _broker, Logger& _logger);
	virtual ~EventTransceiver();

	virtual void Initialize();
	virtual void Initialize(Object_ptr _pServer);
	bool Initialized() const { return initialized; }
	virtual void Shutdown();

	// Return the class factory for a received event type
	virtual EventClassFactory::Ptr GetClassFactory(const EventType& eventType);

	// Register a class factory for an EventType
	virtual void RegisterEventClassFactory(const EventType& eventType, EventClassFactory::Ptr& eventClassFactory, bool throwOnDuplicateRegistration = true);
	virtual void RegisterEventClassFactory(const EventType& eventType, EventClassFactory* eventClassFactory, bool throwOnDuplicateRegistration = true);

	// returns the signal for a specific event name or type
	virtual InformSignalType& SignalForEventName(const string& aliasName);
	virtual InformSignalType& SignalForEventType(const EventType& eventType);

	// transmits events to the event manager
	virtual void TransmitEvent(const ::CosNotification::StructuredEvent& notification);
	virtual void TransmitEvent(const IEventBase& eventBase);

	// StructuredPushConsumer operations
    virtual void offer_change(const ::CosNotification::EventTypeSeq& added, const ::CosNotification::EventTypeSeq& removed);
    virtual void push_structured_event(const ::CosNotification::StructuredEvent& notification);
    virtual void disconnect_structured_push_consumer();

    // This signal is fired whenever a message is received (unless the filter is active and the message is not included);
    // the event name will have already been modified to match the alias
    virtual ScopedConnectionPtr SubscribeAllEvents(InformSignalType::slot_type slot);

    // returns a reference to an event of type T upcast from receivedEvent if it is the correct event type
    // and domain matching evtSettings
    template<typename T> static
    const T& UpcastEventRef(const IEventBase& receivedEvent, const EventSettings& evtSettings, const string& caller)
    {
    	return UpcastEventRef<T>(receivedEvent, evtSettings.GetType(), caller);
    }

    // returns a reference to an event of type T upcast from receivedEvent if it is the correct event type
    // and domain matching evtType
    template<typename T> static
    const T& UpcastEventRef(const IEventBase& receivedEvent, const EventType& evtType, const string& caller)
    {
    	using namespace std;

    	if (receivedEvent.GetEventType() != evtType)
		{
    		std::stringstream stm;
    		if (caller.length())
    			stm << caller << ": r";
    		else
    			stm << "R";

    		stm << "eceived event name=" << receivedEvent.GetEventName() << " with type=" << receivedEvent.GetEventType()
    		    << ", but a type of " << evtType << " was expected";

    		string msg = stm.str();
    		throw runtime_error(msg);
		}

    	try
    	{
    		const T& sample = dynamic_cast<const T&>(receivedEvent);
    		return sample;
    	}
    	catch(bad_cast& bcex)
    	{
    		std::stringstream stm;

    		if (caller.length())
    			stm << caller << ": r";
    		else
    			stm << "R";

    		stm << "eceived event name=" << receivedEvent.GetEventName() << " type=" << receivedEvent.GetEventType()
    		    << " expecting it to be a " << typeid(T).name() << ", but it was actually a " << typeid(receivedEvent).name()
    		    << "; event will be ignored";

    		string msg = stm.str();
    		throw runtime_error(msg);
    	}
    	catch(exception& ex)
    	{
    		std::stringstream stm;

    		if (caller.length())
    			stm << caller << ": c";
    		else
    			stm << "C";

    		stm << "aught " << typeid(ex).name() << " '" << ex.what() << "' while upcasting received event to " << typeid(T).name();

    		string msg = stm.str();
    		throw runtime_error(msg);
    	}

    	// should not get here
   }

private:

	typedef ::LiquidRobotics::SMAC::IEventManager EventMgr_type;
	typedef ::LiquidRobotics::SMAC::IEventManager_var EventMgr_var_type;
	typedef ::CosNotifyComm::StructuredPushConsumer EventSink_type;
	typedef ::CosNotifyComm::StructuredPushConsumer_var EventSink_var_type;
	typedef boost::shared_ptr<InformSignalType> InformSignalPtr;
	typedef std::map<string,InformSignalPtr> AliasSignalMap;
	typedef std::map<EventType,InformSignalPtr> EventTypeSignalMap;
	typedef std::map<EventType,EventClassFactory::Ptr> EventClassFactoryMap;
	typedef LiquidRobotics::SMACFramework::LockableConceptMutex LockableConceptMutex;

	class LocalEventClassFactory : public EventClassFactory
	{
	public:

		IEventBase::Ptr CreateCustomEvent(const IEventBase::CORBA_StructuredEvent& hint, IObjectBroker& broker)
		{
			IEventBase::Ptr retval;
			retval.reset(new EventBase(broker));
			return retval;
		}
	};

    void CheckInitialized(bool init = true) const;
    void RouteEvent(const IEventBase& receivedEvent);
    void ShutdownCallback(IObjectBroker& sender);
    void TryCatchCallback(InformSignalType& sig, const IEventBase& receivedEvent, const string& caller, const string& action) const;

    const Settings& settings;
    IObjectBroker& broker;
    Logger& logger;
    LockableConceptMutex localMutex;
    bool initialized;
    bool shutDown;
    bool noServer;
    EventMgr_var_type eventMgr;
    EventSink_var_type eventSink;
    Object_ptr pServer;
    string serverRef;
    AliasSignalMap aliasSignalMap;
    InformSignalType informSignal;
    EventTypeSignalMap eventTypeSignalMap;
    EventClassFactoryMap eventClassFactoryMap;
    EventClassFactory::Ptr localFactory;
    ScopedConnectionPtr connectionToObjectBrokerShutdown;
};

}
}

#endif
