//------------------------------------------------------------------------------
// <copyright file="GPSEventSource.h" 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>
//   Declarations for the GPSEventSource class, which patches data received from
//   a serial port into the NMEA receiver and fires events to clients.
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#ifndef GPSEVENTSOURCE_H_
#define GPSEVENTSOURCE_H_

#include "EventTransceiver.h"
#include "GPSEventSource.h"
#include "GPSSampleEvent.h"
#include "LqrSMACLockableConcept.h"
#include "LqrSMACDeclareException.h"
#include "LqrSMACGeodetic.h"
#include "LqrSMACIObjectBroker.h"
#include "LqrSMACQueueWorker.h"
#include "LqrSMACSemaphore.h"
#include "LqrSMACSerialPort.h"
#include "LqrSMACSharedLib.h"
#include "LqrSMACLogger.h"
#include "NMEAParser.h"
#include "LqrSMACSerialListener.h"
#include "LqrSMACLockableConcept.h"
#include "LqrSMACSignalSubscriber.h"
#include <map>
#include <stdexcept>
#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
#include <boost/thread.hpp>

namespace LiquidRobotics {
namespace GPSListenerLib {

class SHARED_LIB_PUBLIC GPSEventSource
{
public:

	typedef std::vector<unsigned> BaudRateVectorType;
	typedef LiquidRobotics::SMACFramework::ByteArray ByteArray;
	typedef LiquidRobotics::EventLib::EventTransceiver EventTransceiver;
	typedef LiquidRobotics::EventLib::GPSSampleEvent GPSSampleEvent;
	typedef LiquidRobotics::SMACFramework::Geodetic Geodetic;
	typedef LiquidRobotics::SMACFramework::IObjectBroker IObjectBroker;
	typedef LiquidRobotics::SMACFramework::ISerialPort ISerialPort;
	typedef LiquidRobotics::SMACFramework::Logger Logger;
	typedef LiquidRobotics::SMACFramework::LockableConceptMutex LockableConceptMutex;
	typedef boost::signals2::scoped_connection ScopedConnection;
	typedef boost::shared_ptr<ScopedConnection> ScopedConnectionPtr;
	typedef LiquidRobotics::SMACFramework::Semaphore Semaphore;
	typedef LiquidRobotics::SMACFramework::SerialListener SerialListener;
	typedef std::string string;
	typedef std::vector<string> StringArray;

	DECLARE_EXCEPTION(AlreadyInitialized,std::logic_error);
	DECLARE_EXCEPTION(NotInitialized,std::logic_error);

	GPSEventSource(IObjectBroker& _broker, ISerialPort& _sp, string::size_type _maxString, ISerialPort::TimeSpan _readTimeout, const BaudRateVectorType& brvect, Logger& _logger, Logger& _tonedDownLogger);
	virtual ~GPSEventSource();

	void Initialize();
	void Shutdown();

	void StartSimulation(double latCenter, double lonCenter, double speeKt, double radiusM, bool movingCW);
	void StopSimulation();

	typedef boost::signals2::signal<void (GPSEventSource& sender, const GPSSampleEvent& gpsSample)> GPSSampleSignalType;
private:
	LockableConceptMutex firingMutex;
	GPSSampleSignalType GPSSampleSignal;
public:
	LiquidRobotics::SMACFramework::SignalSubscriber<GPSSampleSignalType> gpsSampleSubscriber;

	typedef boost::signals2::signal<void (GPSEventSource& sender, const string& nmeaLine)> NMEASignalType;
private:
	NMEASignalType NMEASignal;
public:
	LiquidRobotics::SMACFramework::SignalSubscriber<NMEASignalType> nmeaSubscriber;

	typedef boost::signals2::signal<void (GPSEventSource& sender, bool online)> StatusSignalType;
private:
	StatusSignalType statusSignal;
public:
	LiquidRobotics::SMACFramework::SignalSubscriber<StatusSignalType> statusSubscriber;

	struct SimulationMode
	{
		SimulationMode();
		SimulationMode(Geodetic::Location _locCenter, double _speedKt, double _radiusM, bool _movingCW, double _angle = 0.0);

		double GetAngle() const;
		double GetAngle(double timeOffset) const;
		Geodetic::Location GetPosition() const;
		Geodetic::Location GetPosition(double timeOffset) const;

		bool simulating;
		Geodetic::Location locCenter;
		double speedKt;
		double radiusM;
		bool movingCW;
		double startTime;
		double angle;
	};

private:

	struct SerialPortReceiver
	{
		SerialPortReceiver(GPSEventSource& _eventSource);

		void operator()(SerialListener& sender, const ByteArray& receivedData);

		GPSEventSource& eventSource;
	};

	friend struct SerialPortReceiver;

	typedef boost::shared_ptr<SimulationMode> SimulationModePtr;
	typedef LiquidRobotics::SMACFramework::QueueWorker<SimulationModePtr> SimulationWorkerBase;

	class SimulationWorker : public SimulationWorkerBase
	{
	public:

		SimulationWorker() : enabled(false) { }

		virtual int DetermineReadTimeout();				// number of milliseconds to wait for read ready, <0=wait forever

		bool enabled;
	};

	class BaudRateTracker
	{
	public:

		BaudRateTracker(Logger& _logger, ISerialPort& _sp, const BaudRateVectorType& _brvect);

		void Initialize();
		void Shutdown();
		bool BaudRateSolid() const;
		void NoteGoodSample();
		void NoteBadSample();

	private:

		typedef boost::thread Thread;

		void Background();

		LockableConceptMutex mut;
		ISerialPort& sp;
		Logger& logger;
		BaudRateVectorType brvect;
		Semaphore sem;
		bool requestShutdown;
		bool baudRateSolid;
		unsigned numBadSamples;
		unsigned numGoodSamples;
		Thread thd;
	};

	class RepeatedErrorFilter
	{
	public:

		void ReportError(Logger::LogStream& stm, const string& errorMessage);

	private:

		typedef std::map<string,time_t> RepeatedErrorMapType;

		RepeatedErrorMapType repeatedErrorMap;
	};

	typedef boost::shared_ptr<NMEAParser> NMEAParserPtr;
	typedef boost::shared_ptr<SerialListener> SerialListenerPtr;
	typedef boost::shared_ptr<StringArray> StringArrayPtr;

	struct SentenceData
	{
		string sentence;
		StringArrayPtr sentenceArray;
	};

	typedef boost::shared_ptr<SentenceData> SentenceDataPtr;
	typedef std::map<string, SentenceDataPtr> SentenceMap;
	typedef std::map<string,size_t> ExpectedMapType;

	double ConvertPositionValue(double nmeaVal);
	static StringArrayPtr CopySentence(const StringArray& sentence);
	void EndTransmission();
	bool GetLatLon(const string& tag, const string& latNum, const string& latHemi, const string& lonNum, const string& lonHemi, double& latResult, double& lonResult);
	void NoteParsingError(NMEAParser& sender, const string& errorMessage);
	void ReceivedSerialData(const ByteArray& receivedData);
	//void ReceivedSentence(NMEAParser& sender, const string& rawLine, const StringArray& sentence);
	void ReceivedSentence(const string& rawLine, const StringArray& sentence, bool last);
	void OnSimulationWorkerItem(SimulationWorkerBase& sender, const SimulationModePtr& item);
	void OnSimulationWorkerTimeout(LiquidRobotics::SMACFramework::QueueWorkerBase& sender);
	void GPSOnlineTrackerBackground();

	IObjectBroker& broker;
	ISerialPort& sp;
	const string::size_type maxString;
	const ISerialPort::TimeSpan readTimeout;
	Logger& logger;
	Logger& tonedDownLogger;
	ScopedConnectionPtr serialPortReceiverConnection;
	SerialPortReceiver serialPortReceiver;
	SerialListenerPtr serialListener;
	bool initialized;
	bool shutDown;
	BaudRateTracker brTracker;
	SentenceMap sentenceMap;
	bool useRecommendedMinimum;
	ByteArray pendingBytes;
	ExpectedMapType expectedMap;
	RepeatedErrorFilter repeatedErrorFilter;
	LockableConceptMutex simMutex;
	SimulationMode simMode;
	SimulationWorker simWorker;
	bool gpsOnlineTrackerExit;
	Semaphore goodGPSReport;
	boost::shared_ptr<boost::thread> gpsOnlineTracker;
	ScopedConnectionPtr simWorkerWorkItemProcessedConnection;
	ScopedConnectionPtr simWorkerReadTimeoutConnection;

	static const string NMEATime;
	static const string NMEALocation;
	static const string NMEASpeed;
	static const string NMEARecommendedMinimum;
	static const string NMEAAccuracy;
};

}
}

#endif /* GPSEVENTSOURCE_H_ */
