//----------------------------------------------------------------------------------
// <copyright file="Alarm.cpp" company="LiquidRobotics">
//	Copyright (c) Liquid Robotics Corporation.  All rights reserved.
// </copyright>
//
// <summary>
//	Alarm Interface. Will send a script command to Regulus.
// </summary>
//
// <owner>Andrew Trapani</owner>
//----------------------------------------------------------------------------------

#include "AlarmReporter.h"

using namespace std;
namespace LiquidRobotics {
using namespace SMACFramework;
using namespace EventLib;
typedef boost::multiprecision::cpp_int cpp_int;

AlarmReporter::AlarmReporter(PayloadManagerClient::Ptr _pmClient, AlarmSettings& _alarmSettings,
		EventTransceiver& _eventTransceiver, IObjectBroker& _broker, Logger& _logger)
: broker(_broker)
, eventTransceiver(_eventTransceiver)
, pmClient(_pmClient)
, alarmSettings(_alarmSettings)
, logger(_logger)
{
	long minutes = (long) alarmSettings.GetAlarmReporterSettings().GetMinTime();
	Logger::Locked(logger)->Debug << "(Alarm) Time between one time alarms: " << minutes << " minutes" << endl;
	timeLimit.tv_sec = minutes*60;
	timeLimit.tv_usec = 0; //Microseconds aren't supported on this platform.
}

//TODO: Change to uint_32 for ID names
void AlarmReporter::CreateAndSendAlarmMessage(const double latitude, const double longitude, const timeval& timestamp,
		const alarmTypes alarmType, const int notificationId, const string& message) {
	AlarmMessage evt(alarmSettings.GetEventSettings(), broker);

	evt.SetGeneratorAddress(pmClient->GetMyBoardAddress());
	const int enumerationType = (int)pmClient->GetMyBoardAddress();
	evt.SetGenerationLocation(latitude, longitude);
	evt.SetGenerationTime(timestamp);

	const ByteArray* ba = new ByteArray();
	string tempMessage;
	const AlarmReporterSettings& alarmReporterSettings = alarmSettings.GetAlarmReporterSettings();
	if (CheckAlarm(alarmType, alarmReporterSettings.GetReporterTypeId(), enumerationType, notificationId)) {
		if (message.size() > 0) {
			tempMessage = string(alarmReporterSettings.GetReporterName() + ": " + message);
		} else
		{
			tempMessage = string(alarmReporterSettings.GetReporterName());
		}
		const string concatMessage = tempMessage;
		evt.SetAlarmMessage(alarmReporterSettings.GetReporterTypeId(), alarmType, enumerationType,
				notificationId, concatMessage, *ba);

		eventTransceiver.TransmitEvent(evt);
	}
}

cpp_int AlarmReporter::GetUniqueId(const int reporterTypeId, const int enumerationType,
		const int notificationId) {
	using namespace boost::multiprecision;
	int leftShiftAmount = sizeof(int)*8;
	//Highest 32 bits are reporter type id
	//Middle 32 bits are enumeration type
	//Lowest 32 bits are notification id
	cpp_int uniqueId = (((cpp_int(reporterTypeId) << leftShiftAmount)
			+ cpp_int(enumerationType)) << leftShiftAmount)
					+ cpp_int(notificationId);

	return uniqueId;
}

bool AlarmReporter::CheckAlarm(const alarmTypes alarmType, const int reporterTypeId,
		const int enumerationType, const int notificationId) {
	bool sendTheAlarm = false;
	cpp_int uniqueId = GetUniqueId(reporterTypeId, enumerationType, notificationId);
	if (alarmType == nonAlarmEvent_AT || alarmType == alarmEvent_AT) {
		sendTheAlarm = CheckOneTimeAlarmsForUniqueId(uniqueId);
	} else if(alarmType == alarmSet_AT) {
		sendTheAlarm = CheckSetAlarmsForUniqueId(uniqueId, true);
	} else if (alarmType == alarmCleared_AT) {
		sendTheAlarm = CheckSetAlarmsForUniqueId(uniqueId, false);
	} else {
		Logger::Locked(logger)->Warning << "(Alarm) Unknown alarm type (" << alarmType << ")."
				<< "Should be between " << alarmCleared_AT << " and " <<  nonAlarmEvent_AT
				<< endl;
	}

	return sendTheAlarm;
}

/* Determines whether or not to send the alarm.
 * Returns true if the alarm should be sent
 * Returns false if the alarm should not be sent
 *    This means the alarm status has not changed.
 * Warning: There is no time limit.
 * If the code frequently calls set->clear->set->clear, it will send all of them.
 */
bool AlarmReporter::CheckSetAlarmsForUniqueId(cpp_int uniqueId, bool setTo) {
	bool sendTheAlarm = false;
	bool previousVal = setAndClearedAlarms[uniqueId];

	if (setAndClearedAlarms.count(uniqueId) == 0) {
		setAndClearedAlarms[uniqueId] = setTo;
		sendTheAlarm = true;
	} else { //count should be 1
		if (previousVal != setTo) {
			setAndClearedAlarms[uniqueId] = setTo;
			sendTheAlarm = true;
		}
	}
	if (sendTheAlarm) {
		Logger::Locked(logger)->Debug << "Sending set/clear alarm " << endl;
	} else {
		Logger::Locked(logger)->Debug << "Not sending duplicate set/clear alarm " << endl;
	}

	return sendTheAlarm;
}

/* Determine if it has been long enough since the last time this alarm/event was sent
 * If so, return true, if not, return false
 */
bool AlarmReporter::CheckOneTimeAlarmsForUniqueId(cpp_int uniqueId) {
	bool sendTheAlarm = false;
	timeval timeNow;
	gettimeofday(&timeNow, NULL);
	if (timeLimit.tv_sec == 0) {
		//Always send the alarm
		alarmTracker[uniqueId] = timeNow; //In case the timeLimit is changed
		sendTheAlarm = true;
		Logger::Locked(logger)->Debug << "No time limit. Sending alarm " << uniqueId << endl;
	} else {
		Logger::Locked(logger)->Debug << "Checking one time alarm ("
				<< uniqueId << ")" << endl;
		if (alarmTracker.count(uniqueId) == 0) {
			alarmTracker[uniqueId] = timeNow;
			sendTheAlarm = true;
			Logger::Locked(logger)->Debug << "First time seeing this alarm"<< endl;
		} else {
			Logger::Locked(logger)->Debug << "(Alarm) Checking Time TimeNow (" <<  alarmTracker[uniqueId].tv_sec
			        << ") - LastTime (" << timeNow.tv_sec << ") = ("
					<< (timeNow.tv_sec - alarmTracker[uniqueId].tv_sec)
					<< ") >? " << timeLimit.tv_sec
					<< endl;
			if ((timeNow.tv_sec - alarmTracker[uniqueId].tv_sec) > timeLimit.tv_sec) {
				alarmTracker[uniqueId] = timeNow;
				sendTheAlarm = true;
				Logger::Locked(logger)->Debug << "Sending the alarm" << endl;
			} else {
				Logger::Locked(logger)->Debug << "Not sending the alarm" << endl;
			}
		}
	}

	return sendTheAlarm;
}

/* These functions send a comment or alarm to WGMS */
void AlarmReporter::sendAlarmWithType(const alarmTypes alarmType, const int notificationId, const string& message) {
	const double latitude = 0;
	const double longitude = 0;
	timeval timeNow;
	gettimeofday(&timeNow, NULL);
	sendAlarmWithType(alarmType, latitude, longitude, timeNow, notificationId, message);
}

void AlarmReporter::sendAlarmWithType(const alarmTypes alarmType, const timeval& timestamp,
		const int notificationId, const string& message) {
	const double latitude = 0;
	const double longitude = 0;
	sendAlarmWithType(alarmType, latitude, longitude, timestamp, notificationId, message);
}

void AlarmReporter::sendAlarmWithType(const alarmTypes alarmType, const double latitude, const double longitude,
		const int notificationId, const string& message) {
	timeval timeNow;
	gettimeofday(&timeNow, NULL);
	sendAlarmWithType(alarmType, latitude, longitude, timeNow, notificationId, message);
}

void AlarmReporter::sendAlarmWithType(const alarmTypes alarmType, const double latitude, const double longitude,
		const timeval& timestamp, const int notificationId, const string& message) {
	switch(alarmType) {
	case nonAlarmEvent_AT:
		ReportEvent(latitude, longitude, timestamp, notificationId, message);
		break;
	case alarmEvent_AT:
		ReportAlarm(latitude, longitude, timestamp, notificationId, message);
		break;
	case alarmSet_AT:
		SetAlarm(latitude, longitude, timestamp, notificationId, message);
		break;
	case alarmCleared_AT:
		ClearAlarm(latitude, longitude, timestamp, notificationId);
		break;
	}
}

/* Report a predefined event as a comment*/
void AlarmReporter::ReportEvent(const int notificationId, const string& message) {
	const double latitude = 0;
	const double longitude = 0;
	timeval timeNow;
	gettimeofday(&timeNow, NULL);
	ReportEvent(latitude, longitude, timeNow, notificationId, message);
}

void AlarmReporter::ReportEvent(const timeval& timestamp,
		const int notificationId, const string& message) {
	const double latitude = 0;
	const double longitude = 0;
	ReportEvent(latitude, longitude, timestamp, notificationId, message);
}

void AlarmReporter::ReportEvent(const double latitude, const double longitude, const timeval& timestamp,
		const int notificationId, const string& message) {
	static const alarmTypes alarmType = nonAlarmEvent_AT;
	CreateAndSendAlarmMessage(latitude, longitude, timestamp, alarmType, notificationId, message);
}

/* Report a predefined alarm. Set the red light. */
void AlarmReporter::ReportAlarm(const int notificationId, const string& message) {
	double latitude = 0;
	double longitude = 0;
	timeval timeNow;
	gettimeofday(&timeNow, NULL);
	ReportAlarm(latitude, longitude, timeNow, notificationId, message);
}

void AlarmReporter::ReportAlarm(const timeval& timestamp,
		const int notificationId, const string& message) {
	double latitude = 0;
	double longitude = 0;
	ReportAlarm(latitude, longitude, timestamp, notificationId, message);
}

void AlarmReporter::ReportAlarm(const double latitude, const double longitude, const timeval& timestamp,
		const int notificationId, const string& message) {
	static const alarmTypes alarmType = alarmEvent_AT;
	CreateAndSendAlarmMessage(latitude, longitude, timestamp, alarmType, notificationId, message);
}

/* Set a predefined alarm and remember it. Set the red light. */
void AlarmReporter::SetAlarm(const int notificationId, const string& message) {
	const double latitude = 0;
	const double longitude = 0;
	timeval timeNow;
	gettimeofday(&timeNow, NULL);
	SetAlarm(latitude, longitude, timeNow, notificationId, message);
}

void AlarmReporter::SetAlarm(const timeval& timestamp, const int notificationId, const string& message) {
	const double latitude = 0;
	const double longitude = 0;
	SetAlarm(latitude, longitude, timestamp, notificationId, message);
}

void AlarmReporter::SetAlarm(const double latitude, const double longitude, const timeval& timestamp,
		const int notificationId, const string& message) {
	static const alarmTypes alarmType = alarmSet_AT;
	CreateAndSendAlarmMessage(latitude, longitude, timestamp, alarmType, notificationId, message);
}

/* Clear a predefined alarm. Set the red light yellow.  */
void AlarmReporter::ClearAlarm(const int notificationId) {
	const double latitude = 0;
	const double longitude = 0;
	timeval timeNow;
	gettimeofday(&timeNow, NULL);
	ClearAlarm(latitude, longitude, timeNow, notificationId);
}

void AlarmReporter::ClearAlarm(const timeval& timestamp, const int notificationId) {
	const double latitude = 0;
	const double longitude = 0;
	ClearAlarm(latitude, longitude, timestamp, notificationId);
}

void AlarmReporter::ClearAlarm(const double latitude, const double longitude, const timeval& timestamp,
		const int notificationId) {
	static const alarmTypes alarmType = alarmCleared_AT;
	static const string message("");
	CreateAndSendAlarmMessage(latitude, longitude, timestamp, alarmType, notificationId, message);
}

}
