//----------------------------------------------------------------------------------
// <copyright file="TelemetryEventBase.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 TelemetryEventBase, an IEventBase-derived class that includes
//   the option to set various commonly-used telemetry fields.  Events that derive
//   from TelemetryEventBase are not necessarily used for telemetry.
// </summary>
//
// <owner>Mike Cookson</owner>
//----------------------------------------------------------------------------------

#include "TelemetryEventBase.h"
#include "nsextend.h"
#include <sys/time.h>

using namespace std;

namespace LiquidRobotics {
namespace EventLib {

const TelemetryEventBase::PropertyNameType TelemetryEventBase::T_BoardIDTag = "Telem-BoardID";
const TelemetryEventBase::PropertyNameType TelemetryEventBase::T_TaskIDTag = "Telem-TaskID";
const TelemetryEventBase::PropertyNameType TelemetryEventBase::T_LatitudeTag = "Telem-Latitude";
const TelemetryEventBase::PropertyNameType TelemetryEventBase::T_LongitudeTag = "Telem-Longitude";
const TelemetryEventBase::PropertyNameType TelemetryEventBase::T_GenerationTimeTagSec = "Telem-TimeSec";
const TelemetryEventBase::PropertyNameType TelemetryEventBase::T_GenerationTimeTagMicroSec = "Telem-TimeUSec";
const TelemetryEventBase::PropertyNameType TelemetryEventBase::T_TargetBoardIDTag = "Telem-TargetBoardID";
const TelemetryEventBase::PropertyNameType TelemetryEventBase::T_TargetTaskIDTag = "Telem-TargetTaskID";

inline void BreakBoardAddress(EventPropertyValue::UInt16 boardAddress, EventPropertyValue::UInt8& boardID, EventPropertyValue::UInt8& taskID)
{
	boardID = (EventPropertyValue::UInt8) (boardAddress >> 8);
	taskID = (EventPropertyValue::UInt8) boardAddress;
}

inline EventPropertyValue::UInt16 MakeBoardAddress(EventPropertyValue::UInt8 boardID, EventPropertyValue::UInt8 taskID)
{
	EventPropertyValue::UInt16 result = (EventPropertyValue::UInt16) boardID;
	result <<= 8;
	result |= (EventPropertyValue::UInt16) taskID;
	return result;
}

TelemetryEventBase::TelemetryEventBase(IObjectBroker& _broker)
: EventBase(_broker)
{
	Init();
}

TelemetryEventBase::TelemetryEventBase(const EventSettings& _eventSettings, IObjectBroker& _broker)
: EventBase(_eventSettings, _broker)
{
	Init();
}

TelemetryEventBase::TelemetryEventBase(const EventName& _eventName, const EventType& _eventType, IObjectBroker& _broker)
: EventBase(_eventName, _eventType, _broker)
{
	Init();
}

TelemetryEventBase::TelemetryEventBase(const EventName& _eventName, const EventType& _eventType, const IOR& _senderIOR, IObjectBroker& _broker)
: EventBase(_eventName, _eventType, _senderIOR, _broker)
{
	Init();
}

TelemetryEventBase::TelemetryEventBase(const TelemetryEventBase& other)
: EventBase(other)
{
}

ostream& TelemetryEventBase::Describe(ostream& stm) const
{
	EventBase::Describe(stm);

	bool first = true;
	stm << " <";

	if (HasGeneratorAddress())
	{
		if (first) first = false; else stm << " ";

		EventPropertyValue::UInt16 boardAddress = GetGeneratorAddress();
		stm << "from 0x" << utos(boardAddress, 16, 4);
	}

#if 0
	if (HasTargetAddress())
	{
		if (first) first = false; else stm << " ";

		EventPropertyValue::UInt16 boardAddress = GetTargetAddress();
		stm << "to 0x" << utos(boardAddress, 16, 4);
	}
#endif

	if (HasGenerationTime())
	{
		if (first) first = false; else stm << " ";

		timeval t = GetGenerationTime();
		tm comp;
		gmtime_r(&t.tv_sec, &comp);

		stm << ultos(comp.tm_year+1900, 10, 4) << "/" << ultos(comp.tm_mon+1, 10, 2) << "/" << ultos(comp.tm_mday, 10, 2) << " "
			<< ultos(comp.tm_hour, 10, 2) << ":" << ultos(comp.tm_min, 10, 2) << ":" << ultos(comp.tm_sec, 10, 2) << "."
			<< ultos((t.tv_usec+500)/1000, 10, 3) << " UTC";
	}

	if (HasGenerationLocation())
	{
		if (first) first = false; else stm << " ";

		EventPropertyValue::DoubleFloat lat, lon;
		GetGenerationLocation(lat, lon);

		stm << "(";
		stm.precision(15);
		stm << lat << ",";
		stm.precision(15);
		stm << lon << ")";
	}

	stm << ">";
	return stm;
}

IEventBase::Ptr TelemetryEventBase::Duplicate() const
{
	IEventBase::Ptr result;
	result.reset(new TelemetryEventBase(broker));

	((TelemetryEventBase*) result.get())->Copy(*this);
	return result;
}

void TelemetryEventBase::ClearGeneratorAddress()
{
	if (ContainsProperty(T_BoardIDTag))		ClearProperty(T_BoardIDTag);
	if (ContainsProperty(T_TaskIDTag))		ClearProperty(T_TaskIDTag);
}

EventPropertyValue::UInt16 TelemetryEventBase::GetGeneratorAddress() const
{
	EventPropertyValue::UInt8 boardID;
	EventPropertyValue::UInt8 taskID;

	GetGeneratorAddress(boardID, taskID);

	EventPropertyValue::UInt16 result = MakeBoardAddress(boardID, taskID);
	return result;
}

void TelemetryEventBase::GetGeneratorAddress(EventPropertyValue::UInt8& boardID, EventPropertyValue::UInt8& taskID) const
{
	boardID = GetProperty(T_BoardIDTag).GetUInt8();
	taskID =  GetProperty(T_TaskIDTag).GetUInt8();
}

bool TelemetryEventBase::HasGeneratorAddress() const
{
	return ContainsProperty(T_BoardIDTag) && ContainsProperty(T_TaskIDTag);
}

void TelemetryEventBase::SetGeneratorAddress(EventPropertyValue::UInt16 boardAddress)
{
	EventPropertyValue::UInt8 boardID;
	EventPropertyValue::UInt8 taskID;

	BreakBoardAddress(boardAddress, boardID, taskID);
	SetGeneratorAddress(boardID, taskID);
}

void TelemetryEventBase::SetGeneratorAddress(EventPropertyValue::UInt8 boardID, EventPropertyValue::UInt8 taskID)
{
	PropertyValueType bid(broker), tid(broker);
	bid.SetUInt8(boardID);
	tid.SetUInt8(taskID);

	SetProperty(T_BoardIDTag, bid);
	SetProperty(T_TaskIDTag, tid);
}

void TelemetryEventBase::ClearGenerationTime()
{
	if (ContainsProperty(T_GenerationTimeTagSec))		ClearProperty(T_GenerationTimeTagSec);
	if (ContainsProperty(T_GenerationTimeTagMicroSec))	ClearProperty(T_GenerationTimeTagMicroSec);
}

timeval TelemetryEventBase::GetGenerationTime() const
{
	timeval result;
	result.tv_sec = GetProperty(T_GenerationTimeTagSec).GetUInt32();
	result.tv_usec = GetProperty(T_GenerationTimeTagMicroSec).GetUInt32();

	return result;
}

bool TelemetryEventBase::HasGenerationTime() const
{
	return ContainsProperty(T_GenerationTimeTagSec) && ContainsProperty(T_GenerationTimeTagMicroSec);
}

void TelemetryEventBase::SetGenerationTime(const timeval& newValue)
{
	PropertyValueType secs(broker), usecs(broker);
	secs.SetUInt32(newValue.tv_sec);
	usecs.SetUInt32(newValue.tv_usec);

	SetProperty(T_GenerationTimeTagSec, secs);
	SetProperty(T_GenerationTimeTagMicroSec, usecs);
}

void TelemetryEventBase::ClearGenerationLocation()
{
	if (ContainsProperty(T_LatitudeTag))		ClearProperty(T_LatitudeTag);
	if (ContainsProperty(T_LongitudeTag))		ClearProperty(T_LongitudeTag);
}

void TelemetryEventBase::GetGenerationLocation(EventPropertyValue::Int32& latitude, EventPropertyValue::Int32& longitude) const
{
	EventPropertyValue::DoubleFloat lat, lon;

	GetGenerationLocation(lat, lon);

	latitude = ConvertDoublePosToInt(lat);
	longitude = ConvertDoublePosToInt(lon);
}

void TelemetryEventBase::GetGenerationLocation(EventPropertyValue::DoubleFloat& latitude, EventPropertyValue::DoubleFloat& longitude) const
{
	latitude = GetProperty(T_LatitudeTag).GetDoubleFloat();
	longitude = GetProperty(T_LongitudeTag).GetDoubleFloat();
}

bool TelemetryEventBase::HasGenerationLocation() const
{
	return ContainsProperty(T_LatitudeTag) && ContainsProperty(T_LongitudeTag);
}

void TelemetryEventBase::SetGenerationLocation(const EventPropertyValue::Int32& latitude, const EventPropertyValue::Int32& longitude)
{
	SetGenerationLocation(ConvertIntPosToDouble(latitude), ConvertIntPosToDouble(longitude));
}

void TelemetryEventBase::SetGenerationLocation(const EventPropertyValue::DoubleFloat& latitude, const EventPropertyValue::DoubleFloat& longitude)
{
	PropertyValueType lat(broker), lon(broker);
	lat.SetDoubleFloat(latitude);
	lon.SetDoubleFloat(longitude);

	SetProperty(T_LatitudeTag, lat);
	SetProperty(T_LongitudeTag, lon);
}

EventPropertyValue::Int32 TelemetryEventBase::ConvertDoublePosToInt(EventPropertyValue::DoubleFloat dblLoc)
{
	EventPropertyValue::Int32 sign = (dblLoc < 0) ? -1 : 1;
	EventPropertyValue::DoubleFloat absLoc = sign*dblLoc;
	EventPropertyValue::Int32 degrees = (EventPropertyValue::Int32) floor(absLoc);
	EventPropertyValue::DoubleFloat temp = 60*(absLoc-degrees);
	EventPropertyValue::Int32 minutes = (EventPropertyValue::Int32) floor(temp);
	EventPropertyValue::Int32 remainder = (EventPropertyValue::Int32) (10000L*(temp-minutes)+0.5);
	EventPropertyValue::Int32 result = sign*(degrees*600000L + minutes*10000L + remainder);
	return result;
}

EventPropertyValue::DoubleFloat TelemetryEventBase::ConvertIntPosToDouble(EventPropertyValue::Int32 intLoc)
{
	EventPropertyValue::Int32 sign = (intLoc < 0) ? -1 : 1;
	EventPropertyValue::Int32 temp = intLoc*sign;
	EventPropertyValue::Int32 degrees = temp/600000L;
	EventPropertyValue::Int32 minutes = (temp%600000L)/10000L;
	EventPropertyValue::Int32 remainder = temp%10000L;
	EventPropertyValue::DoubleFloat result = sign*(degrees + minutes/60.0 + remainder/600000.0);
	return result;
}

void TelemetryEventBase::Init()
{
	timeval tv;

	if (!gettimeofday(&tv, NULL))
		SetGenerationTime(tv);
}

#if 0
void TelemetryEventBase::ClearTargetAddress()
{
	if (ContainsProperty(T_TargetBoardIDTag))		ClearProperty(T_TargetBoardIDTag);
	if (ContainsProperty(T_TargetTaskIDTag))		ClearProperty(T_TargetTaskIDTag);
}

EventPropertyValue::UInt16 TelemetryEventBase::GetTargetAddress() const
{
	EventPropertyValue::UInt8 boardID;
	EventPropertyValue::UInt8 taskID;

	GetTargetAddress(boardID, taskID);

	EventPropertyValue::UInt16 result = MakeBoardAddress(boardID, taskID);
	return result;
}

void TelemetryEventBase::GetTargetAddress(EventPropertyValue::UInt8& boardID, EventPropertyValue::UInt8& taskID) const
{
	boardID = GetProperty(T_TargetBoardIDTag).GetUInt8();
	taskID =  GetProperty(T_TargetTaskIDTag).GetUInt8();
}

bool TelemetryEventBase::HasTargetAddress() const
{
	return ContainsProperty(T_TargetBoardIDTag) && ContainsProperty(T_TargetTaskIDTag);
}

void TelemetryEventBase::SetTargetAddress(EventPropertyValue::UInt16 boardAddress)
{
	EventPropertyValue::UInt8 boardID;
	EventPropertyValue::UInt8 taskID;

	BreakBoardAddress(boardAddress, boardID, taskID);
	SetTargetAddress(boardID, taskID);
}

void TelemetryEventBase::SetTargetAddress(EventPropertyValue::UInt8 boardID, EventPropertyValue::UInt8 taskID)
{
	PropertyValueType bid(broker), tid(broker);
	bid.SetUInt8(boardID);
	tid.SetUInt8(taskID);

	SetProperty(T_TargetBoardIDTag, bid);
	SetProperty(T_TargetTaskIDTag, tid);
}
#endif

}
}
