/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "sdp/SdpMediaSection.h"
#include "sdp/RsdparsaSdpMediaSection.h"

#include "sdp/RsdparsaSdpGlue.h"
#include "sdp/RsdparsaSdpInc.h"

#include <ostream>

#ifdef CRLF
#  undef CRLF
#endif
#define CRLF "\r\n"

namespace mozilla {

RsdparsaSdpMediaSection::RsdparsaSdpMediaSection(
    size_t level, RsdparsaSessionHandle session,
    const RustMediaSection* const section,
    const RsdparsaSdpAttributeList* sessionLevel)
    : SdpMediaSection(level), mSession(std::move(session)), mSection(section) {
  switch (sdp_rust_get_media_type(section)) {
    case RustSdpMediaValue::kRustAudio:
      mMediaType = kAudio;
      break;
    case RustSdpMediaValue::kRustVideo:
      mMediaType = kVideo;
      break;
    case RustSdpMediaValue::kRustApplication:
      mMediaType = kApplication;
      break;
  }

  RsdparsaSessionHandle attributeSession(sdp_new_reference(mSession.get()));
  mAttributeList.reset(new RsdparsaSdpAttributeList(std::move(attributeSession),
                                                    section, sessionLevel));

  LoadFormats();
  LoadConnection();
}

unsigned int RsdparsaSdpMediaSection::GetPort() const {
  return sdp_get_media_port(mSection);
}

void RsdparsaSdpMediaSection::SetPort(unsigned int port) {
  sdp_set_media_port(mSection, port);
}

unsigned int RsdparsaSdpMediaSection::GetPortCount() const {
  return sdp_get_media_port_count(mSection);
}

SdpMediaSection::Protocol RsdparsaSdpMediaSection::GetProtocol() const {
  switch (sdp_get_media_protocol(mSection)) {
    case RustSdpProtocolValue::kRustRtpSavpf:
      return kRtpSavpf;
    case RustSdpProtocolValue::kRustUdpTlsRtpSavp:
      return kUdpTlsRtpSavp;
    case RustSdpProtocolValue::kRustTcpDtlsRtpSavp:
      return kTcpDtlsRtpSavp;
    case RustSdpProtocolValue::kRustUdpTlsRtpSavpf:
      return kUdpTlsRtpSavpf;
    case RustSdpProtocolValue::kRustTcpDtlsRtpSavpf:
      return kTcpDtlsRtpSavpf;
    case RustSdpProtocolValue::kRustDtlsSctp:
      return kDtlsSctp;
    case RustSdpProtocolValue::kRustUdpDtlsSctp:
      return kUdpDtlsSctp;
    case RustSdpProtocolValue::kRustTcpDtlsSctp:
      return kTcpDtlsSctp;
    case RustSdpProtocolValue::kRustRtpAvp:
      return kRtpAvp;
    case RustSdpProtocolValue::kRustRtpAvpf:
      return kRtpAvpf;
    case RustSdpProtocolValue::kRustRtpSavp:
      return kRtpSavp;
  }
  MOZ_CRASH("invalid media protocol");
}

const SdpConnection& RsdparsaSdpMediaSection::GetConnection() const {
  MOZ_ASSERT(mConnection);
  return *mConnection;
}

SdpConnection& RsdparsaSdpMediaSection::GetConnection() {
  MOZ_ASSERT(mConnection);
  return *mConnection;
}

uint32_t RsdparsaSdpMediaSection::GetBandwidth(const std::string& type) const {
  return sdp_get_media_bandwidth(mSection, type.c_str());
}

const std::vector<std::string>& RsdparsaSdpMediaSection::GetFormats() const {
  return mFormats;
}

const SdpAttributeList& RsdparsaSdpMediaSection::GetAttributeList() const {
  return *mAttributeList;
}

SdpAttributeList& RsdparsaSdpMediaSection::GetAttributeList() {
  return *mAttributeList;
}

SdpDirectionAttribute RsdparsaSdpMediaSection::GetDirectionAttribute() const {
  return SdpDirectionAttribute(mAttributeList->GetDirection());
}

void RsdparsaSdpMediaSection::AddCodec(const std::string& pt,
                                       const std::string& name,
                                       uint32_t clockrate, uint16_t channels) {
  StringView rustName{name.c_str(), name.size()};

  // call the rust interface
  auto nr = sdp_media_add_codec(mSection, std::stoul(pt), rustName, clockrate,
                                channels);

  if (NS_SUCCEEDED(nr)) {
    // If the rust call was successful, adjust the shadow C++ structures
    mFormats.push_back(pt);

    // Add a rtpmap in mAttributeList
    SdpRtpmapAttributeList* rtpmap = new SdpRtpmapAttributeList();
    if (mAttributeList->HasAttribute(SdpAttribute::kRtpmapAttribute)) {
      const SdpRtpmapAttributeList& old = mAttributeList->GetRtpmap();
      for (auto it = old.mRtpmaps.begin(); it != old.mRtpmaps.end(); ++it) {
        rtpmap->mRtpmaps.push_back(*it);
      }
    }

    SdpRtpmapAttributeList::CodecType codec =
        SdpRtpmapAttributeList::kOtherCodec;
    if (name == "opus") {
      codec = SdpRtpmapAttributeList::kOpus;
    } else if (name == "VP8") {
      codec = SdpRtpmapAttributeList::kVP8;
    } else if (name == "VP9") {
      codec = SdpRtpmapAttributeList::kVP9;
    } else if (name == "H264") {
      codec = SdpRtpmapAttributeList::kH264;
    }

    rtpmap->PushEntry(pt, codec, name, clockrate, channels);
    mAttributeList->SetAttribute(rtpmap);
  }
}

void RsdparsaSdpMediaSection::ClearCodecs() {
  // Clear the codecs in rust
  sdp_media_clear_codecs(mSection);

  mFormats.clear();
  mAttributeList->RemoveAttribute(SdpAttribute::kRtpmapAttribute);
  mAttributeList->RemoveAttribute(SdpAttribute::kFmtpAttribute);
  mAttributeList->RemoveAttribute(SdpAttribute::kSctpmapAttribute);
  mAttributeList->RemoveAttribute(SdpAttribute::kRtcpFbAttribute);
}

void RsdparsaSdpMediaSection::AddDataChannel(const std::string& name,
                                             uint16_t port, uint16_t streams,
                                             uint32_t message_size) {
  StringView rustName{name.c_str(), name.size()};
  auto nr = sdp_media_add_datachannel(mSection, rustName, port, streams,
                                      message_size);
  if (NS_SUCCEEDED(nr)) {
    // Update the formats
    mFormats.clear();
    LoadFormats();

    // Update the attribute list
    RsdparsaSessionHandle sessHandle(sdp_new_reference(mSession.get()));
    auto sessAttributes = mAttributeList->mSessionAttributes;
    mAttributeList.reset(new RsdparsaSdpAttributeList(
        std::move(sessHandle), mSection, sessAttributes));
  }
}

void RsdparsaSdpMediaSection::Serialize(std::ostream& os) const {
  os << "m=" << mMediaType << " " << GetPort();
  if (GetPortCount()) {
    os << "/" << GetPortCount();
  }
  os << " " << GetProtocol();
  for (auto i = mFormats.begin(); i != mFormats.end(); ++i) {
    os << " " << (*i);
  }
  os << CRLF;

  // We dont do i=

  if (mConnection) {
    os << *mConnection;
  }

  BandwidthVec* bwVec = sdp_get_media_bandwidth_vec(mSection);
  char* bwString = sdp_serialize_bandwidth(bwVec);
  if (bwString) {
    os << bwString;
    sdp_free_string(bwString);
  }

  // We dont do k= because they're evil

  os << *mAttributeList;
}

void RsdparsaSdpMediaSection::LoadFormats() {
  RustSdpFormatType formatType = sdp_get_format_type(mSection);
  if (formatType == RustSdpFormatType::kRustIntegers) {
    U32Vec* vec = sdp_get_format_u32_vec(mSection);
    size_t len = u32_vec_len(vec);
    for (size_t i = 0; i < len; i++) {
      uint32_t val;
      u32_vec_get(vec, i, &val);
      mFormats.push_back(std::to_string(val));
    }
  } else {
    StringVec* vec = sdp_get_format_string_vec(mSection);
    mFormats = convertStringVec(vec);
  }
}

UniquePtr<SdpConnection> convertRustConnection(RustSdpConnection conn) {
  auto address = convertExplicitlyTypedAddress(&conn.addr);
  return MakeUnique<SdpConnection>(address.first, address.second, conn.ttl,
                                   conn.amount);
}

void RsdparsaSdpMediaSection::LoadConnection() {
  RustSdpConnection conn;
  nsresult nr;
  if (sdp_media_has_connection(mSection)) {
    nr = sdp_get_media_connection(mSection, &conn);
    if (NS_SUCCEEDED(nr)) {
      mConnection = convertRustConnection(conn);
    }
  } else if (sdp_session_has_connection(mSession.get())) {
    nr = sdp_get_session_connection(mSession.get(), &conn);
    if (NS_SUCCEEDED(nr)) {
      mConnection = convertRustConnection(conn);
    }
  }
}

}  // namespace mozilla
