//------------------------------------------------------------------------------
// <copyright file="PayloadPortSlave.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 PayloadPortSlave, which implements payload message exchange
//  and is a basis for the SMC implementation of a payload interface
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#ifndef PAYLOADPORTSLAVE_H_
#define PAYLOADPORTSLAVE_H_

#include <algorithm>
#include <deque>
#include <map>
#include <stdexcept>
#include <vector>
#include <stdexcept>
#include <string>
#include <stdint.h>
#include <string.h>
#include <boost/smart_ptr/shared_array.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/utility.hpp>
#include <boost/thread/mutex.hpp>
#include "PayloadMessage.h"
#include "PayloadProtocol.h"
#include "HDLCPort.h"
#include "LqrSMACByteArray.h"
#include "LqrSMACByteStream.h"
#include "LqrSMACSerialPortSettings.h"
#include "LqrSMACLogger.h"
#include "LqrSMACSharedLib.h"
#include "LqrSMACThreadSafeQueue.h"
#include "LqrSMACZeroObject.h"
#include "FCCMessage.h"
#include "FCCMessage_CommHeaderMessage.h"
#include "FCCMessage_CommHeaderMessageGeneric.h"
#include "PPSByteStream.h"

namespace LiquidRobotics {
namespace PayloadInterface {

class SHARED_LIB_PUBLIC PayloadPortSlave : public boost::noncopyable
{
public:

	typedef LiquidRobotics::SMACFramework::SerialPortSettings SerialPortSettings;
	typedef SMACFramework::ByteArray::byte byte;
	typedef PayloadMessage::BoardAddress BoardAddress;
	typedef PayloadMessage::byte BoardID;
	typedef PayloadMessage::byte TaskID;
	typedef SMACFramework::ByteArray ByteArray;
	typedef SMACFramework::Logger Logger;
	typedef FCCMessage::Level2Wrapper Level2Wrapper;
	typedef LiquidRobotics::SMACFramework::BitConvert BitConvert;
	typedef LiquidRobotics::SMACFramework::ByteStream ByteStream;
	typedef std::string string;

	struct SHARED_LIB_PUBLIC MessageWrapper
	{
		typedef boost::shared_ptr<MessageWrapper> Ptr;

		MessageWrapper(PayloadMessage::Ptr _message)
		: message(_message)
		{
		}

		virtual ~MessageWrapper()
		{
		}

		virtual void OnDiscard()
		{
		}

		virtual void OnEnqueue()
		{
		}

		virtual void OnTransmit()
		{
		}

		PayloadMessage::Ptr message;
	};

	typedef LiquidRobotics::SMACFramework::ThreadSafeQueue<MessageWrapper::Ptr> TSQueue;

	class SHARED_LIB_PUBLIC ThreadSafeMap
	{
	public:

		typedef BoardID Key;
		typedef MessageWrapper::Ptr Item;
		typedef PayloadMessage::Ptr Item0;
		typedef TSQueue Queue;
		typedef std::map<BoardID, Queue::Ptr> MapType;
		typedef MapType::iterator MapTypeIterator;

		ThreadSafeMap();
		virtual ~ThreadSafeMap();

		Item Dequeue(Key key);
		bool Empty() const;
		bool Empty(Key key) const;
		void Enqueue(Key key, const Item& item);
		void Enqueue(Key key, const Item0& item);
		void Flush();
		void Flush(Key key);
		int Size() const;
		int Size(Key key) const;
		bool TryDequeue(Key key, Item0& item);

	private:

		typedef boost::mutex Mutex;
		typedef boost::unique_lock<Mutex> Lock;

		Mutex* pMutex;
		MapType items;
	};

	PayloadPortSlave(const SerialPortSettings& _portSettings, Logger& _logger);
	virtual ~PayloadPortSlave();

	virtual HDLCPort::Ptr InitHDLC();
	virtual void Run();
	virtual void Run(HDLCPort::Ptr pHDLC);

	// Attemps to pull the next message from the queue that matches the given board address (i.e. the board ID
	// matches); returns an empty smart pointer upon failure
	virtual PayloadMessage::Ptr DequeueMessage(BoardAddress boardAddress);

	// Enqueues a message with a wrapper
	virtual void EnqueueMessage(MessageWrapper::Ptr wrapper);

	// Enqueues a message to be transmitted when polled
	virtual void EnqueueMessage(PayloadMessage::Ptr message);

	// Flushes the queue of messages pending transmission that match the pattern
	virtual void FlushMessageQueue(BoardAddress boardAddress = 0xFFFF);

	// 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;

    // Enqueues a debug message
    virtual void EnqueueDebugMessage(const string& message, uint8_t debugLevel = 5);


protected:

	// This method is called to wait for a message from the port; it can be overridden to perform pre and post
	// processing
	virtual PayloadMessage::Ptr WaitForMessage(HDLCPort::Ptr pHDLC);

	// This method sends a message from bytes, encoding it first
	virtual void SendBytes(HDLCPort::Ptr pHDLC, const ByteArray& msgBytes);

	// 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 whenever an forwarded or copied ICD message is received
	virtual PayloadMessage::Ptr OnICDMessageReceived(PayloadMessage::Ptr messageReceived);

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

	// This method if called when a telemetry packet is received
	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& consoleData,
			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
	typedef std::vector<FCCMessage::ACK_FILE_LIST_RECORD> ACK_FILE_LIST_RECORD_ARRAY;
	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
	typedef std::vector<FCCMessage::ACK_FILE_LIST_DETAILED_RECORD> ACK_FILE_LIST_DETAILED_RECORD_ARRAY;
	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)
	typedef std::vector<PayloadProtocol::LRI_ID_PACKET> LRI_ID_PACKET_ARRAY;
	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);

	// 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(PayloadMessage::BoardAddress destinationAddress) const;

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

protected:

	//typedef ThreadSafeMap<BoardID, PayloadMessage::Ptr> MessageQueueByBoardID;
	typedef ThreadSafeMap MessageQueueByBoardID;

	const SerialPortSettings portSettings;
	PayloadMessage::Header lastMessageHeaderReceived;
	PayloadMessage::Ptr lastMessage;
	MessageQueueByBoardID msgQByBoardID;
	Logger& logger;
	HDLCPort::Ptr hdlcPortPtr;

	static const unsigned MaxLength;
};

}
}

#endif /* PAYLOADPORTSLAVE_H_ */
