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

#ifndef __GPSListenerProgram_H__
#define __GPSListenerProgram_H__

#include "LqrSMACIdl.h"
#include "LqrSMACSharedLib.h"
#include "EventTransceiver.h"
#include "GPSEventSource.h"
#include "GPSListenerSettings.h"
#include "GPSListenerPersistentSettings.h"
#include "LqrSMACLockableConcept.h"
#include "LqrSMACObjectBroker.h"
#include "LqrSMACQueueWorker.h"
#include "LqrSMACTimeCounter.h"
#include <boost/thread.hpp>

extern "C" SHARED_LIB_PUBLIC int GPSListenerProgram(int argc, char* argv[]);

namespace LiquidRobotics {
namespace GPSListenerLib {

class SHARED_LIB_PUBLIC GPSListenerProg : public LiquidRobotics::EventLib::EventTransceiver
										, public GPSEventSource
										, public LiquidRobotics::SMACFramework::ICommandableBase
{
public:

	typedef LiquidRobotics::EventLib::IEventBase::Ptr IEventBasePtr;
	typedef LiquidRobotics::EventLib::EventTransceiver EventTransceiver;
	typedef LiquidRobotics::SMACFramework::ObjectBroker ObjectBrokerType;
	typedef boost::unique_lock<GPSListenerProg> Lock;
	typedef LiquidRobotics::SMACFramework::Logger Logger;
	typedef LiquidRobotics::SMACFramework::QueueWorker<IEventBasePtr> EventQueueWorker;
	typedef LiquidRobotics::SMACFramework::SerialPort SerialPort;
	typedef std::string string;

	GPSListenerProg(ObjectBrokerType& _broker, const GPSListenerSettings& _settings,
			GPSListenerPersistentSettings& _persistentSettings, const string& _persistentFileName,
			ISerialPort& _serialPort, Logger& _logger, Logger& _tonedDownLogger);
	virtual ~GPSListenerProg();

	void Initialize(Object_ptr server);
	void Shutdown();

	// IPingable operations
    virtual ::CORBA::Double Ping();

    // ICommandable
    virtual void SendCommand(const ::CORBA::Any& command, const char* requester, const char* transactionID, ::CORBA::Any_OUT_arg result);

	// Lockable concept used internally and usable by external actors
	virtual void lock();
	virtual bool try_lock();
	virtual void unlock();

    // This is the base class for command-specific derived classes; the use case is:
    //
    // (1) Construct the derived class, probably passing in a reference to the program
    //
    // (2) Call Parse with the command details, the recipient determines if it can handle the command
    //     and saves any parsed data locally if so, returns true (will handle command) or false (will
    //     not handle the command)  If the command may not complete immediately, copies of the
    //     requester and transactionID are made.
    //
    // (3) Assuming that the command will be handled, call RunImmediate, which executes the command as
    //     far as it can and returns true if complete (result has been set) or false if it is not
    //     complete.  RunImmediate throws runtime errors as necessary
    //
    // (4) If RunImmediate did not complete, the command via ICommandBase is executed on another thread
    //     with the Run command.
	class ICommandBase
	{
	public:
		virtual bool Parse(const StringArray& command, const char* _requester, const char* _transactionID) = 0;
		virtual bool RunImmediate(::CORBA::Any_OUT_arg& result) = 0;
		virtual void Run() = 0;
	};

	typedef boost::shared_ptr<ICommandBase> ICommandBasePtr;

private:

	typedef LiquidRobotics::SMACFramework::LockableConceptMutex LockableConceptMutex;
	typedef boost::signals2::scoped_connection ScopedConnection;
	typedef boost::shared_ptr<ScopedConnection> ScopedConnectionPtr;
	typedef std::vector<string> StringArray;
	typedef boost::thread Thread;
	typedef boost::shared_ptr<Thread> ThreadPtr;
	typedef LiquidRobotics::SMACFramework::TimeCounter TimeCounter;
	typedef LiquidRobotics::EventLib::IEventBase IEventBase;

	friend class SensorAutoStartCmd;
	friend class SensorDefaultOutputCmd;
	friend class SensorSimulationCmd;
	friend class SensorStartCmd;
	friend class SensorStopCmd;
	friend class SensorOutputCmd;

	bool GetAutoStartStatus() const;
	void SetAutoStartStatus(bool newValue);
	string GetDefaultOutputFileName() const;
	void SetDefaultOutputFileName(const string& newValue);
	string GetOutputFileName() const;
	void HandleListenerEvent(GPSEventSource& sender, const GPSSampleEvent& gpsSample);
	void HandleNMEAData(GPSEventSource& sender, const string& sentence);
	void HandleSetTimeWorker(EventQueueWorker& sender, IEventBasePtr evt);
	void HandleSlowWorker(EventQueueWorker& sender, IEventBasePtr evt);
	void ForwardEvent(const GPSSampleEvent& gpsSample);
	void OnHostGPSSample(EventTransceiver& sender, const IEventBase& receivedEvent);
	void OutputGPS(const GPSSampleEvent& gpsSample);
	void OutputLine(const string& line);
	void OutputNMEA(const string& nmeaString);
	void PPSThread();
	void SetTime(const GPSSampleEvent& gpsSample);
	void SetOutputFileName();
	void SetOutputFileName(const string& newValue);
	void StartSensor();
	void StatusChange(GPSEventSource& sender, bool gpsOnline);
	void StopSensor();
	void UpdateHWClock();

	struct PPSInfo
	{
		PPSInfo()
		: sinceSetting(0,false)
		, running(true)
		{
			lastTime.tv_sec = 0;
			lastTime.tv_usec = 0;
		}

		bool IsRunning()
		{
			LockableConceptMutex::Lock lock(mutex);
			return running;
		}

		void StopRunning()
		{
			LockableConceptMutex::Lock lock(mutex);
			running = false;
		}

		void StoreTimeForPPS(timeval newValue)
		{
			LockableConceptMutex::Lock lock(mutex);
			sinceSetting.Reset(0,true);
			lastTime = newValue;
		}

		bool GetTimeToSet(timeval& value)
		{
			LockableConceptMutex::Lock lock(mutex);

			if (sinceSetting.IsRunning() && (sinceSetting.GetCount() < 1000))
			{
				value = lastTime;
				++value.tv_sec;
				value.tv_usec = 0;
				return true;
			}
			else
				return false;
		}

		LockableConceptMutex mutex;
		TimeCounter sinceSetting;
		timeval lastTime;
		bool running;
	};

	LockableConceptMutex mutex;
	ObjectBrokerType& broker;
	const GPSListenerSettings& settings;
	GPSListenerPersistentSettings& persistentSettings;
	const string& persistentFileName;
	ISerialPort& serialPort;
	Logger& logger;
	Logger& tonedDownLogger;
	string outputFileName;
	bool outputStarted;
	bool recordedOutputFileNameFailure;
	bool previouslySetHWClock;
	double timePreviouslySetHWClock;
	bool receivedStatusNotification;
	bool lastStatusNotification;
	ThreadPtr ppsThread;
	PPSInfo ppsInfo;
	EventQueueWorker timeSetWorker;
	EventQueueWorker slowWorker;
	ScopedConnectionPtr connectionToEventSource;
	ScopedConnectionPtr connection2ToEventSource;
	ScopedConnectionPtr connection3ToEventSource;
	ScopedConnectionPtr connectionToTimeWorker;
	ScopedConnectionPtr connectionToSlowWorker;
	ScopedConnectionPtr connectionToHostGpsSampleEvent;

	static const string SensorCategory;
	static const string AutoStartCmd;
	static const string DefaultOutputCmd;
	static const string OutputCmd;
	static const string ShowSimulationCmd;
	static const string SimulationCmd;
	static const string StartCmd;
	static const string StopCmd;
};

}
}

#endif
