//------------------------------------------------------------------------------
// <copyright file="SSQController.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 SSQController class, which implements the controller
//  role business logic for the interface to the SSQ sensor.
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#ifndef __SSQCONTROLLER_H__
#define __SSQCONTROLLER_H__

#include "SSQClassConfig.h"
#include "SSQControllerSettings.h"
#include "SSQCommunicator.h"
#include "SensorController.h"
#include "LqrSMACLockableConcept.h"
#include <sys/sysinfo.h>
#include <boost/thread.hpp>
#include <boost/thread/locks.hpp>
#include <boost/thread/recursive_mutex.hpp>
#include <map>

namespace LiquidRobotics {

namespace EventLib {
	class GPSSampleEvent;
};

namespace SMACFramework {
	class CmdOutputFormatter;
}

namespace SimpleSBDQueue {

typedef EventLib::SensorController<SSQControllerBase0,SSQControllerCommand,SSQCmdID,EventLib::SensorControllerExceptions> SSQControllerBase;

/*! \class SSQController
    \brief Controller role class for SimpleSBDQueue

	SSQController implements the typical Controller role for SimpleSBDQueue,
	which is to say that it implements the commands, events and functonality
	(via SBDModem and SSQController), but does not implement CORBA
	interfaces.
*/
class SHARED_LIB_PUBLIC SSQController
: public SSQControllerBase
{
public:

	typedef boost::shared_ptr<CommandQueue> CommandQueuePtr;
	typedef SSQControllerBase Base;
	typedef LiquidRobotics::SMACFramework::ILockableConcept ILockableConcept;
	typedef std::string string;
	typedef boost::signals2::signal<void (SSQController& sender, const ByteArray& packetData, unsigned MTMSN) > PacketReceivedSignalType;

	//! SDSDisposition is the enumerated disposition list for message sends
	enum SDSDisposition
	{
		SDS_SUCCESS = 0,                // message was sent successfully
		SDS_TOO_LONG = 1,               // message could not be sent because it was too long
		SDS_TIMEOUT = 2,                // message took longer than time to live
		SDS_ZERO_LENGTH = 3             // message is zero length
	};

	typedef boost::signals2::signal<void (SSQController& sender, uint32_t priority, uint16_t senderID, uint32_t senderTransaction, unsigned MOMSN, SDSDisposition disposition) > PacketSendDispositionSignalType;
	typedef boost::signals2::signal<void (SSQController& sender, const SSQCommunicator::Statistics& stats) > ChannelStatsSignalType;

	//! Constructor for the SSQController class
	SSQController(
			const SSQControllerSettings& _settings,					/**< [in] Reference on settings to be used */
			IObjectBroker& _broker,									/**< [in] Reference on the object broker to be used (used by the base class) */
			Logger& _logger,										/**< [in] Reference on the Logger instance to be used */
			GPSSignalSubscriber::Ptr _gpsSubscriber,				/**< [in] Subscriber interface for GPS events */
			ShutdownSignalSubscriber::Ptr _shutdownSubscriber		/**< [in] Subscriber interface for shutdown events */);

	//! Destructor for the SSQController class
	virtual ~SSQController();

	//! Second-phase construction for SSQController class (called just after constructor)
	void Initialize();

	//! First-phase destruction for SSQController class (called just before destructor)
	void Shutdown();

	//! Implements generic "start" command: begins communicating with the modem, can throw exceptions if there are failures
	virtual void Start();

	//! Implements generic "stop" command: shuts down communication to modem
	virtual void Stop();

	//! Adds a message to the queue of messages waiting to be sent
	virtual void EnqueueMessage(
    		const PriorityType priority,							/**< [in] Priority of the message (determines which queue it goes into) */
    		const ByteArray& message,								/**< [in] Data body of the message */
    		const SenderIDType senderID,							/**< [in] ID of sender (mentioned in disposition events) */
    		const SenderTransactionType senderTransaction,			/**< [in] Sender's assigned transaction number (mentioned in disposition events) */
            const QueuePosition queuePos = QUEUE_END,				/**< [in] Where in the queue to insert the message (default = end) */
            const TTLType timeToLive = 0);							/**< [in] Number of seconds for the message to live (0 = forever) */

    //! Stores GPS (to be used in SBDIX command)
	virtual void ForwardGPS(
    		double latitude,										/**< [in] Current latitude in whole and fractional degrees */
    		double longitude										/**< [in] Current longitude in whole and fractional degrees */);

	//! Queues up a request for a mailbox check (modem should check in as soon as possible)
	virtual void RequestMailboxCheck();

	//! Adds a subscription to the "packet recieved" event; the return value must be retained to hold the subscription.  Acquire the "firing mutex" before releasing the subscription.
	virtual ScopedConnectionPtr SubscribePacketReceived(
    		PacketReceivedSignalType::slot_type slot				/**< [in] boost::bind callback for event */);

    //! Adds a subscription to the "packet send disposition" event; the return value must be retained to hold the subscription.  Acquire the "firing mutex" before releasing the subscription.
	virtual ScopedConnectionPtr SubscribePacketSendDisposition(
    		PacketSendDispositionSignalType::slot_type slot			/**< [in] boost::bind callback for event */);

	//! Adds a subscription to the "channel statistics" event; the return value must be retained to hold the subscription.  Acquire the "firing mutex" before releasing the subscription.
	virtual ScopedConnectionPtr SubscribeChannelStats(
    		ChannelStatsSignalType::slot_type slot					/**< [in] boost::bind callback for event */);

	//! Returns a mutex reference that must be held while unsubscribing from signals
	virtual ILockableConcept& GetFiringMutex();

protected:

	typedef LiquidRobotics::EventLib::GPSSampleEvent GPSSampleEvent;

	//! Handles incoming GPS events (overrides base class member)
	virtual void OnGPSSampleEvent(
			const GPSSampleEvent& sample							/**< [in] Accessor for incoming GPS event */);

	//! Handles incoming system shutdown notification events (overrides base class member)
	virtual void OnShutdownEvent();

	//! Parses incoming command and creates command class instances to be executed on a background thread
	virtual CommandPtr ParseCommand(
			TCon::ParsingContext& context,							/**< [in] Parsing context (contains the command text, tokens, information about requestor) */
			bool internalCmd = false								/**< [in] Indicates if this is an internal command (default = false) */);

	//! Handles received packets from the SSQCommunicator instance
	virtual void OnReceivedPacket(
    		SSQCommunicator& sender,								/**< [in] Reference on the sender (SSQCommunicator) */
    		const ByteArray& packetData,							/**< [in] Data from the SBD packet */
    		unsigned MTMSN											/**< [in] Packet serial number */);

	//! Handles a request for a packet full of data to be sent
	virtual void OnRequestData(
    		SSQCommunicator& sender,								/**< [in] Reference on the sender (SSQCommunicator) */
    		size_t maxPacketSize,									/**< [out] Maximum size of the packet to send */
    		ByteArray& packetData,									/**< [out] Data for the SBD packet (0 bytes = nothing to send) */
    		SSQCommunicator::CookieType& cookie,					/**< [out] Cookie to send back in the "on sent packet" event */
    		unsigned& newMailboxCheckTimeMS						/**< [out] Milliseconds to the next mailbox check to override defaults (do not fill if not overriding) */);

	//! Handles the "successful send" event (i.e. data previously returned by OnRequestData has been sent successfully)
	virtual void OnSentPacket(
    		SSQCommunicator& sender,								/**< [in] Reference on the sender (SSQCommunicator) */
    		SSQCommunicator::CookieType cookie,						/**< [in] Cookie of the message that is being addressed */
    		unsigned MOMSN											/**< [in] Mobile-originated serial number that accompanied the sent message */);

	//! Handles the "statistics" event from the "communicator" instance
	virtual void OnStatistics(
    		SSQCommunicator& sender,								/**< [in] Reference on the sender (SSQCommunicator) */
    		const SSQCommunicator::Statistics& stats				/**< [in] The buffer of statistics */);

private:

	typedef SSQCommunicator::CookieType CookieType;

	//! QueueEntry entity class, which holds a message stored in the outbound queue
	struct QueueEntry
	{
		typedef LiquidRobotics::SMACFramework::TimeCounter TimeCounter;

        //! Constructor for QueueEntry class
		QueueEntry(
        		PriorityType _priority,								/**< [in] Priority of the message (selects which queue to be used) */
        		const ByteArray& _message,							/**< [in] Data carried in the message */
        		const SenderIDType _senderID,						/**< [in] ID that sender indicated */
        		const SenderTransactionType _senderTransaction,		/**< [in] Transaction ID that sender indicated */
                const QueuePosition _queuePos,						/**< [in] Where in the queue to append the message */
                const TTLType _timeToLive,							/**< [in] Number of seconds allow the message to stay in pending state (0=forever) */
                CookieType _cookie									/**< [in] The cookie assigned by this process to uniquely-identify the message */)
        : priority(_priority)
        , message(_message.begin(), _message.end())
        , senderID(_senderID)
        , senderTransaction(_senderTransaction)
        , queuePos(_queuePos)
        , timeToLive(_timeToLive)
        , cookie(_cookie)
        {
        }

        PriorityType priority;										//!< Priority of the message (selects which queue to be used)
        ByteArray message;											//!< Data carried in the message
        SenderIDType senderID;										//!< ID that sender indicated
        SenderTransactionType senderTransaction;					//!< Transaction ID that sender indicated
        QueuePosition queuePos;										//!< Where in the queue to append the message
        TTLType timeToLive;											//!< Number of seconds allow the message to stay in pending state (0=forever)
        CookieType cookie;											//!< The cookie assigned by this process to uniquely-identify the message
        TimeCounter queueTime;										//!< Timer to check against TTL (remembers when message was enqueued)
	};

	typedef boost::shared_ptr<QueueEntry> QueueEntryPtr;
	typedef std::deque<QueueEntryPtr> Queue;
	typedef boost::shared_ptr<Queue> QueuePtr;
	typedef std::map<PriorityType,QueuePtr> QueueMap;
	typedef std::map<CookieType,QueueEntryPtr> CookieMap;
	typedef LiquidRobotics::SMACFramework::LockableConceptMutex LockableConceptMutex;

	//! Central location for implementation of "start" function, which can originate in the Initialize function or in Start
	void DoStart();

	//! Returns a psuedo queue entry for the message in the "next message" packed buffer or nil if nothing in the buffer
	QueueEntryPtr GetNextMessageBufferQueueEntry() const;

	//! Returns the next message to be sent (nil if nothing to send); fires event timeouts and zero length item errors
	QueueEntryPtr GetNextQueueEntry(
    		bool peek = false										/**< [in] true if just looking, false if extracting the message from queue (default) */);

	//! Packs the "next message" buffer to the given size
	void PackNextMessageBuffer(
    		size_t maxPacketSize									/**< [in] the maximum number of bytes for a message */);

	//! Returns the next cookie (never returns 0)
	CookieType PullCookie();

	//! Unlinks the queue entry from all queues and deletes it
	void UnlinkQueueEntry(
			      QueueEntryPtr qep										/**< [in] The queue entry to be deleted */);

	//! Goes through the queues and fires error messages for any messages that exceed the maximum packet size or which are too old
	void ValidateQueueContent(
    		size_t maxPacketSize									/**< [in] the maximum number of bytes for any message */);

	const SSQControllerSettings& settings;							//!< reference on settings for this controller instance
	bool initialized;												//!< true if instance initialized, false if not initialized
	double lat, lon;												//!< latitude and longitude of last received GPS message
	bool hasGPS;													//!< true if any GPS message received, false if no GPS message received
	LockableConceptMutex cookieMutex;								//!< mutex to hold while assigning a cookie
	LockableConceptMutex firingMutex;								//!< mutex to hold while firing events
	QueueMap queueMap;												//!< map of all queues of messages
	Queue nextMessageQueue;											//!< queue of messages that fill the buffer
	CookieMap cookieMap;											//!< map of cookies to queue entries
	CookieType nextCookie;											//!< next cookie to be assigned
	CookieType nextMessageCookie;									//!< cookie that represents the message in the queue (0=not assigned)
	SSQCommunicator::Ptr commPtr;									//!< pointer to communicator (nil when not started)
	SSQCommunicator::ScopedConnectionPtr receivedPacketConnection;	//!< connection to "received packet" event of communicator
	SSQCommunicator::ScopedConnectionPtr requestDataConnection;		//!< connection to "request data" event of communicator
	SSQCommunicator::ScopedConnectionPtr sentPacketConnection;		//!< connection to "sent packet" event of communicator
	SSQCommunicator::ScopedConnectionPtr statsConnection;			//!< connection to "statistics" event of communicator
	PacketReceivedSignalType packetReceivedSignal;					//!< signal for outgoing "packet received" event
	PacketSendDispositionSignalType packetSendDispositionSignal;	//!< signal for outgoing "packet send disposition" event
	ChannelStatsSignalType channelStatsSignal;						//!< signal for outgoing "channel statistics" event
};

//! Smart pointer to a SSQController
typedef boost::shared_ptr<SSQController> SSQControllerPtr;

}
}

#endif
