//------------------------------------------------------------------------------
// <copyright file="SSQProgram.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 SSQProgram, which orchestrates program startup and shutdown
//  and interfaces the Controller class to CORBA
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#include "SSQProgram.h"
#include "LqrSMACIdl.h"
#include "SBDPacketEvent.h"
#include "SBDStatisticsEvent.h"

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

namespace LiquidRobotics {
namespace SimpleSBDQueue {

SSQProgram::SSQProgram(IObjectBroker& _broker, const SSQProgramSettings& _settings, Logger& _logger)
: Base(_broker, _settings.GetSensorProgramSettings(), _logger)
, broker(_broker)
, settings(_settings)
, logger(_logger)
, latSave(0)
, lonSave(0)
{
}

SSQProgram::~SSQProgram()
{
}

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

	const EventSettings& enqueueEventSpec = settings.GetEnqueuePacketEvent();

	if (!enqueueEventSpec.Empty())
	{
		RegisterEventClassFactory(enqueueEventSpec.GetType(), new EventTransceiver::TEventClassFactory<SBDPacketEvent>());
		InformSignalType& enqueueSig = SignalForEventName(enqueueEventSpec.GetName());
		connectionToEnqueueEvent.reset( new ScopedConnection(enqueueSig.connect(boost::bind(&SSQProgram::OnEnqueueEvent, this, _1, _2))) );
	}
	else
		Logger::Locked(logger)->Info << "Settings for 'enqueue' event are empty, so the class factory is not being registered and the event is not being trapped" << endl;
	
	ctrl.reset(new SSQController(settings.GetControllerSettings(), broker, logger, gpsSignalSubscriber, shutdownSignalSubscriber));
	commandCompletedConnection = ctrl->SubscribeCommandComplete2(boost::bind(&SSQProgram::OnCommandCompleted, this, _1, _2, _3));
	packetReceivedConnection = ctrl->SubscribePacketReceived(boost::bind(&SSQProgram::OnPacketReceived, this, _1, _2, _3));
	packetSendDispositionConnection = ctrl->SubscribePacketSendDisposition(boost::bind(&SSQProgram::OnPacketSendDisposition, this, _1, _2, _3, _4, _5, _6));
	channelStatsConnection = ctrl->SubscribeChannelStats(boost::bind(&SSQProgram::OnChannelStats, this, _1, _2));
	ctrl->Initialize();
}

void SSQProgram::Shutdown()
{
	commandCompletedConnection.reset();

	{
		ILockableConcept::Lock firingLock(ctrl->GetFiringMutex());
		
		packetReceivedConnection.reset();
		packetSendDispositionConnection.reset();
		channelStatsConnection.reset();
	}

	connectionToEnqueueEvent.reset();
	ctrl->Shutdown();
	Base::Shutdown();
}

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

void SSQProgram::GPSSampleHandlerAsSample(EventTransceiver& sender, const GPSSampleEvent& receivedEvent)
{
	if (receivedEvent.HasGPS())
	{
		latSave = receivedEvent.GetLatitude();
		lonSave = receivedEvent.GetLongitude();
	}

	Base::GPSSampleHandlerAsSample(sender, receivedEvent);
}

void SSQProgram::OnPacketReceived(SSQController& sender, const ByteArray& packetData, unsigned MTMSN)
{
    const EventSettings& evtSpec = settings.GetReceivedPacketEvent();
    Logger::Locked(logger)->Notice << "Transmitting CORBA event " << evtSpec.GetName() << endl;

    try
    {
        SBDPacketEvent receiveEvent(evtSpec, broker);
        receiveEvent.SetGeneratorAddress(pmClient->GetMyBoardAddress());
        timeval tv;
        if (!gettimeofday(&tv, NULL))
            receiveEvent.SetGenerationTime(tv);
        receiveEvent.SetGenerationLocation(latSave, lonSave);
        receiveEvent.SetPacketData(packetData);
        receiveEvent.SetPacketSerialNumber(MTMSN);
        receiveEvent.SetPacketDirection(SBDPacketEvent::RECEIVED_FROM_COMM_CHANNEL);
        if (logger.LogEnabled(Logger::LT_DEBUG))
        {
            Logger::Lock logLock(logger);
            receiveEvent.Describe(logger.Debug);
            logger.Debug << endl;
        }
        TransmitEvent(receiveEvent);
    }
    catch(CORBA::Exception& ex)
    {
        Logger::Lock logLock(logger);
        logger.Error << "Caught " << DecodeCORBAException(ex) << " while attempting to transmit event " << evtSpec.GetName() << endl;
    }
}

void SSQProgram::OnPacketSendDisposition(SSQController& sender, uint32_t priority, uint16_t senderID, uint32_t senderTransaction, unsigned MOMSN, SSQController::SDSDisposition disposition)
{
    const EventSettings& evtSpec = settings.GetTransmitDispositionEvent();
    Logger::Locked(logger)->Notice << "Transmitting CORBA event " << evtSpec.GetName() << endl;

    try
    {
        SBDPacketEvent dispositionEvent(evtSpec, broker);
        dispositionEvent.SetGeneratorAddress(pmClient->GetMyBoardAddress());
        timeval tv;
        if (!gettimeofday(&tv, NULL))
            dispositionEvent.SetGenerationTime(tv);
        dispositionEvent.SetGenerationLocation(latSave, lonSave);
        if (~priority) dispositionEvent.SetPriority(priority);
        if (++senderID) dispositionEvent.SetReferenceID(--senderID);
        if (~senderTransaction) dispositionEvent.SetTransactionID(senderTransaction);
        dispositionEvent.SetPacketDirection(SBDPacketEvent::SENDING_TO_COMM_CHANNEL);
        dispositionEvent.SetPacketSerialNumber(MOMSN);
        dispositionEvent.SetDisposition((SBDPacketEvent::Disposition) disposition);
        if (logger.LogEnabled(Logger::LT_DEBUG))
        {
            Logger::Lock logLock(logger);
            dispositionEvent.Describe(logger.Debug);
            logger.Debug << endl;
        }
        TransmitEvent(dispositionEvent);
    }
    catch(CORBA::Exception& ex)
    {
        Logger::Lock logLock(logger);
        logger.Error << "Caught " << DecodeCORBAException(ex) << " while attempting to transmit event " << evtSpec.GetName() << endl;
    }
}

void SSQProgram::OnChannelStats(SSQController& sender, const SSQCommunicator::Statistics& stats)
{
	const EventSettings& evtSpec = settings.GetChannelStatsEvent();
	Logger::Locked(logger)->Notice << "Transmitting CORBA event " << evtSpec.GetName() << endl;

	try
	{
		SBDStatisticsEvent statsEvent(evtSpec, broker);
		statsEvent.SetGeneratorAddress(pmClient->GetMyBoardAddress());
		timeval tv;
		if (!gettimeofday(&tv, NULL))
			statsEvent.SetGenerationTime(tv);
		statsEvent.SetGenerationLocation(latSave, lonSave);
		statsEvent.Init(stats.communicating, stats.networkOK, stats.accessOK, stats.model, stats.serialNum, stats.simNum,
				stats.numSent, stats.numReceived, stats.numSessions, stats.numSessionsSuccessful, stats.reliable);
		if (logger.LogEnabled(Logger::LT_DEBUG))
		{
			Logger::Locked lockedLogger(logger);
			statsEvent.Describe(lockedLogger->Debug);
			lockedLogger->Debug << endl;
		}
		TransmitEvent(statsEvent);
	}
	catch(CORBA::Exception& ex)
	{
		Logger::Lock logLock(logger);
		logger.Error << "Caught " << DecodeCORBAException(ex) << " while attempting to transmit event " << evtSpec.GetName() << endl;
	}
}

void SSQProgram::OnEnqueueEvent(EventTransceiver& sender, const IEventBase& receivedEvent)
{
    const EventSettings& enqueueEventSpec = settings.GetEnqueuePacketEvent();
    const SBDPacketEvent& packet =
        EventTransceiver::UpcastEventRef<SBDPacketEvent>(receivedEvent, enqueueEventSpec,
                "SSQProgram::OnEnqueueEvent");

    SSQController::PriorityType priority;

    if (packet.HasPriority())
        priority = packet.GetPriority();
    else
        priority = 3;

    ByteArray message;

    if (packet.HasPacketData())
        packet.GetPacketData(message);

    SSQController::SenderIDType senderID;

    if (packet.HasGeneratorAddress())
        senderID = packet.GetGeneratorAddress();
    else
        senderID = ~((SSQController::SenderIDType) 0);

    SSQController::SenderTransactionType senderTransaction;

    if (packet.HasTransactionID())
        senderTransaction = packet.GetTransactionID();
    else
        senderTransaction = ~((SSQController::SenderTransactionType) 0);

    SSQController::QueuePosition queuePos;

    if (packet.HasQueueStrategy())
        queuePos = (SSQController::QueuePosition) packet.GetQueueStrategy();
    else
        queuePos = SSQController::QUEUE_END;

    SSQController::TTLType ttl;

    if (packet.HasTimeToLive())
        ttl = packet.GetTimeToLive();
    else
        ttl = 0;

    ctrl->EnqueueMessage(priority, message, senderID, senderTransaction, queuePos, ttl);
}

void SSQProgram::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);
	SSQController::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");
	}
}

}
}
