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

#import "ios/chrome/browser/favicon/model/favicon_loader_impl.h"

#import "base/functional/callback_helpers.h"
#import "components/favicon/core/large_icon_service_impl.h"
#import "components/favicon_base/fallback_icon_style.h"
#import "components/favicon_base/favicon_types.h"
#import "ios/chrome/common/ui/favicon/favicon_attributes.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"
#import "third_party/skia/include/core/SkBitmap.h"
#import "ui/gfx/codec/png_codec.h"
#import "url/gurl.h"

namespace {

// Dummy URL for the favicon case.
const char kTestFaviconURL[] = "http://test/favicon";
// Dummy URL for the fallback case.
const char kTestFallbackURL[] = "http://test/fallback";

// Size of dummy favicon image.
const CGFloat kTestFaviconSize = 57;

// FaviconLoaderImplTest is parameterized on this enum to test both
// FaviconLoaderImpl::FaviconForPageUrl and
// FaviconLoaderImpl::FaviconForIconUrl.
enum FaviconUrlType { TEST_PAGE_URL, TEST_ICON_URL };

// FakeLargeIconService mimics a LargeIconService that returns a LargeIconResult
// with a test favicon image for kTestFaviconURL and a LargeIconResult
// initialized with FallbackIconStyle.
class FakeLargeIconService : public favicon::LargeIconServiceImpl {
 public:
  FakeLargeIconService()
      : favicon::LargeIconServiceImpl(
            /*favicon_service=*/nullptr,
            /*image_fetcher=*/nullptr,
            /*desired_size_in_dip_for_server_requests=*/0,
            /*icon_type_for_server_requests=*/
            favicon_base::IconType::kTouchIcon,
            /*google_server_client_param=*/"test_chrome") {}

  // Returns LargeIconResult with valid bitmap if `page_url` is
  // `kTestFaviconURL`, or LargeIconResult with fallback style.
  base::CancelableTaskTracker::TaskId
  GetLargeIconRawBitmapOrFallbackStyleForPageUrl(
      const GURL& page_url,
      int min_source_size_in_pixel,
      int desired_size_in_pixel,
      favicon_base::LargeIconCallback callback,
      base::CancelableTaskTracker* tracker) override {
    if (page_url.spec() == kTestFaviconURL) {
      favicon_base::FaviconRawBitmapResult bitmapResult;
      bitmapResult.expired = false;

      // Create bitmap.
      SkBitmap bitmap;
      bitmap.allocN32Pixels(30, 30);
      std::optional<std::vector<uint8_t>> data =
          gfx::PNGCodec::EncodeBGRASkBitmap(bitmap,
                                            /*discard_transparency=*/false);
      bitmapResult.bitmap_data =
          base::MakeRefCounted<base::RefCountedBytes>(std::move(data.value()));

      favicon_base::LargeIconResult result(bitmapResult);
      std::move(callback).Run(result);
    } else {
      auto fallback = std::make_unique<favicon_base::FallbackIconStyle>();
      favicon_base::LargeIconResult result(std::move(fallback));
      fallback = NULL;
      std::move(callback).Run(result);
    }

    return 1;
  }

  // Returns the same as `GetLargeIconRawBitmapOrFallbackStyleForPageUrl`.
  base::CancelableTaskTracker::TaskId
  GetLargeIconRawBitmapOrFallbackStyleForIconUrl(
      const GURL& icon_url,
      int min_source_size_in_pixel,
      int desired_size_in_pixel,
      favicon_base::LargeIconCallback callback,
      base::CancelableTaskTracker* tracker) override {
    return GetLargeIconRawBitmapOrFallbackStyleForPageUrl(
        icon_url, min_source_size_in_pixel, desired_size_in_pixel,
        std::move(callback), tracker);
  }
};

class FaviconLoaderImplTest
    : public PlatformTest,
      public ::testing::WithParamInterface<FaviconUrlType> {
 public:
  // Callback passed to FaviconForUrl(...).
  using Callback = base::RepeatingCallback<void(FaviconAttributes*, bool)>;

  FaviconLoaderImplTest() : favicon_loader_(&large_icon_service_) {}

  FaviconLoaderImplTest(const FaviconLoaderImplTest&) = delete;
  FaviconLoaderImplTest& operator=(const FaviconLoaderImplTest&) = delete;

  // Invokes FaviconForPageUrl(...) or FaviconForIconUrl(...) depending on
  // the TEST_P param. Since FakeLargeIconService is synchronous, the method
  // itself is also synchronous.
  void FaviconForUrl(const GURL& url, const Callback& callback) {
    if (GetParam() == TEST_PAGE_URL) {
      favicon_loader_.FaviconForPageUrl(url, kTestFaviconSize, kTestFaviconSize,
                                        /*fallback_to_google_server=*/false,
                                        base::CallbackToBlock(callback));
    } else {
      favicon_loader_.FaviconForIconUrl(url, kTestFaviconSize, kTestFaviconSize,
                                        base::CallbackToBlock(callback));
    }
  }

 private:
  FakeLargeIconService large_icon_service_;
  FaviconLoaderImpl favicon_loader_;
};

// Tests that image is returned when a favicon is retrieved from
// LargeIconService.
TEST_P(FaviconLoaderImplTest, FaviconForPageUrl) {
  int call_count = 0;
  FaviconForUrl(GURL(kTestFaviconURL),
                base::BindRepeating(
                    [](int& counter, FaviconAttributes* attrs, bool cached) {
                      ++counter;
                      if (counter == 1) {
                        EXPECT_TRUE(cached);
                        EXPECT_FALSE(attrs.faviconImage);
                      } else {
                        EXPECT_FALSE(cached);
                        EXPECT_TRUE(attrs.faviconImage);
                      }
                    },
                    std::ref(call_count)));
  EXPECT_GE(call_count, 1);
}

// Tests that fallback data is provided when no favicon is retrieved from
// LargeIconService.
TEST_P(FaviconLoaderImplTest, FallbackIcon) {
  int call_count = 0;
  FaviconForUrl(GURL(kTestFallbackURL),
                base::BindRepeating(
                    [](int& counter, FaviconAttributes* attrs, bool cached) {
                      ++counter;
                      if (counter == 1) {
                        // Check that a monogram is received.
                        EXPECT_FALSE(attrs.faviconImage);
                        EXPECT_TRUE(attrs.monogramString);
                        EXPECT_TRUE(attrs.textColor);
                        EXPECT_TRUE(attrs.backgroundColor);
                        EXPECT_TRUE(cached);
                      } else if (counter == 2) {
                        // Check that the monogram is used as a fallback.
                        EXPECT_FALSE(attrs.faviconImage);
                        EXPECT_TRUE(attrs.monogramString);
                        EXPECT_TRUE(attrs.textColor);
                        EXPECT_TRUE(attrs.backgroundColor);
                        EXPECT_FALSE(cached);
                      }
                    },
                    std::ref(call_count)));
  EXPECT_EQ(call_count, 2);
}

// Tests that when favicon is in cache, the callback is synchronously called.
TEST_P(FaviconLoaderImplTest, Cache) {
  // Favicon retrieval that should put it in the cache.
  {
    int call_count = 0;
    FaviconForUrl(GURL(kTestFaviconURL),
                  base::BindRepeating([](int& counter, FaviconAttributes* attrs,
                                         bool cached) { ++counter; },
                                      std::ref(call_count)));
    ASSERT_EQ(call_count, 2);
  }

  // The callback should be immediately executed.
  int call_count = 0;
  FaviconForUrl(GURL(kTestFaviconURL),
                base::BindRepeating(
                    [](int& counter, FaviconAttributes* attrs, bool cached) {
                      ++counter;
                      EXPECT_TRUE(cached);
                      EXPECT_TRUE(attrs.faviconImage);
                    },
                    std::ref(call_count)));
  EXPECT_EQ(call_count, 1);
}

INSTANTIATE_TEST_SUITE_P(ProgrammaticFaviconLoaderImplTest,
                         FaviconLoaderImplTest,
                         ::testing::Values(FaviconUrlType::TEST_PAGE_URL,
                                           FaviconUrlType::TEST_ICON_URL));

}  // namespace
