/*

 *  Copyright (c) 2023 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/h265_profile_tier_level.h"



#include <string>



#include "rtc_base/string_to_number.h"



namespace webrtc {



namespace {



const char kH265FmtpProfile[] = "profile-id";

const char kH265FmtpTier[] = "tier-flag";

const char kH265FmtpLevel[] = "level-id";



}  // anonymous namespace



// Annex A of https://www.itu.int/rec/T-REC-H.265 (08/21), section A.3.

absl::optional<H265Profile> StringToH265Profile(const std::string& profile) {

  absl::optional<int> i = rtc::StringToNumber<int>(profile);

  if (!i.has_value()) {

    return absl::nullopt;

  }



  switch (i.value()) {

    case 1:

      return H265Profile::kProfileMain;

    case 2:

      return H265Profile::kProfileMain10;

    case 3:

      return H265Profile::kProfileMainStill;

    case 4:

      return H265Profile::kProfileRangeExtensions;

    case 5:

      return H265Profile::kProfileHighThroughput;

    case 6:

      return H265Profile::kProfileMultiviewMain;

    case 7:

      return H265Profile::kProfileScalableMain;

    case 8:

      return H265Profile::kProfile3dMain;

    case 9:

      return H265Profile::kProfileScreenContentCoding;

    case 10:

      return H265Profile::kProfileScalableRangeExtensions;

    case 11:

      return H265Profile::kProfileHighThroughputScreenContentCoding;

    default:

      return absl::nullopt;

  }

}



// Annex A of https://www.itu.int/rec/T-REC-H.265 (08/21), section A.4,

// tiers and levels.

absl::optional<H265Tier> StringToH265Tier(const std::string& tier) {

  absl::optional<int> i = rtc::StringToNumber<int>(tier);

  if (!i.has_value()) {

    return absl::nullopt;

  }



  switch (i.value()) {

    case 0:

      return H265Tier::kTier0;

    case 1:

      return H265Tier::kTier1;

    default:

      return absl::nullopt;

  }

}



absl::optional<H265Level> StringToH265Level(const std::string& level) {

  const absl::optional<int> i = rtc::StringToNumber<int>(level);

  if (!i.has_value())

    return absl::nullopt;



  switch (i.value()) {

    case 30:

      return H265Level::kLevel1;

    case 60:

      return H265Level::kLevel2;

    case 63:

      return H265Level::kLevel2_1;

    case 90:

      return H265Level::kLevel3;

    case 93:

      return H265Level::kLevel3_1;

    case 120:

      return H265Level::kLevel4;

    case 123:

      return H265Level::kLevel4_1;

    case 150:

      return H265Level::kLevel5;

    case 153:

      return H265Level::kLevel5_1;

    case 156:

      return H265Level::kLevel5_2;

    case 180:

      return H265Level::kLevel6;

    case 183:

      return H265Level::kLevel6_1;

    case 186:

      return H265Level::kLevel6_2;

    default:

      return absl::nullopt;

  }

}



std::string H265ProfileToString(H265Profile profile) {

  switch (profile) {

    case H265Profile::kProfileMain:

      return "1";

    case H265Profile::kProfileMain10:

      return "2";

    case H265Profile::kProfileMainStill:

      return "3";

    case H265Profile::kProfileRangeExtensions:

      return "4";

    case H265Profile::kProfileHighThroughput:

      return "5";

    case H265Profile::kProfileMultiviewMain:

      return "6";

    case H265Profile::kProfileScalableMain:

      return "7";

    case H265Profile::kProfile3dMain:

      return "8";

    case H265Profile::kProfileScreenContentCoding:

      return "9";

    case H265Profile::kProfileScalableRangeExtensions:

      return "10";

    case H265Profile::kProfileHighThroughputScreenContentCoding:

      return "11";

  }

}



std::string H265TierToString(H265Tier tier) {

  switch (tier) {

    case H265Tier::kTier0:

      return "0";

    case H265Tier::kTier1:

      return "1";

  }

}



std::string H265LevelToString(H265Level level) {

  switch (level) {

    case H265Level::kLevel1:

      return "30";

    case H265Level::kLevel2:

      return "60";

    case H265Level::kLevel2_1:

      return "63";

    case H265Level::kLevel3:

      return "90";

    case H265Level::kLevel3_1:

      return "93";

    case H265Level::kLevel4:

      return "120";

    case H265Level::kLevel4_1:

      return "123";

    case H265Level::kLevel5:

      return "150";

    case H265Level::kLevel5_1:

      return "153";

    case H265Level::kLevel5_2:

      return "156";

    case H265Level::kLevel6:

      return "180";

    case H265Level::kLevel6_1:

      return "183";

    case H265Level::kLevel6_2:

      return "186";

  }

}



absl::optional<H265ProfileTierLevel> ParseSdpForH265ProfileTierLevel(

    const SdpVideoFormat::Parameters& params) {

  static const H265ProfileTierLevel kDefaultProfileTierLevel(

      H265Profile::kProfileMain, H265Tier::kTier0, H265Level::kLevel3_1);

  bool profile_tier_level_specified = false;



  absl::optional<H265Profile> profile;

  const auto profile_it = params.find(kH265FmtpProfile);

  if (profile_it != params.end()) {

    profile_tier_level_specified = true;

    const std::string& profile_str = profile_it->second;

    profile = StringToH265Profile(profile_str);

    if (!profile) {

      return absl::nullopt;

    }

  } else {

    profile = H265Profile::kProfileMain;

  }

  absl::optional<H265Tier> tier;

  const auto tier_it = params.find(kH265FmtpTier);

  if (tier_it != params.end()) {

    profile_tier_level_specified = true;

    const std::string& tier_str = tier_it->second;

    tier = StringToH265Tier(tier_str);

    if (!tier) {

      return absl::nullopt;

    }

  } else {

    tier = H265Tier::kTier0;

  }

  absl::optional<H265Level> level;

  const auto level_it = params.find(kH265FmtpLevel);

  if (level_it != params.end()) {

    profile_tier_level_specified = true;

    const std::string& level_str = level_it->second;

    level = StringToH265Level(level_str);

    if (!level) {

      return absl::nullopt;

    }

  } else {

    level = H265Level::kLevel3_1;

  }



  // Spec Table A.9, level 1 to level 3.1 does not allow high tiers.

  if (level <= H265Level::kLevel3_1 && tier == H265Tier::kTier1) {

    return absl::nullopt;

  }



  return !profile_tier_level_specified

             ? kDefaultProfileTierLevel

             : H265ProfileTierLevel(profile.value(), tier.value(),

                                    level.value());

}



bool H265IsSameProfileTierLevel(const SdpVideoFormat::Parameters& params1,

                                const SdpVideoFormat::Parameters& params2) {

  const absl::optional<H265ProfileTierLevel> ptl1 =

      ParseSdpForH265ProfileTierLevel(params1);

  const absl::optional<H265ProfileTierLevel> ptl2 =

      ParseSdpForH265ProfileTierLevel(params2);

  return ptl1 && ptl2 && ptl1->profile == ptl2->profile &&

         ptl1->tier == ptl2->tier && ptl1->level == ptl2->level;

}



}  // namespace webrtc