//-------------------------------------------------------------------------------
// <copyright file="GPSSampleEvent.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>
//   Declaration of the GPSSampleEvent class, which models a GPS sample and which
//   can be used with the EventLib classes to forward events to subscribers.
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------

#include "GPSSampleEvent.h"
#include "nsextend.h"
#include <boost/foreach.hpp>
#include <sys/time.h>

using namespace std;
using namespace LiquidRobotics::EventLib;

namespace LiquidRobotics {
namespace EventLib {

const GPSSampleEvent::PropertyNameType GPSSampleEvent::timeTagSeconds("GPS-TimeSec");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::timeTagMicroseconds("GPS-TimeUSec");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::latitudeTag("GPS-Latitude");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::longitudeTag("GPS-Longitude");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::numSatellitesTag("GPS-NumSatellites");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::degreesTrueTag("GPS-DegreesTrue");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::degreesMagneticTag("GPS-DegreesMagnetic");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::speedKnotsTag("GPS-SpeedKnots");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::speedKmhTag("GPS-SpeedKmh");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::sentencesTag("GPS-NMEASentences");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::rmsAccuracyTag("GPS-RMSAccuracy");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::elevationTag("GPS-Elevation");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::magneticDeclinationTag("GPS-MagneticDeclination");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::simulatedTag("GPS-Simulated");
const GPSSampleEvent::PropertyNameType GPSSampleEvent::sourceNameTag("GPS-SourceName");

#define IMPL_PROP(proptag,proptype,propname,basetype) \
void GPSSampleEvent::Clear##propname () { if (ContainsProperty(proptag)) ClearProperty(proptag); } \
basetype GPSSampleEvent::Get##propname () const { return GetProperty(proptag).Get##proptype (); } \
bool GPSSampleEvent::Get##propname (basetype & outval) const { const bool result = Has##propname (); if (result) outval = Get##propname (); return result; } \
bool GPSSampleEvent::Has##propname () const { return ContainsProperty(proptag); } \
void GPSSampleEvent::Set##propname (const basetype & newValue) { PropertyValueType prop(broker); prop.Set##proptype (newValue); SetProperty(proptag, prop); }

GPSSampleEvent::GPSSampleEvent(IObjectBroker& _broker)
: Base(_broker)
{
}

GPSSampleEvent::GPSSampleEvent(const EventSettings& _eventSettings, IObjectBroker& _broker)
: Base(_eventSettings, _broker)
{
}

GPSSampleEvent::GPSSampleEvent(const EventName& _eventName, const EventType& _eventType, IObjectBroker& _broker)
: Base(_eventName, _eventType, _broker)
{
}

GPSSampleEvent::GPSSampleEvent(const EventName& _eventName, const EventType& _eventType, const IOR& _senderIOR, IObjectBroker& _broker)
: Base(_eventName, _eventType, _senderIOR, _broker)
{
}

GPSSampleEvent::GPSSampleEvent(const GPSSampleEvent& other)
: Base(other)
{
}

ostream& GPSSampleEvent::Describe(ostream& stm) const
{
	Base::Describe(stm);

	if (HasTime())
	{
		timeval t = GetTime();
		//time_t t2 = t.tv_sec - timezone;
		tm comp;
		gmtime_r(&t.tv_sec, &comp);

		stm << " time=" << t.tv_sec << " ("
						<< 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 (HasGPS())
	{
		stm << " latitude=" << setprecision(9) << GetLatitude()
		    << " longitude=" << setprecision(10) << GetLongitude()
			<< " satellites=" << GetNumSatellites();
	}

	if (HasElevation())
		stm << " elevation=" << setprecision(5) << GetElevation();

	if (HasRMSAccuracy())
		stm << " rmsAccuracy=" << setprecision(5) << GetRMSAccuracy();

	if (HasCourse())
	{
		double degreesTrue, degreesMagnetic, speedKnots, speedKmh;

		GetCourse(degreesTrue, degreesMagnetic, speedKnots, speedKmh);

		stm << " degreesTrue=" << setprecision(4) << degreesTrue
		    << " degreesMagnetic=" << setprecision(4) << degreesMagnetic
		    << " speedKnots=" << setprecision(4) << speedKnots
		    << " speedKmh=" << setprecision(4) << speedKmh;
	}

	if (HasMagneticDeclination())
		stm << " magneticDeclination=" << GetMagneticDeclination();

	if (HasSentences())
	{
		StringArray sentences;

		GetSentences(sentences);

		stm << " sentences (" << sentences.size() << ") = {";

		BOOST_FOREACH(string sentence, sentences)
			stm << " '" << sentence << "'";

		stm << " }";
	}

	if (GetSimulated())
		stm << " SIMULATED";

	return stm;
}

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

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

void GPSSampleEvent::ClearTime()
{
	if (ContainsProperty(timeTagSeconds))		ClearProperty(timeTagSeconds);
	if (ContainsProperty(timeTagMicroseconds))	ClearProperty(timeTagMicroseconds);
}

timeval GPSSampleEvent::GetTime() const
{
	timeval result;
	result.tv_sec = GetProperty(timeTagSeconds).GetUInt32();
	result.tv_usec = GetProperty(timeTagMicroseconds).GetUInt32();

	return result;
}

bool GPSSampleEvent::GetTime(timeval& outval) const
{
	const bool result = HasTime();

	if (result)
		outval = GetTime();

	return result;
}

bool GPSSampleEvent::HasTime() const
{
	return ContainsProperty(timeTagSeconds) && ContainsProperty(timeTagMicroseconds);
}

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

	SetProperty(timeTagSeconds, secs);
	SetProperty(timeTagMicroseconds, usecs);
}

void GPSSampleEvent::ClearGPS()
{
	if (ContainsProperty(latitudeTag))		ClearProperty(latitudeTag);
	if (ContainsProperty(longitudeTag))		ClearProperty(longitudeTag);
	if (ContainsProperty(numSatellitesTag))	ClearProperty(numSatellitesTag);
}

void GPSSampleEvent::GetGPS(double& latitude, double& longitude) const
{
	latitude = GetLatitude();
	longitude = GetLongitude();
}

double GPSSampleEvent::GetLatitude() const
{
	return GetProperty(latitudeTag).GetDoubleFloat();
}

double GPSSampleEvent::GetLongitude() const
{
	return GetProperty(longitudeTag).GetDoubleFloat();
}

unsigned GPSSampleEvent::GetNumSatellites() const
{
	return GetProperty(numSatellitesTag).GetUInt32();
}

bool GPSSampleEvent::HasGPS() const
{
	return ContainsProperty(latitudeTag) && ContainsProperty(longitudeTag) && ContainsProperty(numSatellitesTag);
}

void GPSSampleEvent::SetGPS(double latitude, double longitude, unsigned numSatellites)
{
	PropertyValueType lat(broker), lon(broker), numSat(broker);
	lat.SetDoubleFloat(latitude);
	lon.SetDoubleFloat(longitude);
	numSat.SetUInt32(numSatellites);

	SetProperty(latitudeTag, lat);
	SetProperty(longitudeTag, lon);
	SetProperty(numSatellitesTag, numSat);
}

void GPSSampleEvent::ClearCourse()
{
	if (ContainsProperty(degreesTrueTag))		ClearProperty(degreesTrueTag);
	if (ContainsProperty(degreesMagneticTag))	ClearProperty(degreesMagneticTag);
	if (ContainsProperty(speedKnotsTag))		ClearProperty(speedKnotsTag);
	if (ContainsProperty(speedKmhTag))			ClearProperty(speedKmhTag);
}

void GPSSampleEvent::GetCourse(double& degreesTrue, double& degreesMagnetic, double& speedKnots, double& speedKmh) const
{
	degreesTrue = GetProperty(degreesTrueTag).GetDoubleFloat();
	degreesMagnetic = GetProperty(degreesMagneticTag).GetDoubleFloat();
	speedKnots = GetProperty(speedKnotsTag).GetDoubleFloat();
	speedKmh = GetProperty(speedKmhTag).GetDoubleFloat();
}

bool GPSSampleEvent::HasCourse() const
{
	return ContainsProperty(degreesTrueTag)
		&& ContainsProperty(degreesMagneticTag)
		&& ContainsProperty(speedKnotsTag)
		&& ContainsProperty(speedKmhTag);
}

void GPSSampleEvent::SetCourse(double degreesTrue, double degreesMagnetic, double speedKnots, double speedKmh)
{
	PropertyValueType degT(broker), degM(broker), spdN(broker), spdK(broker);
	degT.SetDoubleFloat(degreesTrue);
	degM.SetDoubleFloat(degreesMagnetic);
	spdN.SetDoubleFloat(speedKnots);
	spdK.SetDoubleFloat(speedKmh);

	SetProperty(degreesTrueTag, degT);
	SetProperty(degreesMagneticTag, degM);
	SetProperty(speedKnotsTag, spdN);
	SetProperty(speedKmhTag, spdK);
}

void GPSSampleEvent::ClearSentences()
{
	if (ContainsProperty(sentencesTag))		ClearProperty(sentencesTag);
}

bool GPSSampleEvent::HasSentences() const
{
	return ContainsProperty(sentencesTag);
}

void GPSSampleEvent::GetSentences(StringArray& sentences) const
{
	sentences.clear();

	PropertyValueType::StringVectorPtr sent = GetProperty(sentencesTag).GetStringVector();

	if (sent)
	{
		sentences.clear();
		sentences.swap(*sent);
	}
}

void GPSSampleEvent::SetSentences(const StringArray& sentences)
{
	PropertyValueType sent(broker);
	sent.SetStringVector(sentences);

	SetProperty(sentencesTag, sent);
}

IMPL_PROP(rmsAccuracyTag,DoubleFloat,RMSAccuracy,double)
IMPL_PROP(elevationTag,DoubleFloat,Elevation,double)
IMPL_PROP(magneticDeclinationTag,DoubleFloat,MagneticDeclination,double)

bool GPSSampleEvent::GetSimulated() const
{
	bool result;

	if (ContainsProperty(simulatedTag))
		result = GetProperty(simulatedTag).GetBoolean();
	else
		result = false;

	return result;
}

void GPSSampleEvent::SetSimulated(bool newValue)
{
	PropertyValueType sent(broker);
	sent.SetBoolean(newValue);

	SetProperty(simulatedTag, sent);
}

string GPSSampleEvent::GetSourceName() const
{
	string result;

	if (ContainsProperty(sourceNameTag))
		result = GetProperty(sourceNameTag).GetString();

	return result;
}

void GPSSampleEvent::SetSourceName(const string& sourceName)
{
	PropertyValueType sent(broker);
	sent.SetString(sourceName);

	SetProperty(sourceNameTag, sent);
}

}
}
