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

#include "EventTransceiver.h"
#include "EventTransceiver_Settings.h"
#include "LqrSMACIObjectBroker-inline.h"
#include "LqrSMACObjectBroker-inline.h"
#include <stdexcept>
#include "nsextend.h"

using namespace std;
using namespace LiquidRobotics::SMACFramework;

namespace LiquidRobotics {
namespace EventLib {

IMPLEMENT_INNER_EXCEPTION(EventTransceiver,AlreadyInitialized,std::logic_error);
IMPLEMENT_INNER_EXCEPTION(EventTransceiver,AlreadyShutDown,std::logic_error);
IMPLEMENT_INNER_EXCEPTION(EventTransceiver,NotEnabledForReceive,std::logic_error);
IMPLEMENT_INNER_EXCEPTION(EventTransceiver,NotInitialized,std::logic_error);

static const string AlreadyInitialized_text = "This instance has already been initialized";
static const string AlreadyShutDown_text = "This instance has been shut down";
static const string NotEnabledForReceive_text = "This instance is not enabled for receive";
static const string NotInitialized_text = "This instance has not been initialized";

EventTransceiver::EventTransceiver(const Settings& _settings, IObjectBroker& _broker, Logger& _logger)
: settings(_settings)
, broker(_broker)
, logger(_logger)
, initialized(false)
, shutDown(false)
, noServer(false)
{
	localFactory.reset(new LocalEventClassFactory);
}

EventTransceiver::~EventTransceiver()
{
}

void EventTransceiver::Initialize()
{
	{
		Logger::Lock logLock(logger);
		logger.Info << "Initializing EventTransceiver with no server" << endl;
	}

	ILockableConceptLock mlock(localMutex);
	CheckInitialized(false);
	noServer = true;

	try
	{
		string serviceName = settings.GetEventServiceName();
		string serviceKind = settings.GetEventServiceKind();

		eventMgr = broker.GetNamedInstance< LiquidRobotics::SMAC::IEventManager_var, LiquidRobotics::SMAC::IEventManager>(serviceName.c_str(), serviceKind.c_str());
		eventSink = broker.GetNamedInstance<EventSink_var_type,EventSink_type>(serviceName.c_str(), serviceKind.c_str());
	}
	catch(CORBA::Exception& ex)
	{
		{
			Logger::Lock lock(logger);
			logger.Error << "EventTransceiver::Initialize caught " << DecodeCORBAException(ex) << endl;
		}

		eventSink = NULL;
		eventMgr = NULL;
		throw;
	}
	catch(exception& ex)
	{
		{
			Logger::Lock lock(logger);
			logger.Error << "EventTransceiver::Initialize caught " << typeid(ex).name() << ": " << ex.what() << endl;
		}

		eventSink = NULL;
		eventMgr = NULL;
		throw;
	}

	initialized = true;
}

void EventTransceiver::Initialize(CORBA::Object_ptr _pServer)
{
	{
		Logger::Lock logLock(logger);
		logger.Info << "Initializing EventTransceiver" << endl;
	}
	CheckInitialized(false);
	pServer = _pServer;
	serverRef = broker.GetStringizedReference(pServer);

	string serviceName = settings.GetEventServiceName();
	string serviceKind = settings.GetEventServiceKind();

	//eventMgr = broker.GetNamedInstance<EventMgr_var_type,EventMgr_type>(serviceName.c_str(), serviceKind.c_str());
	//typedef ::LiquidRobotics::SMAC::IEventManager EventMgr_type;
	//typedef ::LiquidRobotics::SMAC::IEventManager_var EventMgr_var_type;

	try
	{
		eventMgr = broker.GetNamedInstance< LiquidRobotics::SMAC::IEventManager_var, LiquidRobotics::SMAC::IEventManager>(serviceName.c_str(), serviceKind.c_str());
		eventSink = broker.GetNamedInstance<EventSink_var_type,EventSink_type>(serviceName.c_str(), serviceKind.c_str());

		if (settings.GetReceiveEnable())
		{
			stringstream ss;
			string srvName;
			string srvType;
			const char* ior = serverRef.c_str();

			if (broker.GetAdvertizedInstanceNameFromStringizedName(ior, srvName, srvType))
			{
				ss << srvName;
				if (srvType.length()) ss << "/" << srvType;
			}
			else
			{
				ss << "unknown";
			}

			ss << "-" << getpid();

			string nickName = ss.str();

			eventMgr->AddSubscriber2(serverRef.c_str(), nickName.c_str());

			if (settings.EventFilterEnabled())
			{
				typedef LiquidRobotics::SMAC::EventManagerCustomFilter EventManagerCustomFilter;
				EventManagerCustomFilter cf;

				settings.GetCustomFilter(cf);
				eventMgr->SetCustomFilter(serverRef.c_str(), cf);
			}
		}
	}
	catch(CORBA::Exception& ex)
	{
		{
			Logger::Lock lock(logger);
			logger.Error << "EventTransceiver::Initialize caught " << DecodeCORBAException(ex) << endl;
		}

		eventSink = NULL;
		eventMgr = NULL;
		throw;
	}
	catch(exception& ex)
	{
		{
			Logger::Lock lock(logger);
			logger.Error << "EventTransceiver::Initialize caught " << typeid(ex).name() << ": " << ex.what() << endl;
		}

		eventSink = NULL;
		eventMgr = NULL;
		throw;
	}

	if (!connectionToObjectBrokerShutdown)
		connectionToObjectBrokerShutdown.reset(
				new ScopedConnection(
						broker.ShutdownSignal.connect(
								boost::bind(&EventTransceiver::ShutdownCallback, this, _1)
								)));

	initialized = true;
}

void EventTransceiver::ShutdownCallback(IObjectBroker& sender)
{
	connectionToObjectBrokerShutdown.reset();

	// not a good idea to throw during shutdown operations
	try
	{
		Shutdown();
	}
	catch(...)
	{
	}

}

void EventTransceiver::Shutdown()
{
	CheckInitialized();
	shutDown = true;

	if (settings.GetReceiveEnable())
	{
		try { eventMgr->DeleteSubscriber(serverRef.c_str()); }
		catch(...) { }
	}

    eventMgr = NULL;
    eventSink = NULL;
    pServer = NULL;
}

EventTransceiver::InformSignalType& EventTransceiver::SignalForEventName(const string& aliasName)
{
	ILockableConceptLock mlock(localMutex);
	AliasSignalMap::iterator it = aliasSignalMap.find(aliasName);

	if (it == aliasSignalMap.end())
	{
		InformSignalPtr newPtr;
		newPtr.reset(new InformSignalType);
		aliasSignalMap[aliasName] = newPtr;
		return *newPtr;
	}
	else
		return *it->second;
}

EventTransceiver::InformSignalType& EventTransceiver::SignalForEventType(const EventType& eventType)
{
	ILockableConceptLock mlock(localMutex);
	EventTypeSignalMap::iterator it = eventTypeSignalMap.find(eventType);

	if (it == eventTypeSignalMap.end())
	{
		InformSignalPtr newPtr;
		newPtr.reset(new InformSignalType);
		eventTypeSignalMap[eventType] = newPtr;
		return *newPtr;
	}
	else
		return *it->second;
}

void EventTransceiver::TransmitEvent(const ::CosNotification::StructuredEvent& notification)
{
	CheckInitialized();
	eventSink->push_structured_event(notification);
}

void EventTransceiver::TransmitEvent(const IEventBase& eventBase)
{
	CheckInitialized();
	IEventBase::CORBA_StructuredEvent_var se = eventBase.CreateStructuredEvent();
	eventSink->push_structured_event(se.in());
}

void EventTransceiver::CheckInitialized(bool init) const
{
	if (shutDown)
		throw AlreadyShutDown(AlreadyShutDown_text);

	else if (init && !initialized)
		throw NotInitialized(NotInitialized_text);

	else if (!init && initialized)
		throw AlreadyInitialized(AlreadyInitialized_text);
}

void EventTransceiver::offer_change(const ::CosNotification::EventTypeSeq& added, const ::CosNotification::EventTypeSeq& removed)
{
	// does nothing
}

void EventTransceiver::push_structured_event(const ::CosNotification::StructuredEvent& notification)
{
	if (settings.GetReceiveEnable())
	{
		IEventBase::Ptr receivedEvent;
		const EventType eventType(notification.header.fixed_header.event_type);

		try
		{
			receivedEvent = GetClassFactory(eventType)->CreateCustomEvent(notification, broker);
			receivedEvent->InitFromStructuredEvent(notification);

			bool filtered = false;

			if (settings.EventFilterEnabled())
			{
				if (!settings.EventNameEnabled(receivedEvent->GetEventName())
				 && !settings.EventTypeEnabled(receivedEvent->GetEventType()))

					filtered = true;
			}

			const Settings::StringSet& dndList = settings.DoNotDisplayList();
			ILockableConceptLock mlock(localMutex);
			const bool display = settings.DisplayReceivedEvents() && (dndList.find(receivedEvent->GetEventName()) == dndList.end());
			const bool full = display && settings.FullyDescribeReceivedEvents();

			if (display)
			{
				Logger::Lock lock(logger);
				Logger::LogStream& stm = filtered ? logger.Debug : logger.Info;

				stm << "Received " << typeid(*receivedEvent).name() << " ";

				if (full)
				{
					stm << *receivedEvent
						<< " Raw: ";

					receivedEvent->DescribeGeneric(stm);
				}
				else
					stm << receivedEvent->GetEventName() << " " << receivedEvent->GetEventType();

				if (filtered)
					stm << " (filtered)";

				stm << endl;
			}

			if (!filtered)
			{
				if (settings.HaveAliasForEvent(receivedEvent->GetEventName()))
				{
					string oldName = receivedEvent->GetEventName();
					string newName = settings.AliasForEvent(oldName);

					receivedEvent->SetEventName(newName);

					if (display)
					{
						Logger::Lock lock(logger);
						logger.Info << "  event name '" << oldName << "' changed to alias '" << newName << "'" << endl;
					}
				}

				mlock.unlock();
				RouteEvent(*receivedEvent);
			}
		}
		catch(exception& ex)
		{
			Logger::Lock lock(logger);

			logger.Error << "Caught exception type "
						 << typeid(ex).name()
						 << " '" << ex.what() << "'"
						 << " while processing received event (name="
						 << notification.header.fixed_header.event_name
						 << ";type="
						 << notification.header.fixed_header.event_type.type_name
						 << "/"
						 << notification.header.fixed_header.event_type.domain_name
						 << ")"
						 << endl;
		}
		catch(::CORBA::Exception& corba_ex)
		{
			Logger::Lock lock(logger);

			logger.Error << "Caught CORBA exception "
						 << DecodeCORBAException(corba_ex)
						 << " while processing received event (name="
						 << notification.header.fixed_header.event_name
						 << ";type="
						 << notification.header.fixed_header.event_type.type_name
						 << "/"
						 << notification.header.fixed_header.event_type.domain_name
						 << ")"
						 << endl;
		}
		catch(...)
		{
			Logger::Lock lock(logger);

			logger.Error << "Caught generic exception while processing received event (name="
						 << notification.header.fixed_header.event_name
						 << ";type="
						 << notification.header.fixed_header.event_type.type_name
						 << "/"
						 << notification.header.fixed_header.event_type.domain_name
						 << ")"
						 << endl;
		}
	}
	else if (settings.DisplayReceivedEvents())
	{
		Logger::Lock lock(logger);
		logger.Error << "EventTransceiver::push_structured_event received event with receiver disabled!" << endl;
	}
}

void EventTransceiver::RouteEvent(const IEventBase& receivedEvent)
{
	ILockableConceptLock mlock(localMutex);
	const Settings::StringSet& dndList = settings.DoNotDisplayList();
	const bool display = settings.DisplayReceivedEvents() && (dndList.find(receivedEvent.GetEventName()) != dndList.end());
	stringstream msg;

	if (display)
	{
		msg << typeid(receivedEvent).name() << " " << receivedEvent.GetEventName() << " " << receivedEvent.GetEventType();

		{
			Logger::Lock lock(logger);
			logger.Info << "RouteEvent " << msg.str() << endl;
		}
	}

	mlock.unlock();
	TryCatchCallback(informSignal, receivedEvent, "RouteEvent", "processing inform signal");
	mlock.lock();
	AliasSignalMap::const_iterator foundAlias = aliasSignalMap.find(receivedEvent.GetEventName());

	if (foundAlias != aliasSignalMap.end())
	{
		mlock.unlock();
		TryCatchCallback(*foundAlias->second, receivedEvent, "EventTransceiver::RouteEvent", "processing signal alias");
	}
	else
	{
		EventTypeSignalMap::const_iterator foundEventType = eventTypeSignalMap.find(receivedEvent.GetEventType());

		if (foundEventType != eventTypeSignalMap.end())
		{
			mlock.unlock();
			TryCatchCallback(*foundEventType->second, receivedEvent, "EventTransceiver::RouteEvent", "processing signal event type");
		}
		else if (display)
			Logger::Locked(logger)->Info << "EventTransceiver::RouteEvent: no handler found for " << msg.str()  << endl;
	}
}

void EventTransceiver::TryCatchCallback(InformSignalType& sig, const IEventBase& receivedEvent, const string& caller, const string& action) const
{
	try
	{
		sig(const_cast<EventTransceiver&>(*this), receivedEvent);
	}
	catch(CORBA::Exception& ex)
	{
		Logger::Locked(logger)->Error << caller << " caught exception " << DecodeCORBAException(ex) << " while " << action  << endl;
	}
	catch(exception& ex)
	{
		Logger::Locked(logger)->Error << caller << " caught exception " << typeid(ex).name() << " '" << ex.what() << "' while " << action << endl;
	}
	catch(...)
	{
		Logger::Locked(logger)->Error << caller << " caught generic exception while " << action<< endl;
	}
}

void EventTransceiver::disconnect_structured_push_consumer()
{
	// does nothing (could reconnect)
	Logger::Lock lock(logger);
	logger.Error << "EventTransceiver::disconnect_structured_push_consumer was called" << endl;
}

void EventTransceiver::RegisterEventClassFactory(const EventType& eventType, EventClassFactory::Ptr& eventClassFactory, bool throwOnDuplicateRegistration)
{
	ILockableConceptLock mlock(localMutex);
	EventClassFactoryMap::iterator foundFactory = eventClassFactoryMap.find(eventType);

	if (foundFactory != eventClassFactoryMap.end())
	{
		if (throwOnDuplicateRegistration)
		{
			stringstream stm;
			stm << "An event class factory has already been registered for " << eventType;

			{
				Logger::Lock lock(logger);
				logger.Error << stm.str() << endl;
			}

			throw invalid_argument(stm.str());
		}
	}
	else
		eventClassFactoryMap[eventType] = eventClassFactory;
}

void EventTransceiver::RegisterEventClassFactory(const EventType& eventType, EventClassFactory* eventClassFactory, bool throwOnDuplicateRegistration)
{
	EventClassFactory::Ptr cf;
	cf.reset(eventClassFactory);

	RegisterEventClassFactory(eventType, cf, throwOnDuplicateRegistration);
}

EventTransceiver::EventClassFactory::Ptr EventTransceiver::GetClassFactory(const EventType& eventType)
{
	ILockableConceptLock mlock(localMutex);
	EventClassFactoryMap::iterator foundFactory = eventClassFactoryMap.find(eventType);

	return (foundFactory == eventClassFactoryMap.end()) ? localFactory : foundFactory->second;
}

EventTransceiver::ScopedConnectionPtr EventTransceiver::SubscribeAllEvents(InformSignalType::slot_type slot)
{
	ILockableConceptLock mlock(localMutex);
	ScopedConnectionPtr result;
	result.reset(new ScopedConnection(informSignal.connect(slot)));
	return result;
}

}
}
