// Copyright 2014 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/bitmap_fetcher/bitmap_fetcher_service.h"

#include <stddef.h>

#include "build/build_config.h"
#include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/browser_task_environment.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"

namespace {

class TestService : public BitmapFetcherService {
 public:
  explicit TestService(content::BrowserContext* context)
      : BitmapFetcherService(context) {}
  ~TestService() override = default;

  // Create a fetcher, but don't start downloading. That allows side-stepping
  // the decode step, which requires a utility process.
  std::unique_ptr<BitmapFetcher> CreateFetcher(
      const GURL& url,
      const net::NetworkTrafficAnnotationTag& traffic_annotation) override {
    return std::make_unique<BitmapFetcher>(url, this, traffic_annotation);
  }
};

}  // namespace

class BitmapFetcherServiceTest : public testing::Test {
 public:
  BitmapFetcherServiceTest()
      : url1_(GURL("http://example.org/sample-image-1.png")),
        url2_(GURL("http://example.org/sample-image-2.png")) {
  }

  void SetUp() override {
    service_ = std::make_unique<TestService>(&profile_);
    images_changed_count_ = 0;
  }

  const std::vector<std::unique_ptr<BitmapFetcherRequest>>& requests() const {
    return service_->requests_;
  }
  const std::vector<std::unique_ptr<BitmapFetcher>>& active_fetchers() const {
    return service_->active_fetchers_;
  }
  size_t cache_size() const { return service_->cache_.size(); }

  void OnBitmapFetched(const SkBitmap bitmap) { images_changed_count_++; }

  BitmapFetcherService::RequestId RequestImage(const GURL& url) {
    return service_->RequestImageForTesting(
        url,
        base::BindOnce(&BitmapFetcherServiceTest::OnBitmapFetched,
                       base::Unretained(this)),
        TRAFFIC_ANNOTATION_FOR_TESTS);
  }

  // Simulate finishing a URL fetch and decode for the given fetcher.
  void CompleteFetch(const GURL& url) {
    BitmapFetcher* fetcher =
        const_cast<BitmapFetcher*>(service_->FindFetcherForUrl(url));
    ASSERT_TRUE(fetcher);

    // Create a non-empty bitmap.
    SkBitmap image;
    image.allocN32Pixels(2, 2);
    image.eraseColor(SK_ColorGREEN);

    fetcher->OnImageDecoded(image);
  }

  void FailFetch(const GURL& url) {
    BitmapFetcher* fetcher =
        const_cast<BitmapFetcher*>(service_->FindFetcherForUrl(url));
    ASSERT_TRUE(fetcher);
    fetcher->OnImageDecoded(SkBitmap());
  }

  // A failed decode results in a nullptr image.
  void FailDecode(const GURL& url) {
    const BitmapFetcher* fetcher = service_->FindFetcherForUrl(url);
    ASSERT_TRUE(fetcher);
    const_cast<BitmapFetcher*>(fetcher)->OnDecodeImageFailed();
  }

  BitmapFetcherService* service() { return service_.get(); }

  int images_changed_count() const { return images_changed_count_; }

  const GURL url1_;
  const GURL url2_;

 private:
  // |task_environment_| must outlive |service_|.
  content::BrowserTaskEnvironment task_environment_;
  TestingProfile profile_;

  std::unique_ptr<BitmapFetcherService> service_;

  int images_changed_count_;
};

TEST_F(BitmapFetcherServiceTest, RequestInvalidUrl) {
  const BitmapFetcherService::RequestId invalid_request_id =
      BitmapFetcherService::REQUEST_ID_INVALID;
  GURL invalid_url;
  ASSERT_FALSE(invalid_url.is_valid());

  BitmapFetcherService::RequestId request_id = RequestImage(invalid_url);
  EXPECT_EQ(invalid_request_id, request_id);
}

TEST_F(BitmapFetcherServiceTest, CancelInvalidRequest) {
  service()->CancelRequest(BitmapFetcherService::REQUEST_ID_INVALID);
  service()->CancelRequest(23);
}

TEST_F(BitmapFetcherServiceTest, OnlyFirstRequestCreatesFetcher) {
  EXPECT_EQ(0U, active_fetchers().size());

  RequestImage(url1_);
  EXPECT_EQ(1U, active_fetchers().size());

  RequestImage(url1_);
  EXPECT_EQ(1U, active_fetchers().size());
}

TEST_F(BitmapFetcherServiceTest, CompletedFetchNotifiesAllObservers) {
  RequestImage(url1_);
  RequestImage(url1_);
  RequestImage(url1_);
  RequestImage(url1_);

  EXPECT_EQ(1U, active_fetchers().size());
  EXPECT_EQ(4U, requests().size());

  CompleteFetch(url1_);
  EXPECT_EQ(4, images_changed_count());
}

TEST_F(BitmapFetcherServiceTest, CancelRequest) {
  RequestImage(url1_);
  RequestImage(url1_);
  BitmapFetcherService::RequestId requestId = RequestImage(url2_);
  RequestImage(url1_);
  RequestImage(url1_);

  service()->CancelRequest(requestId);
  EXPECT_EQ(4U, requests().size());

  CompleteFetch(url2_);
  EXPECT_EQ(0, images_changed_count());

  CompleteFetch(url1_);
  EXPECT_EQ(4, images_changed_count());
}

TEST_F(BitmapFetcherServiceTest, CacheRequest) {
  RequestImage(url1_);
  CompleteFetch(url1_);

  // No caching on Android.
#if BUILDFLAG(IS_ANDROID)
  EXPECT_EQ(0U, cache_size());
#else
  EXPECT_EQ(1U, cache_size());
#endif
}

#if !BUILDFLAG(IS_ANDROID)
TEST_F(BitmapFetcherServiceTest, FailedNullRequestsAreHandled) {
  RequestImage(url1_);
  RequestImage(url2_);

  CompleteFetch(url1_);
  EXPECT_EQ(1U, cache_size());

  FailDecode(url2_);
  EXPECT_EQ(1U, cache_size());
}

TEST_F(BitmapFetcherServiceTest, FailedRequestsDontEnterCache) {
  RequestImage(url1_);
  RequestImage(url2_);

  EXPECT_EQ(0U, cache_size());

  CompleteFetch(url1_);
  EXPECT_EQ(1U, cache_size());

  FailFetch(url2_);
  EXPECT_EQ(1U, cache_size());
}
#endif  // !BUILDFLAG(IS_ANDROID)
