//------------------------------------------------------------------------------
// <copyright file="PayloadMessage.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>
// </summary>
//
// <owner></owner>
//------------------------------------------------------------------------------


#ifndef _PAYLOADMESSAGE_H_
#define _PAYLOADMESSAGE_H_

#include <iostream>
#include <string>
#include <vector>
#include <boost/serialization/access.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/shared_ptr.hpp>
#include "LqrSMACByteArray.h"
#include "CRC16.h"
#include "LqrSMACSharedLib.h"

namespace LiquidRobotics {
namespace PayloadInterface {

class SHARED_LIB_PUBLIC PayloadMessage
{
public:

	typedef SMACFramework::ByteArray::byte byte;
	typedef unsigned short ushort;
	typedef std::string string;

	typedef ushort BoardAddress;
	typedef byte BoardID;
	typedef byte TaskID;
	typedef SMACFramework::ByteArray ByteArray;
	typedef CRC16::CRCType CRCType;
	typedef ushort MessageLength;			// I would have used the enum here, but there may be other messages added without this module being updated
	typedef ushort MessageType;
	typedef ushort TransactionID;
	typedef boost::shared_ptr<PayloadMessage> Ptr;

	enum EMessageType
	{
		LRI_NAK = 0,						// negative-acknowledge including rejection type (to host)
		LRI_ACK = 1,						// acknowledge including response data (to host)
		DATA_LINK_NAK = 4,					// meant to indicate a communication problem, sometimes used to indicate negative-ack (to host)
		DATA_LINK_ACK = 5,					// meant to indicate successful communication, sometimes used to indicate acknowledge (to host)
		REQUEST_ID = 0x10,					// request enumeration  (from host)
		FLOAT_TELEMETRY_DATA = 0x15,		// telemetry forwarded (from host)
		REQUEST_PIB_STATUS = 0x22,			// request status (from host)
		SEND_FWD_ICD_CMD = 0x24,			// forward or send an ICD-based command/host command (from host)
		POWER_NOTIFICATION = 0x30,			// indicate change in power (from host)
		REQUEST_WAITING_PACKET = 0x40,		// request transmission of a buffered message (from host)
		ACK_REQUESTED_PACKET = 0x41			// message received in response to REQUEST_WAITING_PACKET can be deleted (from host)
	};

	class SHARED_LIB_PUBLIC Header
	{
	public:

		Header();
		Header(const ByteArray& input);
		Header(const ByteArray& input, MessageLength& index, CRC16& crc);
		Header(MessageType messageType, BoardAddress sourceAddress, BoardAddress destinationAddress, TransactionID transactionID, MessageLength messageLength);
		Header(const Header& other);

		virtual ~Header();

		Header& operator=(const Header& other);
		bool operator==(const Header& other) const;

		MessageType GetMessageType() const;
		void SetMessageType(MessageType messageType);

		bool GetNoAckHaveMessagesFlag() const;
		void SetNoAckHaveMessagesFlag(bool set = true);

		BoardAddress GetSourceAddress() const;
		void SetSourceAddress(BoardAddress sourceAddress);

		BoardAddress GetDestinationAddress() const;
		void SetDestinationAddress(BoardAddress destinationAddress);

		TransactionID GetTransactionID() const;
		void SetTransactionID(TransactionID transactionID);

		MessageLength GetMessageLength() const;
		void SetMessageLength(MessageLength messageLength);

		void ToArray(ByteArray& result) const;
		void FromArray(const ByteArray& input);
		void FromArray(const ByteArray& input, MessageLength& index, CRC16& crc);
		string ToString() const;

	private:

		friend class boost::serialization::access;

		template<class Archive>
		void serialize(Archive& ar, const unsigned int version)
		{
			ar & BOOST_SERIALIZATION_NVP(messageType);
			ar & BOOST_SERIALIZATION_NVP(sourceAddress);
			ar & BOOST_SERIALIZATION_NVP(destinationAddress);
			ar & BOOST_SERIALIZATION_NVP(transactionID);
			ar & BOOST_SERIALIZATION_NVP(messageLength);
		}

		static void TestMessageType(MessageType _messageType);

		MessageType messageType;
		BoardAddress sourceAddress;
		BoardAddress destinationAddress;
		TransactionID transactionID;
		MessageLength messageLength;
	};

	PayloadMessage();
	PayloadMessage(MessageType messageType, BoardAddress sourceAddress, BoardAddress destinationAddress, TransactionID transactionID, const ByteArray& data);
	PayloadMessage(MessageType messageType, BoardAddress sourceAddress, BoardAddress destinationAddress, TransactionID transactionID, const byte* pData = NULL, MessageLength dataLength = 0);
	PayloadMessage(const Header& header, const ByteArray& data);
	PayloadMessage(const Header& header, const byte* pData = NULL);
	PayloadMessage(const ByteArray& data);
	PayloadMessage(const byte* pData, MessageLength dataLength);
	PayloadMessage(const PayloadMessage& right);

	virtual ~PayloadMessage();

	PayloadMessage& operator=(const PayloadMessage& other);
	bool operator==(const PayloadMessage& other) const;

	void ClearData();
	const ByteArray& GetData() const;
	void GetData(ByteArray& result) const;
	MessageLength GetDataLength() const;
	void SetData(const ByteArray& data);
	void SetData(const byte* pData, unsigned dataLength);

	const Header& GetHeader() const;

	MessageType GetMessageType() const;
	void SetMessageType(MessageType messageType);

	bool GetNoAckHaveMessagesFlag() const;
	void SetNoAckHaveMessagesFlag(bool set = true);

	BoardAddress GetSourceAddress() const;
	void SetSourceAddress(BoardAddress sourceAddress);

	BoardAddress GetDestinationAddress() const;
	void SetDestinationAddress(BoardAddress destinationAddress);

	TransactionID GetTransactionID() const;
	void SetTransactionID(TransactionID transactionID);

	CRCType GetCRC() const;
	CRCType CalculateCRC() const;

	static CRCType CalculateCRC(const byte* pData, MessageLength dataLength);

	void ToArray(ByteArray& result) const;
	void FromArray(const ByteArray& input);
	string ToString() const;

	static string ToString(MessageType messageType);

private:

	friend class Header;
	friend class boost::serialization::access;

	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		ar & BOOST_SERIALIZATION_NVP(header);
		ar & BOOST_SERIALIZATION_NVP(data);
		ar & BOOST_SERIALIZATION_NVP(crc);
	}

	static void Get(const ByteArray& output, MessageLength& index, ushort& data, CRC16& crcUpdate);
	static void Put(ByteArray& output, ushort data, CRC16& crcUpdate);

	void Update();

	Header header;
	ByteArray data;
	CRC16 crc;
};

SHARED_LIB_PUBLIC ::std::ostream& operator<<(::std::ostream& out, const PayloadMessage& message);
SHARED_LIB_PUBLIC ::std::ostream& operator<<(::std::ostream& out, const PayloadMessage::Header& header);

inline PayloadMessage::BoardAddress MakeBoardAddress(PayloadMessage::BoardID boardID, PayloadMessage::TaskID taskID)
{
	PayloadMessage::BoardAddress boardAddress = boardID;

	boardAddress <<= 8;
	boardAddress |= taskID;
	return boardAddress;
}

inline PayloadMessage::BoardID GetBoardID(PayloadMessage::BoardAddress boardAddress)
{
	return (PayloadMessage::BoardID) (boardAddress >> 8);
}

inline PayloadMessage::TaskID GetTaskID(PayloadMessage::BoardAddress boardAddress)
{
	return (PayloadMessage::TaskID) boardAddress;
}

}
}

#endif /* PAYLOADMESSAGE_H_ */
