/*
 * Copyright (c) 2023, Alliance for Open Media. All rights reserved
 *
 * This source code is subject to the terms of the BSD 3-Clause Clear License
 * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
 * License was not distributed with this source code in the LICENSE file, you
 * can obtain it at www.aomedia.org/license/software-license/bsd-3-c-c. If the
 * Alliance for Open Media Patent License 1.0 was not distributed with this
 * source code in the PATENTS file, you can obtain it at
 * www.aomedia.org/license/patent.
 */
#include "iamf/obu/obu_header.h"

#include <cstddef>
#include <cstdint>
#include <limits>
#include <vector>

#include "absl/container/flat_hash_set.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "iamf/common/leb_generator.h"
#include "iamf/common/read_bit_buffer.h"
#include "iamf/common/utils/macros.h"
#include "iamf/common/utils/numeric_utils.h"
#include "iamf/common/utils/validation_utils.h"
#include "iamf/common/write_bit_buffer.h"
#include "iamf/obu/types.h"

namespace iamf_tools {

const absl::flat_hash_set<ObuType> kTemporalUnitObuTypes = {
    kObuIaAudioFrame,     kObuIaAudioFrameId0,  kObuIaAudioFrameId1,
    kObuIaAudioFrameId2,  kObuIaAudioFrameId3,  kObuIaAudioFrameId4,
    kObuIaAudioFrameId5,  kObuIaAudioFrameId6,  kObuIaAudioFrameId7,
    kObuIaAudioFrameId8,  kObuIaAudioFrameId9,  kObuIaAudioFrameId10,
    kObuIaAudioFrameId11, kObuIaAudioFrameId12, kObuIaAudioFrameId13,
    kObuIaAudioFrameId14, kObuIaAudioFrameId15, kObuIaAudioFrameId16,
    kObuIaAudioFrameId17, kObuIaParameterBlock, kObuIaTemporalDelimiter};

namespace {

// Returns `true` if this `ObuType` is allowed to have the `obu_redundant_copy`
// flag set. `false` otherwise.
bool IsRedundantCopyAllowed(ObuType type) {
  if (kObuIaAudioFrameId0 <= type && type <= kObuIaAudioFrameId17) {
    return false;
  }

  switch (type) {
    case kObuIaTemporalDelimiter:
    case kObuIaAudioFrame:
    case kObuIaParameterBlock:
      return false;
    default:
      return true;
  }
}

// Returns `true` if this `ObuType` is allowed to have the
// `obu_trimming_status_flag` flag set. `false` otherwise.
bool IsTrimmingStatusFlagAllowed(ObuType type) {
  if (kObuIaAudioFrameId0 <= type && type <= kObuIaAudioFrameId17) {
    return true;
  }
  switch (type) {
    case kObuIaAudioFrame:
      return true;
    default:
      return false;
  }
}

// Validates the OBU and returns an error if anything is non-comforming.
// Requires that all fields including `obu_size_` are initialized.
absl::Status Validate(const ObuHeader& header) {
  // Validate member fields are self-consistent.
  if (!header.obu_extension_flag && header.extension_header_size > 0) {
    return absl::InvalidArgumentError(
        "`obu_extension_flag_` implied there was no extension header, "
        "but `extension_header_size_` indicates there is one.");
  }

  RETURN_IF_NOT_OK(ValidateContainerSizeEqual("extension_header_bytes_",
                                              header.extension_header_bytes,
                                              header.extension_header_size));

  // Validate IAMF imposed requirements.
  if (header.obu_redundant_copy && !IsRedundantCopyAllowed(header.obu_type)) {
    return absl::InvalidArgumentError(absl::StrCat(
        "The redundant copy flag is not allowed to be set for obu_type= ",
        header.obu_type));
  }

  if (header.obu_trimming_status_flag &&
      !IsTrimmingStatusFlagAllowed(header.obu_type)) {
    return absl::InvalidArgumentError(absl::StrCat(
        "The trimming status flag flag is not allowed to be set for "
        "obu_type= ",
        header.obu_type));
  }

  return absl::OkStatus();
}

absl::Status WriteFieldsAfterObuSize(const ObuHeader& header,
                                     WriteBitBuffer& wb) {
  // These fields are conditionally in the OBU.
  if (header.obu_trimming_status_flag) {
    RETURN_IF_NOT_OK(wb.WriteUleb128(header.num_samples_to_trim_at_end));
    RETURN_IF_NOT_OK(wb.WriteUleb128(header.num_samples_to_trim_at_start));
  }

  // These fields are conditionally in the OBU.
  if (header.obu_extension_flag) {
    RETURN_IF_NOT_OK(wb.WriteUleb128(header.extension_header_size));
    RETURN_IF_NOT_OK(ValidateContainerSizeEqual("extension_header_bytes_",
                                                header.extension_header_bytes,
                                                header.extension_header_size));
    RETURN_IF_NOT_OK(
        wb.WriteUint8Span(absl::MakeConstSpan(header.extension_header_bytes)));
  }

  return absl::OkStatus();
}

// IAMF imposes two restrictions on the size of an entire OBU.
//   - IAMF v1.1.0 imposes a maximum size of an entire OBU must be 2 MB or less.
//   - IAMF v1.1.0 also imposes a maximum size of `obu_size` must be 2^21 - 4 or
//     less.
//
// The second restriction is equivalent when `obu_size` is written using the
// minimal number of bytes. It is less strict than the first restriction if
// `obu_size` is written using padded bytes. Therefore the second restriction is
// irrelevant.
absl::Status ValidateObuIsUnderTwoMegabytes(DecodedUleb128 obu_size,
                                            size_t size_of_obu_size) {
  CHECK_LE(size_of_obu_size, kMaxLeb128Size);

  // Subtract out `obu_size` and all preceding data (one byte).
  const uint32_t max_obu_size =
      kEntireObuSizeMaxTwoMegabytes - 1 - size_of_obu_size;

  if (obu_size > max_obu_size) {
    return absl::InvalidArgumentError(
        absl::StrCat("obu_size= ", obu_size,
                     " results in an OBU greater than 2 MB in size."));
  }

  return absl::OkStatus();
}

absl::Status GetSizeOfEncodedLeb128(const LebGenerator& leb_generator,
                                    DecodedUleb128 leb128, size_t& size) {
  // Calculate how many bytes `obu_size` will take up based on the current leb
  // generator.
  WriteBitBuffer temp_wb_obu_size_only(8, leb_generator);
  RETURN_IF_NOT_OK(temp_wb_obu_size_only.WriteUleb128(leb128));
  size = temp_wb_obu_size_only.bit_buffer().size();
  return absl::OkStatus();
}

// Validates the header and initializes the output argument. On success
// `obu_size` is set to imply the associated payload has a size of
// `payload_serialized_size`.
absl::Status GetObuSizeAndValidate(const LebGenerator& leb_generator,
                                   const ObuHeader& header,
                                   int64_t payload_serialized_size,
                                   DecodedUleb128& obu_size) {
  // Validate to avoid issues with the `static_cast` below.
  if (0 > payload_serialized_size ||
      payload_serialized_size > std::numeric_limits<uint32_t>::max()) {
    return absl::InvalidArgumentError(absl::StrCat(
        "Payload size must fit into a `uint32_t`. payload_serialized_size= ",
        payload_serialized_size));
  }

  // Set `obu_size`. It depends on the size of all fields after `obu_size` and
  // `payload_serialized_size`.
  {
    WriteBitBuffer temp_wb_after_obu_size(64, leb_generator);
    RETURN_IF_NOT_OK(WriteFieldsAfterObuSize(header, temp_wb_after_obu_size));

    if (!temp_wb_after_obu_size.IsByteAligned() ||
        temp_wb_after_obu_size.bit_buffer().size() >
            std::numeric_limits<uint32_t>::max()) {
      return absl::UnknownError(absl::StrCat(
          "Result from `WriteFieldsAfterObuSize()` was not byte-aligned ",
          "or it did not fit into a `uint32_t`. `bit_offset` is ",
          temp_wb_after_obu_size.bit_offset()));
    }
    // Get the size of fields after `obu_size`.
    const uint32_t fields_after_obu_size =
        temp_wb_after_obu_size.bit_buffer().size();

    // `obu_size` represents the size of all fields after itself and
    // serialized_size. Sum them to get `obu_size`, while ensuring they fit into
    // a `uint32_t`.
    RETURN_IF_NOT_OK(AddUint32CheckOverflow(
        static_cast<uint32_t>(fields_after_obu_size),
        static_cast<uint32_t>(payload_serialized_size), obu_size));

    size_t size_of_obu_size;
    RETURN_IF_NOT_OK(
        GetSizeOfEncodedLeb128(leb_generator, obu_size, size_of_obu_size));

    RETURN_IF_NOT_OK(
        ValidateObuIsUnderTwoMegabytes(obu_size, size_of_obu_size));
  }

  // Validate the OBU.
  RETURN_IF_NOT_OK(Validate(header));

  return absl::OkStatus();
}

// Returns the size of the payload associated with the OBU, i.e. the number of
// bytes that contain payload data. See
// https://aomediacodec.github.io/iamf/#obu_size for more details.
int64_t GetObuPayloadSize(DecodedUleb128 obu_size,
                          uint8_t num_samples_to_trim_at_end_size,
                          uint8_t num_samples_to_trim_at_start_size,
                          uint8_t extension_header_size_size,
                          uint8_t extension_header_bytes_size) {
  return static_cast<int64_t>(obu_size) -
         (num_samples_to_trim_at_end_size + num_samples_to_trim_at_start_size +
          extension_header_size_size + extension_header_bytes_size);
}

absl::Status FillHeaderMetadata(ReadBitBuffer& rb,
                                HeaderMetadata& output_header_metadata) {
  uint64_t obu_type_uint64_t = 0;
  RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(5, obu_type_uint64_t));
  output_header_metadata.obu_type = static_cast<ObuType>(obu_type_uint64_t);
  // We don't care about the next three bits.
  bool dummy_bool;
  RETURN_IF_NOT_OK(rb.ReadBoolean(dummy_bool));
  RETURN_IF_NOT_OK(rb.ReadBoolean(dummy_bool));
  RETURN_IF_NOT_OK(rb.ReadBoolean(dummy_bool));
  DecodedUleb128 obu_size;
  int8_t size_of_obu_size = 0;
  RETURN_IF_NOT_OK(rb.ReadULeb128(obu_size, size_of_obu_size));
  // The extra byte is for the `obu_type` field + the three boolean fields.
  output_header_metadata.total_obu_size = obu_size + size_of_obu_size + 1;
  return absl::OkStatus();
}

}  // namespace

bool ObuHeader::IsTemporalUnitObuType(const ObuType obu_type) {
  return kTemporalUnitObuTypes.contains(obu_type);
}

absl::StatusOr<HeaderMetadata> ObuHeader::PeekObuTypeAndTotalObuSize(
    ReadBitBuffer& rb) {
  const uint64_t position_before_header = rb.Tell();
  HeaderMetadata header_metadata;
  auto header_metadata_status = FillHeaderMetadata(rb, header_metadata);
  RETURN_IF_NOT_OK(rb.Seek(position_before_header));
  if (!header_metadata_status.ok()) {
    return header_metadata_status;
  }
  return header_metadata;
}

absl::Status ObuHeader::ValidateAndWrite(int64_t payload_serialized_size,
                                         WriteBitBuffer& wb) const {
  DecodedUleb128 obu_size;
  RETURN_IF_NOT_OK(GetObuSizeAndValidate(wb.leb_generator_, *this,
                                         payload_serialized_size, obu_size));

  // Write the OBU Header to the buffer.
  RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(obu_type, 5));
  RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(obu_redundant_copy, 1));
  RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(obu_trimming_status_flag, 1));
  RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(obu_extension_flag, 1));
  RETURN_IF_NOT_OK(wb.WriteUleb128(obu_size));

  RETURN_IF_NOT_OK(WriteFieldsAfterObuSize(*this, wb));

  return absl::OkStatus();
}

// Reads all the fields of the OBU Header as defined in the IAMF spec
// (https://aomediacodec.github.io/iamf/#obu-header-syntax). Most of these
// fields are stored directly in the ObuHeader struct; however, for reasons
// relating to the existing encoder, `obu_type` and `obu_size` are not. We
// instead use `output_obu_type` and `output_payload_serialized_size` as output
// parameters. Note that `output_payload_serialized_size` is a derived value
// from `obu_size`, as this is the value the caller is more interested in.
absl::Status ObuHeader::ReadAndValidate(
    ReadBitBuffer& rb, int64_t& output_payload_serialized_size) {
  uint64_t obu_type_uint64_t = 0;
  RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(5, obu_type_uint64_t));
  obu_type = static_cast<ObuType>(obu_type_uint64_t);
  RETURN_IF_NOT_OK(rb.ReadBoolean(obu_redundant_copy));
  RETURN_IF_NOT_OK(rb.ReadBoolean(obu_trimming_status_flag));
  RETURN_IF_NOT_OK(rb.ReadBoolean(obu_extension_flag));
  DecodedUleb128 obu_size;
  int8_t size_of_obu_size = 0;
  RETURN_IF_NOT_OK(rb.ReadULeb128(obu_size, size_of_obu_size));
  RETURN_IF_NOT_OK(ValidateObuIsUnderTwoMegabytes(obu_size, size_of_obu_size));
  int8_t num_samples_to_trim_at_end_size = 0;
  int8_t num_samples_to_trim_at_start_size = 0;
  if (obu_trimming_status_flag) {
    RETURN_IF_NOT_OK(rb.ReadULeb128(num_samples_to_trim_at_end,
                                    num_samples_to_trim_at_end_size));
    RETURN_IF_NOT_OK(rb.ReadULeb128(num_samples_to_trim_at_start,
                                    num_samples_to_trim_at_start_size));
  }
  int8_t extension_header_size_size = 0;
  if (obu_extension_flag) {
    RETURN_IF_NOT_OK(
        rb.ReadULeb128(extension_header_size, extension_header_size_size));
    extension_header_bytes.resize(extension_header_size);
    RETURN_IF_NOT_OK(rb.ReadUint8Span(absl::MakeSpan(extension_header_bytes)));
  }
  output_payload_serialized_size = GetObuPayloadSize(
      obu_size, num_samples_to_trim_at_end_size,
      num_samples_to_trim_at_start_size, extension_header_size_size,
      extension_header_bytes.size());
  if (output_payload_serialized_size < 0) {
    return absl::InvalidArgumentError(
        "obu_size not valid for OBU flags. Negative remaining payload size.");
  }

  RETURN_IF_NOT_OK(Validate(*this));

  return absl::OkStatus();
}

void ObuHeader::Print(const LebGenerator& leb_generator,
                      int64_t payload_serialized_size) const {
  // Generate the header. Solely for the purpose of getting the correct
  // `obu_size`.
  DecodedUleb128 obu_size;
  if (!GetObuSizeAndValidate(leb_generator, *this, payload_serialized_size,
                             obu_size)
           .ok()) {
    LOG(ERROR) << "Error printing OBU header";
    return;
  }
  LOG(INFO) << "  obu_type= " << obu_type;
  LOG(INFO) << "  size_of(payload_) " << payload_serialized_size;

  LOG(INFO) << "  obu_type= " << absl::StrCat(obu_type);
  LOG(INFO) << "  obu_redundant_copy= " << obu_redundant_copy;
  LOG(INFO) << "  obu_trimming_status_flag= " << obu_trimming_status_flag;
  LOG(INFO) << "  obu_extension_flag= " << obu_extension_flag;

  LOG(INFO) << "  obu_size=" << obu_size;

  if (obu_trimming_status_flag) {
    LOG(INFO) << "  num_samples_to_trim_at_end= " << num_samples_to_trim_at_end;
    LOG(INFO) << "  num_samples_to_trim_at_start= "
              << num_samples_to_trim_at_start;
  }
  if (obu_extension_flag) {
    LOG(INFO) << "  extension_header_size= " << extension_header_size;
    LOG(INFO) << "  extension_header_bytes omitted.";
  }
}

}  // namespace iamf_tools
