/*
 *  Copyright (c) 2020 The WebM 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 <cstdint>
#include <new>
#include <memory>

#include "./vpx_config.h"

#include "gtest/gtest.h"
#include "test/codec_factory.h"
#include "test/encode_test_driver.h"
#include "test/util.h"
#include "test/yuv_video_source.h"
#if CONFIG_VP9_DECODER
#include "vpx/vp8dx.h"
#endif
#include "vpx/vpx_codec.h"
#include "vpx/vpx_encoder.h"
#include "vpx/vpx_ext_ratectrl.h"
#include "vpx/vpx_image.h"
#include "vpx/vpx_tpl.h"
#include "vpx_dsp/vpx_dsp_common.h"

namespace {

constexpr int kShowFrameCount = 10;
constexpr int kKeyframeQp = 10;
constexpr int kLeafQp = 40;
constexpr int kArfQp = 15;

// Simple external rate controller for testing.
class RateControllerForTest {
 public:
  RateControllerForTest() : current_gop_(-1) {}
  ~RateControllerForTest() {}

  void StartNextGop() { ++current_gop_; }

  vpx_rc_gop_decision_t GetCurrentGop() const {
    vpx_rc_gop_decision_t gop_decision;
    if (current_gop_ == 0) {
      gop_decision.use_key_frame = 1;
      gop_decision.use_alt_ref = 1;
      gop_decision.gop_coding_frames =
          kShowFrameCount - 1 + gop_decision.use_alt_ref;
      // key frame
      gop_decision.update_type[0] = VPX_RC_KF_UPDATE;
      gop_decision.update_ref_index[0] = 0;
      gop_decision.ref_frame_list[0] = get_kf_ref_frame();
      // arf
      gop_decision.update_type[1] = VPX_RC_ARF_UPDATE;
      gop_decision.update_ref_index[1] = 1;
      gop_decision.ref_frame_list[1] = get_arf_ref_frame();
      // leafs
      for (int i = 2; i < gop_decision.gop_coding_frames; ++i) {
        gop_decision.update_type[i] = VPX_RC_LF_UPDATE;
        gop_decision.update_ref_index[i] = 2;
        gop_decision.ref_frame_list[i] = get_leaf_ref_frame(i);
      }
    } else {
      // Pad a overlay-only GOP as the last GOP.
      EXPECT_EQ(current_gop_, 1);
      gop_decision.use_key_frame = 0;
      gop_decision.use_alt_ref = 0;
      gop_decision.gop_coding_frames = 1;

      gop_decision.update_type[0] = VPX_RC_OVERLAY_UPDATE;
      gop_decision.update_ref_index[0] = 1;
      gop_decision.ref_frame_list[0] = get_ovl_ref_frame();
    }
    return gop_decision;
  }

  int CalculateFrameDecision(int frame_index) {
    if (current_gop_ == 0 && frame_index == 0) {
      // Key frame, first frame in the first GOP.
      return kKeyframeQp;
    } else if (frame_index == 1) {
      // ARF, we always use ARF for this test.
      return kArfQp;
    } else {
      return kLeafQp;
    }
  }

 private:
  vpx_rc_ref_frame_t get_kf_ref_frame() const {
    vpx_rc_ref_frame_t ref_frame;
    ref_frame.index[0] = -1;
    ref_frame.index[1] = -1;
    ref_frame.index[2] = -1;
    ref_frame.name[0] = VPX_RC_INVALID_REF_FRAME;
    ref_frame.name[1] = VPX_RC_INVALID_REF_FRAME;
    ref_frame.name[2] = VPX_RC_INVALID_REF_FRAME;
    return ref_frame;
  }
  vpx_rc_ref_frame_t get_arf_ref_frame() const {
    vpx_rc_ref_frame_t ref_frame;
    ref_frame.index[0] = 0;
    ref_frame.index[1] = -1;
    ref_frame.index[2] = -1;
    ref_frame.name[0] = VPX_RC_GOLDEN_FRAME;
    ref_frame.name[1] = VPX_RC_INVALID_REF_FRAME;
    ref_frame.name[2] = VPX_RC_INVALID_REF_FRAME;
    return ref_frame;
  }
  vpx_rc_ref_frame_t get_leaf_ref_frame(int count) const {
    vpx_rc_ref_frame_t ref_frame;
    ref_frame.index[0] = 0;
    ref_frame.index[1] = 1;
    ref_frame.index[2] = count > 2 ? 2 : -1;
    ref_frame.name[0] = VPX_RC_GOLDEN_FRAME;
    ref_frame.name[1] = VPX_RC_ALTREF_FRAME;
    ref_frame.name[2] =
        count > 2 ? VPX_RC_LAST_FRAME : VPX_RC_INVALID_REF_FRAME;
    return ref_frame;
  }
  vpx_rc_ref_frame_t get_ovl_ref_frame() const {
    vpx_rc_ref_frame_t ref_frame;
    ref_frame.index[0] = 1;
    ref_frame.index[1] = -1;
    ref_frame.index[2] = -1;
    ref_frame.name[0] = VPX_RC_ALTREF_FRAME;
    ref_frame.name[1] = VPX_RC_INVALID_REF_FRAME;
    ref_frame.name[2] = VPX_RC_INVALID_REF_FRAME;
    return ref_frame;
  }

  int current_gop_;
};

// Callbacks used in this test.
vpx_rc_status_t rc_test_create_model(
    void * /*priv*/, const vpx_rc_config_t * /*ratectrl_config*/,
    vpx_rc_model_t *rate_ctrl_model_ptr) {
  std::unique_ptr<RateControllerForTest> test_controller(
      new RateControllerForTest());
  *rate_ctrl_model_ptr = test_controller.release();
  return VPX_RC_OK;
}

vpx_rc_status_t rc_test_send_firstpass_stats(
    vpx_rc_model_t /*rate_ctrl_model*/,
    const vpx_rc_firstpass_stats_t *first_pass_stats) {
  EXPECT_EQ(first_pass_stats->num_frames, kShowFrameCount);
  for (int i = 0; i < first_pass_stats->num_frames; ++i) {
    EXPECT_DOUBLE_EQ(first_pass_stats->frame_stats[i].frame, i);
  }
  return VPX_RC_OK;
}

vpx_rc_status_t rc_test_send_tpl_gop_stats(
    vpx_rc_model_t /*rate_ctrl_model*/, const VpxTplGopStats *tpl_gop_stats) {
  EXPECT_GT(tpl_gop_stats->size, 0);

  for (int i = 0; i < tpl_gop_stats->size; ++i) {
    EXPECT_GT(tpl_gop_stats->frame_stats_list[i].num_blocks, 0);
  }
  return VPX_RC_OK;
}

vpx_rc_status_t rc_test_get_encodeframe_decision(
    vpx_rc_model_t rate_ctrl_model, const int frame_gop_index,
    vpx_rc_encodeframe_decision_t *frame_decision) {
  RateControllerForTest *test_controller =
      static_cast<RateControllerForTest *>(rate_ctrl_model);
  frame_decision->q_index =
      test_controller->CalculateFrameDecision(frame_gop_index);
  frame_decision->rdmult =
      frame_decision->q_index * frame_decision->q_index / 2;
  frame_decision->delta_q_uv = 0;
  return VPX_RC_OK;
}

vpx_rc_status_t rc_test_get_gop_decision(vpx_rc_model_t rate_ctrl_model,
                                         vpx_rc_gop_decision_t *gop_decision) {
  RateControllerForTest *test_controller =
      static_cast<RateControllerForTest *>(rate_ctrl_model);
  test_controller->StartNextGop();
  *gop_decision = test_controller->GetCurrentGop();
  return VPX_RC_OK;
}

vpx_rc_status_t rc_delete_model(vpx_rc_model_t rate_ctrl_model) {
  RateControllerForTest *test_controller =
      static_cast<RateControllerForTest *>(rate_ctrl_model);
  delete test_controller;
  return VPX_RC_OK;
}

class ExtRateCtrlTest : public ::libvpx_test::EncoderTest,
                        public ::testing::Test {
 protected:
  ExtRateCtrlTest()
      : EncoderTest(&::libvpx_test::kVP9), received_show_frame_count_(0),
        current_frame_qp_(0) {}

  ~ExtRateCtrlTest() override = default;

  void SetUp() override {
    InitializeConfig();
#if CONFIG_REALTIME_ONLY
    SetMode(::libvpx_test::kRealTime);
#else
    SetMode(::libvpx_test::kTwoPassGood);
#endif
  }

  void PreEncodeFrameHook(::libvpx_test::VideoSource *video,
                          ::libvpx_test::Encoder *encoder) override {
    if (video->frame() == 0) {
      vpx_rc_funcs_t rc_funcs = {};
      rc_funcs.rc_type = VPX_RC_GOP_QP;
      rc_funcs.create_model = rc_test_create_model;
      rc_funcs.send_firstpass_stats = rc_test_send_firstpass_stats;
      rc_funcs.send_tpl_gop_stats = rc_test_send_tpl_gop_stats;
      rc_funcs.get_gop_decision = rc_test_get_gop_decision;
      rc_funcs.get_encodeframe_decision = rc_test_get_encodeframe_decision;
      rc_funcs.delete_model = rc_delete_model;
      encoder->Control(VP9E_SET_EXTERNAL_RATE_CONTROL, &rc_funcs);
    }
  }

#if CONFIG_VP9_DECODER
  bool HandleDecodeResult(const vpx_codec_err_t res_dec,
                          const ::libvpx_test::VideoSource & /*video*/,
                          ::libvpx_test::Decoder *decoder) override {
    EXPECT_EQ(VPX_CODEC_OK, res_dec) << decoder->DecodeError();
    decoder->Control(VPXD_GET_LAST_QUANTIZER, &current_frame_qp_);
    return VPX_CODEC_OK == res_dec;
  }

  void FramePktHook(const vpx_codec_cx_pkt_t *pkt) override {
    // We are not comparing current_frame_qp_ here because the encoder will
    // pack ARF and the next show frame into one pkt. Therefore, we might
    // receive two frames in one pkt. However, one thing we are sure is that
    // each pkt will have just one show frame. Therefore, we can check if the
    // received show frame count match the actual show frame count.
    if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
      ++received_show_frame_count_;
    }
  }
#endif  // CONFIG_VP9_DECODER

  int received_show_frame_count_;
  int current_frame_qp_;
};

TEST_F(ExtRateCtrlTest, EncodeTest) {
  cfg_.rc_target_bitrate = 4000;
  cfg_.g_lag_in_frames = 25;

  std::unique_ptr<libvpx_test::VideoSource> video;
  video.reset(new (std::nothrow) libvpx_test::YUVVideoSource(
      "bus_352x288_420_f20_b8.yuv", VPX_IMG_FMT_I420, 352, 288, 30, 1, 0,
      kShowFrameCount));

  ASSERT_NE(video, nullptr);
  ASSERT_NO_FATAL_FAILURE(RunLoop(video.get()));
#if CONFIG_VP9_DECODER
  EXPECT_EQ(received_show_frame_count_, kShowFrameCount);
#endif
}

}  // namespace
