/*
 * Copyright (c) 2026, 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 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
 * aomedia.org/license/patent-license/.
 */

#include <cstring>

#include "third_party/googletest/src/googletest/include/gtest/gtest.h"

#include "av2/encoder/ci_syntax.h"
#include "av2/decoder/decoder.h"
#include "av2/decoder/decodeframe.h"
#include "avm_dsp/bitwriter_buffer.h"
#include "avm_dsp/bitreader_buffer.h"
#include "avm_mem/avm_mem.h"

namespace {

static void rb_error_handler(void *data, avm_codec_err_t error,
                             const char *detail) {
  (void)data;
  (void)error;
  (void)detail;
}

static uint32_t write_ci_obu(const ContentInterpretation *ci, uint8_t *dst) {
  struct avm_write_bit_buffer wb = { dst, 0 };
  av2_write_ci_info(ci, &wb);
  avm_wb_write_bit(&wb, 0);  // ci_extension_present_flag
  avm_wb_write_bit(&wb, 1);  // trailing stop bit
  int pad = (8 - wb.bit_offset % 8) % 8;
  if (pad > 0) {
    avm_wb_write_literal(&wb, 0, pad);
  }
  return avm_wb_bytes_written(&wb);
}

class CiTest : public ::testing::Test {
 protected:
  void SetUp() override {
    pbi_ = static_cast<AV2Decoder *>(avm_memalign(32, sizeof(AV2Decoder)));
    ASSERT_NE(pbi_, nullptr);
    memset(pbi_, 0, sizeof(*pbi_));
    memset(buf_, 0, sizeof(buf_));
  }
  void TearDown() override { avm_free(pbi_); }

  AV2Decoder *pbi_;
  uint8_t buf_[4096];
};

TEST_F(CiTest, MinimalCiRoundtrip) {
  ContentInterpretation src;
  av2_initialize_ci_params(&src);

  uint32_t written = write_ci_obu(&src, buf_);
  ASSERT_GT(written, 0u);

  // Set up decoder state for CI OBU reading.
  pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
  pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
  struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                    rb_error_handler };
  uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
  ASSERT_EQ(read, written);

  const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
  EXPECT_EQ(dst->ci_scan_type_idc, AVM_SCAN_TYPE_UNSPECIFIED);
  EXPECT_EQ(dst->ci_color_description_present_flag, 0);
  EXPECT_EQ(dst->ci_chroma_sample_position_present_flag, 0);
  EXPECT_EQ(dst->ci_aspect_ratio_info_present_flag, 0);
  EXPECT_EQ(dst->ci_timing_info_present_flag, 0);
}

TEST_F(CiTest, ColorInfoExplicitRoundtrip) {
  ContentInterpretation src;
  av2_initialize_ci_params(&src);
  src.ci_scan_type_idc = AVM_SCAN_TYPE_PROGRESSIVE;
  src.ci_color_description_present_flag = 1;
  src.color_info.color_description_idc = 0;  // explicit
  src.color_info.color_primaries = AVM_CICP_CP_BT_709;
  src.color_info.transfer_characteristics = AVM_CICP_TC_SRGB;
  src.color_info.matrix_coefficients = AVM_CICP_MC_BT_709;
  src.color_info.full_range_flag = 1;

  uint32_t written = write_ci_obu(&src, buf_);
  ASSERT_GT(written, 0u);

  pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
  pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
  struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                    rb_error_handler };
  uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
  ASSERT_EQ(read, written);

  const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
  EXPECT_EQ(dst->ci_color_description_present_flag, 1);
  EXPECT_EQ(dst->color_info.color_description_idc, 0);
  EXPECT_EQ(dst->color_info.color_primaries, AVM_CICP_CP_BT_709);
  EXPECT_EQ(dst->color_info.transfer_characteristics, AVM_CICP_TC_SRGB);
  EXPECT_EQ(dst->color_info.matrix_coefficients, AVM_CICP_MC_BT_709);
  EXPECT_EQ(dst->color_info.full_range_flag, 1);
}

TEST_F(CiTest, ColorInfoImplicitRoundtrip) {
  ContentInterpretation src;
  av2_initialize_ci_params(&src);
  src.ci_color_description_present_flag = 1;
  src.color_info.color_description_idc = 1;  // implicit
  src.color_info.full_range_flag = 0;

  uint32_t written = write_ci_obu(&src, buf_);
  ASSERT_GT(written, 0u);

  pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
  pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
  struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                    rb_error_handler };
  uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
  ASSERT_EQ(read, written);

  const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
  EXPECT_EQ(dst->color_info.color_description_idc, 1);
  EXPECT_EQ(dst->color_info.full_range_flag, 0);
}

// Progressive scan: only ci_chroma_sample_position[0] is written.
TEST_F(CiTest, ChromaSamplePositionProgressive) {
  ContentInterpretation src;
  av2_initialize_ci_params(&src);
  src.ci_scan_type_idc = AVM_SCAN_TYPE_PROGRESSIVE;
  src.ci_chroma_sample_position_present_flag = 1;
  src.ci_chroma_sample_position[0] = AVM_CSP_LEFT;

  uint32_t written = write_ci_obu(&src, buf_);
  ASSERT_GT(written, 0u);

  pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
  pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
  struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                    rb_error_handler };
  uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
  ASSERT_EQ(read, written);

  const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
  EXPECT_EQ(dst->ci_chroma_sample_position[0], AVM_CSP_LEFT);
  // Progressive: [1] is copied from [0].
  EXPECT_EQ(dst->ci_chroma_sample_position[1], AVM_CSP_LEFT);
}

// Non-progressive scan: both chroma sample positions are written.
TEST_F(CiTest, ChromaSamplePositionInterlace) {
  ContentInterpretation src;
  av2_initialize_ci_params(&src);
  src.ci_scan_type_idc = AVM_SCAN_TYPE_INTERLACE;
  src.ci_chroma_sample_position_present_flag = 1;
  src.ci_chroma_sample_position[0] = AVM_CSP_LEFT;
  src.ci_chroma_sample_position[1] = AVM_CSP_TOPLEFT;

  uint32_t written = write_ci_obu(&src, buf_);
  ASSERT_GT(written, 0u);

  pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
  pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
  struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                    rb_error_handler };
  uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
  ASSERT_EQ(read, written);

  const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
  EXPECT_EQ(dst->ci_chroma_sample_position[0], AVM_CSP_LEFT);
  EXPECT_EQ(dst->ci_chroma_sample_position[1], AVM_CSP_TOPLEFT);
}

TEST_F(CiTest, SarInfoStandardRoundtrip) {
  ContentInterpretation src;
  av2_initialize_ci_params(&src);
  src.ci_aspect_ratio_info_present_flag = 1;
  src.sar_info.sar_aspect_ratio_idc = AVM_SAR_IDC_1_TO_1;

  uint32_t written = write_ci_obu(&src, buf_);
  ASSERT_GT(written, 0u);

  pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
  pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
  struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                    rb_error_handler };
  uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
  ASSERT_EQ(read, written);

  const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
  EXPECT_EQ(dst->sar_info.sar_aspect_ratio_idc, AVM_SAR_IDC_1_TO_1);
}

// SAR_IDC_255 = explicit SAR with width/height.
TEST_F(CiTest, SarInfoExplicitRoundtrip) {
  ContentInterpretation src;
  av2_initialize_ci_params(&src);
  src.ci_aspect_ratio_info_present_flag = 1;
  src.sar_info.sar_aspect_ratio_idc = AVM_SAR_IDC_255;
  src.sar_info.sar_width = 4;
  src.sar_info.sar_height = 3;

  uint32_t written = write_ci_obu(&src, buf_);
  ASSERT_GT(written, 0u);

  pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
  pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
  struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                    rb_error_handler };
  uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
  ASSERT_EQ(read, written);

  const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
  EXPECT_EQ(dst->sar_info.sar_aspect_ratio_idc, AVM_SAR_IDC_255);
  EXPECT_EQ(dst->sar_info.sar_width, 4);
  EXPECT_EQ(dst->sar_info.sar_height, 3);
}

TEST_F(CiTest, TimingInfoRoundtrip) {
  ContentInterpretation src;
  av2_initialize_ci_params(&src);
  src.ci_timing_info_present_flag = 1;
  src.timing_info.num_units_in_display_tick = 1000;
  src.timing_info.time_scale = 30000;
  src.timing_info.equal_elemental_interval = 1;
  src.timing_info.num_ticks_per_elemental_duration = 1;

  uint32_t written = write_ci_obu(&src, buf_);
  ASSERT_GT(written, 0u);

  pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
  pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
  struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                    rb_error_handler };
  uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
  ASSERT_EQ(read, written);

  const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
  EXPECT_EQ(dst->timing_info.num_units_in_display_tick, 1000u);
  EXPECT_EQ(dst->timing_info.time_scale, 30000u);
  EXPECT_EQ(dst->timing_info.equal_elemental_interval, 1);
  EXPECT_EQ(dst->timing_info.num_ticks_per_elemental_duration, 1u);
}

TEST_F(CiTest, TimingInfoNoEqualInterval) {
  ContentInterpretation src;
  av2_initialize_ci_params(&src);
  src.ci_timing_info_present_flag = 1;
  src.timing_info.num_units_in_display_tick = 1001;
  src.timing_info.time_scale = 60000;
  src.timing_info.equal_elemental_interval = 0;

  uint32_t written = write_ci_obu(&src, buf_);
  ASSERT_GT(written, 0u);

  pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
  pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
  struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                    rb_error_handler };
  uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
  ASSERT_EQ(read, written);

  const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
  EXPECT_EQ(dst->timing_info.num_units_in_display_tick, 1001u);
  EXPECT_EQ(dst->timing_info.time_scale, 60000u);
  EXPECT_EQ(dst->timing_info.equal_elemental_interval, 0);
}

// All optional fields enabled.
TEST_F(CiTest, AllFieldsPresent) {
  ContentInterpretation src;
  av2_initialize_ci_params(&src);
  src.ci_scan_type_idc = AVM_SCAN_TYPE_PROGRESSIVE;
  src.ci_color_description_present_flag = 1;
  src.ci_chroma_sample_position_present_flag = 1;
  src.ci_aspect_ratio_info_present_flag = 1;
  src.ci_timing_info_present_flag = 1;

  src.color_info.color_description_idc = 0;
  src.color_info.color_primaries = AVM_CICP_CP_BT_2020;
  src.color_info.transfer_characteristics = AVM_CICP_TC_SMPTE_2084;
  src.color_info.matrix_coefficients = AVM_CICP_MC_BT_2020_NCL;
  src.color_info.full_range_flag = 0;

  src.ci_chroma_sample_position[0] = AVM_CSP_CENTER;

  src.sar_info.sar_aspect_ratio_idc = AVM_SAR_IDC_1_TO_1;

  src.timing_info.num_units_in_display_tick = 1;
  src.timing_info.time_scale = 24;
  src.timing_info.equal_elemental_interval = 1;
  src.timing_info.num_ticks_per_elemental_duration = 1;

  uint32_t written = write_ci_obu(&src, buf_);
  ASSERT_GT(written, 0u);

  pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
  pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
  struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                    rb_error_handler };
  uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
  ASSERT_EQ(read, written);

  const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
  EXPECT_EQ(dst->ci_scan_type_idc, AVM_SCAN_TYPE_PROGRESSIVE);
  EXPECT_EQ(dst->color_info.color_primaries, AVM_CICP_CP_BT_2020);
  EXPECT_EQ(dst->color_info.transfer_characteristics, AVM_CICP_TC_SMPTE_2084);
  EXPECT_EQ(dst->color_info.matrix_coefficients, AVM_CICP_MC_BT_2020_NCL);
  EXPECT_EQ(dst->ci_chroma_sample_position[0], AVM_CSP_CENTER);
  EXPECT_EQ(dst->sar_info.sar_aspect_ratio_idc, AVM_SAR_IDC_1_TO_1);
  EXPECT_EQ(dst->timing_info.time_scale, 24u);
}

TEST_F(CiTest, ScanTypeSweep) {
  const int scan_types[] = { AVM_SCAN_TYPE_UNSPECIFIED,
                             AVM_SCAN_TYPE_PROGRESSIVE, AVM_SCAN_TYPE_INTERLACE,
                             AVM_SCAN_TYPE_INTERLACE_COMPLEMENTARY };
  for (int si = 0; si < 4; si++) {
    ContentInterpretation src;
    av2_initialize_ci_params(&src);
    src.ci_scan_type_idc = (avm_pic_scan_type_t)scan_types[si];

    memset(buf_, 0, sizeof(buf_));
    uint32_t written = write_ci_obu(&src, buf_);
    ASSERT_GT(written, 0u) << "scan=" << scan_types[si];

    for (int m = 0; m < MAX_NUM_MLAYERS; m++) {
      av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]);
    }
    memset(pbi_->obus_in_frame_unit_data, 0,
           sizeof(pbi_->obus_in_frame_unit_data));
    pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
    pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
    pbi_->common.error.error_code = AVM_CODEC_OK;
    struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                      rb_error_handler };
    uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
    ASSERT_EQ(read, written) << "scan=" << scan_types[si];
    EXPECT_EQ(pbi_->common.ci_params_per_layer[0].ci_scan_type_idc,
              scan_types[si]);
  }
}

TEST_F(CiTest, ColorIdcHighValues) {
  const int idc_values[] = { 0, 1, 4, 8, 20 };
  for (int ci = 0; ci < 5; ci++) {
    ContentInterpretation src;
    av2_initialize_ci_params(&src);
    src.ci_color_description_present_flag = 1;
    src.color_info.color_description_idc = idc_values[ci];
    if (idc_values[ci] == 0) {
      src.color_info.color_primaries = AVM_CICP_CP_BT_2020;
      src.color_info.transfer_characteristics = AVM_CICP_TC_SMPTE_2084;
      src.color_info.matrix_coefficients = AVM_CICP_MC_BT_2020_NCL;
    }

    memset(buf_, 0, sizeof(buf_));
    uint32_t written = write_ci_obu(&src, buf_);
    ASSERT_GT(written, 0u) << "idc=" << idc_values[ci];

    for (int m = 0; m < MAX_NUM_MLAYERS; m++) {
      av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]);
    }
    memset(pbi_->obus_in_frame_unit_data, 0,
           sizeof(pbi_->obus_in_frame_unit_data));
    pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
    pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
    pbi_->common.error.error_code = AVM_CODEC_OK;
    struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                      rb_error_handler };
    uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
    ASSERT_EQ(read, written) << "idc=" << idc_values[ci];
    EXPECT_EQ(
        pbi_->common.ci_params_per_layer[0].color_info.color_description_idc,
        idc_values[ci]);
  }
}

TEST_F(CiTest, FlagCombinations) {
  // Timing combinations are tested separately in TimingInfoRoundtrip,
  // TimingInfoNoEqualInterval, and AllFieldsPresent. The combined sweep
  // skips them because av2_read_timing_info_header uses avm_internal_error
  // which requires setjmp context not available in loop resets.
  for (int flags = 0; flags < 8; flags++) {
    bool color = flags & 1;
    bool chroma = (flags >> 1) & 1;
    bool sar = (flags >> 2) & 1;
    bool timing = (flags >> 3) & 1;

    ContentInterpretation src;
    av2_initialize_ci_params(&src);
    src.ci_scan_type_idc = AVM_SCAN_TYPE_PROGRESSIVE;
    src.ci_color_description_present_flag = color;
    src.ci_chroma_sample_position_present_flag = chroma;
    src.ci_aspect_ratio_info_present_flag = sar;
    src.ci_timing_info_present_flag = timing;

    if (color) {
      src.color_info.color_description_idc = 1;
    }
    if (chroma) {
      src.ci_chroma_sample_position[0] = AVM_CSP_LEFT;
    }
    if (sar) {
      src.sar_info.sar_aspect_ratio_idc = AVM_SAR_IDC_1_TO_1;
    }
    if (timing) {
      src.timing_info.num_units_in_display_tick = 1;
      src.timing_info.time_scale = 30;
    }

    memset(buf_, 0, sizeof(buf_));
    uint32_t written = write_ci_obu(&src, buf_);
    ASSERT_GT(written, 0u) << "flags=" << flags;

    // Reset CI params and OBU tracking without wiping error state.
    for (int m = 0; m < MAX_NUM_MLAYERS; m++) {
      av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]);
    }
    memset(pbi_->obus_in_frame_unit_data, 0,
           sizeof(pbi_->obus_in_frame_unit_data));
    pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
    pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
    pbi_->common.error.error_code = AVM_CODEC_OK;
    struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                      rb_error_handler };
    uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
    ASSERT_EQ(read, written) << "flags=" << flags;

    const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
    EXPECT_EQ(dst->ci_color_description_present_flag, color)
        << "flags=" << flags;
    EXPECT_EQ(dst->ci_chroma_sample_position_present_flag, chroma)
        << "flags=" << flags;
    EXPECT_EQ(dst->ci_aspect_ratio_info_present_flag, sar) << "flags=" << flags;
    EXPECT_EQ(dst->ci_timing_info_present_flag, timing) << "flags=" << flags;
  }
}

TEST_F(CiTest, ChromaSamplePositionValueSweep) {
  const int csp_values[] = { AVM_CSP_LEFT,       AVM_CSP_CENTER,
                             AVM_CSP_TOPLEFT,    AVM_CSP_TOP,
                             AVM_CSP_BOTTOMLEFT, AVM_CSP_BOTTOM,
                             AVM_CSP_UNSPECIFIED };
  for (int ci = 0; ci < 7; ci++) {
    ContentInterpretation src;
    av2_initialize_ci_params(&src);
    src.ci_scan_type_idc = AVM_SCAN_TYPE_INTERLACE;
    src.ci_chroma_sample_position_present_flag = 1;
    src.ci_chroma_sample_position[0] = csp_values[ci];
    src.ci_chroma_sample_position[1] = csp_values[(ci + 1) % 7];

    memset(buf_, 0, sizeof(buf_));
    uint32_t written = write_ci_obu(&src, buf_);
    ASSERT_GT(written, 0u) << "csp=" << csp_values[ci];

    for (int m = 0; m < MAX_NUM_MLAYERS; m++) {
      av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]);
    }
    memset(pbi_->obus_in_frame_unit_data, 0,
           sizeof(pbi_->obus_in_frame_unit_data));
    pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
    pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
    pbi_->common.error.error_code = AVM_CODEC_OK;
    struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                      rb_error_handler };
    uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
    ASSERT_EQ(read, written) << "csp=" << csp_values[ci];
    EXPECT_EQ(pbi_->common.ci_params_per_layer[0].ci_chroma_sample_position[0],
              csp_values[ci]);
    EXPECT_EQ(pbi_->common.ci_params_per_layer[0].ci_chroma_sample_position[1],
              csp_values[(ci + 1) % 7]);
  }
}

TEST_F(CiTest, SarIdcStandardValueSweep) {
  const int sar_values[] = { AVM_SAR_IDC_UNSPECIFIED,
                             AVM_SAR_IDC_1_TO_1,
                             AVM_SAR_IDC_12_TO_11,
                             14,
                             17,
                             254 };
  for (int si = 0; si < 6; si++) {
    ContentInterpretation src;
    av2_initialize_ci_params(&src);
    src.ci_aspect_ratio_info_present_flag = 1;
    src.sar_info.sar_aspect_ratio_idc = sar_values[si];

    memset(buf_, 0, sizeof(buf_));
    uint32_t written = write_ci_obu(&src, buf_);
    ASSERT_GT(written, 0u) << "sar=" << sar_values[si];

    for (int m = 0; m < MAX_NUM_MLAYERS; m++) {
      av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]);
    }
    memset(pbi_->obus_in_frame_unit_data, 0,
           sizeof(pbi_->obus_in_frame_unit_data));
    pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
    pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
    pbi_->common.error.error_code = AVM_CODEC_OK;
    struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                      rb_error_handler };
    uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
    ASSERT_EQ(read, written) << "sar=" << sar_values[si];
    EXPECT_EQ(pbi_->common.ci_params_per_layer[0].sar_info.sar_aspect_ratio_idc,
              sar_values[si]);
  }
}

TEST_F(CiTest, ColorFieldBoundaryValues) {
  struct {
    int primaries;
    int transfer;
    int matrix;
  } params[] = {
    { 0, 0, 0 },
    { AVM_CICP_CP_EBU_3213, AVM_CICP_TC_HLG, AVM_CICP_MC_ICTCP },
  };
  for (int pi = 0; pi < 2; pi++) {
    ContentInterpretation src;
    av2_initialize_ci_params(&src);
    src.ci_color_description_present_flag = 1;
    src.color_info.color_description_idc = 0;  // explicit
    src.color_info.color_primaries =
        (avm_color_primaries_t)params[pi].primaries;
    src.color_info.transfer_characteristics =
        (avm_transfer_characteristics_t)params[pi].transfer;
    src.color_info.matrix_coefficients =
        (avm_matrix_coefficients_t)params[pi].matrix;

    memset(buf_, 0, sizeof(buf_));
    uint32_t written = write_ci_obu(&src, buf_);
    ASSERT_GT(written, 0u) << "pi=" << pi;

    for (int m = 0; m < MAX_NUM_MLAYERS; m++) {
      av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]);
    }
    memset(pbi_->obus_in_frame_unit_data, 0,
           sizeof(pbi_->obus_in_frame_unit_data));
    pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
    pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
    pbi_->common.error.error_code = AVM_CODEC_OK;
    struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                      rb_error_handler };
    uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
    ASSERT_EQ(read, written) << "pi=" << pi;

    const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
    EXPECT_EQ(dst->color_info.color_primaries, params[pi].primaries);
    EXPECT_EQ(dst->color_info.transfer_characteristics, params[pi].transfer);
    EXPECT_EQ(dst->color_info.matrix_coefficients, params[pi].matrix);
  }
}

TEST_F(CiTest, TimingInfoBoundaryValues) {
  struct {
    uint32_t units;
    uint32_t scale;
    int equal;
    uint32_t ticks;
  } params[] = {
    { 1, 1, 0, 0 },
    { 1, 1, 1, 2 },
    { 0x7FFFFFFF, 0x7FFFFFFF, 1, 65535 },
  };
  for (int ti = 0; ti < 3; ti++) {
    ContentInterpretation src;
    av2_initialize_ci_params(&src);
    src.ci_timing_info_present_flag = 1;
    src.timing_info.num_units_in_display_tick = params[ti].units;
    src.timing_info.time_scale = params[ti].scale;
    src.timing_info.equal_elemental_interval = params[ti].equal;
    src.timing_info.num_ticks_per_elemental_duration = params[ti].ticks;

    memset(buf_, 0, sizeof(buf_));
    uint32_t written = write_ci_obu(&src, buf_);
    ASSERT_GT(written, 0u) << "ti=" << ti;

    pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
    pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
    struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                      rb_error_handler };
    uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
    ASSERT_EQ(read, written) << "ti=" << ti;

    const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
    EXPECT_EQ(dst->timing_info.num_units_in_display_tick, params[ti].units);
    EXPECT_EQ(dst->timing_info.time_scale, params[ti].scale);
    EXPECT_EQ(dst->timing_info.equal_elemental_interval, params[ti].equal);
    if (params[ti].equal) {
      EXPECT_EQ(dst->timing_info.num_ticks_per_elemental_duration,
                params[ti].ticks);
    }
  }
}

// All 4 scan types with chroma present — verifies the ci_scan_type_idc != 1
// condition.
TEST_F(CiTest, ScanTypeChromaInteraction) {
  const int scan_types[] = { AVM_SCAN_TYPE_UNSPECIFIED,
                             AVM_SCAN_TYPE_PROGRESSIVE, AVM_SCAN_TYPE_INTERLACE,
                             AVM_SCAN_TYPE_INTERLACE_COMPLEMENTARY };
  for (int si = 0; si < 4; si++) {
    ContentInterpretation src;
    av2_initialize_ci_params(&src);
    src.ci_scan_type_idc = (avm_pic_scan_type_t)scan_types[si];
    src.ci_chroma_sample_position_present_flag = 1;
    src.ci_chroma_sample_position[0] = AVM_CSP_LEFT;
    src.ci_chroma_sample_position[1] = AVM_CSP_TOP;

    memset(buf_, 0, sizeof(buf_));
    uint32_t written = write_ci_obu(&src, buf_);
    ASSERT_GT(written, 0u) << "scan=" << scan_types[si];

    for (int m = 0; m < MAX_NUM_MLAYERS; m++) {
      av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]);
    }
    memset(pbi_->obus_in_frame_unit_data, 0,
           sizeof(pbi_->obus_in_frame_unit_data));
    pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1;
    pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1;
    pbi_->common.error.error_code = AVM_CODEC_OK;
    struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr,
                                      rb_error_handler };
    uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb);
    ASSERT_EQ(read, written) << "scan=" << scan_types[si];

    const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0];
    EXPECT_EQ(dst->ci_chroma_sample_position[0], AVM_CSP_LEFT);
    if (scan_types[si] == AVM_SCAN_TYPE_PROGRESSIVE) {
      // Progressive: [1] copied from [0]
      EXPECT_EQ(dst->ci_chroma_sample_position[1], AVM_CSP_LEFT);
    } else {
      // Non-progressive: [1] read from bitstream
      EXPECT_EQ(dst->ci_chroma_sample_position[1], AVM_CSP_TOP);
    }
  }
}

}  // namespace
