#define MS_CLASS "RTC::SCTP::Packet"
// #define MS_LOG_DEV_LEVEL 3

#include "RTC/SCTP/association/StateCookie.hpp"
#include "Logger.hpp"
#include "MediaSoupErrors.hpp"

namespace RTC
{
	namespace SCTP
	{
		/* Class methods. */

		bool StateCookie::IsMediasoupStateCookie(const uint8_t* buffer, size_t bufferLength)
		{
			MS_TRACE();

			if (bufferLength != StateCookie::StateCookieLength)
			{
				return false;
			}

			if (Utils::Byte::Get8Bytes(buffer, 0) != StateCookie::Magic1)
			{
				return false;
			}

			auto* negotiatedCapabilitiesField = reinterpret_cast<NegotiatedCapabilitiesField*>(
			  const_cast<uint8_t*>(buffer) + StateCookie::NegotiatedCapabilitiesOffset);

			if (ntohs(negotiatedCapabilitiesField->magic2) != StateCookie::Magic2)
			{
				return false;
			}

			return true;
		}

		StateCookie* StateCookie::Parse(const uint8_t* buffer, size_t bufferLength)
		{
			MS_TRACE();

			if (!StateCookie::IsMediasoupStateCookie(buffer, bufferLength))
			{
				MS_WARN_TAG(sctp, "not a StateCookie generated by mediasoup");

				return nullptr;
			}

			auto* stateCookie = new StateCookie(const_cast<uint8_t*>(buffer), bufferLength);

			return stateCookie;
		}

		StateCookie* 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)
		{
			MS_TRACE();

			// This may throw.
			StateCookie::Write(
			  buffer,
			  bufferLength,
			  localVerificationTag,
			  remoteVerificationTag,
			  localInitialTsn,
			  remoteInitialTsn,
			  remoteAdvertisedReceiverWindowCredit,
			  tieTag,
			  negotiatedCapabilities);

			return new StateCookie(buffer, StateCookie::StateCookieLength);
		}

		void StateCookie::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)
		{
			MS_TRACE();

			if (bufferLength < StateCookie::StateCookieLength)
			{
				MS_THROW_TYPE_ERROR("buffer too small");
			}

			Utils::Byte::Set8Bytes(buffer, 0, StateCookie::Magic1);
			Utils::Byte::Set4Bytes(buffer, 8, localVerificationTag);
			Utils::Byte::Set4Bytes(buffer, 12, remoteVerificationTag);
			Utils::Byte::Set4Bytes(buffer, 16, localInitialTsn);
			Utils::Byte::Set4Bytes(buffer, 20, remoteInitialTsn);
			Utils::Byte::Set4Bytes(buffer, 24, remoteAdvertisedReceiverWindowCredit);
			Utils::Byte::Set8Bytes(buffer, 28, tieTag);

			auto* negotiatedCapabilitiesField = reinterpret_cast<NegotiatedCapabilitiesField*>(
			  buffer + StateCookie::NegotiatedCapabilitiesOffset);

			negotiatedCapabilitiesField->reserved = 0;
			negotiatedCapabilitiesField->bitA     = negotiatedCapabilities.partialReliability;
			negotiatedCapabilitiesField->bitB     = negotiatedCapabilities.messageInterleaving;
			negotiatedCapabilitiesField->bitC     = negotiatedCapabilities.reConfig;
			negotiatedCapabilitiesField->bitD     = negotiatedCapabilities.zeroChecksum;
			negotiatedCapabilitiesField->magic2   = htons(StateCookie::Magic2);
			negotiatedCapabilitiesField->maxOutboundStreams =
			  htons(negotiatedCapabilities.maxOutboundStreams);
			negotiatedCapabilitiesField->maxInboundStreams =
			  htons(negotiatedCapabilities.maxInboundStreams);
		}

		Types::SctpImplementation StateCookie::DetermineSctpImplementation(
		  const uint8_t* buffer, size_t bufferLength)
		{
			MS_TRACE();

			if (bufferLength < StateCookie::Magic1Length)
			{
				return Types::SctpImplementation::UNKNOWN;
			}

			const std::string_view magic1(reinterpret_cast<const char*>(buffer), StateCookie::Magic1Length);

			if (magic1 == "msworker")
			{
				return Types::SctpImplementation::MEDIASOUP;
			}
			else if (magic1 == "dcSCTP00")
			{
				return Types::SctpImplementation::DCSCTP;
			}
			else if (magic1 == "KAME-BSD")
			{
				return Types::SctpImplementation::USRSCTP;
			}
			else
			{
				return Types::SctpImplementation::UNKNOWN;
			}
		}

		/* Instance methods. */

		StateCookie::StateCookie(uint8_t* buffer, size_t bufferLength)
		  : Serializable(buffer, bufferLength)
		{
			MS_TRACE();

			SetLength(StateCookie::StateCookieLength);
		}

		StateCookie::~StateCookie()
		{
			MS_TRACE();
		}

		void StateCookie::Dump(int indentation) const
		{
			MS_TRACE();

			auto negotiatedCapabilities = GetNegotiatedCapabilities();

			MS_DUMP_CLEAN(indentation, "<SCTP::StateCookie>");
			MS_DUMP_CLEAN(indentation, "  length: %zu (buffer length: %zu)", GetLength(), GetBufferLength());
			MS_DUMP_CLEAN(indentation, "  local verification tag: %" PRIu32, GetLocalVerificationTag());
			MS_DUMP_CLEAN(indentation, "  remote verification tag: %" PRIu32, GetRemoteVerificationTag());
			MS_DUMP_CLEAN(indentation, "  local initial tsn: %" PRIu32, GetLocalInitialTsn());
			MS_DUMP_CLEAN(indentation, "  remote initial tsn: %" PRIu32, GetRemoteInitialTsn());
			MS_DUMP_CLEAN(
			  indentation,
			  "  remote advertised receiver window credit: %" PRIu32,
			  GetRemoteAdvertisedReceiverWindowCredit());
			MS_DUMP_CLEAN(indentation, "  tie-tag: %" PRIu64, GetTieTag());
			negotiatedCapabilities.Dump(indentation + 1);
			MS_DUMP_CLEAN(indentation, "</SCTP::StateCookie>");
		}

		StateCookie* StateCookie::Clone(uint8_t* buffer, size_t bufferLength) const
		{
			MS_TRACE();

			auto* clonedStateCookie = new StateCookie(buffer, bufferLength);

			Serializable::CloneInto(clonedStateCookie);

			return clonedStateCookie;
		}

		NegotiatedCapabilities StateCookie::GetNegotiatedCapabilities() const
		{
			MS_TRACE();

			auto* negotiatedCapabilitiesField = GetNegotiatedCapabilitiesField();

			NegotiatedCapabilities negotiatedCapabilities;

			negotiatedCapabilities.maxOutboundStreams =
			  ntohs(negotiatedCapabilitiesField->maxOutboundStreams);
			negotiatedCapabilities.maxInboundStreams =
			  ntohs(negotiatedCapabilitiesField->maxInboundStreams);
			negotiatedCapabilities.partialReliability  = negotiatedCapabilitiesField->bitA;
			negotiatedCapabilities.messageInterleaving = negotiatedCapabilitiesField->bitB;
			negotiatedCapabilities.reConfig            = negotiatedCapabilitiesField->bitC;
			negotiatedCapabilities.zeroChecksum        = negotiatedCapabilitiesField->bitD;

			// NOTE: No need to std::move(). Copy elision (RVO) is used for free in GCC
			// and clang in C++17 or higher.
			return negotiatedCapabilities;
		}
	} // namespace SCTP
} // namespace RTC
