// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <string>

#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "media/base/video_codecs.h"
#include "media/mojo/mojom/media_types.mojom.h"
#include "media/mojo/services/test_helpers.h"
#include "media/mojo/services/video_decode_stats_recorder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"

using testing::_;

namespace media {

namespace {

// Aliases for readability.
const ukm::SourceId kSourceIdA = 1u;
const ukm::SourceId kSourceIdB = 2u;
const bool kIsTopFrameA = true;
const bool kIsTopFrameB = false;
const uint64_t kPlayerIdA = 1234u;
const uint64_t kPlayerIdB = 5678u;
const VideoCodecProfile kProfileA = H264PROFILE_MIN;
const VideoCodecProfile kProfileB = VP9PROFILE_MIN;
const int kFpsA = 30;
const int kFpsB = 60;

MATCHER_P(MojoEq, value, "") {
  return arg.Equals(value);
}

}  // namespace

class VideoDecodeStatsRecorderTest : public ::testing::Test {
 public:
  VideoDecodeStatsRecorderTest() : kSizeA_(1280, 720), kSizeB_(640, 360) {}

  ~VideoDecodeStatsRecorderTest() override = default;

  void SetUp() override {}

  void TearDown() override {}

  void MakeRecorder(ukm::SourceId source_id,
                    bool is_top_frame,
                    uint64_t player_id) {
    recorder_ = std::make_unique<VideoDecodeStatsRecorder>(
        base::BindRepeating(&VideoDecodeStatsRecorderTest::PrintSavePerfRecord,
                            base::Unretained(this)),
        source_id, is_top_frame, MediaPlayerUkmId(player_id));
  }

  void PrintSavePerfRecord(ukm::SourceId source_id,
                           bool is_top_frame,
                           mojom::PredictionFeatures features,
                           mojom::PredictionTargets targets,
                           uint64_t player_id,
                           base::OnceClosure save_done_cb) {
    SavePerfRecord(source_id, is_top_frame, features, targets, player_id,
                   std::move(save_done_cb));
  }

  MOCK_METHOD6(SavePerfRecord,
               void(ukm::SourceId source_id,
                    bool is_top_frame,
                    mojom::PredictionFeatures features,
                    mojom::PredictionTargets targets,
                    uint64_t player_id,
                    base::OnceClosure save_done_cb));

 protected:
  std::unique_ptr<VideoDecodeStatsRecorder> recorder_;

  // Class members to avoid static initializer.
  const gfx::Size kSizeA_;
  const gfx::Size kSizeB_;
};

TEST_F(VideoDecodeStatsRecorderTest, SaveOnStartNewRecord) {
  MakeRecorder(kSourceIdA, kIsTopFrameA, kPlayerIdA);
  recorder_->StartNewRecord(MakeFeaturesPtr(kProfileA, kSizeA_, kFpsA));
  recorder_->UpdateRecord(MakeTargetsPtr(3, 2, 1));

  // Expect save with all the 'A' state upon starting a record with 'B' state.
  EXPECT_CALL(*this,
              SavePerfRecord(kSourceIdA, kIsTopFrameA,
                             MojoEq(MakeFeatures(kProfileA, kSizeA_, kFpsA)),
                             MojoEq(MakeTargets(3, 2, 1)), kPlayerIdA, _));
  recorder_->StartNewRecord(MakeFeaturesPtr(kProfileB, kSizeB_, kFpsB));

  // Update 'B's counts and test the opposite direction by starting a new record
  // again using 'A' state. Note, this mixes A's construction state w/ B's
  // "features".
  recorder_->UpdateRecord(MakeTargetsPtr(6, 5, 4));
  EXPECT_CALL(*this,
              SavePerfRecord(kSourceIdA, kIsTopFrameA,
                             MojoEq(MakeFeatures(kProfileB, kSizeB_, kFpsB)),
                             MojoEq(MakeTargets(6, 5, 4)), kPlayerIdA, _));
  recorder_->StartNewRecord(MakeFeaturesPtr(kProfileA, kSizeA_, kFpsA));
}

TEST_F(VideoDecodeStatsRecorderTest, SaveOnDestruction) {
  MakeRecorder(kSourceIdA, kIsTopFrameA, kPlayerIdA);
  recorder_->StartNewRecord(MakeFeaturesPtr(kProfileA, kSizeA_, kFpsA));
  recorder_->UpdateRecord(MakeTargetsPtr(3, 2, 1));

  // Expect save with all the 'A' state upon destruction.
  EXPECT_CALL(*this,
              SavePerfRecord(kSourceIdA, kIsTopFrameA,
                             MojoEq(MakeFeatures(kProfileA, kSizeA_, kFpsA)),
                             MojoEq(MakeTargets(3, 2, 1)), kPlayerIdA, _));
  recorder_.reset();

  // Repeat with 'B' state just to be sure
  MakeRecorder(kSourceIdB, kIsTopFrameB, kPlayerIdB);
  recorder_->StartNewRecord(MakeFeaturesPtr(kProfileB, kSizeB_, kFpsB));
  recorder_->UpdateRecord(MakeTargetsPtr(3, 2, 1));

  // Expect save with all the 'B' state upon destruction.
  EXPECT_CALL(*this,
              SavePerfRecord(kSourceIdB, kIsTopFrameB,
                             MojoEq(MakeFeatures(kProfileB, kSizeB_, kFpsB)),
                             MojoEq(MakeTargets(3, 2, 1)), kPlayerIdB, _));
  recorder_.reset();
}

}  // namespace media
