//-------------------------------------------------------------------------------------------------
// <copyright file="LWProgram.cpp" company="LiquidRobotics">
//  Copyright (c) Liquid Robotics Corporation, confidential and proprietary.  All rights reserved. 
// </copyright>
//
// <summary>
//	Implementation of LWProgram, the lua wrapper's module representing the sensor
//	pattern's program class. "Program" classes implement CORBA interface methods and
//	interact with the EventManager; they provide any required connections between the
//	controller and the CORBA world.
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------------------------

#include "LWProgram.h"
#include "LqrSMACLuaInstance.h"
#include "LqrSMACIdl.h"
#include "DataSetEvent.h"
#include "SendCommandCompleteEvent.h"

using namespace std;
using namespace LiquidRobotics::SMACFramework;
using namespace LiquidRobotics::EventLib;

namespace LiquidRobotics {
namespace LuaWrapper {

LWProgram::LWProgram(IObjectBroker& _broker, const LWProgramSettings& _settings, Logger& _logger)
: Base(_broker, _settings.GetSensorProgramSettings(), _logger)
, broker(_broker)
, settings(_settings)
, logger(_logger)
{
}

LWProgram::~LWProgram()
{
}

void LWProgram::Initialize(Object_ptr _pServer, PayloadManagerClient::Ptr _pmClient)
{
	Base::Initialize(_pServer, _pmClient);

	ctrl.reset(new LWController(settings.GetControllerSettings(), broker, logger, gpsSignalSubscriber,
			shutdownSignalSubscriber, settings.GetSensorProgramSettings().GetPayloadManagerClientSettings(),
			settings.GetSensorProgramSettings().GetCommandCompleteEvent()));
	commandCompletedConnection = ctrl->SubscribeCommandComplete2(boost::bind(&LWProgram::OnCommandCompleted, this, _1, _2, _3));
	publishDataSetConnection = ctrl->SubscribePublishDataSet(boost::bind(&LWProgram::OnPublishDataSetEvent, this, _1, _2, _3, _4, _5, _6, _7));
	publishEventConnection = ctrl->SubscribePublishEvent(boost::bind(&LWProgram::OnPublishEvent, this, _1, _2));

	const EventSettings& commandCompleteSettings = settings.GetSensorProgramSettings().GetCommandCompleteEvent();
	RegisterEventClassFactory(commandCompleteSettings.GetType(), new EventTransceiver::TEventClassFactory<SendCommandCompleteEvent>);
	// I'll allow the controller to take charge of handling the event generically

	ctrl->Initialize();

	allEventsConnection = SubscribeAllEvents(boost::bind(&LWProgram::OnAnyEventReceived, this, _1, _2));
}

void LWProgram::OnPublishDataSetEvent(LWController& sender, const string& instanceName, const string& eventName,
		const string& eventType, const string& eventDomain, const string& dataSetName, const string& content)
{
	Logger::Locked(logger)->Info << "OnPublishDataSetEvent"
								 << " instanceName=" << instanceName
								 << " eventName=" << eventName
								 << " eventType=" << eventType
								 << " eventDomain=" << eventDomain
								 << " dataSetName=" << dataSetName
								 << " content=" << content.length() << " bytes"
								 << endl;

	try
	{
		const EventSettings evtSettings(eventName, eventType, eventDomain);
		const ByteArray contentAlias((ByteArray::byte*) content.data(), content.length());
		Logger::Locked(logger)->Debug << contentAlias.ToStringSplitView() << endl;

		DataSetEvent evt(evtSettings, broker);
		evt.SetGeneratorAddress(pmClient->GetMyBoardAddress());
		evt.SetDataSetName(dataSetName);
		evt.SetContent(contentAlias);

		Logger::Locked(logger)->Debug << evt << endl;

		TransmitEvent(evt);
	}
	catch(CORBA::Exception& ex)
	{
		Logger::Locked(logger)->Error << "Caught " << DecodeCORBAException(ex) << " while transmitting data set event" << endl;
	}
	catch(std::exception& ex)
	{
		Logger::Locked(logger)->Error << "Caught " << typeid(ex).name() << ": '" << ex.what() << "' while transmitting data set event" << endl;
	}
	catch(...)
	{
		Logger::Locked(logger)->Error << "Caught generic exception while transmitting data set event" << endl;
	}
}

void LWProgram::OnPublishEvent(LWController& sender, LWController::IEventBase& evt)
{
	{
		Logger::LogStream& log = logger.Info;
		Logger::Lock logLock(logger);

		log << "OnPublishEvent ";

		if (logger.LogEnabled(Logger::LT_DEBUG))
			evt.DescribeGeneric(log);
		else
			evt.Describe(log);

		log << endl;
	}

	try
	{
		TransmitEvent(evt);
	}
	catch(CORBA::Exception& ex)
	{
		Logger::Locked(logger)->Error << "Caught " << DecodeCORBAException(ex) << " while transmitting event" << endl;
	}
	catch(std::exception& ex)
	{
		Logger::Locked(logger)->Error << "Caught " << typeid(ex).name() << ": '" << ex.what() << "' while transmitting event" << endl;
	}
	catch(...)
	{
		Logger::Locked(logger)->Error << "Caught generic exception while transmitting event" << endl;
	}
}

void LWProgram::Shutdown()
{
	allEventsConnection.reset();
	publishDataSetConnection.reset();
	publishEventConnection.reset();
	commandCompletedConnection.reset();
	Base::Shutdown();
}

void LWProgram::OnCommandCompleted(const EventLib::SensorControllerBaseCommandID& cmdID, const string& result, bool success)
{
	FireCommandCompleteEvent(result, cmdID.GetRequester(), cmdID.GetTransactionID(), true, success);
}

void LWProgram::SendCommand(const ::CORBA::Any& command, const char* requester, const char* transactionID, ::CORBA::Any_OUT_arg result)
{
	using namespace LiquidRobotics::EventLib;
	using namespace LiquidRobotics::SMAC;

	if (!ctrl)
		ThrowCORBAException<NotReady>("The controller object has not been instantiated");

	const char* cmd0;

	if (!(command >>= cmd0))
	{
		logger.Error << "Incoming command type is not a string" << endl;
		ThrowCORBAException2<BadParameter>("command", "Incoming command type is not a string");
	}

	typedef SensorControllerExceptions Exceptions;

	SensorControllerBaseCommandID cmdID(requester, transactionID);
	LWController::ParsingContext context(*ctrl, cmd0, cmdID);

	try
	{
		string resultStr = ctrl->SendCommand(context);
		EventPropertyValue epv(broker);

		result.ptr() = new Any;
		epv.SetUInt8Vector((const EventPropertyValue::UInt8*) resultStr.data(), resultStr.length());
		epv.CopyOut(*result.ptr());
	}
	catch(CORBA::Exception& exc)			// this would not be typically returned from the command
	{
		Logger::Locked(logger)->Error << "Caught " << DecodeCORBAException(exc) << " in SendCommand" << endl;
		throw;
	}
	catch(Exceptions::Aborted& exc)
	{
		string whatStr = exc.what();
		ThrowCORBAException<NotReady>(whatStr);
	}
	catch(Exceptions::CommFailure& exc)
	{
		string whatStr = exc.what();
		ThrowCORBAException<CommFailure>(whatStr);
	}
	catch(Exceptions::NotReady& exc)
	{
		string whatStr = exc.what();
		ThrowCORBAException<NotReady>(whatStr);
	}
	catch(Exceptions::ReturnsAsynchronously& exc)
	{
		ThrowCORBAException<WillCompleteAsynchronously>();
	}
	catch(Exceptions::UnavailableInPowerOffState& exc)
	{
		string whatStr = exc.what();
		ThrowCORBAException<UnavailableInPowerOffState>(whatStr);
	}
	catch(invalid_argument& exc)				// this will be thrown if an invalid argument was provided, typically for the command
	{
		string whatStr = exc.what();
		ThrowCORBAException2<BadParameter>("command", whatStr);
	}
	catch(std::exception& exc)
	{
		string whatStr = exc.what();
		stringstream msg;
		msg << "Caught exception " << typeid(exc).name() << " '" << whatStr << "'";
		ThrowCORBAException<NotReady>(msg.str());
	}
	catch(...)
	{
		ThrowCORBAException<NotReady>("Caught unknown exception type");
	}
}

void LWProgram::OnAnyEventReceived(EventTransceiver& sender, const IEventBase& receivedEvent)
{
	if (ctrl.get())
		ctrl->ForwardAnyEventReceived(receivedEvent);
}

}
}
