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

#include "ui/color/color_provider_manager.h"

#include <array>
#include <optional>
#include <vector>

#include "base/functional/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/color/color_mixer.h"
#include "ui/color/color_mixers.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_key.h"
#include "ui/color/color_recipe.h"
#include "ui/color/color_test_ids.h"
#include "ui/gfx/color_palette.h"

namespace ui {

namespace {

class ColorProviderManagerTest : public testing::Test {
 public:
  ColorProviderManagerTest() { ColorProviderManager::ResetForTesting(); }
  ColorProviderManagerTest(const ColorProviderManagerTest&) = delete;
  ColorProviderManagerTest& operator=(const ColorProviderManagerTest&) = delete;
  ~ColorProviderManagerTest() override {
    ColorProviderManager::ResetForTesting();
  }
};

ColorProvider* GetLightNormalColorProvider() {
  return ColorProviderManager::GetForTesting().GetColorProviderFor(
      ui::ColorProviderKey());
}

class TestInitializerSupplier : public ColorProviderKey::InitializerSupplier {
  void AddColorMixers(ColorProvider* provider,
                      const ColorProviderKey& key) const override {}
};

}  // namespace

// Verifies that color providers endure for each call to GetColorProviderFor().
TEST_F(ColorProviderManagerTest, Persistence) {
  base::HistogramTester histogram_tester;
  ColorProvider* provider = GetLightNormalColorProvider();
  ASSERT_NE(nullptr, provider);
  EXPECT_EQ(provider, GetLightNormalColorProvider());
  histogram_tester.ExpectTotalCount(
      "Views.Browser.TimeSpentInitializingColorProvider", 1);
}

// Verifies that the initializer is called for each newly created color
// provider.
TEST_F(ColorProviderManagerTest, SetInitializer) {
  ColorProviderManager::GetForTesting().AppendColorProviderInitializer(
      base::BindRepeating([](ColorProvider* provider, const ColorProviderKey&) {
        provider->AddMixer()[kColorTest0] = {SK_ColorBLUE};
      }));

  base::HistogramTester histogram_tester;
  ColorProvider* provider = GetLightNormalColorProvider();
  ASSERT_NE(nullptr, provider);
  EXPECT_EQ(SK_ColorBLUE, provider->GetColor(kColorTest0));
  histogram_tester.ExpectTotalCount(
      "Views.Browser.TimeSpentInitializingColorProvider", 1);
}

// Verifies resetting the manager clears the provider. This is useful to keep
// unit tests isolated from each other.
TEST_F(ColorProviderManagerTest, Reset) {
  ColorProviderManager::GetForTesting().AppendColorProviderInitializer(
      base::BindRepeating([](ColorProvider* provider, const ColorProviderKey&) {
        provider->AddMixer()[kColorTest0] = {SK_ColorBLUE};
      }));

  base::HistogramTester histogram_tester;
  ColorProvider* provider = GetLightNormalColorProvider();
  ASSERT_NE(nullptr, provider);
  EXPECT_EQ(SK_ColorBLUE, provider->GetColor(kColorTest0));
  ColorProviderManager::ResetForTesting();
  EXPECT_EQ(gfx::kPlaceholderColor,
            GetLightNormalColorProvider()->GetColor(kColorTest0));
  histogram_tester.ExpectTotalCount(
      "Views.Browser.TimeSpentInitializingColorProvider", 2);
}

TEST_F(ColorProviderManagerTest, LookupWithDeletedMember) {
  ColorProviderManager& manager = ColorProviderManager::GetForTesting();
  ColorProviderKey key;

  {
    TestInitializerSupplier supplier;
    key.app_controller = &supplier;

    EXPECT_TRUE(manager.GetColorProviderFor(key));
    key.app_controller = &supplier;
  }

  // key.app_controller is now invalid but shouldn't be dereferenced so the key
  // is still safe to use.
  EXPECT_TRUE(manager.GetColorProviderFor(key));
}

TEST_F(ColorProviderManagerTest, KeyOrderIsStable) {
  std::array<ColorProviderKey, 2> keys;

  // Allocate two suppliers.
  std::vector<TestInitializerSupplier> supplier(2);
  keys[0].app_controller = &supplier[0];
  keys[1].app_controller = &supplier[1];

  // Validate order.
  ASSERT_LT(keys[0], keys[1]);

  // Delete the higher of the two suppliers.
  supplier.pop_back();

  // Verify that the order hasn't changed.
  EXPECT_LT(keys[0], keys[1]);
}

TEST_F(ColorProviderManagerTest, ForcedColorsCrash) {
  ColorProviderManager& manager = ColorProviderManager::GetForTesting();
  manager.AppendColorProviderInitializer(base::BindRepeating(AddColorMixers));

  ColorProviderKey key;
  key.forced_colors = ColorProviderKey::ForcedColors::kSystem;

  EXPECT_TRUE(manager.GetColorProviderFor(key));
}

}  // namespace ui
