/*
 *  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 <optional>
#include <string>

#include "api/rtp_parameters.h"
#include "api/video/resolution.h"
#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";

// Used to align frame width and height for luma picture size calculation.
// Use the maximum value allowed by spec to get upper bound of luma picture
// size for given resolution.
constexpr int kMinCbSizeYMax = 64;

struct LevelConstraint {
  const int max_luma_picture_size;
  const double max_luma_sample_rate;
  const int max_pic_width_or_height_in_pixels;
  const H265Level level;
};

// This is from ITU-T H.265 (09/2023) Table A.8, A.9 & A.11 – Level limits.
// The max_pic_width_or_height_in_luma_samples is pre-calculated following
// ITU-T H.265 section A.4.1, that is, find the largest integer value that
// is multiple of minimal MinCbSizeY(8 according to equation 7-10 and 7-12), is
// less than sqrt(max_luma_picture_size * 8). For example, at level 1,
// max_luma_picture_size is 36864, so pic_width_in_luma_samples <= sqrt(36864 *
// 8) = 543.06. The largest integer that is multiple of 8 and less than 543.06
// is 536.
constexpr LevelConstraint kLevelConstraints[] = {
    {.max_luma_picture_size = 36864,
     .max_luma_sample_rate = 552960,
     .max_pic_width_or_height_in_pixels = 536,
     .level = H265Level::kLevel1},
    {.max_luma_picture_size = 122880,
     .max_luma_sample_rate = 3686400,
     .max_pic_width_or_height_in_pixels = 984,
     .level = H265Level::kLevel2},
    {.max_luma_picture_size = 245760,
     .max_luma_sample_rate = 7372800,
     .max_pic_width_or_height_in_pixels = 1400,
     .level = H265Level::kLevel2_1},
    {.max_luma_picture_size = 552960,
     .max_luma_sample_rate = 16588800,
     .max_pic_width_or_height_in_pixels = 2096,
     .level = H265Level::kLevel3},
    {.max_luma_picture_size = 983040,
     .max_luma_sample_rate = 33177600,
     .max_pic_width_or_height_in_pixels = 2800,
     .level = H265Level::kLevel3_1},
    {.max_luma_picture_size = 2228224,
     .max_luma_sample_rate = 66846720,
     .max_pic_width_or_height_in_pixels = 4216,
     .level = H265Level::kLevel4},
    {.max_luma_picture_size = 2228224,
     .max_luma_sample_rate = 133693400,
     .max_pic_width_or_height_in_pixels = 4216,
     .level = H265Level::kLevel4_1},
    {.max_luma_picture_size = 8912896,
     .max_luma_sample_rate = 267386880,
     .max_pic_width_or_height_in_pixels = 8440,
     .level = H265Level::kLevel5},
    {.max_luma_picture_size = 8912896,
     .max_luma_sample_rate = 534773760,
     .max_pic_width_or_height_in_pixels = 8440,
     .level = H265Level::kLevel5_1},
    {.max_luma_picture_size = 8912896,
     .max_luma_sample_rate = 1069547520,
     .max_pic_width_or_height_in_pixels = 8440,
     .level = H265Level::kLevel5_2},
    {.max_luma_picture_size = 35651584,
     .max_luma_sample_rate = 1069547520,
     .max_pic_width_or_height_in_pixels = 16888,
     .level = H265Level::kLevel6},
    {.max_luma_picture_size = 35651584,
     .max_luma_sample_rate = 2139095040,
     .max_pic_width_or_height_in_pixels = 16888,
     .level = H265Level::kLevel6_1},
    {.max_luma_picture_size = 35651584,
     .max_luma_sample_rate = 4278190080,
     .max_pic_width_or_height_in_pixels = 16888,
     .level = H265Level::kLevel6_2},
};

}  // anonymous namespace

// Annex A of https://www.itu.int/rec/T-REC-H.265 (08/21), section A.3.
std::optional<H265Profile> StringToH265Profile(const std::string& profile) {
  std::optional<int> i = StringToNumber<int>(profile);
  if (!i.has_value()) {
    return std::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 std::nullopt;
  }
}

// Annex A of https://www.itu.int/rec/T-REC-H.265 (08/21), section A.4,
// tiers and levels.
std::optional<H265Tier> StringToH265Tier(const std::string& tier) {
  std::optional<int> i = StringToNumber<int>(tier);
  if (!i.has_value()) {
    return std::nullopt;
  }

  switch (i.value()) {
    case 0:
      return H265Tier::kTier0;
    case 1:
      return H265Tier::kTier1;
    default:
      return std::nullopt;
  }
}

std::optional<H265Level> StringToH265Level(const std::string& level) {
  const std::optional<int> i = StringToNumber<int>(level);
  if (!i.has_value())
    return std::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 std::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";
  }
}

std::optional<H265ProfileTierLevel> ParseSdpForH265ProfileTierLevel(
    const CodecParameterMap& params) {
  static const H265ProfileTierLevel kDefaultProfileTierLevel(
      H265Profile::kProfileMain, H265Tier::kTier0, H265Level::kLevel3_1);
  bool profile_tier_level_specified = false;

  std::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 std::nullopt;
    }
  } else {
    profile = H265Profile::kProfileMain;
  }
  std::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 std::nullopt;
    }
  } else {
    tier = H265Tier::kTier0;
  }
  std::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 std::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 std::nullopt;
  }

  return !profile_tier_level_specified
             ? kDefaultProfileTierLevel
             : H265ProfileTierLevel(profile.value(), tier.value(),
                                    level.value());
}

bool H265IsSameProfileTierLevel(const CodecParameterMap& params1,
                                const CodecParameterMap& params2) {
  const std::optional<H265ProfileTierLevel> ptl1 =
      ParseSdpForH265ProfileTierLevel(params1);
  const std::optional<H265ProfileTierLevel> ptl2 =
      ParseSdpForH265ProfileTierLevel(params2);
  return ptl1 && ptl2 && ptl1->profile == ptl2->profile &&
         ptl1->tier == ptl2->tier && ptl1->level == ptl2->level;
}

bool H265IsSameProfile(const CodecParameterMap& params1,
                       const CodecParameterMap& params2) {
  const std::optional<H265ProfileTierLevel> ptl1 =
      ParseSdpForH265ProfileTierLevel(params1);
  const std::optional<H265ProfileTierLevel> ptl2 =
      ParseSdpForH265ProfileTierLevel(params2);
  return ptl1 && ptl2 && ptl1->profile == ptl2->profile;
}

bool H265IsSameTier(const CodecParameterMap& params1,
                    const CodecParameterMap& params2) {
  const std::optional<H265ProfileTierLevel> ptl1 =
      ParseSdpForH265ProfileTierLevel(params1);
  const std::optional<H265ProfileTierLevel> ptl2 =
      ParseSdpForH265ProfileTierLevel(params2);
  return ptl1 && ptl2 && ptl1->tier == ptl2->tier;
}

std::optional<H265Level> GetSupportedH265Level(const Resolution& resolution,
                                               float max_fps) {
  int aligned_width =
      (resolution.width + kMinCbSizeYMax - 1) & ~(kMinCbSizeYMax - 1);
  int aligned_height =
      (resolution.height + kMinCbSizeYMax - 1) & ~(kMinCbSizeYMax - 1);

  for (int i = std::ssize(kLevelConstraints) - 1; i >= 0; --i) {
    const LevelConstraint& level_constraint = kLevelConstraints[i];
    if (level_constraint.max_luma_picture_size <=
            aligned_width * aligned_height &&
        level_constraint.max_luma_sample_rate <=
            aligned_width * aligned_height * max_fps &&
        level_constraint.max_pic_width_or_height_in_pixels >= aligned_width &&
        level_constraint.max_pic_width_or_height_in_pixels >= aligned_height) {
      return level_constraint.level;
    }
  }
  return std::nullopt;
}

}  // namespace webrtc
