/*
 *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "api/video_codecs/sdp_video_format.h"

#include <optional>
#include <span>
#include <string>

#include "absl/container/inlined_vector.h"
#include "absl/strings/match.h"
#include "api/rtp_parameters.h"
#include "api/video/video_codec_type.h"
#include "api/video_codecs/av1_profile.h"
#include "api/video_codecs/h264_profile_level_id.h"
#include "api/video_codecs/scalability_mode.h"
#ifdef RTC_ENABLE_H265
#include "api/video_codecs/h265_profile_tier_level.h"
#endif
#include "api/video_codecs/video_codec.h"
#include "api/video_codecs/vp9_profile.h"
#include "media/base/media_constants.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_builder.h"

namespace webrtc {

namespace {

// TODO(bugs.webrtc.org/15847): remove code duplication of IsSameCodecSpecific
// in media/base/codec.cc
std::string GetFmtpParameterOrDefault(const CodecParameterMap& params,
                                      const std::string& name,
                                      const std::string& default_value) {
  const auto it = params.find(name);
  if (it != params.end()) {
    return it->second;
  }
  return default_value;
}

std::string H264GetPacketizationModeOrDefault(const CodecParameterMap& params) {
  // If packetization-mode is not present, default to "0".
  // https://tools.ietf.org/html/rfc6184#section-6.2
  return GetFmtpParameterOrDefault(params, kH264FmtpPacketizationMode, "0");
}

bool H264IsSamePacketizationMode(const CodecParameterMap& left,
                                 const CodecParameterMap& right) {
  return H264GetPacketizationModeOrDefault(left) ==
         H264GetPacketizationModeOrDefault(right);
}

#ifdef RTC_ENABLE_H265
std::string GetH265TxModeOrDefault(const CodecParameterMap& params) {
  // If TxMode is not present, a value of "SRST" must be inferred.
  // https://tools.ietf.org/html/rfc7798@section-7.1
  return GetFmtpParameterOrDefault(params, kH265FmtpTxMode, "SRST");
}

bool IsSameH265TxMode(const CodecParameterMap& left,
                      const CodecParameterMap& right) {
  return absl::EqualsIgnoreCase(GetH265TxModeOrDefault(left),
                                GetH265TxModeOrDefault(right));
}
#endif

// Some (video) codecs are actually families of codecs and rely on parameters
// to distinguish different incompatible family members.
bool IsSameCodecSpecific(const std::string& name1,
                         const CodecParameterMap& params1,
                         const std::string& name2,
                         const CodecParameterMap& params2) {
  // The assumption when calling this function is that the two formats have the
  // same name.
  RTC_DCHECK(absl::EqualsIgnoreCase(name1, name2));

  VideoCodecType codec_type = PayloadStringToCodecType(name1);
  switch (codec_type) {
    case kVideoCodecH264:
      return H264IsSameProfile(params1, params2) &&
             H264IsSamePacketizationMode(params1, params2);
    case kVideoCodecVP9:
      return VP9IsSameProfile(params1, params2);
    case kVideoCodecAV1:
      // https://aomediacodec.github.io/av1-rtp-spec/#723-usage-with-the-sdp-offeranswer-model
      //   These media configuration parameters are asymmetrical and the
      //   answerer MAY declare its own media configuration
      // TODO(bugs.webrtc.org/396434695): for backward compability we currently
      // compare profile.
      return AV1IsSameProfile(params1, params2);
#ifdef RTC_ENABLE_H265
    case kVideoCodecH265:
      return H265IsSameProfile(params1, params2) &&
             H265IsSameTier(params1, params2) &&
             IsSameH265TxMode(params1, params2);
#endif
    default:
      return true;
  }
}

}  // namespace

SdpVideoFormat::SdpVideoFormat(const std::string& name) : name(name) {}

SdpVideoFormat::SdpVideoFormat(const std::string& name,
                               const CodecParameterMap& parameters)
    : name(name), parameters(parameters) {}

SdpVideoFormat::SdpVideoFormat(
    const std::string& name,
    const CodecParameterMap& parameters,
    const absl::InlinedVector<ScalabilityMode, kScalabilityModeCount>&
        scalability_modes)
    : name(name),
      parameters(parameters),
      scalability_modes(scalability_modes) {}

SdpVideoFormat::SdpVideoFormat(
    const SdpVideoFormat& format,
    const absl::InlinedVector<ScalabilityMode, kScalabilityModeCount>& modes)
    : SdpVideoFormat(format) {
  scalability_modes = modes;
}

SdpVideoFormat::SdpVideoFormat(const SdpVideoFormat&) = default;
SdpVideoFormat::SdpVideoFormat(SdpVideoFormat&&) = default;
SdpVideoFormat& SdpVideoFormat::operator=(const SdpVideoFormat&) = default;
SdpVideoFormat& SdpVideoFormat::operator=(SdpVideoFormat&&) = default;

SdpVideoFormat::~SdpVideoFormat() = default;

std::string SdpVideoFormat::ToString() const {
  StringBuilder builder;
  builder << "Codec name: " << name << ", parameters: {";
  for (const auto& kv : parameters) {
    builder << " " << kv.first << "=" << kv.second;
  }

  builder << " }";
  if (!scalability_modes.empty()) {
    builder << ", scalability_modes: [";
    bool first = true;
    for (const auto scalability_mode : scalability_modes) {
      if (first) {
        first = false;
      } else {
        builder << ", ";
      }
      builder << ScalabilityModeToString(scalability_mode);
    }
    builder << "]";
  }

  if (packetization) {
    builder << ", packetization: " << *packetization;
  }
  if (tx_mode) {
    builder << ", tx_mode: " << *tx_mode;
  }

  return builder.Release();
}

bool SdpVideoFormat::IsSameCodec(const SdpVideoFormat& other) const {
  // Two codecs are considered the same if the name matches (case insensitive)
  // and certain codec-specific parameters match.
  return absl::EqualsIgnoreCase(name, other.name) &&
         IsSameCodecSpecific(name, parameters, other.name, other.parameters);
}

bool SdpVideoFormat::IsCodecInList(
    std::span<const SdpVideoFormat> formats) const {
  for (const auto& format : formats) {
    if (IsSameCodec(format)) {
      return true;
    }
  }
  return false;
}

bool operator==(const SdpVideoFormat& a, const SdpVideoFormat& b) {
  return a.name == b.name && a.parameters == b.parameters &&
         a.scalability_modes == b.scalability_modes &&
         a.packetization == b.packetization && a.tx_mode == b.tx_mode;
}

const SdpVideoFormat SdpVideoFormat::VP8() {
  return SdpVideoFormat(kVp8CodecName, {});
}

const SdpVideoFormat SdpVideoFormat::H264() {
  // H264 will typically require more tweaking like setting
  // * packetization-mode (which defaults to 0 but 1 is more common)
  // * level-asymmetry-allowed (which defaults to 0 but 1 is more common)
  // * profile-level-id of which there are many.
  return SdpVideoFormat(kH264CodecName, {});
}

const SdpVideoFormat SdpVideoFormat::H265() {
  return SdpVideoFormat(kH265CodecName, {});
}

const SdpVideoFormat SdpVideoFormat::VP9Profile0() {
  return SdpVideoFormat(
      kVp9CodecName,
      {{kVP9FmtpProfileId, VP9ProfileToString(VP9Profile::kProfile0)}});
}

const SdpVideoFormat SdpVideoFormat::VP9Profile1() {
  return SdpVideoFormat(
      kVp9CodecName,
      {{kVP9FmtpProfileId, VP9ProfileToString(VP9Profile::kProfile1)}});
}

const SdpVideoFormat SdpVideoFormat::VP9Profile2() {
  return SdpVideoFormat(
      kVp9CodecName,
      {{kVP9FmtpProfileId, VP9ProfileToString(VP9Profile::kProfile2)}});
}

const SdpVideoFormat SdpVideoFormat::VP9Profile3() {
  return SdpVideoFormat(
      kVp9CodecName,
      {{kVP9FmtpProfileId, VP9ProfileToString(VP9Profile::kProfile3)}});
}

const SdpVideoFormat SdpVideoFormat::AV1Profile0() {
  // https://aomediacodec.github.io/av1-rtp-spec/#72-sdp-parameters
  return SdpVideoFormat(
      kAv1CodecName,
      {{kAv1FmtpProfile, AV1ProfileToString(AV1Profile::kProfile0).data()},
       {kAv1FmtpLevelIdx, "5"},
       {kAv1FmtpTier, "0"}});
}

const SdpVideoFormat SdpVideoFormat::AV1Profile1() {
  // https://aomediacodec.github.io/av1-rtp-spec/#72-sdp-parameters
  return SdpVideoFormat(
      kAv1CodecName,
      {{kAv1FmtpProfile, AV1ProfileToString(AV1Profile::kProfile1).data()},
       {kAv1FmtpLevelIdx, "5"},
       {kAv1FmtpTier, "0"}});
}

std::optional<SdpVideoFormat> FuzzyMatchSdpVideoFormat(
    std::span<const SdpVideoFormat> supported_formats,
    const SdpVideoFormat& format) {
  std::optional<SdpVideoFormat> res;
  int best_parameter_match = 0;
  for (const auto& supported_format : supported_formats) {
    if (absl::EqualsIgnoreCase(supported_format.name, format.name)) {
      int matching_parameters = 0;
      for (const auto& kv : supported_format.parameters) {
        auto it = format.parameters.find(kv.first);
        if (it != format.parameters.end() && it->second == kv.second) {
          matching_parameters += 1;
        }
      }

      if (!res || matching_parameters > best_parameter_match) {
        res = supported_format;
        best_parameter_match = matching_parameters;
      }
    }
  }

  if (!res) {
    RTC_LOG(LS_INFO) << "Failed to match SdpVideoFormat " << format.ToString();
  } else if (*res != format) {
    RTC_LOG(LS_INFO) << "Matched SdpVideoFormat " << format.ToString()
                     << " with " << res->ToString();
  }

  return res;
}

}  // namespace webrtc
