/*
 *  Copyright (c) 2020 The WebRTC 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 "call/adaptation/resource_adaptation_processor.h"

#include "api/adaptation/resource.h"
#include "api/scoped_refptr.h"
#include "api/video/video_adaptation_counters.h"
#include "call/adaptation/resource_adaptation_processor_interface.h"
#include "call/adaptation/test/fake_frame_rate_provider.h"
#include "call/adaptation/test/fake_resource.h"
#include "call/adaptation/video_source_restrictions.h"
#include "call/adaptation/video_stream_input_state_provider.h"
#include "rtc_base/event.h"
#include "rtc_base/gunit.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/task_queue_for_test.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"

namespace webrtc {

namespace {

const int kDefaultFrameRate = 30;
const int kDefaultFrameSize = 1280 * 720;
constexpr TimeDelta kDefaultTimeout = TimeDelta::Seconds(5);

class VideoSourceRestrictionsListenerForTesting
    : public VideoSourceRestrictionsListener {
 public:
  VideoSourceRestrictionsListenerForTesting()
      : restrictions_updated_count_(0),
        restrictions_(),
        adaptation_counters_(),
        reason_(nullptr) {}
  ~VideoSourceRestrictionsListenerForTesting() override {}

  size_t restrictions_updated_count() const {
    RTC_DCHECK_RUN_ON(&sequence_checker_);
    return restrictions_updated_count_;
  }
  VideoSourceRestrictions restrictions() const {
    RTC_DCHECK_RUN_ON(&sequence_checker_);
    return restrictions_;
  }
  VideoAdaptationCounters adaptation_counters() const {
    RTC_DCHECK_RUN_ON(&sequence_checker_);
    return adaptation_counters_;
  }
  rtc::scoped_refptr<Resource> reason() const {
    RTC_DCHECK_RUN_ON(&sequence_checker_);
    return reason_;
  }

  // VideoSourceRestrictionsListener implementation.
  void OnVideoSourceRestrictionsUpdated(
      VideoSourceRestrictions restrictions,
      const VideoAdaptationCounters& adaptation_counters,
      rtc::scoped_refptr<Resource> reason,
      const VideoSourceRestrictions& unfiltered_restrictions) override {
    RTC_DCHECK_RUN_ON(&sequence_checker_);
    ++restrictions_updated_count_;
    restrictions_ = restrictions;
    adaptation_counters_ = adaptation_counters;
    reason_ = reason;
  }

 private:
  SequenceChecker sequence_checker_;
  size_t restrictions_updated_count_ RTC_GUARDED_BY(&sequence_checker_);
  VideoSourceRestrictions restrictions_ RTC_GUARDED_BY(&sequence_checker_);
  VideoAdaptationCounters adaptation_counters_
      RTC_GUARDED_BY(&sequence_checker_);
  rtc::scoped_refptr<Resource> reason_ RTC_GUARDED_BY(&sequence_checker_);
};

class ResourceAdaptationProcessorTest : public ::testing::Test {
 public:
  ResourceAdaptationProcessorTest()
      : frame_rate_provider_(),
        input_state_provider_(&frame_rate_provider_),
        resource_(FakeResource::Create("FakeResource")),
        other_resource_(FakeResource::Create("OtherFakeResource")),
        video_stream_adapter_(
            std::make_unique<VideoStreamAdapter>(&input_state_provider_,
                                                 &frame_rate_provider_,
                                                 field_trials_)),
        processor_(std::make_unique<ResourceAdaptationProcessor>(
            video_stream_adapter_.get())) {
    video_stream_adapter_->AddRestrictionsListener(&restrictions_listener_);
    processor_->AddResource(resource_);
    processor_->AddResource(other_resource_);
  }
  ~ResourceAdaptationProcessorTest() override {
    if (processor_) {
      DestroyProcessor();
    }
  }

  void SetInputStates(bool has_input, int fps, int frame_size) {
    input_state_provider_.OnHasInputChanged(has_input);
    frame_rate_provider_.set_fps(fps);
    input_state_provider_.OnFrameSizeObserved(frame_size);
  }

  void RestrictSource(VideoSourceRestrictions restrictions) {
    SetInputStates(
        true, restrictions.max_frame_rate().value_or(kDefaultFrameRate),
        restrictions.target_pixels_per_frame().has_value()
            ? restrictions.target_pixels_per_frame().value()
            : restrictions.max_pixels_per_frame().value_or(kDefaultFrameSize));
  }

  void DestroyProcessor() {
    if (resource_) {
      processor_->RemoveResource(resource_);
    }
    if (other_resource_) {
      processor_->RemoveResource(other_resource_);
    }
    video_stream_adapter_->RemoveRestrictionsListener(&restrictions_listener_);
    processor_.reset();
  }

  static void WaitUntilTaskQueueIdle() {
    ASSERT_TRUE(rtc::Thread::Current()->ProcessMessages(0));
  }

 protected:
  rtc::AutoThread main_thread_;
  webrtc::test::ScopedKeyValueConfig field_trials_;
  FakeFrameRateProvider frame_rate_provider_;
  VideoStreamInputStateProvider input_state_provider_;
  rtc::scoped_refptr<FakeResource> resource_;
  rtc::scoped_refptr<FakeResource> other_resource_;
  std::unique_ptr<VideoStreamAdapter> video_stream_adapter_;
  std::unique_ptr<ResourceAdaptationProcessor> processor_;
  VideoSourceRestrictionsListenerForTesting restrictions_listener_;
};

}  // namespace

TEST_F(ResourceAdaptationProcessorTest, DisabledByDefault) {
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  // Adaptation does not happen when disabled.
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());
}

TEST_F(ResourceAdaptationProcessorTest, InsufficientInput) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  // Adaptation does not happen if input is insufficient.
  // When frame size is missing (OnFrameSizeObserved not called yet).
  input_state_provider_.OnHasInputChanged(true);
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());
  // When "has input" is missing.
  SetInputStates(false, kDefaultFrameRate, kDefaultFrameSize);
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());
  // Note: frame rate cannot be missing, if unset it is 0.
}

// These tests verify that restrictions are applied, but not exactly how much
// the source is restricted. This ensures that the VideoStreamAdapter is wired
// up correctly but not exactly how the VideoStreamAdapter generates
// restrictions. For that, see video_stream_adapter_unittest.cc.
TEST_F(ResourceAdaptationProcessorTest,
       OveruseTriggersRestrictingResolutionInMaintainFrameRate) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
  EXPECT_TRUE(
      restrictions_listener_.restrictions().max_pixels_per_frame().has_value());
}

TEST_F(ResourceAdaptationProcessorTest,
       OveruseTriggersRestrictingFrameRateInMaintainResolution) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_RESOLUTION);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
  EXPECT_TRUE(
      restrictions_listener_.restrictions().max_frame_rate().has_value());
}

TEST_F(ResourceAdaptationProcessorTest,
       OveruseTriggersRestrictingFrameRateAndResolutionInBalanced) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::BALANCED);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  // Adapting multiple times eventually resticts both frame rate and
  // resolution. Exactly many times we need to adapt depends on
  // BalancedDegradationSettings, VideoStreamAdapter and default input
  // states. This test requires it to be achieved within 4 adaptations.
  for (size_t i = 0; i < 4; ++i) {
    resource_->SetUsageState(ResourceUsageState::kOveruse);
    EXPECT_EQ(i + 1, restrictions_listener_.restrictions_updated_count());
    RestrictSource(restrictions_listener_.restrictions());
  }
  EXPECT_TRUE(
      restrictions_listener_.restrictions().max_pixels_per_frame().has_value());
  EXPECT_TRUE(
      restrictions_listener_.restrictions().max_frame_rate().has_value());
}

TEST_F(ResourceAdaptationProcessorTest, AwaitingPreviousAdaptation) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
  // If we don't restrict the source then adaptation will not happen again
  // due to "awaiting previous adaptation". This prevents "double-adapt".
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
}

TEST_F(ResourceAdaptationProcessorTest, CannotAdaptUpWhenUnrestricted) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());
}

TEST_F(ResourceAdaptationProcessorTest, UnderuseTakesUsBackToUnrestricted) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
  RestrictSource(restrictions_listener_.restrictions());
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(2u, restrictions_listener_.restrictions_updated_count());
  EXPECT_EQ(VideoSourceRestrictions(), restrictions_listener_.restrictions());
}

TEST_F(ResourceAdaptationProcessorTest,
       ResourcesCanNotAdaptUpIfNeverAdaptedDown) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
  RestrictSource(restrictions_listener_.restrictions());

  // Other resource signals under-use
  other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
}

TEST_F(ResourceAdaptationProcessorTest,
       ResourcesCanNotAdaptUpIfNotAdaptedDownAfterReset) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());

  video_stream_adapter_->ClearRestrictions();
  EXPECT_EQ(0, restrictions_listener_.adaptation_counters().Total());
  other_resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());

  // resource_ did not overuse after we reset the restrictions, so adapt
  // up should be disallowed.
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
}

TEST_F(ResourceAdaptationProcessorTest, OnlyMostLimitedResourceMayAdaptUp) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
  other_resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());

  // `other_resource_` is most limited, resource_ can't adapt up.
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
  other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());

  // `resource_` and `other_resource_` are now most limited, so both must
  // signal underuse to adapt up.
  other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(0, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
}

TEST_F(ResourceAdaptationProcessorTest,
       MultipleResourcesCanTriggerMultipleAdaptations) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
  other_resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
  other_resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(3, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());

  // resource_ is not most limited so can't adapt from underuse.
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(3, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
  other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
  // resource_ is still not most limited so can't adapt from underuse.
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());

  // However it will be after overuse
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(3, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());

  // Now other_resource_ can't adapt up as it is not most restricted.
  other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(3, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());

  // resource_ is limited at 3 adaptations and other_resource_ 2.
  // With the most limited resource signalling underuse in the following
  // order we get back to unrestricted video.
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
  // Both resource_ and other_resource_ are most limited.
  other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
  // Again both are most limited.
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
  other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(0, restrictions_listener_.adaptation_counters().Total());
}

TEST_F(ResourceAdaptationProcessorTest,
       MostLimitedResourceAdaptationWorksAfterChangingDegradataionPreference) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  // Adapt down until we can't anymore.
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  int last_total = restrictions_listener_.adaptation_counters().Total();

  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_RESOLUTION);
  // resource_ can not adapt up since we have never reduced FPS.
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(last_total, restrictions_listener_.adaptation_counters().Total());

  other_resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(last_total + 1,
            restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
  // other_resource_ is most limited so should be able to adapt up.
  other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(last_total, restrictions_listener_.adaptation_counters().Total());
}

TEST_F(ResourceAdaptationProcessorTest,
       AdaptsDownWhenOtherResourceIsAlwaysUnderused) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
  other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
  // Does not trigger adapataion because there's no restriction.
  EXPECT_EQ(0, restrictions_listener_.adaptation_counters().Total());

  RestrictSource(restrictions_listener_.restrictions());
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  // Adapts down even if other resource asked for adapting up.
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());

  RestrictSource(restrictions_listener_.restrictions());
  other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
  // Doesn't adapt up because adaptation is due to another resource.
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());
}

TEST_F(ResourceAdaptationProcessorTest,
       TriggerOveruseNotOnAdaptationTaskQueue) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);

  TaskQueueForTest resource_task_queue("ResourceTaskQueue");
  resource_task_queue.PostTask(
      [&]() { resource_->SetUsageState(ResourceUsageState::kOveruse); });

  EXPECT_EQ_WAIT(1u, restrictions_listener_.restrictions_updated_count(),
                 kDefaultTimeout.ms());
}

TEST_F(ResourceAdaptationProcessorTest,
       DestroyProcessorWhileResourceListenerDelegateHasTaskInFlight) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);

  // Wait for `resource_` to signal oversue first so we know that the delegate
  // has passed it on to the processor's task queue.
  rtc::Event resource_event;
  TaskQueueForTest resource_task_queue("ResourceTaskQueue");
  resource_task_queue.PostTask([&]() {
    resource_->SetUsageState(ResourceUsageState::kOveruse);
    resource_event.Set();
  });

  EXPECT_TRUE(resource_event.Wait(kDefaultTimeout));
  // Now destroy the processor while handling the overuse is in flight.
  DestroyProcessor();

  // Because the processor was destroyed by the time the delegate's task ran,
  // the overuse signal must not have been handled.
  EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());
}

TEST_F(ResourceAdaptationProcessorTest,
       ResourceOveruseIgnoredWhenSignalledDuringRemoval) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);

  rtc::Event overuse_event;
  TaskQueueForTest resource_task_queue("ResourceTaskQueue");
  // Queues task for `resource_` overuse while `processor_` is still listening.
  resource_task_queue.PostTask([&]() {
    resource_->SetUsageState(ResourceUsageState::kOveruse);
    overuse_event.Set();
  });
  EXPECT_TRUE(overuse_event.Wait(kDefaultTimeout));
  // Once we know the overuse task is queued, remove `resource_` so that
  // `processor_` is not listening to it.
  processor_->RemoveResource(resource_);

  // Runs the queued task so `processor_` gets signalled kOveruse from
  // `resource_` even though `processor_` was not listening.
  WaitUntilTaskQueueIdle();

  // No restrictions should change even though `resource_` signaled `kOveruse`.
  EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());

  // Delete `resource_` for cleanup.
  resource_ = nullptr;
}

TEST_F(ResourceAdaptationProcessorTest,
       RemovingOnlyAdaptedResourceResetsAdaptation) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);

  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  RestrictSource(restrictions_listener_.restrictions());

  processor_->RemoveResource(resource_);
  EXPECT_EQ(0, restrictions_listener_.adaptation_counters().Total());

  // Delete `resource_` for cleanup.
  resource_ = nullptr;
}

TEST_F(ResourceAdaptationProcessorTest,
       RemovingMostLimitedResourceSetsAdaptationToNextLimitedLevel) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::BALANCED);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);

  other_resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  VideoSourceRestrictions next_limited_restrictions =
      restrictions_listener_.restrictions();
  VideoAdaptationCounters next_limited_counters =
      restrictions_listener_.adaptation_counters();

  resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());

  // Removing most limited `resource_` should revert us back to
  processor_->RemoveResource(resource_);
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  EXPECT_EQ(next_limited_restrictions, restrictions_listener_.restrictions());
  EXPECT_EQ(next_limited_counters,
            restrictions_listener_.adaptation_counters());

  // Delete `resource_` for cleanup.
  resource_ = nullptr;
}

TEST_F(ResourceAdaptationProcessorTest,
       RemovingMostLimitedResourceSetsAdaptationIfInputStateUnchanged) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);

  other_resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  VideoSourceRestrictions next_limited_restrictions =
      restrictions_listener_.restrictions();
  VideoAdaptationCounters next_limited_counters =
      restrictions_listener_.adaptation_counters();

  // Overuse twice and underuse once. After the underuse we don't restrict the
  // source. Normally this would block future underuses.
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());

  // Removing most limited `resource_` should revert us back to, even though we
  // did not call RestrictSource() after `resource_` was overused. Normally
  // adaptation for MAINTAIN_FRAMERATE would be blocked here but for removal we
  // allow this anyways.
  processor_->RemoveResource(resource_);
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  EXPECT_EQ(next_limited_restrictions, restrictions_listener_.restrictions());
  EXPECT_EQ(next_limited_counters,
            restrictions_listener_.adaptation_counters());

  // Delete `resource_` for cleanup.
  resource_ = nullptr;
}

TEST_F(ResourceAdaptationProcessorTest,
       RemovingResourceNotMostLimitedHasNoEffectOnLimitations) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::BALANCED);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);

  other_resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());

  resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  VideoSourceRestrictions current_restrictions =
      restrictions_listener_.restrictions();
  VideoAdaptationCounters current_counters =
      restrictions_listener_.adaptation_counters();
  EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());

  // Removing most limited `resource_` should revert us back to
  processor_->RemoveResource(other_resource_);
  EXPECT_EQ(current_restrictions, restrictions_listener_.restrictions());
  EXPECT_EQ(current_counters, restrictions_listener_.adaptation_counters());

  // Delete `other_resource_` for cleanup.
  other_resource_ = nullptr;
}

TEST_F(ResourceAdaptationProcessorTest,
       RemovingMostLimitedResourceAfterSwitchingDegradationPreferences) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);

  other_resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  VideoSourceRestrictions next_limited_restrictions =
      restrictions_listener_.restrictions();
  VideoAdaptationCounters next_limited_counters =
      restrictions_listener_.adaptation_counters();

  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_RESOLUTION);
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());

  // Revert to `other_resource_` when removing `resource_` even though the
  // degradation preference was different when it was overused.
  processor_->RemoveResource(resource_);
  EXPECT_EQ(next_limited_counters,
            restrictions_listener_.adaptation_counters());

  // After switching back to MAINTAIN_FRAMERATE, the next most limited settings
  // are restored.
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  EXPECT_EQ(next_limited_restrictions, restrictions_listener_.restrictions());

  // Delete `resource_` for cleanup.
  resource_ = nullptr;
}

TEST_F(ResourceAdaptationProcessorTest,
       RemovingMostLimitedResourceSetsNextLimitationsInDisabled) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);

  other_resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  VideoSourceRestrictions next_limited_restrictions =
      restrictions_listener_.restrictions();
  VideoAdaptationCounters next_limited_counters =
      restrictions_listener_.adaptation_counters();
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());

  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::DISABLED);

  // Revert to `other_resource_` when removing `resource_` even though the
  // current degradataion preference is disabled.
  processor_->RemoveResource(resource_);

  // After switching back to MAINTAIN_FRAMERATE, the next most limited settings
  // are restored.
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  EXPECT_EQ(next_limited_restrictions, restrictions_listener_.restrictions());
  EXPECT_EQ(next_limited_counters,
            restrictions_listener_.adaptation_counters());

  // Delete `resource_` for cleanup.
  resource_ = nullptr;
}

TEST_F(ResourceAdaptationProcessorTest,
       RemovedResourceSignalsIgnoredByProcessor) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);

  processor_->RemoveResource(resource_);
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());

  // Delete `resource_` for cleanup.
  resource_ = nullptr;
}

TEST_F(ResourceAdaptationProcessorTest,
       RemovingResourceWhenMultipleMostLimtedHasNoEffect) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);

  other_resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
  // Adapt `resource_` up and then down so that both resource's are most
  // limited at 1 adaptation.
  resource_->SetUsageState(ResourceUsageState::kOveruse);
  RestrictSource(restrictions_listener_.restrictions());
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  RestrictSource(restrictions_listener_.restrictions());
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());

  // Removing `resource_` has no effect since both `resource_` and
  // `other_resource_` are most limited.
  processor_->RemoveResource(resource_);
  EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());

  // Delete `resource_` for cleanup.
  resource_ = nullptr;
}

TEST_F(ResourceAdaptationProcessorTest,
       ResourceOverusedAtLimitReachedWillShareMostLimited) {
  video_stream_adapter_->SetDegradationPreference(
      DegradationPreference::MAINTAIN_FRAMERATE);
  SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);

  bool has_reached_min_pixels = false;
  ON_CALL(frame_rate_provider_, OnMinPixelLimitReached())
      .WillByDefault(testing::Assign(&has_reached_min_pixels, true));

  // Adapt 10 times, which should make us hit the limit.
  for (int i = 0; i < 10; ++i) {
    resource_->SetUsageState(ResourceUsageState::kOveruse);
    RestrictSource(restrictions_listener_.restrictions());
  }
  EXPECT_TRUE(has_reached_min_pixels);
  auto last_update_count = restrictions_listener_.restrictions_updated_count();
  other_resource_->SetUsageState(ResourceUsageState::kOveruse);
  // Now both `resource_` and `other_resource_` are most limited. Underuse of
  // `resource_` will not adapt up.
  resource_->SetUsageState(ResourceUsageState::kUnderuse);
  EXPECT_EQ(last_update_count,
            restrictions_listener_.restrictions_updated_count());
}

}  // namespace webrtc
