// 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 <map>

#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/icon_coalescer.h"
#include "components/services/app_service/public/cpp/icon_types.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia_rep.h"

class AppsIconCoalescerTest : public testing::Test {
 protected:
  using UniqueReleaser = std::unique_ptr<apps::IconLoader::Releaser>;

  class FakeIconLoader : public apps::IconLoader {
   public:
    int NumLoadIconFromIconKeyCalls() { return num_load_calls_; }

    int NumLoadIconFromIconKeyCallsComplete() {
      return num_load_calls_ - pending_callbacks_.size();
    }

    int NumLoadIconFromIconKeyCallsPending() {
      return pending_callbacks_.size();
    }

    int NumPendingReleases() { return num_pending_releases_; }

    void SetCallBackImmediately(bool b) { call_back_immediately_ = b; }

    void CallBack(const std::string& app_id) {
      auto iter = pending_callbacks_.find(app_id);
      if (iter != pending_callbacks_.end()) {
        std::move(iter->second).Run(NewIconValuePtr());
        pending_callbacks_.erase(iter);
      } else {
        NOTREACHED() << "No pending callback for app_id=" << app_id;
      }
    }

   private:
    std::unique_ptr<Releaser> LoadIconFromIconKey(
        const std::string& id,
        const apps::IconKey& icon_key,
        apps::IconType icon_type,
        int32_t size_hint_in_dip,
        bool allow_placeholder_icon,
        apps::LoadIconCallback callback) override {
      num_load_calls_++;
      if (call_back_immediately_) {
        num_load_calls_complete_++;
        std::move(callback).Run(NewIconValuePtr());
      } else {
        pending_callbacks_.insert(std::make_pair(id, std::move(callback)));
      }
      num_pending_releases_++;
      return std::make_unique<IconLoader::Releaser>(
          nullptr, base::BindOnce(&FakeIconLoader::OnRelease,
                                  weak_ptr_factory_.GetWeakPtr()));
    }

    apps::IconValuePtr NewIconValuePtr() {
      auto iv = std::make_unique<apps::IconValue>();
      iv->icon_type = apps::IconType::kUncompressed;
      iv->uncompressed =
          gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(1, 1), 1.0f));
      iv->is_placeholder_icon = false;
      return iv;
    }

    void OnRelease() { num_pending_releases_--; }

    bool call_back_immediately_ = false;
    int num_load_calls_ = 0;
    int num_load_calls_complete_ = 0;
    int num_pending_releases_ = 0;
    std::multimap<std::string, apps::LoadIconCallback> pending_callbacks_;

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

  UniqueReleaser LoadIcon(apps::IconLoader* loader,
                          const std::string& app_id,
                          int* counter,
                          int delta) {
    return loader->LoadIcon(
        app_id, apps::IconType::kUncompressed,
        /*size_hint_in_dip=*/1, /*allow_placeholder_icon=*/false,
        base::BindOnce([](int* counter, int delta,
                          apps::IconValuePtr icon) { *counter += delta; },
                       counter, delta));
  }
};

TEST_F(AppsIconCoalescerTest, CallBackImmediately) {
  FakeIconLoader fake;
  fake.SetCallBackImmediately(true);
  apps::IconCoalescer coalescer(&fake);
  int counter = 0;

  UniqueReleaser releaser = LoadIcon(&coalescer, "the_app_id", &counter, 1000);

  EXPECT_EQ(1, fake.NumLoadIconFromIconKeyCalls());
  EXPECT_EQ(1, fake.NumLoadIconFromIconKeyCallsComplete());
  EXPECT_EQ(0, fake.NumLoadIconFromIconKeyCallsPending());
  EXPECT_EQ(1000, counter);
  EXPECT_EQ(1, fake.NumPendingReleases());

  releaser.reset();

  EXPECT_EQ(0, fake.NumPendingReleases());
}

TEST_F(AppsIconCoalescerTest, CallBackDelayedAndAfterRelease) {
  FakeIconLoader fake;
  apps::IconCoalescer coalescer(&fake);
  int counter = 0;

  UniqueReleaser releaser = LoadIcon(&coalescer, "the_app_id", &counter, 1000);

  EXPECT_EQ(1, fake.NumLoadIconFromIconKeyCalls());
  EXPECT_EQ(0, fake.NumLoadIconFromIconKeyCallsComplete());
  EXPECT_EQ(1, fake.NumLoadIconFromIconKeyCallsPending());
  EXPECT_EQ(0, counter);
  EXPECT_EQ(1, fake.NumPendingReleases());

  fake.CallBack("the_app_id");

  EXPECT_EQ(1, fake.NumLoadIconFromIconKeyCalls());
  EXPECT_EQ(1, fake.NumLoadIconFromIconKeyCallsComplete());
  EXPECT_EQ(0, fake.NumLoadIconFromIconKeyCallsPending());
  EXPECT_EQ(1000, counter);
  EXPECT_EQ(1, fake.NumPendingReleases());

  releaser.reset();

  EXPECT_EQ(0, fake.NumPendingReleases());
}

TEST_F(AppsIconCoalescerTest, CallBackDelayedAndBeforeRelease) {
  FakeIconLoader fake;
  apps::IconCoalescer coalescer(&fake);
  int counter = 0;

  UniqueReleaser releaser = LoadIcon(&coalescer, "the_app_id", &counter, 1000);

  EXPECT_EQ(1, fake.NumLoadIconFromIconKeyCalls());
  EXPECT_EQ(0, fake.NumLoadIconFromIconKeyCallsComplete());
  EXPECT_EQ(1, fake.NumLoadIconFromIconKeyCallsPending());
  EXPECT_EQ(0, counter);
  EXPECT_EQ(1, fake.NumPendingReleases());

  // Even though we release our claim on the outer IconLoader::Releaser (from
  // the IconCoalescer), the inner IconLoader::Releaser (from the
  // FakeIconLoader) isn't released while the callback's still pending.
  releaser.reset();

  EXPECT_EQ(1, fake.NumPendingReleases());

  fake.CallBack("the_app_id");

  EXPECT_EQ(1, fake.NumLoadIconFromIconKeyCalls());
  EXPECT_EQ(1, fake.NumLoadIconFromIconKeyCallsComplete());
  EXPECT_EQ(0, fake.NumLoadIconFromIconKeyCallsPending());
  EXPECT_EQ(1000, counter);
  EXPECT_EQ(0, fake.NumPendingReleases());
}

TEST_F(AppsIconCoalescerTest, MultipleAppIDs) {
  FakeIconLoader fake;
  apps::IconCoalescer coalescer(&fake);
  int ant_counter = 0;
  int bat_counter = 0;
  int cat_counter = 0;
  int dog_counter = 0;
  int emu_counter = 0;

  UniqueReleaser a1 = LoadIcon(&coalescer, "ant", &ant_counter, 10);
  UniqueReleaser b1 = LoadIcon(&coalescer, "bat", &bat_counter, 100);
  UniqueReleaser c1 = LoadIcon(&coalescer, "cat", &cat_counter, 1000);

  EXPECT_EQ(3, fake.NumLoadIconFromIconKeyCalls());
  EXPECT_EQ(0, fake.NumLoadIconFromIconKeyCallsComplete());
  EXPECT_EQ(3, fake.NumLoadIconFromIconKeyCallsPending());
  EXPECT_EQ(0, ant_counter);
  EXPECT_EQ(0, bat_counter);
  EXPECT_EQ(0, cat_counter);
  EXPECT_EQ(0, dog_counter);
  EXPECT_EQ(0, emu_counter);

  UniqueReleaser c2 = LoadIcon(&coalescer, "cat", &cat_counter, 2000);
  UniqueReleaser d1 = LoadIcon(&coalescer, "dog", &dog_counter, 10000);
  UniqueReleaser c4 = LoadIcon(&coalescer, "cat", &cat_counter, 4000);
  UniqueReleaser b2 = LoadIcon(&coalescer, "bat", &bat_counter, 200);

  EXPECT_EQ(4, fake.NumLoadIconFromIconKeyCalls());
  EXPECT_EQ(0, fake.NumLoadIconFromIconKeyCallsComplete());
  EXPECT_EQ(4, fake.NumLoadIconFromIconKeyCallsPending());
  EXPECT_EQ(0, ant_counter);
  EXPECT_EQ(0, bat_counter);
  EXPECT_EQ(0, cat_counter);
  EXPECT_EQ(0, dog_counter);
  EXPECT_EQ(0, emu_counter);

  fake.CallBack("ant");
  fake.CallBack("cat");

  EXPECT_EQ(4, fake.NumLoadIconFromIconKeyCalls());
  EXPECT_EQ(2, fake.NumLoadIconFromIconKeyCallsComplete());
  EXPECT_EQ(2, fake.NumLoadIconFromIconKeyCallsPending());
  EXPECT_EQ(10, ant_counter);
  EXPECT_EQ(0, bat_counter);
  EXPECT_EQ(7000, cat_counter);
  EXPECT_EQ(0, dog_counter);
  EXPECT_EQ(0, emu_counter);

  UniqueReleaser a4 = LoadIcon(&coalescer, "ant", &ant_counter, 40);
  UniqueReleaser b4 = LoadIcon(&coalescer, "bat", &bat_counter, 400);
  UniqueReleaser a2 = LoadIcon(&coalescer, "ant", &ant_counter, 20);

  EXPECT_EQ(5, fake.NumLoadIconFromIconKeyCalls());
  EXPECT_EQ(2, fake.NumLoadIconFromIconKeyCallsComplete());
  EXPECT_EQ(3, fake.NumLoadIconFromIconKeyCallsPending());
  EXPECT_EQ(10, ant_counter);
  EXPECT_EQ(0, bat_counter);
  EXPECT_EQ(7000, cat_counter);
  EXPECT_EQ(0, dog_counter);
  EXPECT_EQ(0, emu_counter);

  // 5 NumLoadIconFromIconKeyCalls, without any releases, means 5
  // NumPendingReleases: {a1}, {a2, a4}, {b*}, {c*} and {d*}. The "a"s are two
  // different groups, as they are separated by a `fake.Callback("ant")` line.
  EXPECT_EQ(5, fake.NumPendingReleases());
  fake.CallBack("ant");

  // We treat the "b"s differently, releasing them (resetting the
  // UniqueReleaser, aka unique_ptr<IconLoader::Releaser>) *before* (not
  // *after) we tickle fake.CallBack. Still, the inner-most releaser isn't let
  // go until both (1) all outer releasers are dropped and (2) the inner
  // IconLoader has actually called back.
  EXPECT_EQ(5, fake.NumPendingReleases());
  b1.reset();
  b2.reset();
  b4.reset();
  EXPECT_EQ(5, fake.NumPendingReleases());
  fake.CallBack("bat");
  EXPECT_EQ(4, fake.NumPendingReleases());

  EXPECT_EQ(5, fake.NumLoadIconFromIconKeyCalls());
  EXPECT_EQ(4, fake.NumLoadIconFromIconKeyCallsComplete());
  EXPECT_EQ(1, fake.NumLoadIconFromIconKeyCallsPending());
  EXPECT_EQ(70, ant_counter);
  EXPECT_EQ(700, bat_counter);
  EXPECT_EQ(7000, cat_counter);
  EXPECT_EQ(0, dog_counter);
  EXPECT_EQ(0, emu_counter);

  // Even though we configure the fake to call back immediately, the next two
  // "dog" calls still wait for the previous (pending) "dog" call. The next
  // three "emu" calls lead to three (immediate) calls on the fake.
  fake.SetCallBackImmediately(true);
  EXPECT_EQ(4, fake.NumPendingReleases());
  UniqueReleaser d2 = LoadIcon(&coalescer, "dog", &dog_counter, 20000);
  UniqueReleaser d4 = LoadIcon(&coalescer, "dog", &dog_counter, 40000);
  EXPECT_EQ(4, fake.NumPendingReleases());
  UniqueReleaser e1 = LoadIcon(&coalescer, "emu", &emu_counter, 100000);
  UniqueReleaser e2 = LoadIcon(&coalescer, "emu", &emu_counter, 200000);
  UniqueReleaser e4 = LoadIcon(&coalescer, "emu", &emu_counter, 400000);
  EXPECT_EQ(7, fake.NumPendingReleases());

  EXPECT_EQ(8, fake.NumLoadIconFromIconKeyCalls());
  EXPECT_EQ(7, fake.NumLoadIconFromIconKeyCallsComplete());
  EXPECT_EQ(1, fake.NumLoadIconFromIconKeyCallsPending());
  EXPECT_EQ(70, ant_counter);
  EXPECT_EQ(700, bat_counter);
  EXPECT_EQ(7000, cat_counter);
  EXPECT_EQ(0, dog_counter);
  EXPECT_EQ(700000, emu_counter);

  fake.CallBack("dog");

  EXPECT_EQ(8, fake.NumLoadIconFromIconKeyCalls());
  EXPECT_EQ(8, fake.NumLoadIconFromIconKeyCallsComplete());
  EXPECT_EQ(0, fake.NumLoadIconFromIconKeyCallsPending());
  EXPECT_EQ(70, ant_counter);
  EXPECT_EQ(700, bat_counter);
  EXPECT_EQ(7000, cat_counter);
  EXPECT_EQ(70000, dog_counter);
  EXPECT_EQ(700000, emu_counter);

  // As mentioned above, {a1} and {a2, a4} are different groups.
  EXPECT_EQ(7, fake.NumPendingReleases());
  a1.reset();
  EXPECT_EQ(6, fake.NumPendingReleases());
  a2.reset();
  EXPECT_EQ(6, fake.NumPendingReleases());
  a4.reset();
  EXPECT_EQ(5, fake.NumPendingReleases());

  // {c*} and {d*} are each one group, but {e1}, {e2} and {e4} are three
  // separate groups.
  EXPECT_EQ(5, fake.NumPendingReleases());
  c1.reset();
  EXPECT_EQ(5, fake.NumPendingReleases());
  c2.reset();
  EXPECT_EQ(5, fake.NumPendingReleases());
  c4.reset();
  EXPECT_EQ(4, fake.NumPendingReleases());
  d1.reset();
  EXPECT_EQ(4, fake.NumPendingReleases());
  d2.reset();
  EXPECT_EQ(4, fake.NumPendingReleases());
  d4.reset();
  EXPECT_EQ(3, fake.NumPendingReleases());
  e1.reset();
  EXPECT_EQ(2, fake.NumPendingReleases());
  e2.reset();
  EXPECT_EQ(1, fake.NumPendingReleases());
  e4.reset();
  EXPECT_EQ(0, fake.NumPendingReleases());
}
