// 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 "ui/views/animation/compositor_animation_runner.h"

#include "base/test/bind.h"
#include "base/timer/timer.h"
#include "ui/compositor/compositor_metrics_tracker.h"
#include "ui/compositor/test/draw_waiter_for_test.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/views/animation/animation_delegate_views.h"
#include "ui/views/buildflags.h"
#include "ui/views/test/widget_test.h"

namespace views::test {
namespace {
constexpr base::TimeDelta kDuration = base::Milliseconds(100);
}

using CompositorAnimationRunnerTest = WidgetTest;

TEST_F(CompositorAnimationRunnerTest, BasicCoverageTest) {
  WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
  widget->Show();

  AnimationDelegateViews delegate(widget->GetContentsView());
  gfx::LinearAnimation animation(
      kDuration, gfx::LinearAnimation::kDefaultFrameRate, &delegate);

  base::RepeatingTimer interval_timer;
  base::RunLoop run_loop;

  animation.Start();
  EXPECT_TRUE(animation.is_animating());
  EXPECT_TRUE(delegate.container()->has_custom_animation_runner());

  interval_timer.Start(FROM_HERE, kDuration, base::BindLambdaForTesting([&]() {
                         if (animation.is_animating()) {
                           return;
                         }

                         interval_timer.Stop();
                         run_loop.Quit();
                       }));

  run_loop.Run();

  // Verifies that AnimationDelegateViews carries location of call sites
  // instead of implementation.
  EXPECT_STREQ(base::Location::Current().file_name(),
               delegate.location_for_test().file_name());
}

namespace {

// Test AnimationDelegateView which has a non-zero expected animation duration
// time, which is required for getting smoothness reports.
class TestAnimationDelegateViews : public AnimationDelegateViews {
 public:
  explicit TestAnimationDelegateViews(View* view)
      : AnimationDelegateViews(view) {}
  TestAnimationDelegateViews(TestAnimationDelegateViews&) = delete;
  TestAnimationDelegateViews& operator=(TestAnimationDelegateViews&) = delete;
  ~TestAnimationDelegateViews() override = default;

  // AnimationDelegateViews:
  base::TimeDelta GetAnimationDurationForReporting() const override {
    return kDuration;
  }
};

}  // namespace

#if BUILDFLAG(IS_CHROMEOS)
// Tests that ui::CompositorMetricsTracker will report for gfx::Animation. Only
// supported on ChromeOS.
// TODO(crbug.com/470035500): Re-enable the tests after flakiness is fixed.
TEST_F(CompositorAnimationRunnerTest, DISABLED_ThroughputTracker) {
  WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
  widget->Show();

  ui::DrawWaiterForTest::WaitForCompositingStarted(widget->GetCompositor());

  int report_count = 0;
  int report_count2 = 0;

  TestAnimationDelegateViews delegate(widget->GetContentsView());

  gfx::LinearAnimation animation(
      kDuration, gfx::LinearAnimation::kDefaultFrameRate, &delegate);

  base::RepeatingTimer interval_timer;
  base::RunLoop run_loop;

  ui::CompositorMetricsTracker tracker1 =
      widget->GetCompositor()->RequestNewCompositorMetricsTracker();
  tracker1.Start(base::BindLambdaForTesting(
      [&](const cc::FrameSequenceMetrics::CustomReportData& data) {
        ++report_count;
        run_loop.Quit();
      }));

  animation.Start();
  EXPECT_TRUE(animation.is_animating());
  EXPECT_TRUE(delegate.container()->has_custom_animation_runner());

  interval_timer.Start(FROM_HERE, kDuration, base::BindLambdaForTesting([&]() {
                         if (animation.is_animating()) {
                           return;
                         }

                         interval_timer.Stop();
                         tracker1.Stop();
                       }));
  run_loop.Run();
  EXPECT_EQ(1, report_count);
  EXPECT_EQ(0, report_count2);

  // Tests that switching metrics reporters for the next animation works as
  // expected.
  base::RunLoop run_loop2;

  ui::CompositorMetricsTracker tracker2 =
      widget->GetCompositor()->RequestNewCompositorMetricsTracker();
  tracker2.Start(base::BindLambdaForTesting(
      [&](const cc::FrameSequenceMetrics::CustomReportData& data) {
        ++report_count2;
        run_loop2.Quit();
      }));

  animation.Start();
  EXPECT_TRUE(animation.is_animating());

  interval_timer.Start(FROM_HERE, kDuration, base::BindLambdaForTesting([&]() {
                         if (animation.is_animating()) {
                           return;
                         }

                         interval_timer.Stop();
                         tracker2.Stop();
                       }));
  run_loop2.Run();
  EXPECT_EQ(1, report_count);
  EXPECT_EQ(1, report_count2);
}
#endif  // BUILDFLAG(IS_CHROMEOS)

// No DesktopAura on ChromeOS.
// Each widget on MACOSX has its own ui::Compositor.
#if BUILDFLAG(ENABLE_DESKTOP_AURA)
using CompositorAnimationRunnerDesktopTest = DesktopWidgetTest;

TEST_F(CompositorAnimationRunnerDesktopTest, SwitchCompositor) {
  WidgetAutoclosePtr widget1(CreateTopLevelNativeWidget());
  widget1->Show();

  WidgetAutoclosePtr widget2(CreateTopLevelNativeWidget());
  widget2->Show();

  ASSERT_NE(widget1->GetCompositor(), widget2->GetCompositor());

  Widget* child = CreateChildNativeWidgetWithParent(widget1.get());
  child->Show();
  AnimationDelegateViews delegate(child->GetContentsView());
  gfx::LinearAnimation animation(
      kDuration, gfx::LinearAnimation::kDefaultFrameRate, &delegate);

  base::RepeatingTimer interval_timer;

  animation.Start();
  EXPECT_TRUE(animation.is_animating());
  EXPECT_TRUE(delegate.container()->has_custom_animation_runner());
  {
    base::RunLoop run_loop;
    interval_timer.Start(FROM_HERE, kDuration,
                         base::BindLambdaForTesting([&]() {
                           if (animation.is_animating()) {
                             return;
                           }
                           interval_timer.Stop();
                           run_loop.Quit();
                         }));
    run_loop.Run();
  }

  EXPECT_FALSE(animation.is_animating());

  Widget::ReparentNativeView(child->GetNativeView(), widget2->GetNativeView());
  widget1.reset();

  animation.Start();
  EXPECT_TRUE(animation.is_animating());
  EXPECT_TRUE(delegate.container()->has_custom_animation_runner());

  {
    base::RunLoop run_loop;
    interval_timer.Start(FROM_HERE, kDuration,
                         base::BindLambdaForTesting([&]() {
                           if (animation.is_animating()) {
                             return;
                           }

                           interval_timer.Stop();
                           run_loop.Quit();
                         }));

    run_loop.Run();
  }
}
#endif

}  // namespace views::test
