//------------------------------------------------------------------------------
// <copyright file="PayloadInterfaceController.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>
// 	Declaration of PayloadInterfaceController, which extends PayloadPortSlave
//  to implement the payload interface for the SMC
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#ifndef PAYLOADINTERFACECONTROLLER_H_
#define PAYLOADINTERFACECONTROLLER_H_

#include "LqrSMACIdl.h"
#include <deque>
#include <list>
#include <string>
#include <stdexcept>
#include <boost/shared_ptr.hpp>
#include "PayloadInterfaceController_Config.h"
#include "PayloadManagerClient.h"
#include "PayloadPortSlave.h"
#include "SCLookup.h"
#include "StorageController.h"
#include "LqrSMACIObjectBroker.h"
#include "LqrSMACLockableConcept.h"
#include "LqrSMACPayloadDescription.h"
#include "LqrSMACSharedLib.h"
#include "LqrSMACWaitableThreadSafeQueue.h"
#include "EventTransceiver.h"
#include "ProxySCDictionary.h"
#include "LatLon.h"
#include "LocalTransactionCounter.h"
#include "CommonSendCommand.h"
#include "TelemetrySubsystem.h"
#include "AlarmSubsystem.h"

namespace LiquidRobotics {
namespace SMACFramework {
	class AsynchronousProcessExecutor;
	class CmdLineParser;
	class TempFile;
}
}

namespace LiquidRobotics {
namespace PayloadInterface {


class SHARED_LIB_PUBLIC PayloadInterfaceController
: public LiquidRobotics::EventLib::EventTransceiver
, public LocalTransactionCounter
, public LiquidRobotics::EventLib::PayloadManagerClient
, public SCLookup
, public CommonSendCommand
, public PayloadPortSlave
, public TelemetrySubsystem
{
public:

	typedef std::string string;
	typedef boost::shared_ptr<std::string> StringPtr;
	typedef std::list<string> StringList;
	typedef PayloadPortSlave Base;
	typedef LiquidRobotics::EventLib::EventSettings EventSettings;
	typedef LiquidRobotics::EventLib::IEventBase IEventBase;
	typedef LiquidRobotics::EventLib::EventTransceiver EventTransceiver;
	typedef LiquidRobotics::EventLib::EventTransceiver::Settings EventTransceiverSettings;
	typedef LiquidRobotics::EventLib::PayloadManagerClient PayloadManagerClient;
	typedef LiquidRobotics::EventLib::PayloadManagerClientSettings PayloadManagerClientSettings;
	typedef CORBA::Any CORBAAny;
	typedef CORBA::Object_ptr Object_ptr;
	typedef LiquidRobotics::SMACFramework::AsynchronousProcessExecutor AsynchronousProcessExecutor;
	typedef boost::shared_ptr<AsynchronousProcessExecutor> AsynchronousProcessExecutorPtr;
	typedef LiquidRobotics::SMACFramework::Logger Logger;
	typedef LiquidRobotics::SMACFramework::TempFile TempFile;
	typedef boost::shared_ptr<TempFile> TempFilePtr;
	typedef LiquidRobotics::SMACFramework::PayloadDescription PayloadDescription;
	typedef PayloadInterfaceController_Config::EventAndDestinationList EventAndDestinationList;


	class SHARED_LIB_PUBLIC ReceivedShutdownRequest : public std::runtime_error
	{
	public:
		ReceivedShutdownRequest();
	};

	PayloadInterfaceController(const SerialPortSettings& _portSettings, const StorageController::Config& _scSettings,
			const PayloadInterfaceController_Config& _config, LiquidRobotics::SMACFramework::IObjectBroker& _broker,
			Logger& _logger, const string& _telemConfigPath);
	virtual ~PayloadInterfaceController();

	virtual void Initialize(Object_ptr serverRef);

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

    // === Overrides of PayloadPortSlave virtual methods ===

	// Overriding this handler to catch the exit from the program
	virtual void Run(HDLCPort::Ptr pHDLC);

protected:

	// Overriding this handler to halt the system when requested
	virtual PayloadMessage::Ptr WaitForMessage(HDLCPort::Ptr pHDLC);

	// This method is called whenever a message is received.  The return value contains the message to be sent
	// in response (or send back a null pointer if no response should be made)
	//
	// Normally, duplicateHeader will be false; if duplicateHeader is true, the receiver should typically return
	// a null pointer and the handler will retransmit the message that was previously sent.
	virtual PayloadMessage::Ptr OnMessageReceived(PayloadMessage::Ptr messageReceived, bool duplicateHeader);

	// This method is called when a requested packet is acked or naked.
	virtual void OnACKRequestedPacketReceived(PayloadMessage::Ptr messageReceived);

	// This method is called when a requested packet is acked or naked.
	virtual void OnTelemetryReceived(PayloadMessage::Ptr messageReceived);

	// This method is called whenever an ASCII- or binary-formatted console command is received
	virtual void OnConsoleMessage(
			BoardAddress source,
			BoardAddress target,
			const Level2Wrapper& receivedLevel2,
			const FCCMessage::CMD_FMT_1E& receivedParams,
			const ByteArray& dataBytes,
			bool asciiFormat,
			bool& acked,
			uint8_t& nakDisp,
			uint8_t& nakSummary,
			bool wantsAck);

	// This method is called whenever an Initialize File Write message is sent to the payload
	virtual void OnInitFileWrite(
			BoardAddress target,
			const Level2Wrapper& receivedLevel2,
			const FCCMessage::CMD_FMT_18& params,
			const ByteArray& paramData,
			bool& acked,
			uint8_t& nakDisp,
			uint8_t& nakSummary);

	// This method is called whenever a Write File Block message is sent to the payload
	virtual void OnWriteFileBlock(
			BoardAddress target,
			const Level2Wrapper& receivedLevel2,
			const FCCMessage::CMD_FMT_19& params,
			const ByteArray& paramData,
			bool& acked,
			uint8_t & nakDisp,
			uint8_t& nakSummary);

	// This method is called whenever a Delete File message is sent to the payload
	virtual void OnDeleteFile(
			BoardAddress target,
			const Level2Wrapper& receivedLevel2,
			const FCCMessage::CMD_FMT_1A& params,
			bool& acked,
			uint8_t& nakDisp,
			uint8_t& nakSummary);

	// This method is called whenever a Read File Request message is sent to the payload
	virtual void OnReadFileRequest(
			BoardAddress target,
			const Level2Wrapper& receivedLevel2,
			const FCCMessage::CMD_FMT_1B& params,
			boost::shared_ptr<ByteArray>& data,
			bool& eof,
			uint8_t& nakDisp,
			uint8_t& nakSummary);

	// This method is called whenever a Get File List message is sent to the payload
	virtual void OnGetFileList(
			BoardAddress target,
			const Level2Wrapper& receivedLevel2,
			const FCCMessage::CMD_FMT_1C& params,
			FCCMessage::ACK_FILE_LIST_COMMON_HEADER& ackFileListHeader,
			ACK_FILE_LIST_RECORD_ARRAY& ackFileListData,
			bool& acked,
			uint8_t& nakDisp,
			uint8_t& nakSummary);

	// This method is called whenever a Get Detailed File List message is sent to the payload
	virtual void OnGetDetailedFileList(
			BoardAddress target,
			const Level2Wrapper& receivedLevel2,
			const FCCMessage::CMD_FMT_1C& params,
			FCCMessage::ACK_FILE_LIST_COMMON_HEADER& ackFileListHeader,
			ACK_FILE_LIST_DETAILED_RECORD_ARRAY& ackFileListData,
			bool& acked,
			uint8_t& nakDisp,
			uint8_t& nakSummary);

	// This method is called whenever a power notification message is received; the user should fill out
	// "ack" and set "acked" to true or set "acked" to false
	virtual void OnPowerNotification(
			const PayloadProtocol::PYLD_POWER_NOTIFICATION& notification,
			PayloadProtocol::ACK_PYLD_POWER_NOTIFICATION_RECORD& ack,
			bool& acked,
			PayloadProtocol::ENakSummary& nakSummary);

	// This method is called by the default OnMessageReceived implementation when an ID/enumeration request is
	// received; the user should fill out the responses array for processing on return (setting the response to 0
	// elements results in no response)
	virtual void OnRequestIDReceived(const PayloadProtocol::LRI_REQUEST_ID& idRequest,
			LRI_ID_PACKET_ARRAY& responses);

	// This method is called by the default OnMessageReceived implementation when a status request message is
	// received; the user should fill out "ack" and set "acked" to true or set "acked" to false
	virtual void OnStatusRequest(
			const PayloadProtocol::LRI_REQUEST_PIB_STATUS& statusRequest,
			PayloadProtocol::LRI_PIB_STATUS& ack,
			bool& acked,
			PayloadProtocol::ENakSummary& nakSummary);

	// Returns true if there are messages queued to be read out by the Host, false if there are none; this method
	// is used to set the "have messages" flag in all responses
	virtual bool HaveMessagesQueued(BoardAddress destinationAddress) const;

	// This method should return true unless intending to exit
	virtual bool Running() const;

	// Returns true if the address is one that should be Nak'd, false if the address is unknown
	virtual bool SendNaksForAddress(BoardAddress destinationAddress) const;

	// Returns the board address of the main task
	virtual PayloadMessage::BoardAddress MainTaskAddress() const;

	void DistributeGPS(const PayloadProtocol::LRI_REQUEST_PIB_STATUS& statusRequest, const LatLon& fix);

	// Override of AlarmSubsystem member
	virtual void ReportAlarm(uint16_t senderAddress, const LiquidRobotics::SMACFramework::ByteArray& level1SysInfo);

	// =================================================================

private:

	struct FileIDQueueMember
	{
		FileIDQueueMember(ProxySC::Ptr _proxySC, StorageController::FileIDType _fileID)
		: proxySC(_proxySC)
		, isFile(true)
		, fileID(_fileID)
		{ }

		FileIDQueueMember(ProxySC::Ptr _proxySC, const ByteArray& _literalData, uint32_t _transactionID, uint16_t _destID, bool _ascii, bool _retFile, bool _wantsAck)
		: proxySC(_proxySC)
		, isFile(false)
		, literalData(_literalData.begin(), _literalData.end())
		, transactionID(_transactionID)
		, destID(_destID)
		, ascii(_ascii)
		, retFile(_retFile)
		, wantsAck(_wantsAck)
		{ }

		ProxySC::Ptr proxySC;
		bool isFile;
		StorageController::FileIDType fileID;
		ByteArray literalData;
		uint32_t transactionID;
		uint16_t destID;
		bool ascii;
		bool retFile;
		bool wantsAck;
	};

	typedef std::deque<ByteArray::byte> ByteDQ;
	typedef boost::shared_ptr<FileIDQueueMember> FileIDQueueMemberPtr;
	typedef std::deque<FileIDQueueMemberPtr> FileIDQueue;
	typedef boost::signals2::scoped_connection ScopedConnection;
	typedef boost::shared_ptr<ScopedConnection> ScopedConnectionPtr;
	typedef LiquidRobotics::SMACFramework::LockableConceptMutex LockableConceptMutex;
	typedef LiquidRobotics::SMACFramework::WaitableThreadSafeQueue<PayloadMessage::Ptr> PayloadMessageQueue;
	typedef std::list<PayloadMessageQueue::Ptr> PayloadMessageQueueList;
	typedef LiquidRobotics::SMACFramework::CmdLineParser CmdLineParser;
	typedef std::map<uint16_t,const PayloadInterfaceController_Config::SetParamInfo*> IDToSetParamInfoMap;
	typedef std::map<string,const PayloadInterfaceController_Config::SetParamInfo*> NameToSetParamInfoMap;

	void AddressableMessageHandler(EventTransceiver& sender, const IEventBase& receivedEvent);
	void AwaitSetParamAck(uint32_t transactionNumber, StringPtr requester, StringPtr transactionID, unsigned milliseconds);
	void AwaitExecFCC(uint32_t transactionNumber, StringPtr requester, StringPtr transactionID, unsigned milliseconds);
	void AwaitWCS(uint32_t transactionNumber, StringPtr requester, StringPtr transactionID, unsigned milliseconds);
	void FCCIncomingMessageEventHandler(EventTransceiver& sender, const IEventBase& receivedEvent);
	void AsynchCmdCmpltHandler(EventTransceiver& sender, const IEventBase& receivedEvent);
	bool DeferredStartupTelem() const;
	void DumpPayloadManagerConfig();
	void EnqueueStartupTelemetryMessage(bool defer = false);
	PayloadMessage::Ptr GetGenericCommandMessage(uint8_t l2Type, uint32_t transactionNumber, uint8_t formatID, uint16_t commandID,
			uint8_t field3, uint8_t field4, uint8_t field5, uint8_t field6, uint8_t field7, uint32_t latitude, uint32_t longitude);
	PayloadMessage::Ptr GetSetParamMessage(const CmdLineParser& parser, unsigned startParam, uint32_t transactionNumber);
	void GPSReceiveHandler(EventTransceiver& sender, const IEventBase& receivedEvent);
    bool HandleAdditionalCommands(const string& command, const string& requester, const string& transactionID, string& result);
	void HandleShutdown();
	void InformDemoServerOfShutdown();
	Logger& GetLogger() const;
	void PMQDistributeMessage(PayloadMessage::Ptr pmsg);
	void PMQRegister(PayloadMessageQueue::Ptr pmq);
	void PMQUnregister(PayloadMessageQueue::Ptr pmq);
	void SCCommandComplete(ProxySC& sender, const ByteArray& data, long transNumber, unsigned destID, bool complete, unsigned resultNum, bool success);
	void SCNewFileAvailable(ProxySC& sender, const string& actualFileName, string& remoteFileName);
	void EnqueueAddressedMessage(BoardAddress source, BoardAddress dest, uint16_t transID, const ByteArray& data);
	void SendShutdownEvent();
	void ShutdownSystem();
	void TelemetryReportComplete(TelemetryBuffer& sender, TelemetryReportKey reportKey, TelemetryBuffer::BoardAddress targetAddress, const ByteArray& content);
	void WriteFileEOFCheck(PayloadMessage::BoardAddress scAddr, StorageController& sc, StorageController::FileState::Ptr item,
			uint32_t offset, uint16_t writeSize, bool& acked, uint8_t& nakDisp, uint8_t& nakSummary);

	//StorageController sc;		-- now all StorageController objects are mapped through pscDict
	const PayloadInterfaceController_Config& config;
	const StorageController::Config& scSettings;
	LiquidRobotics::SMACFramework::IObjectBroker& broker;
	bool halt;
	bool shutdownCompleted;
	bool enqueueStartupTelem;
	bool haveSavedPosition;
	double savedLatitude, savedLongitude;
	double lastGPSEventReceived; // time since startup
	FileIDQueue completedFiles;
	LockableConceptMutex pmqListMutex;
	PayloadMessageQueueList pmqList;
	IDToSetParamInfoMap idToSPIM;
	NameToSetParamInfoMap nameToSPIM;
	ScopedConnectionPtr connectionToFCCIncomingMessageEvent;
	ScopedConnectionPtr connectionToGpsSampleEvent;
	ScopedConnectionPtr connectionToAddressableMessageEvent;
	ScopedConnectionPtr connectionToPayloadAdded;
};

}
}

#endif /* PAYLOADINTERFACECONTROLLER_H_ */
