//--------------------------------------------------------------------------------
// <copyright file="AlarmSubsystem.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 AlarmSubsystem, which factors out the alarm reporting and
//  status methods for PayloadInterfaceController and SV3RemoteController
// </summary>
//
// <owner>Mike Cookson</owner>
//--------------------------------------------------------------------------------

#include "AlarmSubsystem.h"
#include "AlarmMessage.h"
#include "GPSSampleEvent.h"
#include "FCCMessage.h"
#include "PPSByteStream.h"
#include "TelemetryEventBase.h"

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

namespace LiquidRobotics {
namespace PayloadInterface {

AlarmSubsystem::AlarmSubsystem(
		EventTransceiver& _evtTrans,
		Logger& _logger,
		const PayloadInterfaceController_Config& _config,
		IObjectBroker& _broker,
		PayloadManagerClient& _pmClient)
: evtTrans(_evtTrans)
, logger(_logger)
, config(_config)
, broker(_broker)
, pmClient(_pmClient)
, lat(0.0)
, lon(0.0)
{
}

void AlarmSubsystem::Initialize()
{
	// configure EventTransciever to create the appropriate derived class for TelemetryChanged events
	const EventSettings& alarmReportEvent = config.GetAlarmReportEventSettings();
	evtTrans.RegisterEventClassFactory(alarmReportEvent.GetType(), new EventTransceiver::TEventClassFactory<AlarmMessage>);

	// subscribe to the received event
	EventTransceiver::InformSignalType& sig1 = evtTrans.SignalForEventName(alarmReportEvent.GetName());
	connectionToAlarmReport.reset(
			new ScopedConnection(
					sig1.connect(
							boost::bind(&AlarmSubsystem::HandleAlarmReport, this, _1, _2)
							)));

}

void AlarmSubsystem::Shutdown()
{
}

void AlarmSubsystem::HandleAlarmReport(EventTransceiver& sender, const IEventBase& receivedEvent)
{
	const EventSettings& alarmReportEvent = config.GetAlarmReportEventSettings();
	const AlarmMessage& evt =
		EventTransceiver::UpcastEventRef<AlarmMessage>(receivedEvent, alarmReportEvent,
				"AlarmSubsystem::HandleAlarmReport");

	if (evt.HasGeneratorAddress())
	{
		timeval timestamp;

		if (evt.HasGenerationTime())
			timestamp = evt.GetGenerationTime();
		else if (gettimeofday(&timestamp, NULL))
		{
			time(&timestamp.tv_sec);
			timestamp.tv_usec = 0;
		}

		double latval;
		double lonval;

		if (evt.HasGenerationLocation())
			evt.GetGenerationLocation(latval, lonval);
		{
			latval = lat;
			lonval = lon;
		}

		ByteArray level1;

		FormatLevel1(evt.GetGeneratorAddress(), timestamp, latval, lonval,
				evt.GetReporterTypeId(), evt.GetAlarmType(),
				evt.GetEnumerationType(), evt.GetNotificationId(),
				evt.GetFreeText(), *evt.GetFreeData(), level1);

		ReportAlarm(evt.GetGeneratorAddress(), level1);
	}
	else
		Logger::Locked(logger)->Warning << "HandleAlarmReport: no generator address in received alarm (ignored)" << endl;
}

void AlarmSubsystem::FormatLevel1(uint16_t senderAddress,
	uint32_t reportTypeID, uint8_t alarmType, uint32_t enumerationType, uint32_t notificationID,
	const std::string& freeText, const LiquidRobotics::SMACFramework::ByteArray& freeData,
	LiquidRobotics::SMACFramework::ByteArray& level1SysInfo)
{
	timeval timestamp;

	if (gettimeofday(&timestamp, NULL))
	{
		time(&timestamp.tv_sec);
		timestamp.tv_usec = 0;
	}

	FormatLevel1(senderAddress,	timestamp, lat, lon, reportTypeID, alarmType,
			enumerationType, notificationID, freeText, freeData, level1SysInfo);
}

void AlarmSubsystem::FormatLevel1(uint16_t senderAddress, const timeval& timestamp, double latitude, double longitude,
	uint32_t reportTypeID, uint8_t alarmType, uint32_t enumerationType, uint32_t notificationID,
	const std::string& freeText, const LiquidRobotics::SMACFramework::ByteArray& freeData,
	LiquidRobotics::SMACFramework::ByteArray& level1SysInfo)
{
	using namespace LiquidRobotics::PayloadInterface::FCCMessage;
	TELEMETRY_91 telem;
	telem.Set(SYSINFO_EVENT_RECORD_V2, senderAddress, 0);

	TELEMETRY_91_SAMPLE0 sample0;

	sample0.latitude = TelemetryEventBase::ConvertDoublePosToInt(latitude);
	sample0.longitude = TelemetryEventBase::ConvertDoublePosToInt(longitude);
	sample0.sampleTime_T = timestamp.tv_sec;

	const uint8_t stringLen = min((size_t) 255,freeText.length());
	PPSByteStream bsLevel1;

	bsLevel1 << telem
	         << sample0
	         << reportTypeID
	         << alarmType
	         << notificationID
	         << enumerationType
	         << stringLen;

	if (stringLen)
		bsLevel1 << ContainerOut<string>(freeText.substr(0,255));

	bsLevel1 << (uint8_t) min((size_t) 255, freeData.size());

	if (freeData.size() > 255)
	{
		ByteArray copy(freeData.begin(), freeData.begin()+255);
		bsLevel1.Put(copy);
	}
	else
		bsLevel1.Put(freeData);

	const ByteArray& level1 = bsLevel1.AsBytes();
	level1SysInfo.assign(level1.begin(), level1.end());
}

void AlarmSubsystem::SavePosition(double _lat, double _lon)
{
	lat = _lat;
	lon = _lon;
}

}
}
