// Copyright 2023 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/thumbnail/cc/jpeg_thumbnail_helper.h"

#include <cstring>
#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/task_environment.h"
#include "chrome/browser/thumbnail/cc/thumbnail.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkScalar.h"
#include "third_party/skia/include/effects/SkGradient.h"
#include "ui/gfx/codec/jpeg_codec.h"

namespace thumbnail {
namespace {

constexpr int kDimension = 16;
constexpr int kKiB = 1024;

SkPaint SetupPaint() {
  SkColor4f colors[] = {SkColors::kRed, SkColors::kGreen, SkColors::kBlue};
  SkScalar pos[] = {0, SK_Scalar1 / 2, SK_Scalar1};
  SkPaint paint;
  paint.setShader(SkShaders::SweepGradient({256, 256}, {{colors, pos, SkTileMode::kClamp}, {}}));
  return paint;
}

}  // anonymous namespace

class JpegThumbnailHelperTest : public ::testing::Test {
 protected:
  JpegThumbnailHelperTest()
      : task_environment_(
            base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED) {}

  void SetUp() override {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    interface_ = std::make_unique<thumbnail::JpegThumbnailHelper>(
        temp_dir_.GetPath(),
        base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}));
  }

  void TearDown() override {}

  thumbnail::JpegThumbnailHelper& GetInterface() { return *interface_; }
  base::FilePath GetFile(int tab_id) {
    return interface_->GetJpegFilePath(tab_id);
  }

  base::test::TaskEnvironment task_environment_;

 private:
  std::unique_ptr<thumbnail::JpegThumbnailHelper> interface_;
  base::ScopedTempDir temp_dir_;
};

TEST_F(JpegThumbnailHelperTest, CompressThumbnail) {
  // Create a bitmap
  SkBitmap image;
  ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension));
  SkCanvas canvas(image);
  canvas.drawPaint(SetupPaint());
  image.setImmutable();

  // Compress the bitmap
  base::RunLoop loop1;
  base::OnceCallback<void(std::vector<uint8_t>)> once =
      base::BindOnce([](std::vector<uint8_t> jpeg_data) {
        EXPECT_FALSE(jpeg_data.empty());
        SkBitmap bitmap = gfx::JPEGCodec::Decode(jpeg_data);
        EXPECT_FALSE(bitmap.isNull());
        EXPECT_GT(bitmap.width(), 0);
        EXPECT_GT(bitmap.height(), 0);
      }).Then(loop1.QuitClosure());

  GetInterface().Compress(image, std::move(once));
  task_environment_.RunUntilIdle();
  loop1.Run();
}

TEST_F(JpegThumbnailHelperTest, WriteThumbnail) {
  int tab_id = 0;

  // Create a bitmap.
  SkBitmap image;
  ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension));
  SkCanvas canvas(image);
  canvas.drawPaint(SetupPaint());
  image.setImmutable();

  constexpr int kCompressionQuality = 97;
  std::optional<std::vector<uint8_t>> data =
      gfx::JPEGCodec::Encode(image, kCompressionQuality);

  // Write the image.
  base::RunLoop loop1;
  GetInterface().Write(tab_id, data.value(),
                       base::BindOnce(
                           [](base::OnceClosure quit, bool success) {
                             EXPECT_TRUE(success);
                             std::move(quit).Run();
                           },
                           loop1.QuitClosure()));
  task_environment_.RunUntilIdle();
  loop1.Run();

  base::FilePath file_path = GetFile(tab_id);
  EXPECT_TRUE(base::PathExists(file_path));

  // Compare original data with written data.
  std::optional<std::vector<uint8_t>> read_data =
      base::ReadFileToBytes(file_path);
  EXPECT_EQ(data, read_data);
}

TEST_F(JpegThumbnailHelperTest, ReadThumbnail) {
  int tab_id = 0;

  // Create a bitmap
  SkBitmap image;
  ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension));
  SkCanvas canvas(image);
  canvas.drawPaint(SetupPaint());
  image.setImmutable();

  constexpr int kCompressionQuality = 97;
  std::optional<std::vector<uint8_t>> data =
      gfx::JPEGCodec::Encode(image, kCompressionQuality);

  // Write the image
  base::FilePath file_path = GetFile(tab_id);
  base::File file(file_path,
                  base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
  ASSERT_TRUE(file.WriteAndCheck(0, base::as_byte_span(data.value())));

  // Read the image
  base::RunLoop loop1;
  base::OnceCallback<void(std::optional<std::vector<uint8_t>>)> once =
      base::BindOnce([](std::optional<std::vector<uint8_t>> compressed_data) {
        ASSERT_TRUE(compressed_data.has_value());
        EXPECT_FALSE(compressed_data->empty());
        SkBitmap bitmap = gfx::JPEGCodec::Decode(compressed_data.value());
        EXPECT_FALSE(bitmap.isNull());
        EXPECT_GT(bitmap.width(), 0);
        EXPECT_GT(bitmap.height(), 0);
      }).Then(loop1.QuitClosure());

  GetInterface().Read(tab_id, std::move(once));
  task_environment_.RunUntilIdle();
  loop1.Run();
}

TEST_F(JpegThumbnailHelperTest, DeleteThumbnail) {
  int tab_id = 0;

  // Create a bitmap
  SkBitmap image;
  ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension));
  SkCanvas canvas(image);
  canvas.drawPaint(SetupPaint());
  image.setImmutable();

  constexpr int kCompressionQuality = 97;
  std::optional<std::vector<uint8_t>> data =
      gfx::JPEGCodec::Encode(image, kCompressionQuality);

  // Write the image
  base::FilePath file_path = GetFile(tab_id);
  base::File file(file_path,
                  base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
  ASSERT_TRUE(file.WriteAndCheck(0, base::as_byte_span(data.value())));

  // Delete the image
  GetInterface().Delete(tab_id);
  task_environment_.RunUntilIdle();

  // Check deletion occurred
  base::FilePath post_delete_file_path = GetFile(tab_id);
  EXPECT_FALSE(base::PathExists(post_delete_file_path));
}

TEST_F(JpegThumbnailHelperTest, DeleteAllExceptForIds) {
  std::vector<int> tab_ids = {1, 2, 3, 4, 5};
  for (int tab_id : tab_ids) {
    base::WriteFile(GetFile(tab_id), "thumbnail_data");
    EXPECT_TRUE(base::PathExists(GetFile(tab_id)));
  }

  // Create a etc1 thumbnail file to make sure it is not deleted.
  base::FilePath etc_file = GetFile(1).RemoveExtension();
  base::WriteFile(etc_file, "thumbnail_data");
  EXPECT_TRUE(base::PathExists(etc_file));

  GetInterface().DeleteAllExceptForIds({2, 4});
  task_environment_.RunUntilIdle();

  EXPECT_FALSE(base::PathExists(GetFile(1)));
  EXPECT_TRUE(base::PathExists(GetFile(2)));
  EXPECT_FALSE(base::PathExists(GetFile(3)));
  EXPECT_TRUE(base::PathExists(GetFile(4)));
  EXPECT_FALSE(base::PathExists(GetFile(5)));

  EXPECT_TRUE(base::PathExists(etc_file));
}

}  // namespace thumbnail
