/*
 * 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 "third_party/googletest/src/googletest/include/gtest/gtest.h"

#include "av2/decoder/annexF.h"
#include "avm/avmdx.h"
#include "test/codec_factory.h"
#include "test/encode_test_driver.h"
#include "test/y4m_video_source.h"
#include "test/util.h"

namespace {

// ==========================================================================
// Section 1: Direct API tests for SubBitstreamExtractionState
// ==========================================================================

class SBEApiTest : public ::testing::Test {
 protected:
  void SetUp() override { av2_sbe_init(&sbe_); }
  SubBitstreamExtractionState sbe_;
};

TEST_F(SBEApiTest, InitSetsDefaults) {
  // retention_map should be all zeros
  for (int x = 0; x < MAX_NUM_XLAYERS; x++)
    for (int m = 0; m < MAX_NUM_MLAYERS; m++)
      for (int t = 0; t < MAX_NUM_TLAYERS; t++)
        EXPECT_EQ(sbe_.retention_map[x][m][t], 0);

  // xlayer_is_selected should be all zeros
  for (int x = 0; x < MAX_NUM_XLAYERS; x++)
    EXPECT_EQ(sbe_.xlayer_is_selected[x], 0);

  // profile/level/tier/mlayer_cnt should be ANNEX_F_INVALID
  for (int x = 0; x < MAX_NUM_XLAYERS; x++) {
    EXPECT_EQ(sbe_.profile_idc[x], ANNEX_F_INVALID);
    EXPECT_EQ(sbe_.level_idc[x], ANNEX_F_INVALID);
    EXPECT_EQ(sbe_.tier_idc[x], ANNEX_F_INVALID);
    EXPECT_EQ(sbe_.mlayer_cnt[x], ANNEX_F_INVALID);
  }

  EXPECT_EQ(sbe_.bitstream_type_determined, 0);
  EXPECT_EQ(sbe_.is_multistream, 0);
  EXPECT_EQ(sbe_.retention_map_ready, 0);
  EXPECT_EQ(sbe_.global_ops_selected, 0);
  EXPECT_EQ(sbe_.msdo_seen, 0);
  EXPECT_EQ(sbe_.global_lcr_seen, 0);
  EXPECT_EQ(sbe_.global_ops_seen, 0);
  EXPECT_EQ(sbe_.obus_removed, 0);
  EXPECT_EQ(sbe_.obus_retained, 0);
}

TEST_F(SBEApiTest, ProcessMsdoSetsMultistream) {
  int stream_ids[] = { 0, 1 };
  av2_sbe_process_msdo(&sbe_, 2, stream_ids);

  EXPECT_EQ(sbe_.msdo_seen, 1);
  EXPECT_EQ(sbe_.is_multistream, 1);
  EXPECT_EQ(sbe_.bitstream_type_determined, 1);
  EXPECT_EQ(sbe_.xlayer_present[0], 1);
  EXPECT_EQ(sbe_.xlayer_present[1], 1);
  EXPECT_EQ(sbe_.num_xlayers_present, 2);
  // Global OBUs should be retained
  EXPECT_EQ(sbe_.retention_map[GLOBAL_XLAYER_ID][0][0], 1);
}

TEST_F(SBEApiTest, ProcessGlobalLcrSetsMultistream) {
  int xlayer_ids[] = { 0, 2 };
  av2_sbe_process_global_lcr(&sbe_, 2, xlayer_ids);

  EXPECT_EQ(sbe_.global_lcr_seen, 1);
  EXPECT_EQ(sbe_.is_multistream, 1);
  EXPECT_EQ(sbe_.bitstream_type_determined, 1);
  EXPECT_EQ(sbe_.xlayer_present[0], 1);
  EXPECT_EQ(sbe_.xlayer_present[2], 1);
  EXPECT_EQ(sbe_.xlayer_present[1], 0);
  EXPECT_EQ(sbe_.num_xlayers_present, 2);
}

TEST_F(SBEApiTest, ProcessGlobalOpsSelectsXlayers) {
  // ops_xlayer_map = 0x03 means xlayer 0 and 1 are selected
  av2_sbe_process_global_ops(&sbe_, /*ops_id=*/0, /*ops_cnt=*/1,
                             /*selected_ops_id=*/0, /*selected_op_index=*/0,
                             /*ops_xlayer_map=*/0x03,
                             /*ops_mlayer_info_idc=*/0);

  EXPECT_EQ(sbe_.global_ops_selected, 1);
  EXPECT_EQ(sbe_.global_ops_id, 0);
  EXPECT_EQ(sbe_.global_op_idx, 0);
  EXPECT_EQ(sbe_.xlayer_is_selected[0], 1);
  EXPECT_EQ(sbe_.xlayer_is_selected[1], 1);
  EXPECT_EQ(sbe_.xlayer_is_selected[2], 0);
}

TEST_F(SBEApiTest, ProcessGlobalOpsNoMatchDoesNotSelect) {
  // selected_ops_id=5 does not match ops_id=0
  av2_sbe_process_global_ops(&sbe_, /*ops_id=*/0, /*ops_cnt=*/1,
                             /*selected_ops_id=*/5, /*selected_op_index=*/0,
                             /*ops_xlayer_map=*/0x03,
                             /*ops_mlayer_info_idc=*/0);

  EXPECT_EQ(sbe_.global_ops_selected, 0);
  EXPECT_EQ(sbe_.xlayer_is_selected[0], 0);
  EXPECT_EQ(sbe_.xlayer_is_selected[1], 0);
}

TEST_F(SBEApiTest, IsStructuralObu) {
  EXPECT_EQ(is_sbe_structural_obu(OBU_TEMPORAL_DELIMITER), 1);
  EXPECT_EQ(is_sbe_structural_obu(OBU_MULTI_STREAM_DECODER_OPERATION), 1);
  EXPECT_EQ(is_sbe_structural_obu(OBU_LAYER_CONFIGURATION_RECORD), 1);
  EXPECT_EQ(is_sbe_structural_obu(OBU_ATLAS_SEGMENT), 1);
  EXPECT_EQ(is_sbe_structural_obu(OBU_OPERATING_POINT_SET), 1);
  EXPECT_EQ(is_sbe_structural_obu(OBU_BUFFER_REMOVAL_TIMING), 1);
  EXPECT_EQ(is_sbe_structural_obu(OBU_METADATA_SHORT), 1);
  EXPECT_EQ(is_sbe_structural_obu(OBU_METADATA_GROUP), 1);
  EXPECT_EQ(is_sbe_structural_obu(OBU_PADDING), 1);
  // Non-structural OBUs
  EXPECT_EQ(is_sbe_structural_obu(OBU_SEQUENCE_HEADER), 0);
  EXPECT_EQ(is_sbe_structural_obu(OBU_REGULAR_TILE_GROUP), 0);
  EXPECT_EQ(is_sbe_structural_obu(OBU_LEADING_TILE_GROUP), 0);
}

TEST_F(SBEApiTest, ShouldRetainWhenDisabled) {
  // When extraction is not enabled, all OBUs should be retained
  sbe_.extraction_enabled = 0;
  sbe_.retention_map_ready = 1;
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP, 0, 0, 0),
            1);
}

TEST_F(SBEApiTest, ShouldRetainWhenMapNotReady) {
  sbe_.extraction_enabled = 1;
  sbe_.retention_map_ready = 0;
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP, 0, 0, 0),
            1);
}

TEST_F(SBEApiTest, ShouldRetainSelectedLayer) {
  sbe_.extraction_enabled = 1;
  sbe_.retention_map_ready = 1;
  // Mark xlayer=0, mlayer=0, tlayer=0 as retained
  sbe_.retention_map[0][0][0] = 1;

  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP, 0, 0, 0),
            1);
}

TEST_F(SBEApiTest, ShouldRemoveUnselectedXlayer) {
  sbe_.extraction_enabled = 1;
  sbe_.retention_map_ready = 1;
  // Only xlayer=0 is retained
  sbe_.retention_map[0][0][0] = 1;

  // xlayer=1 has no entries in retention map -> remove
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP, 1, 0, 0),
            0);
}

TEST_F(SBEApiTest, ShouldRemoveUnselectedMlayerTlayer) {
  sbe_.extraction_enabled = 1;
  sbe_.retention_map_ready = 1;
  // Only mlayer=0, tlayer=0 retained for xlayer=0
  sbe_.retention_map[0][0][0] = 1;

  // mlayer=1, tlayer=0 not retained -> remove
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP, 0, 1, 0),
            0);
  // mlayer=0, tlayer=1 not retained -> remove
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP, 0, 0, 1),
            0);
}

TEST_F(SBEApiTest, ShouldDiscardInvalidLayerIds) {
  sbe_.extraction_enabled = 1;
  sbe_.retention_map_ready = 1;
  sbe_.retention_map[0][0][0] = 1;

  // Out-of-bounds layer IDs should return 0 (discard)
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP, -1, 0, 0),
            0);
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP,
                                      MAX_NUM_XLAYERS, 0, 0),
            0);
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP, 0, -1, 0),
            0);
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP, 0, 0, -1),
            0);
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP, 0,
                                      MAX_NUM_MLAYERS, 0),
            0);
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP, 0, 0,
                                      MAX_NUM_TLAYERS),
            0);
}

TEST_F(SBEApiTest, PreservesEssentialObuTypesAtBaseLayer) {
  sbe_.extraction_enabled = 1;
  sbe_.retention_map_ready = 1;
  // xlayer=0 is selected (has some retained layers)
  sbe_.retention_map[0][1][0] = 1;  // mlayer=1 retained, but not mlayer=0
  // At (mlayer=0, tlayer=0), essential OBU types should still be preserved
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_SEQUENCE_HEADER, 0, 0, 0), 1);
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_TEMPORAL_DELIMITER, 0, 0, 0),
            1);
  EXPECT_EQ(
      av2_sbe_should_retain_obu(&sbe_, OBU_LAYER_CONFIGURATION_RECORD, 0, 0, 0),
      1);
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_OPERATING_POINT_SET, 0, 0, 0),
            1);
  // But non-essential types at (0,0) should be removed
  EXPECT_EQ(av2_sbe_should_retain_obu(&sbe_, OBU_REGULAR_TILE_GROUP, 0, 0, 0),
            0);
}

TEST_F(SBEApiTest, ExtractSeqHeaderParamsFallback) {
  sbe_.xlayer_is_selected[0] = 1;

  av2_sbe_extract_seq_header_params(&sbe_, 0, /*profile=*/1, /*level=*/5,
                                    /*tier=*/0, /*mlayer_cnt=*/2);

  EXPECT_EQ(sbe_.profile_idc[0], 1);
  EXPECT_EQ(sbe_.level_idc[0], 5);
  EXPECT_EQ(sbe_.tier_idc[0], 0);
  EXPECT_EQ(sbe_.mlayer_cnt[0], 2);
}

TEST_F(SBEApiTest, ExtractSeqHeaderDoesNotOverwriteOps) {
  sbe_.xlayer_is_selected[0] = 1;
  // Simulate OPS already set profile
  sbe_.profile_idc[0] = 3;

  av2_sbe_extract_seq_header_params(&sbe_, 0, /*profile=*/1, /*level=*/5,
                                    /*tier=*/0, /*mlayer_cnt=*/2);

  // profile should NOT be overwritten
  EXPECT_EQ(sbe_.profile_idc[0], 3);
  // But level/tier/mlayer_cnt should be filled in
  EXPECT_EQ(sbe_.level_idc[0], 5);
  EXPECT_EQ(sbe_.tier_idc[0], 0);
  EXPECT_EQ(sbe_.mlayer_cnt[0], 2);
}

TEST_F(SBEApiTest, ExtractSeqHeaderSkipsUnselectedXlayer) {
  sbe_.xlayer_is_selected[0] = 0;  // not selected

  av2_sbe_extract_seq_header_params(&sbe_, 0, /*profile=*/1, /*level=*/5,
                                    /*tier=*/0, /*mlayer_cnt=*/2);

  // Should remain INVALID since xlayer not selected
  EXPECT_EQ(sbe_.profile_idc[0], ANNEX_F_INVALID);
}

TEST_F(SBEApiTest, ProcessLocalOpsMarksXlayer) {
  av2_sbe_process_local_ops(&sbe_, /*pbi=*/nullptr, /*xlayer_id=*/2,
                            /*ops_id=*/0, /*ops_cnt=*/1);

  EXPECT_EQ(sbe_.local_ops_seen[2], 1);
  // Other xlayers should remain unaffected
  EXPECT_EQ(sbe_.local_ops_seen[0], 0);
  EXPECT_EQ(sbe_.local_ops_seen[1], 0);
}

// ==========================================================================
// Section 2: Codec control tests
// ==========================================================================

class SBECodecControlTest : public ::libavm_test::CodecTestWithParam<int>,
                            public ::libavm_test::EncoderTest {
 protected:
  SBECodecControlTest() : EncoderTest(GET_PARAM(0)), speed_(GET_PARAM(1)) {}
  ~SBECodecControlTest() override {}

  void SetUp() override {
    InitializeConfig();
    passes_ = 1;
    cfg_.rc_end_usage = AVM_Q;
    cfg_.rc_min_quantizer = 210;
    cfg_.rc_max_quantizer = 210;
    cfg_.g_threads = 1;
    cfg_.g_profile = 0;
    cfg_.g_lag_in_frames = 0;
    cfg_.g_bit_depth = AVM_BITS_8;
    cfg_.enable_lcr = 1;
    cfg_.enable_ops = 1;
    cfg_.num_ops = 1;
    cfg_.enable_atlas = 1;
    num_mismatch_ = 0;
    layer_frame_cnt_ = 0;
    num_temporal_layers_ = 1;
    num_embedded_layers_ = 1;
    test_mode_ = 0;
    decode_ok_ = true;
  }

  int GetNumEmbeddedLayers() override { return num_embedded_layers_; }

  void PreEncodeFrameHook(::libavm_test::VideoSource *video,
                          ::libavm_test::Encoder *encoder) override {
    (void)video;
    frame_flags_ = 0;
    if (layer_frame_cnt_ == 0) {
      encoder->Control(AVME_SET_CPUUSED, speed_);
      encoder->Control(AVME_SET_NUMBER_MLAYERS, num_embedded_layers_);
      encoder->Control(AVME_SET_NUMBER_TLAYERS, num_temporal_layers_);
      encoder->Control(AVME_SET_MLAYER_ID, 0);
      encoder->Control(AVME_SET_TLAYER_ID, 0);
    }
    layer_frame_cnt_++;
  }

  void PreDecodeFrameHook(::libavm_test::VideoSource *video,
                          ::libavm_test::Decoder *decoder) override {
    (void)video;
    if (layer_frame_cnt_ != 1) return;  // Only configure on first frame

    if (test_mode_ == 1) {
      // AV2D_SET_SELECTED_OPS
      int ops_params[2] = { 0, 0 };
      decoder->Control(AV2D_SET_SELECTED_OPS, ops_params);
    } else if (test_mode_ == 2) {
      // AV2D_SET_SUB_BITSTREAM_EXTRACTION enable then disable
      decoder->Control(AV2D_SET_SUB_BITSTREAM_EXTRACTION, 1);
      decoder->Control(AV2D_SET_SUB_BITSTREAM_EXTRACTION, 0);
    } else if (test_mode_ == 3) {
      // AV2D_SET_SELECTED_LOCAL_OPS
      int local_params[3] = { 0, 0, 0 };
      decoder->Control(AV2D_SET_SELECTED_LOCAL_OPS, local_params);
    } else if (test_mode_ == 4) {
      // AV2D_SET_SELECTED_OPS with invalid params — use raw API
      // First trigger decoder initialization via a valid control
      decoder->Control(AV2D_SET_SUB_BITSTREAM_EXTRACTION, 0);
      int ops_params[2] = { -1, 0 };
      const avm_codec_err_t res = avm_codec_control(
          decoder->GetDecoder(), AV2D_SET_SELECTED_OPS, ops_params);
      EXPECT_EQ(AVM_CODEC_INVALID_PARAM, res);
    } else if (test_mode_ == 5) {
      // AV2D_SET_SELECTED_LOCAL_OPS with invalid params — use raw API
      // First trigger decoder initialization via a valid control
      decoder->Control(AV2D_SET_SUB_BITSTREAM_EXTRACTION, 0);
      int local_params[3] = { -1, 0, 0 };
      const avm_codec_err_t res = avm_codec_control(
          decoder->GetDecoder(), AV2D_SET_SELECTED_LOCAL_OPS, local_params);
      EXPECT_EQ(AVM_CODEC_INVALID_PARAM, res);
    }
  }

  bool HandleDecodeResult(const avm_codec_err_t res_dec,
                          libavm_test::Decoder *decoder) override {
    EXPECT_EQ(AVM_CODEC_OK, res_dec) << decoder->DecodeError();
    if (res_dec != AVM_CODEC_OK) decode_ok_ = false;
    return AVM_CODEC_OK == res_dec;
  }

  void MismatchHook(const avm_image_t *img1, const avm_image_t *img2) override {
    (void)img1;
    (void)img2;
    num_mismatch_++;
  }

  int speed_;
  int num_temporal_layers_;
  int num_embedded_layers_;
  int num_mismatch_;
  int layer_frame_cnt_;
  int test_mode_;
  bool decode_ok_;
};

TEST_P(SBECodecControlTest, SetSelectedOpsEnablesSBE) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 4);
  test_mode_ = 1;
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
  EXPECT_TRUE(decode_ok_);
}

TEST_P(SBECodecControlTest, SetSubBitstreamExtractionExplicit) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 4);
  test_mode_ = 2;
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
  EXPECT_TRUE(decode_ok_);
}

TEST_P(SBECodecControlTest, SetSelectedLocalOpsEnablesSBE) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 4);
  test_mode_ = 3;
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
  EXPECT_TRUE(decode_ok_);
}

TEST_P(SBECodecControlTest, SetSelectedOpsInvalidParams) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 4);
  test_mode_ = 4;
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
}

TEST_P(SBECodecControlTest, SetSelectedLocalOpsInvalidParams) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 4);
  test_mode_ = 5;
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
}

AV2_INSTANTIATE_TEST_SUITE(SBECodecControlTest, ::testing::Values(5));

// ==========================================================================
// Section 3: End-to-end encode/decode test with OPS enabled (no SBE)
// ==========================================================================

class SubBitstreamExtractionEncDecTest
    : public ::libavm_test::CodecTestWithParam<int>,
      public ::libavm_test::EncoderTest {
 protected:
  SubBitstreamExtractionEncDecTest()
      : EncoderTest(GET_PARAM(0)), speed_(GET_PARAM(1)) {}
  ~SubBitstreamExtractionEncDecTest() override {}

  void SetUp() override {
    InitializeConfig();
    passes_ = 1;
    cfg_.rc_end_usage = AVM_Q;
    cfg_.rc_min_quantizer = 210;
    cfg_.rc_max_quantizer = 210;
    cfg_.g_threads = 1;
    cfg_.g_profile = 0;
    cfg_.g_lag_in_frames = 0;
    cfg_.g_bit_depth = AVM_BITS_8;
    cfg_.enable_lcr = 1;
    cfg_.enable_ops = 1;
    cfg_.num_ops = 1;
    cfg_.enable_atlas = 1;
    num_mismatch_ = 0;
    layer_frame_cnt_ = 0;
    num_temporal_layers_ = 1;
    num_embedded_layers_ = 1;
    temporal_layer_id_ = 0;
    embedded_layer_id_ = 0;
  }

  int GetNumEmbeddedLayers() override { return num_embedded_layers_; }

  void PreEncodeFrameHook(::libavm_test::VideoSource *video,
                          ::libavm_test::Encoder *encoder) override {
    (void)video;
    frame_flags_ = 0;
    if (layer_frame_cnt_ == 0) {
      encoder->Control(AVME_SET_CPUUSED, speed_);
      encoder->Control(AVME_SET_NUMBER_MLAYERS, num_embedded_layers_);
      encoder->Control(AVME_SET_NUMBER_TLAYERS, num_temporal_layers_);
      encoder->Control(AVME_SET_MLAYER_ID, 0);
      encoder->Control(AVME_SET_TLAYER_ID, 0);
    }
    // Set layer IDs for 2t2e pattern
    if (num_temporal_layers_ == 2 && num_embedded_layers_ == 2) {
      if (layer_frame_cnt_ % 4 == 0) {
        struct avm_scaling_mode mode = { AVME_ONETWO, AVME_ONETWO };
        encoder->Control(AVME_SET_SCALEMODE, &mode);
        embedded_layer_id_ = 0;
        temporal_layer_id_ = 0;
        encoder->Control(AVME_SET_MLAYER_ID, 0);
        encoder->Control(AVME_SET_TLAYER_ID, 0);
      } else if (layer_frame_cnt_ % 2 == 0) {
        struct avm_scaling_mode mode = { AVME_ONETWO, AVME_ONETWO };
        encoder->Control(AVME_SET_SCALEMODE, &mode);
        embedded_layer_id_ = 0;
        temporal_layer_id_ = 1;
        encoder->Control(AVME_SET_MLAYER_ID, 0);
        encoder->Control(AVME_SET_TLAYER_ID, 1);
      } else if ((layer_frame_cnt_ - 1) % 4 == 0) {
        embedded_layer_id_ = 1;
        temporal_layer_id_ = 0;
        encoder->Control(AVME_SET_MLAYER_ID, 1);
        encoder->Control(AVME_SET_TLAYER_ID, 0);
      } else if ((layer_frame_cnt_ - 1) % 2 == 0) {
        embedded_layer_id_ = 1;
        temporal_layer_id_ = 1;
        encoder->Control(AVME_SET_MLAYER_ID, 1);
        encoder->Control(AVME_SET_TLAYER_ID, 1);
      }
    } else if (num_temporal_layers_ == 2 && num_embedded_layers_ == 1) {
      if (layer_frame_cnt_ % 2 == 0) {
        temporal_layer_id_ = 0;
        encoder->Control(AVME_SET_TLAYER_ID, 0);
      } else {
        temporal_layer_id_ = 1;
        encoder->Control(AVME_SET_TLAYER_ID, 1);
      }
    } else if (num_temporal_layers_ == 1 && num_embedded_layers_ == 2) {
      if (layer_frame_cnt_ % 2 == 0) {
        struct avm_scaling_mode mode = { AVME_ONETWO, AVME_ONETWO };
        encoder->Control(AVME_SET_SCALEMODE, &mode);
        embedded_layer_id_ = 0;
        encoder->Control(AVME_SET_MLAYER_ID, 0);
      } else {
        struct avm_scaling_mode mode = { AVME_NORMAL, AVME_NORMAL };
        encoder->Control(AVME_SET_SCALEMODE, &mode);
        embedded_layer_id_ = 1;
        encoder->Control(AVME_SET_MLAYER_ID, 1);
      }
    }
    layer_frame_cnt_++;
  }

  bool HandleDecodeResult(const avm_codec_err_t res_dec,
                          libavm_test::Decoder *decoder) override {
    EXPECT_EQ(AVM_CODEC_OK, res_dec) << decoder->DecodeError();
    return AVM_CODEC_OK == res_dec;
  }

  void MismatchHook(const avm_image_t *img1, const avm_image_t *img2) override {
    (void)img1;
    (void)img2;
    num_mismatch_++;
  }

  int speed_;
  int temporal_layer_id_;
  int embedded_layer_id_;
  int num_temporal_layers_;
  int num_embedded_layers_;
  int num_mismatch_;
  int layer_frame_cnt_;
};

TEST_P(SubBitstreamExtractionEncDecTest, SinglestreamWithOps) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 10);
  num_temporal_layers_ = 1;
  num_embedded_layers_ = 1;
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
  EXPECT_EQ(num_mismatch_, 0);
}

TEST_P(SubBitstreamExtractionEncDecTest, TwoTemporalLayersWithOps) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 10);
  num_temporal_layers_ = 2;
  num_embedded_layers_ = 1;
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
  EXPECT_EQ(num_mismatch_, 0);
}

TEST_P(SubBitstreamExtractionEncDecTest, TwoEmbeddedLayersWithOps) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 10);
  num_temporal_layers_ = 1;
  num_embedded_layers_ = 2;
  cfg_.g_profile = 1;  // Profile 1 supports max_mlayer_cnt = 2
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
  EXPECT_EQ(num_mismatch_, 0);
}

TEST_P(SubBitstreamExtractionEncDecTest, TwoTempTwoEmbedWithOps) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 10);
  num_temporal_layers_ = 2;
  num_embedded_layers_ = 2;
  cfg_.g_profile = 1;  // Profile 1 supports max_mlayer_cnt = 2
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
  EXPECT_EQ(num_mismatch_, 0);
}

AV2_INSTANTIATE_TEST_SUITE(SubBitstreamExtractionEncDecTest,
                           ::testing::Values(5));

// ==========================================================================
// Section 4: End-to-end SBE filtering tests
// ==========================================================================

class SBEFilteringTest : public ::libavm_test::CodecTestWithParam<int>,
                         public ::libavm_test::EncoderTest {
 protected:
  SBEFilteringTest() : EncoderTest(GET_PARAM(0)), speed_(GET_PARAM(1)) {}
  ~SBEFilteringTest() override {}

  void SetUp() override {
    InitializeConfig();
    passes_ = 1;
    cfg_.rc_end_usage = AVM_Q;
    cfg_.rc_min_quantizer = 210;
    cfg_.rc_max_quantizer = 210;
    cfg_.g_threads = 1;
    cfg_.g_profile = 0;
    cfg_.g_lag_in_frames = 0;
    cfg_.g_bit_depth = AVM_BITS_8;
    cfg_.enable_lcr = 1;
    cfg_.enable_ops = 1;
    cfg_.num_ops = 1;
    cfg_.enable_atlas = 1;
    num_mismatch_ = 0;
    layer_frame_cnt_ = 0;
    num_temporal_layers_ = 1;
    num_embedded_layers_ = 1;
    temporal_layer_id_ = 0;
    embedded_layer_id_ = 0;
    sbe_configured_ = false;
    decode_ok_ = true;
    decoded_frame_count_ = 0;
    encoded_frame_count_ = 0;
    use_local_ops_ = false;
    disable_sbe_after_enable_ = false;
  }

  int GetNumEmbeddedLayers() override { return num_embedded_layers_; }

  void PreEncodeFrameHook(::libavm_test::VideoSource *video,
                          ::libavm_test::Encoder *encoder) override {
    (void)video;
    frame_flags_ = 0;
    if (layer_frame_cnt_ == 0) {
      encoder->Control(AVME_SET_CPUUSED, speed_);
      encoder->Control(AVME_SET_NUMBER_MLAYERS, num_embedded_layers_);
      encoder->Control(AVME_SET_NUMBER_TLAYERS, num_temporal_layers_);
      encoder->Control(AVME_SET_MLAYER_ID, 0);
      encoder->Control(AVME_SET_TLAYER_ID, 0);
    }
    // 2t1e layer assignment
    if (num_temporal_layers_ == 2 && num_embedded_layers_ == 1) {
      if (layer_frame_cnt_ % 2 == 0) {
        temporal_layer_id_ = 0;
        encoder->Control(AVME_SET_TLAYER_ID, 0);
      } else {
        temporal_layer_id_ = 1;
        encoder->Control(AVME_SET_TLAYER_ID, 1);
      }
    }
    // 1t2e layer assignment
    if (num_temporal_layers_ == 1 && num_embedded_layers_ == 2) {
      if (layer_frame_cnt_ % 2 == 0) {
        struct avm_scaling_mode mode = { AVME_ONETWO, AVME_ONETWO };
        encoder->Control(AVME_SET_SCALEMODE, &mode);
        embedded_layer_id_ = 0;
        encoder->Control(AVME_SET_MLAYER_ID, 0);
      } else {
        struct avm_scaling_mode mode = { AVME_NORMAL, AVME_NORMAL };
        encoder->Control(AVME_SET_SCALEMODE, &mode);
        embedded_layer_id_ = 1;
        encoder->Control(AVME_SET_MLAYER_ID, 1);
      }
    }
    layer_frame_cnt_++;
  }

  // Enable SBE on the decoder before decoding frames
  void PreDecodeFrameHook(::libavm_test::VideoSource *video,
                          ::libavm_test::Decoder *decoder) override {
    (void)video;
    if (!sbe_configured_) {
      if (use_local_ops_) {
        // Select local OPS: xlayer_id=0, ops_id=0, op_index=0
        int local_params[3] = { 0, 0, 0 };
        decoder->Control(AV2D_SET_SELECTED_LOCAL_OPS, local_params);
      } else {
        // Select global OPS: ops_id=0, op_index=0
        int ops_params[2] = { 0, 0 };
        decoder->Control(AV2D_SET_SELECTED_OPS, ops_params);
      }
      if (disable_sbe_after_enable_) {
        decoder->Control(AV2D_SET_SUB_BITSTREAM_EXTRACTION, 0);
      }
      sbe_configured_ = true;
    }
  }

  bool HandleDecodeResult(const avm_codec_err_t res_dec,
                          libavm_test::Decoder *decoder) override {
    EXPECT_EQ(AVM_CODEC_OK, res_dec) << decoder->DecodeError();
    if (res_dec != AVM_CODEC_OK) decode_ok_ = false;
    return AVM_CODEC_OK == res_dec;
  }

  void MismatchHook(const avm_image_t *img1, const avm_image_t *img2) override {
    (void)img1;
    (void)img2;
    num_mismatch_++;
  }

  // Count every decompressed frame output by the decoder
  void DecompressedFrameHook(const avm_image_t &img,
                             avm_codec_pts_t pts) override {
    (void)img;
    (void)pts;
    decoded_frame_count_++;
  }

  // Count every encoded frame packet
  void FramePktHook(const avm_codec_cx_pkt_t *pkt,
                    ::libavm_test::DxDataIterator *dec_iter) override {
    (void)dec_iter;
    if (pkt->kind == AVM_CODEC_CX_FRAME_PKT) encoded_frame_count_++;
  }

  int speed_;
  int temporal_layer_id_;
  int embedded_layer_id_;
  int num_temporal_layers_;
  int num_embedded_layers_;
  int num_mismatch_;
  int layer_frame_cnt_;
  bool sbe_configured_;
  bool decode_ok_;
  int decoded_frame_count_;
  int encoded_frame_count_;
  bool use_local_ops_;
  bool disable_sbe_after_enable_;
};

// Test: Singlestream 1 layer with SBE enabled — all frames should be retained
TEST_P(SBEFilteringTest, SingleLayerWithSBE) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 10);
  num_temporal_layers_ = 1;
  num_embedded_layers_ = 1;
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
  EXPECT_TRUE(decode_ok_);
  // With 1 layer, SBE should retain everything
  EXPECT_GT(encoded_frame_count_, 0);
  EXPECT_EQ(decoded_frame_count_, encoded_frame_count_)
      << "Single-layer SBE: all encoded frames should be decoded";
}

// Test: 2 temporal layers with SBE extracting ops_id=0 (only tlayer=0)
TEST_P(SBEFilteringTest, TwoTemporalWithSBE) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 10);
  num_temporal_layers_ = 2;
  num_embedded_layers_ = 1;
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
  EXPECT_TRUE(decode_ok_);
  // Default OPS selects only mlayer=0, tlayer=0.
  // With 2 temporal layers, the SBE must filter out tlayer=1 frames,
  // so fewer frames should be decoded than encoded.
  EXPECT_GT(encoded_frame_count_, 0);
  EXPECT_GT(decoded_frame_count_, 0)
      << "SBE should still produce some decoded frames";
  EXPECT_LT(decoded_frame_count_, encoded_frame_count_)
      << "SBE filtering should remove tlayer=1 frames: decoded="
      << decoded_frame_count_ << " encoded=" << encoded_frame_count_;
}

// Test: 2 embedded layers with SBE extracting ops_id=0 (only mlayer=0)
TEST_P(SBEFilteringTest, TwoEmbeddedWithSBE) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 10);
  num_temporal_layers_ = 1;
  num_embedded_layers_ = 2;
  cfg_.g_profile = 1;  // Profile 1 supports max_mlayer_cnt = 2
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
  EXPECT_TRUE(decode_ok_);
  // Default OPS selects only mlayer=0, tlayer=0.
  // With 2 embedded layers, the SBE must filter out mlayer=1 frames,
  // so fewer frames should be decoded than encoded.
  EXPECT_GT(encoded_frame_count_, 0);
  EXPECT_GT(decoded_frame_count_, 0)
      << "SBE should still produce some decoded frames";
  EXPECT_LT(decoded_frame_count_, encoded_frame_count_)
      << "SBE filtering should remove mlayer=1 frames: decoded="
      << decoded_frame_count_ << " encoded=" << encoded_frame_count_;
}

// Test: 1t1e with AV2D_SET_SELECTED_LOCAL_OPS, decode succeeds
TEST_P(SBEFilteringTest, SingleLayerWithLocalOps) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 10);
  num_temporal_layers_ = 1;
  num_embedded_layers_ = 1;
  use_local_ops_ = true;
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
  EXPECT_TRUE(decode_ok_);
  EXPECT_GT(encoded_frame_count_, 0);
}

// Test: Enable SBE via AV2D_SET_SELECTED_OPS, then disable via
// AV2D_SET_SUB_BITSTREAM_EXTRACTION=0 — all frames should be retained
TEST_P(SBEFilteringTest, DisableSBEAfterEnable) {
  ::libavm_test::Y4mVideoSource video("park_joy_90p_8_420.y4m", 0, 10);
  num_temporal_layers_ = 2;
  num_embedded_layers_ = 1;
  disable_sbe_after_enable_ = true;
  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
  EXPECT_TRUE(decode_ok_);
  // After disabling SBE, all frames should be retained (no filtering)
  EXPECT_GT(encoded_frame_count_, 0);
  EXPECT_EQ(decoded_frame_count_, encoded_frame_count_)
      << "With SBE disabled, all encoded frames should be decoded";
}

AV2_INSTANTIATE_TEST_SUITE(SBEFilteringTest, ::testing::Values(5));

}  // namespace
