//------------------------------------------------------------------------------
// <copyright file="GPSListenerProgram.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 the main module of GPS listener
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#include "GPSListenerProgram.h"
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <string>
#include <typeinfo>
#include "AwaitPPS.h"
#include "LqrSMACCmdLineParser.h"
#include "LqrSMACFilterLogger.h"
#include "LqrSMACGetErrorDescription.h"
#include "LqrSMACObjectBroker.h"
#include "LqrSMACSerialPort.h"
#include "LqrSMACStdOutLogger.h"
#include "LqrSMACUptime.h"
#include "nsextend.h"
#include "PayloadManagerClient.h"
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include "LqrSMACIdl.h"
#include "omniORB4/CORBA.h"
#include <float.h>
#include "GPSListenerTestProgram.h"

using namespace std;
using namespace LiquidRobotics::GPSListenerLib;
using namespace LiquidRobotics::SMAC;
using namespace LiquidRobotics::SMACFramework;
using namespace LiquidRobotics::EventLib;
using namespace CORBA;

#include "GPSListenerProgram_SensorAutoStartCmd.h"
#include "GPSListenerProgram_SensorDefaultOutputCmd.h"
#include "GPSListenerProgram_SensorOutputCmd.h"
#include "GPSListenerProgram_SensorSimulationCmd.h"
#include "GPSListenerProgram_SensorStartCmd.h"
#include "GPSListenerProgram_SensorStopCmd.h"

extern "C" SHARED_LIB_PUBLIC int GPSListenerProgram(int argc, char* argv[])
{
	int result = 1;
	bool rewritexml = false;

	for (int idx = 1; idx < argc; ++idx)
		if (boost::iequals(argv[idx], "--test"))
			return GPSListenerTestProgram(argc, argv);
		else if (boost::iequals(argv[idx], "--rewritexml"))
			rewritexml = true;

	tzset();

	try
	{
		GPSListenerSettings settings;
		const string xmlFileName = "GPSListener-config.xml";
		settings.Load(xmlFileName);
		if (rewritexml) settings.Save(xmlFileName);

		GPSListenerPersistentSettings persistentSettings;
		const string persistentFileName = "GPSListener-persistent-config.xml";
		persistentSettings.Load(persistentFileName, false);

		const string serverName = settings.GetServerName();
		const string serverKind = settings.GetServerKind();
		StdOutLogger logger(settings.GetLoggerSettings());
		FilterLogger tonedDownLogger(logger, Logger::LT_WARNING);

		ObjectBroker broker(true);
		broker.Initialize(argc, argv);

		SerialPortSettings::SerialPortPtr serialPort = settings.GetPortSettings().CreatePort(tonedDownLogger);
		GPSListenerProg prog(broker, settings, persistentSettings, persistentFileName, *serialPort, logger, tonedDownLogger);

		PortableServer::ServantBase_var theServer = 0;
		theServer = new ::POA_LiquidRobotics::SMAC::IPingableCommandableWithEvents_tie<GPSListenerProg>(&prog);

		Object_var serverRef = broker.AdvertizeInstance(theServer.in(), serverName.c_str(), serverKind.c_str());
		PayloadManagerClient pmClient(settings.PMClientSettings(), broker, prog);

		prog.Initialize(serverRef.in());
		pmClient.Register(serverRef.in());

		broker.ListenForRequests();

		prog.Shutdown();

		result = 0;
	}
	catch(CORBA::Exception& ex)
	{
		cout << "Caught CORBA exception " << DecodeCORBAException(ex) << endl;
	}
	catch(exception& ex)
	{
		cout << "Caught exception " << typeid(ex).name() << ": " << ex.what() << endl;
	}

	return result;
}

namespace LiquidRobotics {
namespace GPSListenerLib {

const string GPSListenerProg::SensorCategory = "GPS";
const string GPSListenerProg::AutoStartCmd = "AUTOSTART";
const string GPSListenerProg::DefaultOutputCmd = "DEFAULTOUTPUT";
const string GPSListenerProg::OutputCmd = "OUTPUT";
const string GPSListenerProg::ShowSimulationCmd = "SHOWSIMULATE";
const string GPSListenerProg::SimulationCmd = "SIMULATE";
const string GPSListenerProg::StartCmd = "START";
const string GPSListenerProg::StopCmd = "STOP";

GPSListenerProg::GPSListenerProg(ObjectBrokerType& _broker, const GPSListenerSettings& _settings,
			GPSListenerPersistentSettings& _persistentSettings, const string& _persistentFileName,
			ISerialPort& _serialPort, Logger& _logger, Logger& _tonedDownLogger)
: EventTransceiver(_settings.GetEventTransceiverSettings(), _broker, _logger)
, GPSEventSource(_broker, _serialPort, _settings.GetMaxString(), _settings.GetReadTimeout(), _settings.GetBaudRates(), _logger, _tonedDownLogger)
, broker(_broker)
, settings(_settings)
, persistentSettings(_persistentSettings)
, persistentFileName(_persistentFileName)
, serialPort(_serialPort)
, logger(_logger)
, tonedDownLogger(_tonedDownLogger)
, outputStarted(false)
, recordedOutputFileNameFailure(false)
, previouslySetHWClock(false)
, timePreviouslySetHWClock(0)
, receivedStatusNotification(false)
, lastStatusNotification(true)
{
}

GPSListenerProg::~GPSListenerProg()
{
}

void GPSListenerProg::Initialize(Object_ptr server)
{
	EventTransceiver::Initialize(server);
	GPSEventSource::Initialize();
	timeSetWorker.Initialize();
	slowWorker.Initialize();

	connectionToEventSource = gpsSampleSubscriber.Subscribe(boost::bind(&GPSListenerProg::HandleListenerEvent, this, _1, _2));
	connection2ToEventSource = nmeaSubscriber.Subscribe(boost::bind(&GPSListenerProg::HandleNMEAData, this, _1, _2));
	connection3ToEventSource = statusSubscriber.Subscribe(boost::bind(&GPSListenerProg::StatusChange, this, _1, _2));

	const EventSettings& hostGPSSampleEvent = settings.GetHostGPSSettings();
	RegisterEventClassFactory(hostGPSSampleEvent.GetType(), new EventTransceiver::TEventClassFactory<GPSSampleEvent>);

	// subscribe to GPS sample events
	InformSignalType& sig1 = SignalForEventName(hostGPSSampleEvent.GetName());
	connectionToHostGpsSampleEvent.reset(
			new ScopedConnection(
					sig1.connect(
							boost::bind(&GPSListenerProg::OnHostGPSSample, this, _1, _2)
							)));


	if (settings.GetSetTimeOnPPS())
		ppsThread.reset(new Thread(boost::bind(&GPSListenerProg::PPSThread, this)));

	connectionToTimeWorker = timeSetWorker.SubscribeWorkItemProcessed(boost::bind(&GPSListenerProg::HandleSetTimeWorker, this, _1, _2));
	connectionToSlowWorker = slowWorker.SubscribeWorkItemProcessed(boost::bind(&GPSListenerProg::HandleSlowWorker, this, _1, _2));

	if (persistentSettings.GetAutoStartOutput())
	{
		SetOutputFileName();
		StartSensor();
	}
}

void GPSListenerProg::Shutdown()
{
	if (ppsThread)
	{
		ppsInfo.StopRunning();
		ppsThread->join();
		ppsThread.reset();
	}

	connectionToSlowWorker.reset();
	connectionToTimeWorker.reset();
	connection2ToEventSource.reset();
	connectionToEventSource.reset();

	slowWorker.Shutdown();
	timeSetWorker.Shutdown();
	GPSEventSource::Shutdown();
	EventTransceiver::Shutdown();
}

inline void ReplaceAll(string& io, const string& replacement, const string& pattern)
{
	size_t loc;

	while((loc = io.find(pattern)) != string::npos)
	{
		stringstream newVal;

		if (loc)
			newVal << io.substr(0, loc);

		newVal << replacement;

		const size_t tailStart = loc+pattern.length();

		if (tailStart < io.length())
			newVal << io.substr(tailStart);

		io = newVal.str();
	}
}

string GPSListenerProg::GetOutputFileName() const
{
	Lock lockVar(const_cast<GPSListenerProg&>(*this));
	return outputFileName;
}

void GPSListenerProg::HandleListenerEvent(GPSEventSource& sender, const GPSSampleEvent& gpsSample)
{
	IEventBasePtr dup = gpsSample.Duplicate();
	GPSSampleEvent& dupSample = dynamic_cast<GPSSampleEvent&>(*dup);
	dupSample.SetSourceName("system");

	if (!gpsSample.GetSimulated() && gpsSample.HasTime())
	{
		if (settings.GetSetTimeOnPPS())
			ppsInfo.StoreTimeForPPS(gpsSample.GetTime());
		else
			timeSetWorker.EnqueueWorkItem(dup);
	}

	slowWorker.EnqueueWorkItem(dup);
}

void GPSListenerProg::HandleNMEAData(GPSEventSource& sender, const string& sentence)
{
	OutputNMEA(sentence);
}

void GPSListenerProg::ForwardEvent(const GPSSampleEvent& gpsSample)
{
	if (gpsSample.HasTime())
	{
		const GPSEventSpecArray specs = settings.GetGPSEventSpecs();
		const timeval tv = gpsSample.GetTime();

		for (GPSEventSpecArray::const_iterator ci = specs.begin(); ci != specs.end(); ++ci)
		{
			if (!(tv.tv_sec % ci->divisor))
			{
				GPSSampleEvent copy(gpsSample);
				copy.SetEventSettings(ci->eventSettings);

				try
				{
					if (logger.LogEnabled(Logger::LT_DEBUG))
					{
						Logger::Locked lockedLogger(logger);
						lockedLogger->Debug << "ForwardEvent ";
						copy.Describe(lockedLogger->Debug);
						lockedLogger->Debug << endl;
					}

					TransmitEvent(copy);
				}
				catch(CORBA::Exception& ex)
				{
					Logger::Lock logLock(logger);
					logger.Error << "Caught " << DecodeCORBAException(ex) << " while attempting to transmit event " << ci->eventSettings.GetName() << endl;
				}
			}
		}
	}
	else
	{
		Logger::Lock logLock(logger);
		logger.Info << "Not sending GPS event since there is no time component" << endl;
	}
}

void GPSListenerProg::OutputGPS(const GPSSampleEvent& gpsSample)
{
	if (gpsSample.HasTime() && gpsSample.HasGPS())
	{
		timeval tv = gpsSample.GetTime();
		const double StartEpoch = 25569.0;
		const double SecsPerDay = 86400.0;
		const double timeAsDays = (tv.tv_sec + tv.tv_usec/1000000.0)/SecsPerDay;
		const double thisTime = StartEpoch+timeAsDays;

		double lat, lon;
		gpsSample.GetGPS(lat, lon);

		stringstream line;

		// sig figs:
		//
		//   number of days since 1900 is within 5 digits, let's say 6
		//   the fraction is the number of seconds in a day (5) + 2 digits of fraction
		//   time sig figs should be 6+5+2 = 13
		//
		//   latitude is XX degrees + XX minutes + 5 digits of fractional minutes,
		//   or 9 total
		//
		//   longitude is XXX degrees + XX minutes + 5 digits of fractional minutes,
		//   or 10 total
		line << "GPS"
			 //<< "," << tv.tv_sec
		     << "," << setprecision(13) << thisTime
		     << "," << setprecision(9) << lat
		     << "," << setprecision(10) << lon;

		OutputLine(line.str());
	}

}

void GPSListenerProg::OutputLine(const string& line)
{
	Lock lockVar(*this);
	string fileName = GetOutputFileName();

	{
		Logger::Lock logLock(logger);
		logger.Debug << "OutputLine " << line;

		if (!outputStarted)
			logger.Debug << " (not started)";
		else
			logger.Debug << " >>>> " << fileName;

		logger.Debug << endl;
	}

	if (outputStarted)
	{
		boost::shared_ptr<ofstream> stmp;

		try
		{
			stmp.reset(new ofstream(fileName.c_str(), ios_base::out | ios_base::app));
			*stmp << line << endl;
		}
		catch(std::exception& ex)
		{
			if (stmp || !recordedOutputFileNameFailure)
			{
				if (!recordedOutputFileNameFailure)
					recordedOutputFileNameFailure = !!stmp;

				lockVar.unlock();

				Logger::Lock logLock(logger);
				logger.Error << "Caught " << typeid(ex).name() << " " << ex.what() << endl;
			}
		}
	}
}

void GPSListenerProg::OutputNMEA(const string& nmeaString)
{
	stringstream line;
	line << "NMEA," << nmeaString;

	OutputLine(line.str());
}

void GPSListenerProg::PPSThread()
{
	TimeCounter sinceLastSetting(0,false);
	TimeCounter sinceLastTimeoutReport(0,false);

	while(ppsInfo.IsRunning())
	{
		bool updateClock = false;

		try
		{
			AwaitPPS(settings.GetPPSGPIO(), 1000, true);
			timeval tvNew;
			const bool haveTime = ppsInfo.GetTimeToSet(tvNew);

//Logger::Locked(logger)->Error << "PPS running " << sinceLastSetting.IsRunning() << " elapsed " << sinceLastSetting.GetCount() << " havetime " << haveTime << endl;

			if ((!sinceLastSetting.IsRunning() || (sinceLastSetting.GetCount() >= 60*1000)) && haveTime)
			{
				sinceLastSetting.Reset(0,true);
				const int status = settimeofday(&tvNew, NULL);
				const int errSave = errno;

				if (status)
					Logger::Locked(logger)->Error << "settimeofday() failed with error #" << errSave << ": " << GetErrorDescription(errSave) << endl;
				else
				{
					updateClock = true;
					Logger::Locked(logger)->Info << "The system clock was successfuly set" << endl;
				}
			}

			if (sinceLastTimeoutReport.IsRunning())
			{
				Logger::Locked(logger)->Info << "PPS detected" << endl;
				sinceLastTimeoutReport.Reset(0,false);
			}
		}
		catch(exception& exc)
		{
			const string msg = exc.what();
			const bool isTimeout = boost::istarts_with(msg, "Timeout");

			if (!isTimeout || (!sinceLastTimeoutReport.IsRunning() || (sinceLastTimeoutReport.GetCount() >= 60000)))
				Logger::Locked(logger)->Error << "PPSThread: " << msg << endl;

			if (isTimeout && !sinceLastTimeoutReport.IsRunning())
				sinceLastTimeoutReport.Reset(0,true);

			if (!isTimeout)
			{
				for (unsigned counter = 60; counter-- && ppsInfo.IsRunning(); )
					sleep(1);
			}
		}

		if (updateClock)
			UpdateHWClock();
	}

	Logger::Locked(logger)->Info << "PPSThread existing" << endl;
}

void GPSListenerProg::SetTime(const GPSSampleEvent& gpsSample)
{
	// perhaps set the time with good time and location
	double acc;
	const bool hasAcc = gpsSample.GetRMSAccuracy(acc);
	const bool accOK = !hasAcc || (acc < settings.GetRMSAccuracyThreshold());

	if (!accOK)
		Logger::Locked(logger)->Warning << "RMS error level " << acc << " exceeds configured threshold " << settings.GetRMSAccuracyThreshold() << ": sample will not be sent" << endl;

	else if (gpsSample.HasTime() && gpsSample.HasGPS())
	{
		// get the current time, including fractional time
		const timeval tvNew = gpsSample.GetTime();

		timespec tsNew;
		tsNew.tv_sec = tvNew.tv_sec;
		tsNew.tv_nsec = 1000L*tvNew.tv_usec;

		timespec tsNow;
		clock_gettime(CLOCK_REALTIME, &tsNow);

		if (settings.ShouldSetTime(tsNow, tsNew))
		{
			bool wasSet = true;

			if (settings.GetDateSetDirectly())
			{
				Logger::Locked(logger)->Info << endl;

				int status = settimeofday(&tvNew, NULL);
				int errSave = errno;

				if (status)
				{
					Logger::Locked(logger)->Error << "settimeofday() failed with error #" << errSave << ": " << GetErrorDescription(errSave) << endl;
					wasSet = false;
				}
				else
					Logger::Locked(logger)->Info << "time of day was set directly" << endl;
			}
			else if (settings.GetDateVerb().length())
			{
				time_t newTime = tvNew.tv_sec;
				tm comp;
				gmtime_r(&newTime, &comp);

				string verb = settings.GetDateVerb();

				ReplaceAll(verb, utos(comp.tm_year+1900, 10, 4), "{yyyy}");
				ReplaceAll(verb, utos(comp.tm_year % 100, 10, 2), "{yy}");
				ReplaceAll(verb, utos(comp.tm_mon+1, 10, 2), "{mm}");
				ReplaceAll(verb, utos(comp.tm_mday, 10, 2), "{dd}");
				ReplaceAll(verb, utos(comp.tm_hour, 10, 2), "{HH}");
				ReplaceAll(verb, utos(((comp.tm_hour == 0) || (comp.tm_hour == 12)) ? 12 : (comp.tm_hour % 12), 10, 2), "{hh}");
				ReplaceAll(verb, utos(comp.tm_min, 10, 2), "{MM}");
				ReplaceAll(verb, utos(comp.tm_sec, 10, 2), "{SS}");
				string ampm = (comp.tm_hour > 12) ? "PM" : "AM";
				ReplaceAll(verb, ampm, "{AMPM}");
				ReplaceAll(verb, ampm, "{AM}");
				ReplaceAll(verb, ampm, "{PM}");

				Logger::Locked(logger)->Info << "Executing formatted date verb '" << verb << "'" << endl;

				int status = system(verb.c_str());
				int errSave = errno;

				if (status)
				{
					Logger::Locked(logger)->Error << "Date verb returned ";

					if (status == -1)
						Logger::Locked(logger)->Error << " error #" << errSave << ": " << GetErrorDescription(errSave) << endl;
					else
						Logger::Locked(logger)->Error << status << endl;

					wasSet = false;
				}
				else
					Logger::Locked(logger)->Debug << "Date verb returned successfully" << endl;
			}
			else
			{
				Logger::Locked(logger)->Info << "Time not set: not method was configured" << endl;
				wasSet = false;
			}

			if (wasSet)
				UpdateHWClock();
		}
		else
			Logger::Locked(logger)->Debug << "Time not set: insignificant time delta" << endl;
	}
	else
		Logger::Locked(logger)->Warning << "Sample lacking GPS or time component: sample will not be distributed" << endl;
}

void GPSListenerProg::UpdateHWClock()
{
	const string& hwverb = settings.GetHardwareClockVerb();

	if (hwverb.length())
	{
		if (settings.ShouldSetHWClock(previouslySetHWClock, timePreviouslySetHWClock, GetSystemUpTimeSecondsDouble()))
		{
			Logger::Locked(logger)->Info << "Executing hardware clock verb " << hwverb << endl;

			const int status = system(hwverb.c_str());
			const int errSave = errno;

			if (status == -1)
				Logger::Locked(logger)->Error << "Hardware clock verb returned error #" << errSave << ": " << GetErrorDescription(errSave) << endl;
			else if (status)
				Logger::Locked(logger)->Error << "Hardware clock verb returned " << status << endl;
		}
		else
			Logger::Locked(logger)->Debug << "Hardware clock is not due to be set" << endl;
	}
	else
		Logger::Locked(logger)->Debug << "Hardware clock verb was not configured" << endl;
}

void GPSListenerProg::SetOutputFileName()
{
	Lock lockVar(*this);
	SetOutputFileName(GetDefaultOutputFileName());
}

void GPSListenerProg::SetOutputFileName(const string& newValue)
{
	Lock lockVar(*this);
	outputFileName = newValue;
	recordedOutputFileNameFailure = false;
}

void GPSListenerProg::StartSensor()
{
	Lock lockVar(*this);
	outputStarted = true;
}

void GPSListenerProg::StopSensor()
{
	Lock lockVar(*this);
	outputStarted = false;
}

::CORBA::Double GPSListenerProg::Ping()
{
	using namespace LiquidRobotics::SMACFramework;
	stringstream msg;
	msg << "GPSListenerProg::Ping: ";

	::CORBA::Double result;
	timeval tv;
	int status = gettimeofday(&tv, NULL);
	int errSave = errno;

	if (!status)
	{
		result = tv.tv_usec;
		result /= 1000000.0;
		result += tv.tv_sec;
	}
	else
	{
		msg << "(gettimeofday returned error #" << errSave << " '" << GetErrorDescription(errSave) << "') ";
		result = 0;
	}

	Logger::Lock logLock(logger);
	logger.Info.precision(15);
	logger.Info << msg.str() << result << endl;

	return result;
}

void GPSListenerProg::SendCommand(const ::CORBA::Any& _command, const char* requester, const char* transactionID, ::CORBA::Any_OUT_arg result)
{
	string operation;

	try
	{
		operation = "unpacking command";

		const char* _cmd0;

		if (!(_command >>= _cmd0))
			ThrowCORBAException2<BadParameter>("command", "Non-string command types are not supported");

		const string commandStr = _cmd0;

		{
			Logger::Lock logLock(logger);
			logger.Info << "SendCommand command='" << commandStr << "' requester='" << requester << "' transactionID='" << transactionID << "'" << endl;
		}

		operation = "parsing command into tokens";
		CmdLineParser parser(commandStr);
		const StringArray& tokens = parser.GetTokens();
		ICommandBasePtr cmdp;

		if ((cmdp.reset(new SensorAutoStartCmd(*this)), !cmdp->Parse(tokens, requester, transactionID))
		 && (cmdp.reset(new SensorDefaultOutputCmd(*this)), !cmdp->Parse(tokens, requester, transactionID))
		 && (cmdp.reset(new SensorOutputCmd(*this)), !cmdp->Parse(tokens, requester, transactionID))
		 && (cmdp.reset(new SensorSimulationCmd(*this)), !cmdp->Parse(tokens, requester, transactionID))
		 && (cmdp.reset(new SensorStartCmd(*this)), !cmdp->Parse(tokens, requester, transactionID))
		 && (cmdp.reset(new SensorStopCmd(*this)), !cmdp->Parse(tokens, requester, transactionID)))

			ThrowCORBAException2<BadParameter>("command", "Unknown command type");

		if (!cmdp->RunImmediate(result))
			throw logic_error("GPSListenerProg does not support background execution of commands");
	}
	catch(CORBA::Exception& ex)
	{
		Logger::Lock logLock(logger);
		logger.Error << "GPSListenerProg::SendCommand caught " << DecodeCORBAException(ex) << " while " << operation << endl;
		throw;
	}
	catch(std::exception& ex)
	{
		stringstream msg;
		msg << "GPSListenerProg::SendCommand caught " << typeid(ex).name() << " '" << ex.what() << "' while " << operation;

		Logger::Lock logLock(logger);
		logger.Error << msg.str() << endl;

		ThrowCORBAException2<BadParameter>("command", msg.str());
	}
}

void GPSListenerProg::lock()
{
	mutex.lock();
}

bool GPSListenerProg::try_lock()
{
	return mutex.try_lock();
}

void GPSListenerProg::unlock()
{
	mutex.unlock();
}

bool GPSListenerProg::GetAutoStartStatus() const
{
	Lock lockVar(const_cast<GPSListenerProg&>(*this));
	return persistentSettings.GetAutoStartOutput();
}

void GPSListenerProg::SetAutoStartStatus(bool newValue)
{
	Lock lockVar(*this);

	if (persistentSettings.GetAutoStartOutput() != newValue)
	{
		persistentSettings.SetAutoStartOutput(newValue);
		persistentSettings.Save(persistentFileName);
	}
}

string GPSListenerProg::GetDefaultOutputFileName() const
{
	Lock lockVar(const_cast<GPSListenerProg&>(*this));
	string result;

	if (persistentSettings.HaveDefaultOutputFileName())
		result = persistentSettings.GetDefaultOutputFileName();
	else
		result = settings.GetDefaultOutputFileName();

	return result;
}

void GPSListenerProg::SetDefaultOutputFileName(const string& newValue)
{
	using namespace boost;
	Lock lockVar(*this);

	if (!persistentSettings.HaveDefaultOutputFileName() || !equals(newValue, persistentSettings.GetDefaultOutputFileName()))
	{
		if (!newValue.length())
			persistentSettings.ClearDefaultOutputFileName();
		else
			persistentSettings.SetDefaultOutputFileName(newValue);

		persistentSettings.Save(persistentFileName);
	}
}

void GPSListenerProg::HandleSetTimeWorker(EventQueueWorker& sender, IEventBasePtr evt)
{
	if (sender.CountEnqueued())
		Logger::Locked(logger)->Warning << "Ignoring time setting because there are additional settings waiting" << endl;
	else
	{
		const GPSSampleEvent& gpsSample = dynamic_cast<const GPSSampleEvent&>(*evt);
		SetTime(gpsSample);
	}
}

void GPSListenerProg::HandleSlowWorker(EventQueueWorker& sender, IEventBasePtr evt)
{
	const GPSSampleEvent& gpsSample = dynamic_cast<const GPSSampleEvent&>(*evt);

	if (!gpsSample.GetSimulated())
		OutputGPS(gpsSample);

	ForwardEvent(gpsSample);
}

void GPSListenerProg::StatusChange(GPSEventSource& sender, bool gpsOnline)
{
	receivedStatusNotification = true;
	lastStatusNotification = gpsOnline;

	if (gpsOnline)
	{
		EventBase gpsOnlineEvent(settings.GetSystemGPSOnlineSettings(), broker);
		TransmitEvent(gpsOnlineEvent);
	}
	else
	{
		EventBase gpsOfflineEvent(settings.GetSystemGPSOfflineSettings(), broker);
		TransmitEvent(gpsOfflineEvent);
	}
}

void GPSListenerProg::OnHostGPSSample(EventTransceiver& sender, const IEventBase& receivedEvent)
{
	if (receivedStatusNotification && !lastStatusNotification) {
		const EventSettings& hostGPSSampleEvent = settings.GetHostGPSSettings();
		const GPSSampleEvent& evt =
			EventTransceiver::UpcastEventRef<GPSSampleEvent>(receivedEvent, hostGPSSampleEvent,
					"SV3RemoteController::OnHostGPSSample");

		IEventBasePtr dup = evt.Duplicate();
		GPSSampleEvent& dupSample = dynamic_cast<GPSSampleEvent&>(*dup);

		if (dupSample.HasTime())
			timeSetWorker.EnqueueWorkItem(dup);

		slowWorker.EnqueueWorkItem(dup);
	}
}

}
}
