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

#include "SBGPCTDProgram.h"
#include "SBGPCTDSampleEvent.h"
#include "LqrSMACIdl.h"
#include "RegulusREST.h"
#include "TelemetrySettingsChangedEvent.h"
#include "nsextend.h"
#include <stdexcept>

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

namespace LiquidRobotics {
namespace SeabirdGPCTD {

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

SBGPCTDProgram::~SBGPCTDProgram()
{
}

void SBGPCTDProgram::Initialize(Object_ptr _pServer, PayloadManagerClient::Ptr _pmClient)
{
	Base::Initialize(_pServer, _pmClient);
	pmClient = _pmClient;

	ctrl.reset(new SBGPCTDController(settings.GetControllerSettings(), broker, logger, gpsSignalSubscriber, shutdownSignalSubscriber));

	commandCompletedConnection = ctrl->SubscribeCommandComplete2(boost::bind(&SBGPCTDProgram::OnCommandCompleted, this, _1, _2, _3));
	sampleConnection = ctrl->sampleReport.Subscribe(boost::bind(&SBGPCTDProgram::OnSample, this, _1, _2, _3));
	serialNumberConnection = ctrl->serialNumberReport.Subscribe(boost::bind(&SBGPCTDProgram::OnSerialNumber, this, _1, _2, _3));
	telemetryChangedConnection = ctrl->sampleTelemetryChange.Subscribe(boost::bind(&SBGPCTDProgram::OnTelemetryChanged, this, _1, _2, _3));

	ctrl->Initialize();
}

void SBGPCTDProgram::Shutdown()
{
	//commandCompletedConnection.reset();

	SBGPCTDControllerPtr temp = ctrl;

	if (temp)
	{
		ctrl->sampleReport.Release(sampleConnection);
		ctrl->serialNumberReport.Release(serialNumberConnection);
		ctrl->sampleTelemetryChange.Release(telemetryChangedConnection);
		ctrl->Shutdown();
	}

	Base::Shutdown();
}

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

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

	Base::GPSSampleHandlerAsSample(sender, receivedEvent);
}

void SBGPCTDProgram::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);
	SBGPCTDController::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 SBGPCTDProgram::OnSample(SBGPCTDController& sender, const SBGPCTDSensor::Sample& sample, bool o2Installed)
{
	EventSettings sampleEvent = o2Installed ? settings.GetO2SampleEventSettings() : settings.GetSampleEventSettings();
	SBGPCTDSampleEvent evt(sampleEvent, broker);
	evt.SetGenerationTime(sample.tv);
	const uint16_t myBoardAddress = pmClient->GetMyBoardAddress();
	Logger::Locked(logger)->Debug << "My board address = 0x" << utos(myBoardAddress,16,4) << endl;
	evt.SetGeneratorAddress(myBoardAddress);
	evt.SetGenerationLocation(sample.latitude, sample.longitude);
	evt.SetReading(sample.condSmStr, sample.condSm, sample.tempCStr, sample.tempC, sample.pressureStr, sample.pressure, sample.disOxyHzStr, sample.disOxyHz);
	evt.SetO2Installed(o2Installed);
	Logger::Locked(logger)->Info << "Publishing sample event (" << sampleEvent.GetName() << ")" << endl;
	TransmitEvent(evt);
}

void SBGPCTDProgram::OnSerialNumber(SBGPCTDController& sender, const string& serialNumber, const string& softwareVersion)
{
	Logger::Locked(logger)->Info << "OnSerialNumber serialNumber=" << serialNumber << " softwareVersion=" << softwareVersion << endl;
	const string json = pmClient->GetJSON();

	RegulusREST::PRHardware::Vector hw;

	try 					{ RegulusREST::PRHardware::InitFromRegJSON(json,hw); }
	catch(exception& ex)	{ Logger::Locked(logger)->Error << "Error decoding hardware JSON (" << ex.what() << ")" << endl; }

	RegulusREST::PRInterface sw;

	try						{ sw.InitFromRegJSON(json); }
	catch(exception& ex)	{ Logger::Locked(logger)->Error << "Error decoding interface JSON (" << ex.what() << ")" << endl; }

	bool changed = false;

	if (!hw.size())
	{
		changed = true;
		hw.resize(1);
	}

	const string port = settings.GetControllerSettings().GetSensorSettings().GetSerialPortSettings().GetPortName();

	if (!boost::equals(port, hw[0].tty))
	{
		changed = true;
		hw[0].tty = port;
	}

	if (!boost::equals(hw[0].serialNumber, serialNumber))
	{
		changed = true;
		hw[0].serialNumber = serialNumber;
	}

	if (!boost::equals(hw[0].swver, softwareVersion))
	{
		changed = true;
		hw[0].swver = softwareVersion;
	}

	if (changed)
	{
		Logger::Locked(logger)->Debug << "Updating JSON with serial number and software revision" << endl;
		try {
			pmClient->UpdateJSON(hw,sw);
		} catch(exception& ex) {
			Logger::Locked(logger)->Warning << "While updating JSON with serial number, caught " << typeid(ex).name() << ": " << ex.what() << endl;
		}
	}
	else
		Logger::Locked(logger)->Debug << "Not updating JSON since there was no change in serial number or software revision" << endl;
}

void SBGPCTDProgram::OnTelemetryChanged(SBGPCTDController& sender, unsigned reportSize, unsigned reportIntervalSec)
{
	EventSettings telemChEvent = settings.GetTelemetryChangedEventSettings();
	TelemetrySettingsChangedEvent evt(telemChEvent, broker);

	try
	{
		const uint16_t myBoardAddress = pmClient->GetMyBoardAddress();
		Logger::Locked(logger)->Debug << "My board address = 0x" << utos(myBoardAddress,16,4) << endl;
		evt.SetGeneratorAddress(myBoardAddress);
		evt.SetFormatCode(settings.GetSampleFormatCode());
		evt.SetMaxSamples(reportSize);
		evt.SetMaxRetainTime(reportIntervalSec*(reportSize+1)+60);
		evt.SetPendingMessageTreatment(TelemetrySettingsChangedEvent::PMT_PUBLISH);
		TransmitEvent(evt);
		evt.SetFormatCode(settings.GetO2SampleFormatCode());
		Logger::Locked(logger)->Info << "Publishing telemetry change" << endl;
		TransmitEvent(evt);
	}
	catch(exception& ex)
	{
		Logger::Locked(logger)->Warning << "While sending telemetry settings changed event, caught " << typeid(ex).name() << ": " << ex.what() << endl;
	}
}

}
}
