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

#include "components/viz/service/display/frame_interval_decider.h"

#include <memory>
#include <optional>
#include <utility>
#include <variant>

#include "base/containers/flat_map.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "components/viz/common/surfaces/surface_info.h"
#include "components/viz/service/surfaces/surface.h"
#include "components/viz/service/surfaces/surface_manager.h"
#include "components/viz/service/surfaces/surface_manager_delegate.h"
#include "components/viz/test/compositor_frame_helpers.h"
#include "components/viz/test/stub_surface_client.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace viz {
namespace {

using ContinuousRangeSettings = FrameIntervalDecider::ContinuousRangeSettings;
using FixedIntervalSettings = FrameIntervalDecider::FixedIntervalSettings;
using FrameIntervalClass = FrameIntervalDecider::FrameIntervalClass;
using ResultInterval = FrameIntervalMatcher::ResultInterval;
using Result = FrameIntervalDecider::Result;

constexpr base::TimeTicks kNow = base::TimeTicks() + base::Seconds(1234);

void ExpectResult(Result result, FrameIntervalClass frame_interval_class) {
  ASSERT_TRUE(std::holds_alternative<FrameIntervalClass>(result));
  EXPECT_EQ(frame_interval_class, std::get<FrameIntervalClass>(result));
}

void ExpectResult(Result result, base::TimeDelta interval) {
  ASSERT_TRUE(std::holds_alternative<ResultInterval>(result));
  EXPECT_EQ(interval, std::get<ResultInterval>(result).interval);
}

class TestFrameIntervalMatcher : public FrameIntervalMatcher {
 public:
  explicit TestFrameIntervalMatcher(FrameIntervalMatcherType type)
      : FrameIntervalMatcher(type) {}

  std::optional<Result> Match(const Inputs& matcher_inputs) override {
    last_matcher_inputs_.emplace(matcher_inputs);
    return result_;
  }

  bool has_last_matcher_inputs() const {
    return last_matcher_inputs_.has_value();
  }

  Inputs TakeLastMatcherInputs() {
    CHECK(last_matcher_inputs_);
    Inputs matcher_inputs = last_matcher_inputs_.value();
    last_matcher_inputs_.reset();
    return matcher_inputs;
  }

  void SetResult(std::optional<Result> result) { result_ = result; }

  base::WeakPtr<TestFrameIntervalMatcher> GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  std::optional<Inputs> last_matcher_inputs_;
  std::optional<Result> result_;

  base::WeakPtrFactory<TestFrameIntervalMatcher> weak_ptr_factory_{this};
};

class FrameIntervalDeciderTest : public testing::Test,
                                 public SurfaceManagerDelegate {
 public:
  FrameIntervalDeciderTest() : frame_(MakeDefaultCompositorFrame()) {}
  ~FrameIntervalDeciderTest() override = default;

  void SetUp() override {
    surface_manager_ = std::make_unique<SurfaceManager>(
        this, /*activation_deadline_in_frames=*/std::nullopt,
        /*max_uncommitted_frames=*/0);
    decider_ = std::make_unique<FrameIntervalDecider>();
  }

  void TearDown() override {
    decider_.reset();
    surface_manager_.reset();
  }

  // SurfaceManagerDelegate implementation.
  std::string_view GetFrameSinkDebugLabel(
      const FrameSinkId& frame_sink_id) const override {
    return std::string_view();
  }
  void AggregatedFrameSinksChanged() override {}
  void AddObserver(FrameSinkObserver* obs) override {}
  void RemoveObserver(FrameSinkObserver* obs) override {}
  bool HasViewTransitionToken(
      const blink::ViewTransitionToken& transition_token) override {
    return false;
  }

 protected:
  base::WeakPtr<SurfaceClient> surface_client() {
    return surface_client_.weak_factory.GetWeakPtr();
  }

  Surface* CreateSurface(const FrameSinkId& frame_sink_id,
                         FrameIntervalInputs frame_interval_inputs) {
    LocalSurfaceId local_surface_id =
        LocalSurfaceId(1u, base::UnguessableToken::Create());
    SurfaceId surface_id(frame_sink_id, local_surface_id);
    SurfaceInfo surface_info(surface_id, frame_.device_scale_factor(),
                             frame_.size_in_pixels());
    Surface* surface =
        surface_manager_
            ->CreateSurface(surface_client(), surface_info, SurfaceId())
            .value_or(nullptr);

    UpdateFrame(surface, std::move(frame_interval_inputs));

    return surface;
  }

  void UpdateFrame(Surface* surface,
                   FrameIntervalInputs frame_interval_inputs) {
    uint32_t frame_index = surface->GetActiveFrameIndex() + 1u;
    auto frame = MakeDefaultCompositorFrame();
    frame.metadata.frame_interval_inputs = std::move(frame_interval_inputs);
    ASSERT_TRUE(surface->QueueFrame(std::move(frame), frame_index,
                                    base::ScopedClosureRunner()));
    surface->ActivatePendingFrameForDeadline();
    ASSERT_EQ(surface->GetActiveFrameIndex(), frame_index);
  }

  void DrawSurfaces(std::vector<Surface*> surfaces,
                    base::TimeTicks frame_time) {
    FrameIntervalDecider::ScopedAggregate scoped_aggregate(
        decider_->WrapAggregate(*surface_manager_, frame_time));
    for (auto* surface : surfaces) {
      static_cast<SurfaceObserver*>(&scoped_aggregate)
          ->OnSurfaceWillBeDrawn(surface);
    }
  }

  void InitializeDecider() {
    frame_.metadata.frame_interval_inputs.frame_time = kNow;

    std::vector<std::unique_ptr<FrameIntervalMatcher>> matchers;
    {
      auto matcher = std::make_unique<TestFrameIntervalMatcher>(
          FrameIntervalMatcherType::kInputBoost);
      matchers_.push_back(matcher->GetWeakPtr());
      matchers.push_back(std::move(matcher));
    }
    {
      auto matcher = std::make_unique<TestFrameIntervalMatcher>(
          FrameIntervalMatcherType::kOnlyVideo);
      matchers_.push_back(matcher->GetWeakPtr());
      matchers.push_back(std::move(matcher));
    }
    settings_.result_callback = base::BindRepeating(
        &FrameIntervalDeciderTest::SetFrameInterval, base::Unretained(this));
    decider_->UpdateSettings(settings_, std::move(matchers));
  }

  void SetFrameInterval(Result result, FrameIntervalMatcherType matcher_type) {
    result_ = result;
    matcher_type_ = matcher_type;
  }

  bool has_result() const { return result_.has_value(); }
  bool has_matcher_type() const { return matcher_type_.has_value(); }

  Result TakeLastResult() {
    CHECK(result_);
    Result result = result_.value();
    result_.reset();
    return result;
  }

  FrameIntervalMatcherType TakeLastMatcherType() {
    CHECK(matcher_type_);
    FrameIntervalMatcherType matcher_type = matcher_type_.value();
    matcher_type_.reset();
    return matcher_type;
  }

  CompositorFrame frame_;
  StubSurfaceClient surface_client_;

  FrameIntervalDecider::Settings settings_;
  // Matchers are probably owned by `decider_` so using WeakPtrs to catch
  // lifetime issues.
  std::vector<base::WeakPtr<TestFrameIntervalMatcher>> matchers_;

  std::unique_ptr<SurfaceManager> surface_manager_;
  std::unique_ptr<FrameIntervalDecider> decider_;

  // Set by `result_callback`.
  std::optional<Result> result_;
  std::optional<FrameIntervalMatcherType> matcher_type_;
};

TEST_F(FrameIntervalDeciderTest, Basics) {
  settings_.increase_frame_interval_timeout = base::Milliseconds(50);
  InitializeDecider();

  matchers_[0]->SetResult(FrameIntervalClass::kBoost);
  FrameIntervalInputs inputs;
  inputs.frame_time = kNow;
  Surface* surface1 = CreateSurface(FrameSinkId(0, 1), inputs);
  Surface* surface2 = CreateSurface(FrameSinkId(0, 2), inputs);
  DrawSurfaces({surface1, surface2}, kNow);

  ExpectResult(TakeLastResult(), FrameIntervalClass::kBoost);
  EXPECT_EQ(FrameIntervalMatcherType::kInputBoost, TakeLastMatcherType());
  FrameIntervalMatcher::Inputs inputs_from_matcher =
      matchers_[0]->TakeLastMatcherInputs();
  EXPECT_EQ(kNow, inputs_from_matcher.aggregated_frame_time);
  EXPECT_EQ(settings_.increase_frame_interval_timeout,
            inputs_from_matcher.settings->increase_frame_interval_timeout);
  EXPECT_EQ(2u, inputs_from_matcher.inputs_map.size());
  for (Surface* surface : std::vector<Surface*>{surface1, surface2}) {
    auto itr = inputs_from_matcher.inputs_map.find(
        surface->surface_id().frame_sink_id());
    ASSERT_NE(itr, inputs_from_matcher.inputs_map.end());

    EXPECT_EQ(surface->surface_id().frame_sink_id(), itr->first);
    EXPECT_EQ(kNow, itr->second.frame_time);
  }
  EXPECT_EQ(kNow, frame_.metadata.frame_interval_inputs.frame_time);

  EXPECT_FALSE(matchers_[1]->has_last_matcher_inputs());
}

TEST_F(FrameIntervalDeciderTest, NoMatch) {
  InitializeDecider();
  FrameIntervalInputs inputs;
  inputs.frame_time = kNow;
  Surface* surface = CreateSurface(FrameSinkId(0, 1), inputs);
  DrawSurfaces({surface}, kNow);

  // Check that matchers are consulted
  for (auto matcher : matchers_) {
    EXPECT_TRUE(matcher->has_last_matcher_inputs());
  }

  // Expect return kDefault if nothing matched.
  ExpectResult(TakeLastResult(), FrameIntervalClass::kDefault);
}

TEST_F(FrameIntervalDeciderTest, NoMatchFixedIntervals) {
  FixedIntervalSettings fixed_interval_settings;
  fixed_interval_settings.supported_intervals.insert(base::Milliseconds(8));
  fixed_interval_settings.supported_intervals.insert(base::Milliseconds(16));
  fixed_interval_settings.default_interval = base::Milliseconds(0);
  settings_.interval_settings = fixed_interval_settings;

  InitializeDecider();
  FrameIntervalInputs inputs;
  inputs.frame_time = kNow;
  Surface* surface = CreateSurface(FrameSinkId(0, 1), inputs);
  DrawSurfaces({surface}, kNow);

  EXPECT_TRUE(matchers_[1]->has_last_matcher_inputs());
  ExpectResult(TakeLastResult(), base::Milliseconds(0));
}

TEST_F(FrameIntervalDeciderTest, NoMatchContinuousRange) {
  ContinuousRangeSettings continuous_range_settings;
  continuous_range_settings.min_interval = base::Milliseconds(8);
  continuous_range_settings.max_interval = base::Milliseconds(16);
  continuous_range_settings.default_interval = base::Milliseconds(0);
  settings_.interval_settings = continuous_range_settings;

  InitializeDecider();
  FrameIntervalInputs inputs;
  inputs.frame_time = kNow;
  Surface* surface = CreateSurface(FrameSinkId(0, 1), inputs);
  DrawSurfaces({surface}, kNow);

  EXPECT_TRUE(matchers_[1]->has_last_matcher_inputs());
  ExpectResult(TakeLastResult(), base::Milliseconds(0));
}

TEST_F(FrameIntervalDeciderTest, FirstMatch) {
  InitializeDecider();

  matchers_[1]->SetResult(ResultInterval(base::Milliseconds(32)));

  FrameIntervalInputs inputs;
  inputs.frame_time = kNow;
  DrawSurfaces({CreateSurface(FrameSinkId(0, 1), inputs)}, kNow);

  ExpectResult(TakeLastResult(), base::Milliseconds(32));
  EXPECT_EQ(FrameIntervalMatcherType::kOnlyVideo, TakeLastMatcherType());
  EXPECT_TRUE(matchers_[0]->has_last_matcher_inputs());
  EXPECT_TRUE(matchers_[1]->has_last_matcher_inputs());
}

TEST_F(FrameIntervalDeciderTest, NoChange) {
  InitializeDecider();

  matchers_[0]->SetResult(ResultInterval(base::Milliseconds(32)));

  FrameIntervalInputs inputs;
  inputs.frame_time = kNow;
  Surface* surface = CreateSurface(FrameSinkId(0, 1), inputs);
  DrawSurfaces({surface}, kNow);

  ExpectResult(TakeLastResult(), base::Milliseconds(32));
  EXPECT_EQ(FrameIntervalMatcherType::kInputBoost, TakeLastMatcherType());

  base::TimeTicks now2 = kNow + base::Milliseconds(16);
  inputs.frame_time = now2;
  UpdateFrame(surface, inputs);
  DrawSurfaces({surface}, now2);

  EXPECT_FALSE(has_result());
  EXPECT_FALSE(has_matcher_type());
}

TEST_F(FrameIntervalDeciderTest, IncreaseIntervalDelayFrameInterval) {
  settings_.increase_frame_interval_timeout = base::Milliseconds(60);
  InitializeDecider();

  base::TimeTicks now = kNow;
  matchers_[0]->SetResult(ResultInterval(base::Milliseconds(32)));

  FrameIntervalInputs inputs;
  inputs.frame_time = now;
  Surface* surface = CreateSurface(FrameSinkId(0, 1), inputs);
  DrawSurfaces({surface}, now);
  ExpectResult(TakeLastResult(), base::Milliseconds(32));
  EXPECT_EQ(FrameIntervalMatcherType::kInputBoost, TakeLastMatcherType());

  now = kNow + base::Milliseconds(16);
  matchers_[0]->SetResult(ResultInterval(base::Milliseconds(16)));
  DrawSurfaces({surface}, now);
  ExpectResult(TakeLastResult(), base::Milliseconds(16));
  EXPECT_EQ(FrameIntervalMatcherType::kInputBoost, TakeLastMatcherType());

  now = kNow + base::Milliseconds(32);
  matchers_[0]->SetResult(ResultInterval(base::Milliseconds(32)));
  DrawSurfaces({surface}, now);
  EXPECT_FALSE(has_result());
  EXPECT_FALSE(has_matcher_type());

  now = kNow + base::Milliseconds(96);
  DrawSurfaces({surface}, now);
  ExpectResult(TakeLastResult(), base::Milliseconds(32));
  EXPECT_EQ(FrameIntervalMatcherType::kInputBoost, TakeLastMatcherType());
}

TEST_F(FrameIntervalDeciderTest, IncreaseIntervalDelayIntervalClass) {
  settings_.increase_frame_interval_timeout = base::Milliseconds(60);
  InitializeDecider();

  base::TimeTicks now = kNow;
  matchers_[0]->SetResult(FrameIntervalClass::kDefault);

  FrameIntervalInputs inputs;
  inputs.frame_time = now;
  Surface* surface = CreateSurface(FrameSinkId(0, 1), inputs);
  DrawSurfaces({surface}, now);
  ExpectResult(TakeLastResult(), FrameIntervalClass::kDefault);
  EXPECT_EQ(FrameIntervalMatcherType::kInputBoost, TakeLastMatcherType());

  now = kNow + base::Milliseconds(16);
  matchers_[0]->SetResult(FrameIntervalClass::kBoost);
  DrawSurfaces({surface}, now);
  ExpectResult(TakeLastResult(), FrameIntervalClass::kBoost);
  EXPECT_EQ(FrameIntervalMatcherType::kInputBoost, TakeLastMatcherType());

  now = kNow + base::Milliseconds(32);
  matchers_[0]->SetResult(FrameIntervalClass::kDefault);
  DrawSurfaces({surface}, now);
  EXPECT_FALSE(has_result());
  EXPECT_FALSE(has_matcher_type());

  now = kNow + base::Milliseconds(96);
  DrawSurfaces({surface}, now);
  ExpectResult(TakeLastResult(), FrameIntervalClass::kDefault);
  EXPECT_EQ(FrameIntervalMatcherType::kInputBoost, TakeLastMatcherType());
}

TEST_F(FrameIntervalDeciderTest, IncreaseIntervalDelayVariantSwitch) {
  settings_.increase_frame_interval_timeout = base::Milliseconds(60);
  InitializeDecider();

  base::TimeTicks now = kNow;
  matchers_[0]->SetResult(FrameIntervalClass::kBoost);

  FrameIntervalInputs inputs;
  inputs.frame_time = now;
  Surface* surface = CreateSurface(FrameSinkId(0, 1), inputs);
  DrawSurfaces({surface}, now);
  ExpectResult(TakeLastResult(), FrameIntervalClass::kBoost);
  EXPECT_EQ(FrameIntervalMatcherType::kInputBoost, TakeLastMatcherType());

  now = kNow + base::Milliseconds(16);
  matchers_[0]->SetResult(ResultInterval(base::Milliseconds(16)));
  DrawSurfaces({surface}, now);
  ExpectResult(TakeLastResult(), base::Milliseconds(16));
  EXPECT_EQ(FrameIntervalMatcherType::kInputBoost, TakeLastMatcherType());

  now = kNow + base::Milliseconds(32);
  matchers_[0]->SetResult(FrameIntervalClass::kDefault);
  DrawSurfaces({surface}, now);
  ExpectResult(TakeLastResult(), FrameIntervalClass::kDefault);
  EXPECT_EQ(FrameIntervalMatcherType::kInputBoost, TakeLastMatcherType());

  now = kNow + base::Milliseconds(48);
  matchers_[0]->SetResult(ResultInterval(base::Milliseconds(32)));
  DrawSurfaces({surface}, now);
  ExpectResult(TakeLastResult(), base::Milliseconds(32));
  EXPECT_EQ(FrameIntervalMatcherType::kInputBoost, TakeLastMatcherType());
}

}  // namespace
}  // namespace viz
