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

#include "components/wallpaper/wallpaper_color_calculator.h"

#include <memory>

#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/test/histogram_tester.h"
#include "base/test/null_task_runner.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/wallpaper/wallpaper_color_calculator_observer.h"
#include "components/wallpaper/wallpaper_color_extraction_result.h"
#include "skia/ext/platform_canvas.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/SkColor.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_analysis.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"

using ::testing::ElementsAre;
using ::testing::IsEmpty;

namespace wallpaper {
namespace {

const SkColor kDefaultColor = SK_ColorTRANSPARENT;

const SkColor kGray = SkColorSetRGB(10, 10, 10);

const SkColor kVibrantGreen = SkColorSetRGB(25, 200, 25);

// Image size that causes the WallpaperColorCalculator to synchronously extract
// the prominent color.
constexpr gfx::Size kSyncImageSize = gfx::Size(5, 5);

// Image size that causes the WallpaperColorCalculator to asynchronously extract
// the prominent color.
constexpr gfx::Size kAsyncImageSize = gfx::Size(50, 50);

class TestWallpaperColorCalculatorObserver
    : public WallpaperColorCalculatorObserver {
 public:
  TestWallpaperColorCalculatorObserver() {}

  ~TestWallpaperColorCalculatorObserver() override {}

  bool WasNotified() const { return notified_; }

  // WallpaperColorCalculatorObserver:
  void OnColorCalculationComplete() override { notified_ = true; }

 private:
  bool notified_ = false;

  DISALLOW_COPY_AND_ASSIGN(TestWallpaperColorCalculatorObserver);
};

// Returns an image that will yield a color using the LumaRange::NORMAL and
// SaturationRange::VIBRANT values.
gfx::ImageSkia CreateColorProducingImage(const gfx::Size& size) {
  gfx::Canvas canvas(size, 1.0f, true);
  canvas.DrawColor(kGray);
  canvas.FillRect(gfx::Rect(0, 1, size.height(), 1), kVibrantGreen);
  return gfx::ImageSkia::CreateFrom1xBitmap(canvas.GetBitmap());
}

// Returns an image that will not yield a color using the LumaRange::NORMAL and
// SaturationRange::VIBRANT values.
gfx::ImageSkia CreateNonColorProducingImage(const gfx::Size& size) {
  gfx::Canvas canvas(size, 1.0f, true);
  canvas.DrawColor(kGray);
  return gfx::ImageSkia::CreateFrom1xBitmap(canvas.GetBitmap());
}

}  // namespace

class WallPaperColorCalculatorTest : public testing::Test {
 public:
  WallPaperColorCalculatorTest();
  ~WallPaperColorCalculatorTest() override;

 protected:
  // Installs the given |task_runner| globally and on the |calculator_| instance
  // if it exists.
  void InstallTaskRunner(
      scoped_refptr<base::SingleThreadTaskRunner> task_runner);

  // Creates a new |calculator_| for the given |image| and installs the current
  // |task_runner_|.
  void CreateCalculator(const gfx::ImageSkia& image);

  std::unique_ptr<WallpaperColorCalculator> calculator_;

  // Required for asynchronous calculations.
  scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;

  TestWallpaperColorCalculatorObserver observer_;

  base::HistogramTester histograms_;

 private:
  // Required for asynchronous calculations, e.g. by PostTaskAndReplyImpl.
  std::unique_ptr<base::ThreadTaskRunnerHandle> task_runner_handle_;

  DISALLOW_COPY_AND_ASSIGN(WallPaperColorCalculatorTest);
};

WallPaperColorCalculatorTest::WallPaperColorCalculatorTest()
    : task_runner_(new base::TestMockTimeTaskRunner()) {
  CreateCalculator(CreateColorProducingImage(kAsyncImageSize));
  InstallTaskRunner(task_runner_);
}

WallPaperColorCalculatorTest::~WallPaperColorCalculatorTest() {}

void WallPaperColorCalculatorTest::InstallTaskRunner(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
  task_runner_handle_.reset();
  task_runner_handle_ =
      base::MakeUnique<base::ThreadTaskRunnerHandle>(task_runner);
  if (calculator_)
    calculator_->SetTaskRunnerForTest(task_runner);
}

void WallPaperColorCalculatorTest::CreateCalculator(
    const gfx::ImageSkia& image) {
  std::vector<color_utils::ColorProfile> color_profiles;
  color_profiles.emplace_back(color_utils::LumaRange::NORMAL,
                              color_utils::SaturationRange::VIBRANT);
  calculator_ = base::MakeUnique<WallpaperColorCalculator>(
      image, color_profiles, task_runner_);
  calculator_->AddObserver(&observer_);
}

// Used to group the asynchronous calculation tests.
using WallPaperColorCalculatorAsyncTest = WallPaperColorCalculatorTest;

TEST_F(WallPaperColorCalculatorAsyncTest, MetricsForSuccessfulExtraction) {
  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.Durations", 0);
  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.UserDelay", 0);
  EXPECT_THAT(histograms_.GetAllSamples("Ash.Wallpaper.ColorExtractionResult2"),
              IsEmpty());

  EXPECT_TRUE(calculator_->StartCalculation());
  task_runner_->RunUntilIdle();

  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.Durations", 1);
  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.UserDelay", 1);
  EXPECT_THAT(histograms_.GetAllSamples("Ash.Wallpaper.ColorExtractionResult2"),
              ElementsAre(base::Bucket(RESULT_NORMAL_VIBRANT_OPAQUE, 1)));
}

TEST_F(WallPaperColorCalculatorAsyncTest, MetricsWhenPostingTaskFails) {
  scoped_refptr<base::NullTaskRunner> task_runner = new base::NullTaskRunner();
  InstallTaskRunner(task_runner);

  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.Durations", 0);
  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.UserDelay", 0);
  EXPECT_THAT(histograms_.GetAllSamples("Ash.Wallpaper.ColorExtractionResult2"),
              IsEmpty());

  EXPECT_FALSE(calculator_->StartCalculation());
  task_runner_->RunUntilIdle();

  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.Durations", 0);
  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.UserDelay", 0);
  EXPECT_THAT(histograms_.GetAllSamples("Ash.Wallpaper.ColorExtractionResult2"),
              IsEmpty());

  EXPECT_EQ(kDefaultColor, calculator_->prominent_colors()[0]);
}

TEST_F(WallPaperColorCalculatorAsyncTest,
       ObserverNotifiedOnSuccessfulCalculation) {
  EXPECT_FALSE(observer_.WasNotified());

  EXPECT_TRUE(calculator_->StartCalculation());
  EXPECT_FALSE(observer_.WasNotified());

  task_runner_->RunUntilIdle();
  EXPECT_TRUE(observer_.WasNotified());
}

TEST_F(WallPaperColorCalculatorAsyncTest, ColorUpdatedOnSuccessfulCalculation) {
  std::vector<SkColor> colors = {kDefaultColor};
  calculator_->set_prominent_colors_for_test(colors);

  EXPECT_TRUE(calculator_->StartCalculation());
  EXPECT_EQ(kDefaultColor, calculator_->prominent_colors()[0]);

  task_runner_->RunUntilIdle();
  EXPECT_NE(kDefaultColor, calculator_->prominent_colors()[0]);
}

TEST_F(WallPaperColorCalculatorAsyncTest,
       NoCrashWhenCalculatorDestroyedBeforeTaskProcessing) {
  EXPECT_TRUE(calculator_->StartCalculation());
  calculator_.reset();

  EXPECT_TRUE(task_runner_->HasPendingTask());

  task_runner_->RunUntilIdle();
  EXPECT_FALSE(observer_.WasNotified());
  EXPECT_FALSE(task_runner_->HasPendingTask());
}

// Used to group the synchronous calculation tests.
using WallPaperColorCalculatorSyncTest = WallPaperColorCalculatorTest;

TEST_F(WallPaperColorCalculatorSyncTest, MetricsForSuccessfulExtraction) {
  CreateCalculator(CreateColorProducingImage(kSyncImageSize));
  calculator_->SetTaskRunnerForTest(nullptr);

  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.Durations", 0);
  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.UserDelay", 0);
  EXPECT_THAT(histograms_.GetAllSamples("Ash.Wallpaper.ColorExtractionResult2"),
              IsEmpty());

  EXPECT_TRUE(calculator_->StartCalculation());

  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.Durations", 1);
  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.UserDelay", 0);
  EXPECT_THAT(histograms_.GetAllSamples("Ash.Wallpaper.ColorExtractionResult2"),
              ElementsAre(base::Bucket(RESULT_NORMAL_VIBRANT_OPAQUE, 1)));
}

TEST_F(WallPaperColorCalculatorSyncTest, MetricsForFailedExctraction) {
  CreateCalculator(CreateNonColorProducingImage(kSyncImageSize));

  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.Durations", 0);
  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.UserDelay", 0);
  EXPECT_THAT(histograms_.GetAllSamples("Ash.Wallpaper.ColorExtractionResult2"),
              IsEmpty());

  EXPECT_TRUE(calculator_->StartCalculation());

  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.Durations", 1);
  histograms_.ExpectTotalCount("Ash.Wallpaper.ColorExtraction.UserDelay", 0);
  EXPECT_THAT(histograms_.GetAllSamples("Ash.Wallpaper.ColorExtractionResult2"),
              ElementsAre(base::Bucket(RESULT_NORMAL_VIBRANT_TRANSPARENT, 1)));
}

}  // namespace wallpaper
