//----------------------------------------------------------------------------------
// <copyright file="SBDModem.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 SBDModem class, which sends messages too and
//   receives messages from an Iridium SBD modem (9601, 9602 or 9522B)  Applications
///  can use this class to implement communication scenarios.
// </summary>
//
// <owner>Mike Cookson</owner>
//----------------------------------------------------------------------------------

#ifndef __PPPHOSTLIB_SBDMODEM_H__
#define __PPPHOSTLIB_SBDMODEM_H__

#include "LqrSMACDeclareException.h"
#include "LqrSMACLockableConcept.h"
#include "LqrSMACSerialListener.h"
#include "LqrSMACSerialPort.h"
#include "LqrSMACSerialPortSettings.h"
#include "LqrSMACSharedLib.h"
#include "LqrSMACWaitableThreadSafeQueue.h"
#include <ostream>
#include <string>
#include <vector>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <stdint.h>

namespace LiquidRobotics {

namespace SMACFramework {
	class ByteArray;
	class Logger;
	class TimeCounter;
}

namespace PPPHostLib {

class SBDModemSettings;

/*! \class SBDModem
    \brief Messaging/transaction interface to Iridium modems

	SBDModem is a messaging and transaction interface class for Iridium
    9601/2/3 and 9522B/23 modems.  It contains methods for sending messages
    and expecting responses, and asynchronous events for incoming messages.
    The full use case with a modem must be implemented separately.
*/
class SHARED_LIB_PUBLIC SBDModem
{
public:

	// === Exception classes thrown by this class ===

	DECLARE_EXCEPTION(AccessDenied, std::runtime_error);
	DECLARE_EXCEPTION(ActionFailed, std::runtime_error);
	DECLARE_EXCEPTION(BusyTryLater, std::runtime_error);
	DECLARE_EXCEPTION(NetworkError, std::runtime_error);
    DECLARE_EXCEPTION(ProtocolError, std::runtime_error);
    DECLARE_EXCEPTION(ProtocolTimeout, std::runtime_error);

    typedef boost::shared_ptr<SBDModem> Ptr;
	typedef std::string string;
	typedef std::vector<string> StringArray;
	typedef boost::shared_ptr<StringArray> StringArrayPtr;
	typedef LiquidRobotics::SMACFramework::ByteArray ByteArray;
	typedef LiquidRobotics::SMACFramework::Logger Logger;
	typedef LiquidRobotics::SMACFramework::LockableConceptMutex LockableConceptMutex;
	typedef LiquidRobotics::SMACFramework::SerialListener SerialListener;
	typedef LiquidRobotics::SMACFramework::SerialListenerSettings SerialListenerSettings;
	typedef LiquidRobotics::SMACFramework::SerialPort SerialPort;
	typedef LiquidRobotics::SMACFramework::SerialPortSettings SerialPortSettings;
	typedef SerialPortSettings::SerialPortPtr SerialPortPtr;
	typedef LiquidRobotics::SMACFramework::TimeCounter TimeCounter;

	//! DTERate is the set of responses to the GetDTERate method, which sends "AT+IPR?"
	struct DTERate
	{
		//! Constructor for the DTERate with response from AT+IPR?
		DTERate(const string& iprResponse /**< [in] Response line from AT+IPR? */);

		//! Constructor for the DTERate class with components specified
		DTERate(unsigned _rateID,		/**< [in] The first parameter returned by AT+IPR? */
				bool _autoBaud = false	/**< [in] The second parameter returned by AT+IPR? -- can be left blank to default to false, as would be the case for 9601's */);

		unsigned rateID;				//!< The first parameter returned by AT+IPR?
										//!< 1=600 bps
										//!< 2=1200 bps
										//!< 3=2400 bps
										//!< 4=4800 bps
										//!< 5=9600 bps
										//!< 6=19200 bps (default)
										//!< 7=38400 bps
										//!< 8=57600 bps
										//!< 9=115200 bps

		unsigned rate;					//!< the literal baud rate corresponding with rateID

		bool autoBaud;					//!< the second parameter returned by AT+IPR?: true if
										//!< autobaud is enabled, false if not enabled  Note
										//!< that autobaud is not supported by the 9601; in
										//!< that case, the member will be false
	};

	//! IndicatorEventReport encapsulates CIEV asynchronous report data
	struct IndicatorEventReport
	{
		//! EReportType enumerates the kinds of reports that are reported by CIEV
		enum EReportType
		{
			ERP_SERVICE_AVAILABLE = 0,		//< The parameter indicates whether service is
											//< available (0=no 1=yes)
			ERP_SIGNAL_STRENGTH = 1,		//< The parameter indicates the signal strength (0..5)
			ERP_ANTENNA_FAULT = 2			//< The parameter indicates whether there is an antenna
											//< fault (0=no 1=yes)
		};

		//! Constructor for the IndicatorEventReport class, passing in the tail of the report
		//! string (i.e. the data after +CEIV:)
		IndicatorEventReport(
				const string& tail 			/**< [in] The tail of the report string (data after +CIEV:) */);

		//! Constructor for the IndicatorEventReport class, including values for each member
		IndicatorEventReport(
				EReportType _reportType,	/**< [in] The type of report that was made, the first parameter to +CIEV */
				int _param					/**< [in] The parameter value for the type of report, the second parameter to +CIEV */);

		EReportType reportType;				//< The type of report that was made, the first parameter to +CIEV
		int param;							//< The parameter value for the type of report, the second parameter to +CIEV
	};

	//! IndicatorEventReporting represents the settings for service, signal and antenna status event reporting (AT+CIER); it's used for GetIndicatorEventReporting and SetIndicatorEventReporting
	struct IndicatorEventReporting
	{
		//! Constructor for IndicatorEventReporting, parsing the tail of the return value from CIER?
		IndicatorEventReporting(
				const string& tail		/**< [in] The tail of the report string (data after +CIER:) */);

		//! Constructor for IndicatorEventReporting, with parameters to set the various components
		IndicatorEventReporting(
				bool _reportQualityChanges,		/**< [in] true if reporting signal quality changes (bars), false if not reporting signal quality changes */
				bool _reportServiceChanges,		/**< [in] true if reporting service changes, false if not reporting service changes */
				bool _haveAntennaFaultSetting = false,	/**< [in] true if there is a setting for antenna fault reporting, false if not */
				bool _reportAntennaFault = false);	/**< [in] true if reporting antenna faults, false if not reporting antenna faults */

		//! Converts to a display string
		string ToString() const;

		//! Converts the settings into a settings that can be appended to AT+CIER=
		string ToDataString() const;

		bool enabled;					//< Set by the constructor to be true if any of the report types are enabled
		bool reportQualityChanges;			//< Determines if signal quality changes (number of bars) are reported (true = report, false = do not report)
		bool reportServiceChanges;			//< Determines if changes in service availability are reported (true = report, false = do not report)
		bool haveAntennaFaultSetting;			//< Determines if the antenna fault parameter was in the report (or should be in the report)
		bool reportAntennaFault;			//< Determines if changes in antenna fault state are reported (true = report, false = do not report); note that this parameter is not supported by all modems
	};

	//! RingIndicatorRegStatus represents the state of ring indicator registration (+AREG)
	struct RingIndicatorRegStatus
	{
		//! Enumerations of the summary status
		enum RegistrationSummary
		{
			RIRS_EVENT_SUGGEST_MANUAL_REGISTRATION = 0,		/**< registration is lost, user should initiate ring indicator registration manually */
			RIRS_EVENT_AUTO_REG_SUCCESS = 1,				/**< automated registration was successful */
			RIRS_EVENT_AUTO_REG_FAIL_WILL_RETRY = 2			/**< automated registration failed, but another attempt will be made */
		};

		//! Enumreations of specific error codes
		enum RegistrationErrorCode
		{
			RIRS_NO_ERROR = 0,								/**< registration was successful or no error reported */
			RIRS_SUCCESS_BAD_LOCATION_UPDATE = 2,			/**< registration was successful, but the location updated failed */
			RIRS_FAILED_ACCESS_DENIED = 15,					/**< registration failed, access denied to network */
			RIRS_FAILED_ISU_LOCKED = 16,					/**< registration failed, ISU has been locked and may not make SBD calls (see +CULK command) */
			RIRS_FAILED_GATEWAY_TIMEOUT = 17,				/**< registration failed, Gateway not responding (local session timeout) */
			RIRS_FAILED_CONNECTION_LOST = 18,				/**< registration failed, Connection lost (RF drop) */
			RIRS_FAILED_LINK_FAILURE = 19,					/**< registration failed, Link failure (A protocol error caused termination of the call) */
			RIRS_FAILED_NO_NETWORK_SERVICE = 32,			/**< registration failed, No network service, unable to initiate call */
			RIRS_FAILED_ANTENNA_FAULT = 33,					/**< registration failed, Antenna fault, unable to initiate call */
			RIRS_FAILED_RADIO_DISABLED = 34,				/**< registration failed, Radio is disabled, unable to initiate call (see *Rn command) */
			RIRS_FAILED_TRANSCEIVER_BUSY = 35,				/**< registration failed, ISU is busy, unable to initiate call */
			RIRS_FAILED_TRY_LATER = 36						/**< registration failed, Try later, must wait 3 minutes since last registration */
		};

		//! RingIndicatorRegStatus constructor, with parameters for each member
		RingIndicatorRegStatus(
				unsigned _eventID,			/**< [in] Summary of registration state (RegistrationSummary), first parameter to +AREG */
				unsigned _regError			/**< [in] Registration error code (RegistrationErrorCode), second parameter to +AREG */);

		unsigned eventID;					//< Summary of registration state (RegistrationSummary), first parameter to +AREG
		unsigned regError;					//< Registration error code (RegistrationErrorCode), second parameter to +AREG

	};

	struct SessionStatusExtended
	{
        SessionStatusExtended();
		SessionStatusExtended(const string& response);
		SessionStatusExtended(unsigned _moStatus, unsigned _moMSN, unsigned _mtStatus, unsigned _mtMSN, unsigned _mtMessageSize, unsigned _mtCountQueued);

		bool GoodMOStatus() const;
		bool HaveMTMessage() const;
		bool GoodMTStatus() const;
        void ThrowError() const;

		unsigned moStatus;
		unsigned moMSN;
		unsigned mtStatus;
		unsigned mtMSN;
		unsigned mtMessageSize;
		unsigned mtCountQueued;

		enum MOStatusValues
		{
			MOSV_SUCCESS = 0,
			MOSV_SUCCESS_MT_MESSAGE_TOO_BIG = 1,
			MOSV_SUCCESS_BAD_LOCATION_UPDATE = 2,
			MOSV_CALL_TIMEOUT = 10,
			MOSV_QUEUE_FULL = 11,
			MOSV_TOO_MANY_SEGMENTS = 12,
			MOSV_SESSION_DID_NOT_COMPLETE = 13,
			MOSV_INVALID_SEGMENT_SIZE = 14,
			MOSV_ACCESS_DENIED = 15,
			MOSV_ISU_LOCKED = 16,
			MOSV_GATEWAY_TIMEOUT = 17,
			MOSV_CONNECTION_LOST = 18,
			MOSV_LINK_FAILURE = 19,
			MOSV_NO_NETWORK = 32,
			MOSV_ISU_BUSY = 35
		};

		enum MTStatusValues
		{
			MTSV_NO_SBD_MESSAGE = 0,
			MTSV_SBD_MESSAGE_RECEIVED = 1,
			MTSV_ERROR_OCCURRED = 2
		};
	};

	struct SBDStatus
	{
		SBDStatus(bool _haveMOMessage, bool _moMSN, bool _haveMTMessage, unsigned _mtMSN);
		SBDStatus(bool _haveMOMessage, bool _moMSN, bool _haveMTMessage, unsigned _mtMSN, bool _ringAlertPending, unsigned _mtCountQueued);

		bool haveMOMessage;
		unsigned moMSN;
		bool haveMTMessage;
		unsigned mtMSN;
		bool ringAlertPending;
		unsigned mtCountQueued;
		bool haveRAandMTCount;
	};

	//! NetworkRegistrationNotification enumerates the modes under which network registration operates
	enum NetworkRegistrationNotification {
		NREG_NOTIFY_OFF = 0,						/**< disables notification of network registration changes */
		NREG_NOTIFY_ON = 1,							/**< enables notification of network registration changes */
		NREG_NOTIFY_ON_WITH_LOCATIONS = 2			/**< enables notification of network registration changes with location info */
	};

	//! NetworkRegistrationStatus enumerates the status of network registration
	enum NetworkRegistrationStatus {
		NREG_STATUS_NOT_REGISTERED_IDLE = 0,		/**< not registered, not searching for operator */
		NREG_STATUS_REGISTERED_AT_HOME = 1,			/**< registered on home network */
		NREG_STATUS_NOT_REGISTERED_SEARCHING = 2,	/**< not registered, searching for operator */
		NREG_STATUS_REGISTRATION_DENIED = 3,		/**< registration was denied */
		NREG_STATUS_UNKNOWN = 4,					/**< registration is in an unknown state */
		NREG_STATUS_REGISTERED_ROAMING = 5			/**< registered, not on home network (roaming) */
	};

	//! NetworkRegistration is used by GetNetworkRegistration and SetNetworkRegistration to carry network registration mode settings.
	struct NetworkRegistration
	{
		//! Constructor when network registration is not supported by the
		//! modem (for example, 9601/2/3 do not support network registration)
		NetworkRegistration();

                //! Constructor from tail of the +CREG: message
                NetworkRegistration(const string& tail);

		//! Constructor when two parameters are present
		NetworkRegistration(
				unsigned _notification,				/**< [in] notification mode (NetworkRegistrationNotification), first parameter to +CREG */
				unsigned _status					/**< [in] status of registration (NetworkRegistrationStatus), second parameter to +CREG */);

		//! Constructor when four parameters are present
		NetworkRegistration(
				unsigned _notification,				/**< [in] notification mode (NetworkRegistrationNotification), first parameter to +CREG */
				unsigned _status,					/**< [in] status of registration (NetworkRegistrationStatus), second parameter to +CREG */
				const string& _lac,					/**< [in] third parameter to +CREG */
				const string& _ci					/**< [in] fourth parameter to +CREG */);

		//! Converts to a display string
		string ToString() const;

		unsigned notification;
		unsigned status;
		string lac;
		string ci;
		bool notSupported;
	};

	//! The data type representing the asynchronous event that reports indicators (IndicatorEventReport)
	typedef boost::signals2::signal<void (SBDModem& sender, const IndicatorEventReport& indicators)> IndicatorsSignalType;

	//! The data type representing the asynchronous event that reports the status of a ring indicator registration (RingIndicatorRegStatus)
	typedef boost::signals2::signal<void (SBDModem& sender, const RingIndicatorRegStatus& regStatus)> RegStatusSignalType;

	//! The data type representing the asynchronous event that reports availability of SBD data
	typedef boost::signals2::signal<void (SBDModem& sender)> RingSignalType;

	//! This is a synonym for the boost scoped_connection data type, which holds a subscription to an event/signal; the subscription is deleted when variable instance goes out of scope
	typedef boost::signals2::scoped_connection ScopedConnection;

	//! Smart pointer for ScopedConnection
	typedef boost::shared_ptr<ScopedConnection> ScopedConnectionPtr;

	//! Constructor for the SBD modem class.  Only sets parameter values: call Initialize before using the other class members
	SBDModem(
			Logger& _logger,				/**< [in] A logger to be used during operation */
			SBDModemSettings& _settings		/**< [in] Various settings to be used during operations */);

	//! Destructor for the SBDModem class; call Shutdown before deleting the instance
	virtual ~SBDModem();

	//! Allocates resources (which might fail and throw exceptions) and prepares the class for use.  Call Initialize just after construction before calling any of the other members
	virtual void Intialize();

	//! Deallocates resources allocated during operation or in Initialize; call Shutdown before deleting the instance.
	virtual void Shutdown();

	//! Clears the mobile-originated message serial number (MOMSN) to 0 by sending AT+SBDC; normally the MOMSN is maintained between power cycles.
	virtual void ClearMOMSN();

	//! Clears the mobile-originated message buffer (message to be sent) or the mobile-terminated message buffer (message received) or both.  An exception is thrown if neither buffer is marked to be cleared.
	virtual void ClearSBDBuffers(
			bool moBuffer,					/**< [in] Determines if the message send buffer (MO) should be cleared: true=clear, false=do not clear */
			bool mtBuffer					/**< [in] Determines if the message receive buffer (MT) should be cleared: true=clear, false=do not clear */);

	//! Configures the modem communication interface to be normal for this component to access it; call this right after Initialize if it's not clear whether the settings are correct.
	virtual void ConfigureInterface();

	//! Requests the details of the modem baud rate specification (AT+IPR?; see DTERate)
	virtual DTERate GetDTERate();

	//! Sets the details of the modem baud rate specification (AT+IPR=m,n; see DTERate)
	virtual void SetDTERate(
			const DTERate& newRate			/**< [in] Sets the baud rate and, in some cases, whether automatic baud rate determination is used */);

	//! Reads the state of indicator event reporting (AT+CIER?; service availability, signal level, antenna fault; see IndicatorEventReporting)
	virtual IndicatorEventReporting GetIndicatorEventReporting();

	//! Sets the state of indicator event reporting (AT+CIER=l,m.n; service availability, signal level, antenna faul; see IndicatorEventReporting)
	virtual void SetIndicatorEventReporting(
			const IndicatorEventReporting& newSettings /**< [in] New settings for indicator event reporting, see IndicatorEventReporting */);

	//! Reads the current status of network registration (AT+CREG?; see NetworkRegistration)
	virtual NetworkRegistration GetNetworkRegistration();

	//! Sets the mode of network registration (AT+CREG=n)
	virtual void SetNetworkRegistration(
			unsigned notification			/**< [in] New network registration mode (see NetworkRegistrationNotification) */);

	//! Reads the SIM card number (AT+CICCID)
	virtual string GetSIMCardNumber();

	//! Reads the manufacturer identification (AT+CGMI)
	virtual string GetManufacturer();

	//! Reads the model number (AT+CGMM)
	virtual string GetModel();

	//! Reads an array of revision strings (AT+CGMR)
	virtual StringArrayPtr GetRevisions();

	//! Reads whether ring indication is enabled (AT+SBDMTA?)
	virtual bool GetRingIndication();

	//! Sets whether the modem is listening for ring indications (AT+SBDMTA=n)
	virtual void SetRingIndication(
			bool newStatus					/**< [in] Determines if the modem is listening for ring indications (true) or not (false) */);

	//! Reads SBD status (AT+SBDS or AT+SBDSX, depending on what's supported; see SBDStatus)
	virtual SBDStatus GetSBDStatus();

	//! Reads the modem's IMEI number (AT+CGSN)
	virtual string GetSerialNumber();

    //! Reads the current signal strength (AT+CSQ or AT+CSQF)
    virtual unsigned GetSignalStrength(bool fast = false);

	//! Sends a short message to the modem to determine if it's communicating
	virtual void Ping();

	//! Reads the content of the mobile-terminated (MT or receive) buffer (AT+SBDRB)
	virtual string ReadMTBuffer();

	//! Reads the signal quality in bars, optionally waiting to perform a session (AT+CSQ or AT+CSQF)
	virtual unsigned ReadSignalQuality(
			bool wait = true				/**< [in] Determines if the command should perform a transaction (true) or return the last reading (false) */);

	//! Requests system time (90-ms intervals since start of epoch), throws action failed if no network service
	virtual uint32_t RequestSystemTime();

	//! Requests automatic registration of ring indication (AT+SBDAREG)
	virtual void RingIndicatorAutoRegister(
			bool enable,					/**< [in] Determines if registration of ring indicator is enabled (true) or disabled (false) */
			bool automatic = true			/**< [in] Determines if registration is automatic (true) or if it simply generates an event (false, call RingIndicatorRegister or execute a session with location) */);

	//! Detaches from the ISU, which prevents ring indications from being sent (AT+SBDDET)
	virtual void RingIndicatorDetach();

	//! Registers for ring indications, specifying the location (AT+SBDREG=lat,lon)
	virtual void RingIndicatorRegister(
			double latitude,				/**< [in] latitude in degrees, + for northern hemisphere and - for southern hemisphere */
			double longitude				/**< [in] longitude in degrees, + for eastern hemisphere and - for western hemisphere */);

	//! Executes session (AT+SBDIX or AT+SBDIXA) without specifying the location, but possibly as a result of ring indication; the MO (transmit) buffer is sent and a MT message may be received
	virtual SessionStatusExtended SessionExecute(
			bool fromRingIndicator = false  /**< [in] indicates whether ring indicator was received (true; use AT+SBDIXA) or not (false, use AT+SBDIX) */);

	//! Executes session (AT+SBDIX=lat,lon) specifying the current location
	virtual SessionStatusExtended SessionExecute(
			bool fromRingIndicator,			/**< [in] indicates whether ring indicator was received (true; use AT+SBDIXA) or not (false, use AT+SBDIX) */
			double latitude,				/**< [in] latitude in degrees, + for northern hemisphere and - for southern hemisphere */
			double longitude				/**< [in] longitude in degrees, + for eastern hemisphere and - for western hemisphere */);

	//! Writes the content of the MO (transmit) buffer (AT+SBDWB)
	virtual void WriteMOBuffer(
			const string& message			/**< [in] The message to be written to the buffer (can contain binary data) */);

	//! Returns the semaphore that should be locked before deleting the class instance (i.e. using ILockableConceptMutex::Lock) to prevent a potential deadlock
	virtual LockableConceptMutex& GetEventLock();

	//! Causes the user's routine (slot) to be subscribed to indicator events (+CIER: service availability, signal strength, antenna fault) while the return value is retained (deleting the return value unsubscribes.)  Warning: to prevent a deadlock while releasing the subscription, lock the event lock while releasing the subscription (see GetEventLocK)
	virtual ScopedConnectionPtr SubscribeIndicators(
			IndicatorsSignalType::slot_type slot  /**< [in] The caller's callback, for example boost::bind(&MyObject::MyFunction, this, _1, _2) */);

	//! Causes the user's routine (slot) to be subscribed to network registration events (+CREG) while the return value is retained (deleting the return value unsubscribes.)  Warning: to prevent a deadlock while releasing the subscription, lock the event lock while releasing the subscription (see GetEventLocK)
	virtual ScopedConnectionPtr SubscribeRegStatus(
			RegStatusSignalType::slot_type slot   /**< [in] The caller's callback, for example boost::bind(&MyObject::MyFunction, this, _1, _2) */);

	//! Causes the user's routine (slot) to be subscribed to ring indicator event (SBDRING) while the return value is retained (deleting the return value unsubscribes.)  Warning: to prevent a deadlock while releasing the subscription, lock the event lock while releasing the subscription (see GetEventLocK)
	virtual ScopedConnectionPtr SubscribeRing(
			RingSignalType::slot_type slot        /**< [in] The caller's callback, for example boost::bind(&MyObject::MyFunction, this, _1) */);

	//! Converts a baud rate to a rate ID as used in DTERate, optionally firing an exception (returns rate ID 0 if no match for baud rate and exception not thrown)
	static unsigned BaudRateToRateID(
			unsigned baudRate,				/**< [in] The baud rate for which to find the rate ID */
			bool throwOnError = true		/**< [in] Determines if an exception is thrown on error (true) or if a rate ID of 0 is returned on error (false) */);

	//! Converts a rate ID to a baud rate as used in DTERate, optionally firing an exception (returns baud rate 0 if no match for rate ID and exception not thrown)
	static unsigned RateIDToBaudRate(
			unsigned rateID,				/**< [in] The rate ID for which to find the baud rate */
			bool throwOnError = true		/**< [in] Determines if an exception is thrown on error (true) or if a baud rate of 0 is returned on error (false) */);

	//! Converts a system time number to boost time using the first epoch
	static boost::posix_time::ptime SystemTimeToBoostTime(
			uint32_t systemTime,			/**< [in] A system time in 90ms units since the start of the epoch */
			unsigned epochOffset = 0		/**< [in] Number of epochs to offset the time */);

protected:

	//! Callback for SerialListener's receive event, which is called whenever some data is received from the port with the appropriate inter-character gap
	virtual void OnSerialReceive(SerialListener& sender, const ByteArray& receivedData);

private:

    struct MessageFormatter
    {
        MessageFormatter(const char* _data)
        : data(_data)
        , isBinary(false)
        {
        }

        MessageFormatter(const string& _data)
        : data(_data)
        , isBinary(false)
        {
        }

        MessageFormatter(const char* _data, bool _isBinary)
        : data(_data)
        , isBinary(_isBinary)
        {
        }

        MessageFormatter(const string& _data, bool _isBinary)
        : data(_data)
        , isBinary(_isBinary)
        {
        }

        MessageFormatter(const MessageFormatter& rhs)
        : data(rhs.data)
        , isBinary(rhs.isBinary)
        {
        }

        operator const string&() const { return data; }
        string ToString() const;

        const string data;
        const bool isBinary;
    };

	LockableConceptMutex commandMutex;		//!< Mutex enforcing serialized commands
	LockableConceptMutex mutex;				//!< Mutex protecting the data
	LockableConceptMutex eventMutex;		//!< Mutex held while firing events (made available to clients during unsubscription
	IndicatorsSignalType indicatorsSignal;	//!< The signal that calls clients on +CIEV events
	RegStatusSignalType regStatusSignal;	//!< The signal that calls clients on +CREG events
	RingSignalType ringSignal;				//!< The signal that calls clients on SBDRING events
	bool initialized;						//!< Remembers if Initialize was called (true) or not (false)
	Logger& logger;							//!< Remembers the constructor's logger argument, used to output log information
	const SBDModemSettings& settings;		//!< Remembers the constructor's setting argument
	SerialPortPtr serialPort;				//!< Pointer to the SerialPort instance used to communicate with the modem
	SerialListener::Ptr serialListener;		//!< Pointer to the SerialListener instance that fires events when data is received from the modem (serial port)
	ScopedConnectionPtr listenerSubscription; //!< Subscription to the SerialListener instance

	//! Token holds an ASCII line (with no carriage return or newline terminator) or binary package from the modem
	struct Token
	{
		//! Constructor for Token with items for each data member
		Token(
				const string& _data,			/**< [in] Received data carried by Token */
				bool _binary = false,			/**< [in] Determines if the data is a binary package (true) or an ASCII line (false) */
				char _lineTerminator = '\0'		/**< [in] The last character received on a line (usually \r or \n) */);

		bool binary;						//< Determines if the data is a binary package (true) or an ASCII line (false)
		string data;						//< Received data carried by Token
		char lineTerminator;				//< The last character received on a line (usually \r or \n)
	};

	//! Smart pointer to a Token
	typedef boost::shared_ptr<Token> TokenPtr;

	//! A queue of tokens passed back from the SerialListener callback a potential listener in a method
	typedef LiquidRobotics::SMACFramework::WaitableThreadSafeQueue<TokenPtr> TokenPtrQueue;

	//! Parses out an ASCII line from a received binary array if found at the given offset (returns a non-nil token if found)
	TokenPtr ParseASCIILine(
			const ByteArray& data,			/**< [in] The data buffer that should be checked */
			unsigned& offset				/**< [in,out] The offset within the data buffer to look at (advanced past the token if one was found) */);

	//! Parses out a binary package from a received binary array if found at the given offset (returns a non-nil token if found)
	TokenPtr ParseBinary(
			const ByteArray& data,			/**< [in] The data buffer that should be checked */
			unsigned& offset				/**< [in,out] The offset within the data buffer to look at (advanced past the token if one was found) */);

	//! Attempts to handle the given token and returns true if handled automatically or false if not handled (only unhandled receives are appended to the data returned from a command)
	bool DefaultMessageHandler(
			TokenPtr token					/**< [in] The token that was received and should evaluated */);

	TokenPtr AwaitASCIIToken(TimeCounter& tc, unsigned timeoutMS, TokenPtrQueue::Ptr tpq, bool lastTry, bool throwOnError);
	TokenPtr AwaitBinaryToken(TimeCounter& tc, unsigned timeoutMS, TokenPtrQueue::Ptr tpq, bool lastTry, bool throwOnError);
	TokenPtr AwaitToken(TimeCounter& tc, unsigned timeoutMS, TokenPtrQueue::Ptr tpq, bool lastTry, bool throwOnError);
	SessionStatusExtended DoSessionExecute(
			const string& cmd);
	bool Send(SerialPort::Ptr sp, MessageFormatter cmd, bool lastTry, bool throwOnError);
	bool SendMessageWithStatusResponse(
			MessageFormatter cmd,
			unsigned timeoutMS,
			bool throwOnError = true);

	bool SendMessageExpectTokenWithStatus(
			MessageFormatter cmd,
			bool tokenBinary,
			string& response,
			unsigned timeoutMS,
			bool throwOnError = true);

	bool SendMessageExpectStringListTerminatedByStatus(
			MessageFormatter cmd,
			StringArray& responses,
			unsigned timeoutMS,
			bool throwOnError = true);

	bool SendMessageExpectString2TerminatedByStatus(
			MessageFormatter cmd,
			const string& expected,
			MessageFormatter cmd2,
			unsigned timeoutMS,
			unsigned timeout2MS,
			bool throwOnError = true);


	TokenPtrQueue::Ptr readResults;			//!< Pointer to the queue between the SerialListener callback and a waiting method call (nil if no method call in progress)

	struct TokenPtrQueueHolder
	{
		TokenPtrQueueHolder(TokenPtrQueue::Ptr& _rr);
		~TokenPtrQueueHolder();

		TokenPtrQueue::Ptr tpq;

	private:

		TokenPtrQueue::Ptr& rr;
	};

        struct AwaitingTagHolder
        {
            typedef LiquidRobotics::SMACFramework::ILockableConcept ILockableConcept;

            AwaitingTagHolder(const string newTag, string& _tag, ILockableConcept& _concept);
            ~AwaitingTagHolder();

        private:

            string& tag;
            ILockableConcept& concept;
            ILockableConcept::Lock lock;
        };

        LockableConceptMutex tagProtector;
        string tag;
};

SHARED_LIB_PUBLIC std::ostream& operator<<(std::ostream& s, const SBDModem::DTERate& instance);
SHARED_LIB_PUBLIC std::ostream& operator<<(std::ostream& s, const SBDModem::SessionStatusExtended& instance);

}
}

#endif
