//-------------------------------------------------------------------------------
// <copyright file="SBGPCTDController.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 SBGPCTDController class, which implements the controller
//  role business logic for the SeabirdGPCTD program
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------

#ifndef SBGPCTDCONTROLLER_H_
#define SBGPCTDCONTROLLER_H_

#include "SBGPCTDClassConfig.h"
#include "SBGPCTDControllerPersistSettings.h"
#include "SBGPCTDControllerSettings.h"
#include "SBGPCTDSensor.h"
#include "SensorController.h"
#include "LqrSMACCSVSampleFileSet.h"
#include "LqrSMACSemaphore.h"
#include "LqrSMACLockableConcept.h"
#include <boost/signals2.hpp>
#include <boost/shared_ptr.hpp>

namespace LiquidRobotics {

namespace EventLib {
	class GPSSampleEvent;
};

namespace SMACFramework {
	class CmdOutputFormatter;
}

namespace SeabirdGPCTD {

/*! \brief Defines the base class for the SBGPCTDController
 *  \sa SBGPCTDControllerBase0
 *
 *  This derived class adds default implementation with SensorController; command
 *  clases can access the functions they need through the abstract base class
 *  (SBGPCTDControllerBase0)
 */
typedef EventLib::SensorController<SBGPCTDControllerBase0,SBGPCTDControllerCommand,SBGPCTDCmdID,EventLib::SensorControllerExceptions> SBGPCTDControllerBase;

/*! \brief Controller role implementation class for SeabirdGPCTD program
 *  \sa SBGPCTDControllerBase, SBGPCTDControllerBase0, SBGPCTDProgram, SBGPCTDSensor
 *
 *  The Controller role is to:
 *
 *  - sit between a Program role class and the sensor
 *  - implement non-CORBA business rules
 *  - handle incoming commands (create any temporary command objects and
 *    coordinate their execution)
 *  - provide services to the command classes
 *  - fire any appropriate events to the Program role class
 *  - directly interface the sensor or provide access to a Sensor role class
 *    that directly interfaces the sensor
 */
class SHARED_LIB_PUBLIC SBGPCTDController
: public SBGPCTDControllerBase
{
public:

	//! Defines the typedefs for LockableConcept classes (locks synchronization objects)
	LOCKABLE_CONCEPT_TYPEDEFS();

	//! Define a smart pointer for the CommandQueue (declared in base class)
	typedef boost::shared_ptr<CommandQueue> CommandQueuePtr;

	//! Define a short-cut for the base class
	typedef SBGPCTDControllerBase Base;

	//! Define a short-cut for string (don't want "using namespace" in header files)
	typedef std::string string;

	/*! \brief Class constructor
	 *  \sa Initialize()
	 *
	 *  Performs basic construction, but doesn't allocate any resources.
	 *  Call Initialize() before attempting to use the other methods.
	 */
	SBGPCTDController(
			const SBGPCTDControllerSettings& _settings,			/**< [in] Reference to static settings entity */
			IObjectBroker& _broker,								/**< [in] Reference to wrapper for CORBA object broker */
			Logger& _logger,									/**< [in] Reference to logger class to use during operation */
			GPSSignalSubscriber::Ptr _gpsSubscriber,			/**< [in] Subscriber object that gives the Controller the option of getting GPS sample events */
			ShutdownSignalSubscriber::Ptr _shutdownSubscriber	/**< [in] Subscriber object that gives the Controller the option of getting notified on impending system shutdown */
			);

	/*! \brief Class destructor
	 */
	virtual ~SBGPCTDController();

	/*! \brief Class instance initialization.
	 *  \sa SBGPCTDController(), Shutdown()
	 *
	 *  Performs second-stage preparation for execution (after construction.)
	 *  Class members should not be called until Initialize() is called.
	 */
	void Initialize();

	/*! \brief Class shutdown.
	 *  \sa Initialize()
	 *
	 *  Shuts down any services that were opened in Initialize() or during operation
	 *  and unsubscribes from any events.
	 */
	void Shutdown();

	/*! \brief Informs all subscribers that the telemetry configuration has changed (implements abstract member)
	 *  \sa sampleTelemetryChange, sampleTelemetryChangeSignal, SampleTelemetryChangeSignalType
	 *
	 * This method is used by command classes (and others) after the persistent
	 * settings for telemetry reporting have been changed.  This is done by firing
	 * the sampleTelemetryChangeSignal
	 */
	virtual void ConfigureBlockReporting();

	/*! \brief Gets a pointer to the sensor interface class (implements abstract member)
	 *  \returns Smart pointer to the sensor interface class (throws if cannot be validly instantiated and initialized)
	 *  \sa SBGPCTDSensor
	 *
	 * Returns any existing pointer to the sensor interface, or creates a new one and
	 * attempts to initialize it.  If "turnOn" is set, and if the pointer is new, an
	 * attempt will be made to communicate with the sensor before returning it.  Command
	 * classes that communicate with the sensor call this method.
	 */
	virtual SBGPCTDSensor::Ptr GetSensorPtr(
			bool turnOn = true			/**< [in] true (default) if the sensor should be functional, false if it doesn't matter */
			);

	/*! \brief Get a reference to the persistent settings class (implements abstract member)
	 *  \returns A reference to the persistent settings class
	 *  \sa ConfigureBlockReporting(), SBGPCTDControllerPersistSettings
	 *
	 * Commands that reconfigure the behavior and save those configurations call this
	 * member.
	 */
	virtual SBGPCTDControllerPersistSettings& GetPersistSettings() const;

	/*! \brief Get a reference to the static configuration settings (implements abstract member)
	 *  \returns A reference to the static configuration settings
	 *  \sa SBGPCTDControllerSettings
	 *
	 * Commands that need access to the static settings call this member.
	 */
	virtual const SBGPCTDControllerSettings& GetSettings() const;

	/*! \brief Read the status summary string (implements abstract member)
	 *  \returns A string that summarize the sensor state
	 *
	 * Commands that return the summary status call this member.
	 */
	virtual string GetStatus();

	/*! \brief Reads the dissolved O2 installation flag (implements abstract member)
	 *  \returns true if the O2 sensor is installed, false if it is not installed
	 *
	 *  Note that the installation status is made when the program begins communicating
	 *  with the sensor: it doesn't check everytime that this method is called.
	 */
	virtual bool IsOxygenInstalled() const;

	/*! \brief Saves the current state of the persistent settings structure (implements abstract member)
	 *  \sa GetPersistSettings()
	 *
	 * Commands that change persistent settings call this method to be sure that
	 * the changes are saved.
	 */
	virtual void SavePersistentSettings();

	/*! \brief Turn power on or off to the sensor (implements abstract member)
	 *
	 * Turns power on or off to the sensor, depending on the parameter passed in.
	 * Note that turning power off to the sensor will require communication to be
	 * re-established before any useful commands can be executed.
	 */
	virtual void SetPower(
			bool on				/**< [in] true if turning the sensor on, false if turning the sensor off */
			);

	/*! \brief Start background sampling of data (implements abstract member)
	 *  \sa StopSampling(), Background()
	 *
	 * Samples received through background sampling will be reported as Boost events.
	 * The sampling loop uses the configurable flush time, sample period, block size
	 * and off time values to modify its operation.
	 */
	virtual void StartSampling();

	/*! \brief Stop backgorund sampling of data (implements abstract member)
	 *  \sa StartSampling()
	 */
	virtual void StopSampling();


	//! Defines the signal used to report GPCTD samples
	typedef boost::signals2::signal<void (SBGPCTDController& sender, const SBGPCTDSensor::Sample& sample, bool o2Installed)> SampleReportSignalType;
private:
	LockableConceptMutex firingMutex;				//!< A mutex that prevents deleting signals from this class while they are being used
	SampleReportSignalType sampleReportSignal;		//!< The signal used to report GPCTD samples
public:
	LiquidRobotics::SMACFramework::SignalSubscriber<SampleReportSignalType> sampleReport;	//!< The subscriber for GPCTD samples (used by clients)

	//! Defines the signal used to report the sensor serial number and the software version
	typedef boost::signals2::signal<void (SBGPCTDController& sender, const string& serialNumber, const string& softwareVersion)> SerialNumberReportSignalType;
private:
	SerialNumberReportSignalType serialNumberReportSignal;	//!< The signal used to report GPCTD sensor serial number and software version
public:
	LiquidRobotics::SMACFramework::SignalSubscriber<SerialNumberReportSignalType> serialNumberReport;	//!< The subscriber for serial number/software version (used by clients)

	//! Defines the signal used to report changes in the telemetry configuration
	typedef boost::signals2::signal<void (SBGPCTDController& sender, unsigned reportSize, unsigned sampleIntervalSec)> SampleTelemetryChangeSignalType;
private:
	SampleTelemetryChangeSignalType sampleTelemetryChangeSignal;	//!< The signal used to report changes in the telemetry configuration
public:
	LiquidRobotics::SMACFramework::SignalSubscriber<SampleTelemetryChangeSignalType> sampleTelemetryChange;	//!< The subscriber for telemtry config change events (used by clients)

protected:

	//! Define a short-cut for EventLib's GPSSampleEvent (don't want "using namespace" in header files)
	typedef LiquidRobotics::EventLib::GPSSampleEvent GPSSampleEvent;

	/*! \brief Handler for asynchronous samples from the sensor interface
	 *
	 * The controller uses this to log sample data and to forward it to the internal
	 * handler, which is used by the background sampling loop.
	 */
	virtual void OnAsynchronousSample(
			SBGPCTDSensor& sender,					/**< [in] Reference to the sender of the event (the sensor interface) */
			const SBGPCTDSensor::Sample& sample		/**< [in] Sample data reported */
			);

	/*! \brief Handler for GPS samples coming in from the Program class
	 *
	 * The controller saves the last sample and uses it timestamp and location stamp
	 * samples for the log or being reported to the Program
	 */
	virtual void OnGPSSampleEvent(
			const GPSSampleEvent& sample			/**< [in] Sample from the system */
			);

	/*! \brief Handler for raw messages from the sensor
	 *
	 * The controller uses this to populate the log, if logging of raw data
	 * is enabled.
	 */
	virtual void OnRawReceive(
			SBGPCTDSensor& sender,					/**< [in] Reference to the sender of the event (the sensor interface) */
			const string& message					/**< [in] The message received */
			);

	/*! \brief Handler for raw messages sent to the sensor
	 *
	 * The controller uses this to populate the log, if logging of raw data
	 * is enabled.
	 */
	virtual void OnRawSend(
			SBGPCTDSensor& sender,					/**< [in] Reference to the sender of the event (the sensor interface) */
			const string& message					/**< [in] The message that was sent */
			);

	/*! \brief Handler for the system shutdown notification (from the Program)
	 *
	 * This is not currently used.
	 */
	virtual void OnShutdownEvent();

	/*! \brief Command class dispatch routine (override of base class method)
	 *  \returns A smart pointer to command class instance (must not be nil); throws exceptions if the command is not recognized or invalid.
	 *
	 * The base class (SensorController) calls this method to get a command class
	 * instance given an incoming command.
	 */
	virtual CommandPtr ParseCommand(
			TCon::ParsingContext& context,			/**< [in] Parsing context (contains command line and information needed to respond asynchronously) */
			bool internalCmd = false				/**< [in] t	rue if internal command, false if coming from an external requester */
			);

	/*! \brief Indicate which output formatter to use
	 *  \returns A string indicating one of the output formatters (for example, "xml" or "plain")
	 *  \sa CmdOutputFormatterCF
	 */
	virtual string GetDefaultOutputFormatter() const;

private:

	//! Define a short-cut for SMACFramework's CSVSampleFileSet (don't want "using namespace" in header files)
	typedef LiquidRobotics::SMACFramework::CSVSampleFileSet CSVSampleFileSet;

	//! Define a short-cut for SMACFramework's Semaphore (don't want "using namespace" in header files)
	typedef LiquidRobotics::SMACFramework::Semaphore Semaphore;

	//! Define a short-cut for boost's thread (don't want "using namespace" in header files)
	typedef boost::thread Thread;

	//! Define a smart pointer to a thread
	typedef boost::shared_ptr<Thread> ThreadPtr;

	/*! \brief Background loop that cycles the pump and enables/disables sampling
	 *  \sa StartSampling(), StopSampling()
	 *
	 *  Pseudo-code for the polling process is:
	 *
	 *  \code
	 *  Begin
	 *
	 *    While no polling stop request
	 *
	 *      Try
	 *
	 *        Get a pointer to the sensor interface
	 *
	 *        If this is the first time throught the loop
	 *          Stop autonomous sampling (using sensor interface)
	 *          Set sampling interval (using sensor interface)
	 *        End If
	 *
	 *        If flush time is non-zero and the pump is not on
	 *          Turn the pump on fast (using sensor interface)
	 *          Sleep for flush time
	 *        End If
	 *
	 *        If not already sampling
	 *          Start sampling (using sensor interface)
	 *        End If
	 *
	 *        sample count = 0
	 *
	 *        While sample count < samples per packet and a sample is received within the sample period + 30 seconds
	 *          Increment sample count
	 *          Fire the sample report signal using the received sample data
	 *        End While
	 *
	 *        If no sample received within the last sample period
	 *          Throw runtime error
	 *        End If
	 *
	 *        If the off time is non-zero
	 *          Stop autonomous sampling (using sensor interface)
	 *
	 *          If off time < flush time then
	 *            Turn pump on fast (using sensor interface)
	 *            Wait for flush time
	 *          Else
	 *            Turn pump off (using sensor interface)
	 *            Wait for off time
	 *          End If
	 *        End If
	 *
	 *      Catch exception
	 *        If there is a sensor pointer
	 *          Turn off autonomous sampling (using sensor interface, ignoring exceptions)
	 *          Turn off the pump (using sensor interface, ignoring exceptions)
	 *          Turn off power to the sensor (using sensor interface, ignoring exceptions)
	 *        End If
	 *
	 *        Wait for 1 minute
	 *      End Try/Catch
	 *
	 *    End While
	 *
	 *    Clear background thread pointer
	 *    (Background thread exits)
	 *
	 *  End
	 *  \endcode
	 *
	 *  Note that this function is run on a separate thread (from StartSampling()):
	 *  it's never called directly from a user invocation.
	 */
	void Background(
			unsigned flushSec,			/**< [in] The number of seconds to leave pump on before start of sampling */
			unsigned periodSec,		/**< [in] The number of seconds between samples (sent to the sensor) */
			unsigned blockSize,		/**< [in] The number of samples to report as a block (affects telemetry config) */
			unsigned offSec			/**< [in] The number of seconds to keep sampling and pump off between blocks of samples */
			);

	/*! \brief Initialization that starts after PayloadManagerClient is valid
	 *  \sa Initialize()
	 *
	 *  Initialize() is called when PayloadManagerClient has not been initialized,
	 *  even though there is a reference to it.  Initialize() therefore starts
	 *  a thread and waits until it's safe to use PayloadManagerClient since it
	 *  is required to configure telemetry and before automatic sampling starts.
	 */
	void StartupStage2();

	/*! \brief Determines if O2 sensor is installed given HardwareData from the sensor
	 *  \returns true if O2 is installed, false if it is not
	 *  \sa o2Installed, IsOxygenInstalled()
	 *
	 * This function is used to set a value for the o2Installed flag when starting
	 * communication with the sensor.
	 */
	static bool IsO2Installed(const SBGPCTDSensor::HardwareData& hw);

	/*! \brief Determines the serial number string given HardwareData from the sensor
	 *  \returns A composite of the serial numbers reported by the sensor as a string
	 *  \sa serialNumber, deviceSoftwareVersion, serialNumberReport, serialNumberReportSignal
	 *
	 *  This function is used to report set values for serialNumber and deviceSoftwareVersion
	 *  and to supply data for the serialNumberReportSignal during device initialization.
	 */
	static string GetSerialNumber(const SBGPCTDSensor::HardwareData& hw);


	SBGPCTDSensor::AsynchronousSampleSignalType internalSampleReportSignal;		//!< signal that is used to forward asynchronous samples to internal subscribers
	LiquidRobotics::SMACFramework::SignalSubscriber<SBGPCTDSensor::AsynchronousSampleSignalType> internalSampleReport;  //!< subscriber for internal sample report
	const SBGPCTDControllerSettings& settings;				//!< Reference to the static settings entity
	SBGPCTDControllerPersistSettings::Ptr persist;			//!< Persistent settings entity
	double lat;												//!< Last saved latitude from GPS signal
	double lon;												//!< Last saved longitude from GPS signal
	double hasGPS;											//!< True if any GPS was received, false if not
	LockableConceptMutex getPtrMutex;						//!< A mutex to protect the operation of getting/initializing a Sensor pointer
	SBGPCTDSensor::Ptr sensorPtr;							//!< Smart pointer to the Sensor interface (nil if not allocated)
	bool sensorCommunicating;								//!< True if sensor communicating, false if not (even if pointer is not nil, communication needs to be re-stablished)
	bool o2Installed;										//!< True if o2 was determined to be installed after last comms, false if not installed or not determined
	string serialNumber;									//!< Sensor composite serial number that was determined during communication startup
	string deviceSoftwareVersion;							//!< Sensor software version determined during communication startup
	Semaphore backgroundSem;								//!< A semaphore that signals Background() if it should exit
	ThreadPtr backgroundThread;								//!< Pointer to the background thread (nil if not running)
	bool backgroundSampling;								//!< True if the sensor is performing asynchronous sampling, false if not
	bool backgroundRunning;									//!< True if Background() should be running, false if it should exit (post to backgroundSem)
	CSVSampleFileSet csvSamples;							//!< Sensor sample and raw I/O log
	const string persistFileName;							//!< Name of the persistent file
	ScopedConnectionPtr connectionToAsynchronousSample;		//!< Subscription to sensor interface asynchronous sample signal
	ScopedConnectionPtr connectionToRawReceive;				//!< Subscription to sensor interface raw data received signal
	ScopedConnectionPtr connectionToRawSend;				//!< Subscription to sensor interface raw data sent signal
};

//! Defines a smart pointer to SBGPCTDController
typedef boost::shared_ptr<SBGPCTDController> SBGPCTDControllerPtr;

}
}

#endif /* SBGPCTDCONTROLLER_H_ */
