#ifndef MS_RTC_SCTP_STATE_COOKIE_HPP
#define MS_RTC_SCTP_STATE_COOKIE_HPP

#include "common.hpp"
#include "Utils.hpp"
#include "RTC/SCTP/association/NegotiatedCapabilities.hpp"
#include "RTC/SCTP/public/SctpTypes.hpp"
#include "RTC/Serializable.hpp"
#include <string_view>

namespace RTC
{
	namespace SCTP
	{
		/**
		 * This is the State Cookie we generate and put into a State Cookie
		 * Parameter when we send INIT_ACK Chunk to the remote peer.
		 *
		 * The syntax we use is as follows. Note that we use a fixed length of
		 * StateCookieLength bytes.
		 *
		 *  0                   1                   2                   3
		 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 * |                            Magic 1                            |
		 * |                                                               |
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 * |                    Local Verification Tag                     |
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 * |                    Remote Verification Tag                    |
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 * |                      Local Initial TSN                        |
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 * |                      Remote Initial TSN                       |
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 * |       Remote Advertised Receiver Window Credit (a_rwnd)       |
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 * |                            Tie-Tag                            |
		 * |                                                               |
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 * \                                                               \
		 * /                     Negotiated Capabilities                   /
		 * \                                                               \
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 *
		 * Negotiated Capabilities are serialized as follows:
		 *
		 *  0                   1                   2                   3
		 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 * |   (Reserved)  |       |D|C|B|A|            Magic 2            |
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 * |      Max Outbound Streams     |       Max Inbound Streams     |
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 *
		 * - Flag A (partialReliability): Partial Reliability Extension.
		 * - Flag B (messageInterleaving): Stream Schedulers and User Message
		 *   Interleaving (I-DATA).
		 * - Flag C (reconfig): Stream Reconfiguration.
		 * - Flag D (zeroChecksum): Zero Checksum.
		 */
		class StateCookie : public Serializable
		{
		private:
			struct NegotiatedCapabilitiesField
			{
				uint8_t reserved;
#if defined(MS_LITTLE_ENDIAN)
				uint8_t bitA : 1;
				uint8_t bitB : 1;
				uint8_t bitC : 1;
				uint8_t bitD : 1;
				uint8_t unusedBits : 4;
#elif defined(MS_BIG_ENDIAN)
				uint8_t unusedBits : 4;
				uint8_t bitD : 1;
				uint8_t bitC : 1;
				uint8_t bitB : 1;
				uint8_t bitA : 1;
#endif
				uint16_t magic2;
				uint16_t maxOutboundStreams;
				uint16_t maxInboundStreams;
			};

		public:
			// Fixed total length of our generated State Cookies.
			static constexpr size_t StateCookieLength{ 44 };
			// Offset in the State Cookie where the Negotiated Capabilitied are
			// located.
			static constexpr size_t NegotiatedCapabilitiesOffset{ 36 };
			// Magic value we prefix the State Cookie with. Note that it is
			// "msworker" in ASCII bytes.
			static constexpr uint64_t Magic1{ 0x6D73776F726B6572 };
			static constexpr size_t Magic1Length{ 8 };
			// Magic value used within the Negotiated Capabilities block.
			static constexpr uint16_t Magic2{ 0xAD81 };

		public:
			/**
			 * Whether the given buffer is a StateCookie generated by mediasoup.
			 */
			static bool IsMediasoupStateCookie(const uint8_t* buffer, size_t bufferLength);

			/**
			 * Parse a StateCookie supposely generated by mediasoup.
			 *
			 * @remarks
			 * `bufferLength` must be the exact length of the State Cookie.
			 */
			static StateCookie* Parse(const uint8_t* buffer, size_t bufferLength);

			/**
			 * Create a StateCookie.
			 *
			 * @remarks
			 * `bufferLength` could be greater than the real length of the State
			 * Cookie.
			 */
			static StateCookie* Factory(
			  uint8_t* buffer,
			  size_t bufferLength,
			  uint32_t localVerificationTag,
			  uint32_t remoteVerificationTag,
			  uint32_t localInitialTsn,
			  uint32_t remoteInitialTsn,
			  uint32_t remoteAdvertisedReceiverWindowCredit,
			  uint64_t tieTag,
			  const NegotiatedCapabilities& negotiatedCapabilities);

			/**
			 * Serialize a StateCookie (based on given arguments) in the given buffer.
			 */
			static void Write(
			  uint8_t* buffer,
			  size_t bufferLength,
			  uint32_t localVerificationTag,
			  uint32_t remoteVerificationTag,
			  uint32_t localInitialTsn,
			  uint32_t remoteInitialTsn,
			  uint32_t remoteAdvertisedReceiverWindowCredit,
			  uint64_t tieTag,
			  const NegotiatedCapabilities& negotiatedCapabilities);

			/**
			 * Determine the SCTP implementation of the generator of State Cookie
			 * given in the buffer.
			 */
			static Types::SctpImplementation DetermineSctpImplementation(
			  const uint8_t* buffer, size_t bufferLength);

		public:
			StateCookie(uint8_t* buffer, size_t bufferLength);

			~StateCookie() override;

			void Dump(int indentation = 0) const final;

			StateCookie* Clone(uint8_t* buffer, size_t bufferLength) const final;

			/**
			 * The value of the Initiate Tag field we put in our INIT or INIT_ACK
			 * Chunk. Packets sent by the remote peer must include this value in
			 * their Verification Tag field.
			 */
			uint32_t GetLocalVerificationTag() const
			{
				return Utils::Byte::Get4Bytes(GetBuffer(), 8);
			}

			/**
			 * The value of the Initiate Tag field the peer put in its INIT or
			 * INIT_ACK Chunk. Packets sent by us to the peer must include this value
			 * in their Verification Tag field.
			 */
			uint32_t GetRemoteVerificationTag() const
			{
				return Utils::Byte::Get4Bytes(GetBuffer(), 12);
			}

			/**
			 * The value of the Initial TSN field we put in our INIT or INIT_ACK
			 * Chunk.
			 */
			uint32_t GetLocalInitialTsn() const
			{
				return Utils::Byte::Get4Bytes(GetBuffer(), 16);
			}

			/**
			 * The value of the Initial TSN field the peer put in its INIT or
			 * INIT_ACK Chunk.
			 */
			uint32_t GetRemoteInitialTsn() const
			{
				return Utils::Byte::Get4Bytes(GetBuffer(), 20);
			}

			/**
			 * The value of the Advertised Receiver Window Credit field we put in our
			 * INIT or INIT_ACK Chunk.
			 */
			uint32_t GetRemoteAdvertisedReceiverWindowCredit() const
			{
				return Utils::Byte::Get4Bytes(GetBuffer(), 24);
			}

			/**
			 * Tie-Tag used as a nonce when connecting.
			 */
			uint64_t GetTieTag() const
			{
				return Utils::Byte::Get8Bytes(GetBuffer(), 28);
			}

			/**
			 * Negotiated association capabilities.
			 */
			NegotiatedCapabilities GetNegotiatedCapabilities() const;

		private:
			NegotiatedCapabilitiesField* GetNegotiatedCapabilitiesField() const
			{
				return reinterpret_cast<NegotiatedCapabilitiesField*>(
				  const_cast<uint8_t*>(GetBuffer()) + StateCookie::NegotiatedCapabilitiesOffset);
			}
		};
	} // namespace SCTP
} // namespace RTC

#endif
