/*
 *  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/color_space.h"

#include <cstddef>
#include <cstdint>
#include <string>

#include "absl/types/optional.h"
#include "api/video/hdr_metadata.h"
#include "rtc_base/strings/string_builder.h"

namespace webrtc {
namespace {
// Try to convert `enum_value` into the enum class T. `enum_bitmask` is created
// by the funciton below. Returns true if conversion was successful, false
// otherwise.
template <typename T>
bool SetFromUint8(uint8_t enum_value, uint64_t enum_bitmask, T* out) {
  if ((enum_value < 64) && ((enum_bitmask >> enum_value) & 1)) {
    *out = static_cast<T>(enum_value);
    return true;
  }
  return false;
}

// This function serves as an assert for the constexpr function below. It's on
// purpose not declared as constexpr so that it causes a build problem if enum
// values of 64 or above are used. The bitmask and the code generating it would
// have to be extended if the standard is updated to include enum values >= 64.
int EnumMustBeLessThan64() {
  return -1;
}

template <typename T, size_t N>
constexpr int MakeMask(const int index, const int length, T (&values)[N]) {
  return length > 1
             ? (MakeMask(index, 1, values) +
                MakeMask(index + 1, length - 1, values))
             : (static_cast<uint8_t>(values[index]) < 64
                    ? (uint64_t{1} << static_cast<uint8_t>(values[index]))
                    : EnumMustBeLessThan64());
}

// Create a bitmask where each bit corresponds to one potential enum value.
// `values` should be an array listing all possible enum values. The bit is set
// to one if the corresponding enum exists. Only works for enums with values
// less than 64.
template <typename T, size_t N>
constexpr uint64_t CreateEnumBitmask(T (&values)[N]) {
  return MakeMask(0, N, values);
}

bool SetChromaSitingFromUint8(uint8_t enum_value,
                              ColorSpace::ChromaSiting* chroma_siting) {
  constexpr ColorSpace::ChromaSiting kChromaSitings[] = {
      ColorSpace::ChromaSiting::kUnspecified,
      ColorSpace::ChromaSiting::kCollocated, ColorSpace::ChromaSiting::kHalf};
  constexpr uint64_t enum_bitmask = CreateEnumBitmask(kChromaSitings);

  return SetFromUint8(enum_value, enum_bitmask, chroma_siting);
}

}  // namespace

ColorSpace::ColorSpace() = default;
ColorSpace::ColorSpace(const ColorSpace& other) = default;
ColorSpace::ColorSpace(ColorSpace&& other) = default;
ColorSpace& ColorSpace::operator=(const ColorSpace& other) = default;

ColorSpace::ColorSpace(PrimaryID primaries,
                       TransferID transfer,
                       MatrixID matrix,
                       RangeID range)
    : ColorSpace(primaries,
                 transfer,
                 matrix,
                 range,
                 ChromaSiting::kUnspecified,
                 ChromaSiting::kUnspecified,
                 nullptr) {}

ColorSpace::ColorSpace(PrimaryID primaries,
                       TransferID transfer,
                       MatrixID matrix,
                       RangeID range,
                       ChromaSiting chroma_siting_horz,
                       ChromaSiting chroma_siting_vert,
                       const HdrMetadata* hdr_metadata)
    : primaries_(primaries),
      transfer_(transfer),
      matrix_(matrix),
      range_(range),
      chroma_siting_horizontal_(chroma_siting_horz),
      chroma_siting_vertical_(chroma_siting_vert),
      hdr_metadata_(hdr_metadata ? absl::make_optional(*hdr_metadata)
                                 : absl::nullopt) {}

ColorSpace::PrimaryID ColorSpace::primaries() const {
  return primaries_;
}

ColorSpace::TransferID ColorSpace::transfer() const {
  return transfer_;
}

ColorSpace::MatrixID ColorSpace::matrix() const {
  return matrix_;
}

ColorSpace::RangeID ColorSpace::range() const {
  return range_;
}

ColorSpace::ChromaSiting ColorSpace::chroma_siting_horizontal() const {
  return chroma_siting_horizontal_;
}

ColorSpace::ChromaSiting ColorSpace::chroma_siting_vertical() const {
  return chroma_siting_vertical_;
}

const HdrMetadata* ColorSpace::hdr_metadata() const {
  return hdr_metadata_ ? &*hdr_metadata_ : nullptr;
}

#define PRINT_ENUM_CASE(TYPE, NAME) \
  case TYPE::NAME:                  \
    ss << #NAME;                    \
    break;

std::string ColorSpace::AsString() const {
  char buf[1024];
  rtc::SimpleStringBuilder ss(buf);
  ss << "{primaries:";
  switch (primaries_) {
    PRINT_ENUM_CASE(PrimaryID, kBT709)
    PRINT_ENUM_CASE(PrimaryID, kUnspecified)
    PRINT_ENUM_CASE(PrimaryID, kBT470M)
    PRINT_ENUM_CASE(PrimaryID, kBT470BG)
    PRINT_ENUM_CASE(PrimaryID, kSMPTE170M)
    PRINT_ENUM_CASE(PrimaryID, kSMPTE240M)
    PRINT_ENUM_CASE(PrimaryID, kFILM)
    PRINT_ENUM_CASE(PrimaryID, kBT2020)
    PRINT_ENUM_CASE(PrimaryID, kSMPTEST428)
    PRINT_ENUM_CASE(PrimaryID, kSMPTEST431)
    PRINT_ENUM_CASE(PrimaryID, kSMPTEST432)
    PRINT_ENUM_CASE(PrimaryID, kJEDECP22)
  }
  ss << ", transfer:";
  switch (transfer_) {
    PRINT_ENUM_CASE(TransferID, kBT709)
    PRINT_ENUM_CASE(TransferID, kUnspecified)
    PRINT_ENUM_CASE(TransferID, kGAMMA22)
    PRINT_ENUM_CASE(TransferID, kGAMMA28)
    PRINT_ENUM_CASE(TransferID, kSMPTE170M)
    PRINT_ENUM_CASE(TransferID, kSMPTE240M)
    PRINT_ENUM_CASE(TransferID, kLINEAR)
    PRINT_ENUM_CASE(TransferID, kLOG)
    PRINT_ENUM_CASE(TransferID, kLOG_SQRT)
    PRINT_ENUM_CASE(TransferID, kIEC61966_2_4)
    PRINT_ENUM_CASE(TransferID, kBT1361_ECG)
    PRINT_ENUM_CASE(TransferID, kIEC61966_2_1)
    PRINT_ENUM_CASE(TransferID, kBT2020_10)
    PRINT_ENUM_CASE(TransferID, kBT2020_12)
    PRINT_ENUM_CASE(TransferID, kSMPTEST2084)
    PRINT_ENUM_CASE(TransferID, kSMPTEST428)
    PRINT_ENUM_CASE(TransferID, kARIB_STD_B67)
  }
  ss << ", matrix:";
  switch (matrix_) {
    PRINT_ENUM_CASE(MatrixID, kRGB)
    PRINT_ENUM_CASE(MatrixID, kBT709)
    PRINT_ENUM_CASE(MatrixID, kUnspecified)
    PRINT_ENUM_CASE(MatrixID, kFCC)
    PRINT_ENUM_CASE(MatrixID, kBT470BG)
    PRINT_ENUM_CASE(MatrixID, kSMPTE170M)
    PRINT_ENUM_CASE(MatrixID, kSMPTE240M)
    PRINT_ENUM_CASE(MatrixID, kYCOCG)
    PRINT_ENUM_CASE(MatrixID, kBT2020_NCL)
    PRINT_ENUM_CASE(MatrixID, kBT2020_CL)
    PRINT_ENUM_CASE(MatrixID, kSMPTE2085)
    PRINT_ENUM_CASE(MatrixID, kCDNCLS)
    PRINT_ENUM_CASE(MatrixID, kCDCLS)
    PRINT_ENUM_CASE(MatrixID, kBT2100_ICTCP)
  }

  ss << ", range:";
  switch (range_) {
    PRINT_ENUM_CASE(RangeID, kInvalid)
    PRINT_ENUM_CASE(RangeID, kLimited)
    PRINT_ENUM_CASE(RangeID, kFull)
    PRINT_ENUM_CASE(RangeID, kDerived)
  }
  ss << "}";
  return ss.str();
}

#undef PRINT_ENUM_CASE

bool ColorSpace::set_primaries_from_uint8(uint8_t enum_value) {
  constexpr PrimaryID kPrimaryIds[] = {
      PrimaryID::kBT709,      PrimaryID::kUnspecified, PrimaryID::kBT470M,
      PrimaryID::kBT470BG,    PrimaryID::kSMPTE170M,   PrimaryID::kSMPTE240M,
      PrimaryID::kFILM,       PrimaryID::kBT2020,      PrimaryID::kSMPTEST428,
      PrimaryID::kSMPTEST431, PrimaryID::kSMPTEST432,  PrimaryID::kJEDECP22};
  constexpr uint64_t enum_bitmask = CreateEnumBitmask(kPrimaryIds);

  return SetFromUint8(enum_value, enum_bitmask, &primaries_);
}

bool ColorSpace::set_transfer_from_uint8(uint8_t enum_value) {
  constexpr TransferID kTransferIds[] = {
      TransferID::kBT709,       TransferID::kUnspecified,
      TransferID::kGAMMA22,     TransferID::kGAMMA28,
      TransferID::kSMPTE170M,   TransferID::kSMPTE240M,
      TransferID::kLINEAR,      TransferID::kLOG,
      TransferID::kLOG_SQRT,    TransferID::kIEC61966_2_4,
      TransferID::kBT1361_ECG,  TransferID::kIEC61966_2_1,
      TransferID::kBT2020_10,   TransferID::kBT2020_12,
      TransferID::kSMPTEST2084, TransferID::kSMPTEST428,
      TransferID::kARIB_STD_B67};
  constexpr uint64_t enum_bitmask = CreateEnumBitmask(kTransferIds);

  return SetFromUint8(enum_value, enum_bitmask, &transfer_);
}

bool ColorSpace::set_matrix_from_uint8(uint8_t enum_value) {
  constexpr MatrixID kMatrixIds[] = {
      MatrixID::kRGB,       MatrixID::kBT709,       MatrixID::kUnspecified,
      MatrixID::kFCC,       MatrixID::kBT470BG,     MatrixID::kSMPTE170M,
      MatrixID::kSMPTE240M, MatrixID::kYCOCG,       MatrixID::kBT2020_NCL,
      MatrixID::kBT2020_CL, MatrixID::kSMPTE2085,   MatrixID::kCDNCLS,
      MatrixID::kCDCLS,     MatrixID::kBT2100_ICTCP};
  constexpr uint64_t enum_bitmask = CreateEnumBitmask(kMatrixIds);

  return SetFromUint8(enum_value, enum_bitmask, &matrix_);
}

bool ColorSpace::set_range_from_uint8(uint8_t enum_value) {
  constexpr RangeID kRangeIds[] = {RangeID::kInvalid, RangeID::kLimited,
                                   RangeID::kFull, RangeID::kDerived};
  constexpr uint64_t enum_bitmask = CreateEnumBitmask(kRangeIds);

  return SetFromUint8(enum_value, enum_bitmask, &range_);
}

bool ColorSpace::set_chroma_siting_horizontal_from_uint8(uint8_t enum_value) {
  return SetChromaSitingFromUint8(enum_value, &chroma_siting_horizontal_);
}

bool ColorSpace::set_chroma_siting_vertical_from_uint8(uint8_t enum_value) {
  return SetChromaSitingFromUint8(enum_value, &chroma_siting_vertical_);
}

void ColorSpace::set_hdr_metadata(const HdrMetadata* hdr_metadata) {
  hdr_metadata_ =
      hdr_metadata ? absl::make_optional(*hdr_metadata) : absl::nullopt;
}

}  // namespace webrtc
