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

#include "chrome/browser/ui/thumbnails/thumbnail_scheduler_impl.h"

#include <vector>

#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

class FakeTabCapturer : public ThumbnailScheduler::TabCapturer {
 public:
  FakeTabCapturer() = default;
  ~FakeTabCapturer() override = default;

  bool capture_permitted() const { return capture_permitted_; }

  // ThumbnailScheduler::TabCapturer:
  void SetCapturePermittedByScheduler(bool capture_permitted) override {
    capture_permitted_ = capture_permitted;
  }

 private:
  bool capture_permitted_ = false;
};

}  // namespace

using ::testing::_;
using ::testing::ElementsAre;

class ThumbnailSchedulerImplTest : public ::testing::Test {
 public:
  ThumbnailSchedulerImplTest() : scheduler_(2, 1), tabs_(4) {}
  ~ThumbnailSchedulerImplTest() override = default;

  void SetUp() override { AddTabsToScheduler(); }

  void TearDown() override { RemoveTabsFromScheduler(); }

 protected:
  void AddTabsToScheduler() {
    for (FakeTabCapturer& tab : tabs_) {
      scheduler_.AddTab(&tab);
    }
  }

  void RemoveTabsFromScheduler() {
    for (FakeTabCapturer& tab : tabs_) {
      scheduler_.RemoveTab(&tab);
    }
  }

  std::vector<bool> TabScheduledStates() {
    std::vector<bool> states;
    for (const auto& tab : tabs_) {
      states.push_back(tab.capture_permitted());
    }
    return states;
  }

  ThumbnailSchedulerImpl scheduler_;
  std::vector<FakeTabCapturer> tabs_;
};

TEST_F(ThumbnailSchedulerImplTest, TabsNotScheduledIfCaptureNotNeeded) {
  // By default, tabs' priorities are kNone. No captures should be
  // scheduled.
  EXPECT_THAT(TabScheduledStates(), ElementsAre(false, false, false, false));
}

TEST_F(ThumbnailSchedulerImplTest, HighPriorityCapturesScheduledUpToMax) {
  scheduler_.SetTabCapturePriority(
      &tabs_[0], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, false, false, false));

  scheduler_.SetTabCapturePriority(
      &tabs_[1], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, true, false, false));

  // A new capture of the same priority shouldn't preempt an existing
  // one. Otherwise there may be too much churn: a tab almost done
  // capturing might be descheduled, only to restart capture later.
  scheduler_.SetTabCapturePriority(
      &tabs_[2], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, true, false, false));
}

TEST_F(ThumbnailSchedulerImplTest, LowPriorityCapturesScheduledUpToMax) {
  scheduler_.SetTabCapturePriority(
      &tabs_[0], ThumbnailSchedulerImpl::TabCapturePriority::kLow);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, false, false, false));

  // Similar to the above test, don't preempt.
  scheduler_.SetTabCapturePriority(
      &tabs_[1], ThumbnailSchedulerImpl::TabCapturePriority::kLow);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, false, false, false));
}

TEST_F(ThumbnailSchedulerImplTest, HighPriorityCapturesPreemptLowPriority) {
  scheduler_.SetTabCapturePriority(
      &tabs_[0], ThumbnailSchedulerImpl::TabCapturePriority::kLow);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, false, false, false));

  scheduler_.SetTabCapturePriority(
      &tabs_[1], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, true, false, false));

  // There should never be a low priority capture running while a high
  // priority capture is descheduled. When the high priority tabs
  // exceeds the max total captures (2) minus the max low priority
  // captures (1), the high priority tabs take precedence.
  scheduler_.SetTabCapturePriority(
      &tabs_[2], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(false, true, true, false));
}

TEST_F(ThumbnailSchedulerImplTest, NewTabScheduledWhenCaptureFinished) {
  scheduler_.SetTabCapturePriority(
      &tabs_[0], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  scheduler_.SetTabCapturePriority(
      &tabs_[1], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  scheduler_.SetTabCapturePriority(
      &tabs_[2], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  scheduler_.SetTabCapturePriority(
      &tabs_[3], ThumbnailSchedulerImpl::TabCapturePriority::kLow);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, true, false, false));

  scheduler_.SetTabCapturePriority(
      &tabs_[1], ThumbnailSchedulerImpl::TabCapturePriority::kNone);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, false, true, false));

  scheduler_.SetTabCapturePriority(
      &tabs_[0], ThumbnailSchedulerImpl::TabCapturePriority::kNone);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(false, false, true, true));
}

TEST_F(ThumbnailSchedulerImplTest, PreemptsOnPriorityDowngrade) {
  scheduler_.SetTabCapturePriority(
      &tabs_[0], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  scheduler_.SetTabCapturePriority(
      &tabs_[1], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  scheduler_.SetTabCapturePriority(
      &tabs_[2], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, true, false, false));

  scheduler_.SetTabCapturePriority(
      &tabs_[1], ThumbnailSchedulerImpl::TabCapturePriority::kLow);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, false, true, false));
}

TEST_F(ThumbnailSchedulerImplTest, PreemptsOnPriorityUpgrade) {
  scheduler_.SetTabCapturePriority(
      &tabs_[0], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  scheduler_.SetTabCapturePriority(
      &tabs_[1], ThumbnailSchedulerImpl::TabCapturePriority::kLow);
  scheduler_.SetTabCapturePriority(
      &tabs_[2], ThumbnailSchedulerImpl::TabCapturePriority::kLow);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, true, false, false));

  scheduler_.SetTabCapturePriority(
      &tabs_[2], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, false, true, false));
}

TEST_F(ThumbnailSchedulerImplTest,
       LowPriorityCaptureContinuesOnPriorityUpgrade) {
  scheduler_.SetTabCapturePriority(
      &tabs_[0], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  scheduler_.SetTabCapturePriority(
      &tabs_[1], ThumbnailSchedulerImpl::TabCapturePriority::kLow);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, true, false, false));

  scheduler_.SetTabCapturePriority(
      &tabs_[1], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, true, false, false));
}

TEST_F(ThumbnailSchedulerImplTest, CaptureStopsOnPriorityNone) {
  scheduler_.SetTabCapturePriority(
      &tabs_[0], ThumbnailSchedulerImpl::TabCapturePriority::kHigh);
  scheduler_.SetTabCapturePriority(
      &tabs_[1], ThumbnailSchedulerImpl::TabCapturePriority::kLow);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(true, true, false, false));

  scheduler_.SetTabCapturePriority(
      &tabs_[0], ThumbnailSchedulerImpl::TabCapturePriority::kNone);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(false, true, false, false));

  scheduler_.SetTabCapturePriority(
      &tabs_[1], ThumbnailSchedulerImpl::TabCapturePriority::kNone);
  EXPECT_THAT(TabScheduledStates(), ElementsAre(false, false, false, false));
}
