// Copyright 2019 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_transform.h"

#include "testing/gtest/include/gtest/gtest.h"
#include "ui/color/color_mixer.h"
#include "ui/color/color_recipe.h"
#include "ui/color/color_test_ids.h"
#include "ui/gfx/color_palette.h"

namespace ui {
namespace {

// Tests initialization with an SkColor value.
TEST(ColorRecipeTest, TestColorRecipeInitialization) {
  const auto verify_initialization = [&](SkColor color) {
    ColorTransform transform = color;
    EXPECT_EQ(color, transform.Run(SK_ColorBLACK, ColorMixer()));
  };
  verify_initialization(SK_ColorBLUE);
  verify_initialization(SK_ColorRED);
  verify_initialization(SK_ColorGREEN);
}

// Tests that AlphaBlend() produces a transform that blends its inputs.
TEST(ColorTransformTest, AlphaBlend) {
  const auto blend = [](SkAlpha alpha) {
    const ColorTransform transform =
        AlphaBlend(SK_ColorWHITE, SK_ColorBLACK, alpha);
    return transform.Run(gfx::kPlaceholderColor, ColorMixer());
  };
  EXPECT_EQ(SK_ColorBLACK, blend(SK_AlphaTRANSPARENT));
  EXPECT_EQ(SK_ColorWHITE, blend(SK_AlphaOPAQUE));
  EXPECT_EQ(SK_ColorGRAY, blend(SkColorGetR(SK_ColorGRAY)));
}

// Tests that BlendForMinContrast(), with the default args, produces a transform
// that blends its foreground color to produce readable contrast against its
// background color.
TEST(ColorTransformTest, BlendForMinContrast) {
  const ColorTransform transform =
      BlendForMinContrast(FromTransformInput(), kColorTest0);
  constexpr SkColor kBackground = SK_ColorWHITE;
  ColorMixer mixer;
  mixer[kColorTest0] = {kBackground};
  const auto verify_contrast = [&](SkColor input) {
    EXPECT_GE(
        color_utils::GetContrastRatio(transform.Run(input, mixer), kBackground),
        color_utils::kMinimumReadableContrastRatio);
  };
  verify_contrast(SK_ColorBLACK);
  verify_contrast(SK_ColorWHITE);
  verify_contrast(SK_ColorRED);
}

// Tests that BlendForMinContrast() supports optional args, which can be used to
// blend toward a specific foreground color and with a specific minimum contrast
// ratio.
TEST(ColorTransformTest, BlendForMinContrastOptionalArgs) {
  constexpr float kMinContrast = 6.0f;
  const ColorTransform transform = BlendForMinContrast(
      FromTransformInput(), kColorTest0, kColorTest1, kMinContrast);
  constexpr SkColor kBackground = SK_ColorWHITE;
  ColorMixer mixer;
  mixer[kColorTest0] = {kBackground};
  mixer[kColorTest1] = {gfx::kGoogleBlue900};
  const auto verify_contrast = [&](SkColor input) {
    EXPECT_GE(
        color_utils::GetContrastRatio(transform.Run(input, mixer), kBackground),
        kMinContrast);
  };
  verify_contrast(SK_ColorBLACK);
  verify_contrast(SK_ColorWHITE);
  verify_contrast(gfx::kGoogleBlue500);
}

// Tests that BlendForMinContrastWithSelf() produces a transform that blends its
// input color towards the color with max contrast.
TEST(ColorTransformTest, BlendForMinContrastWithSelf) {
  constexpr float kContrastRatio = 2;
  const ColorTransform transform =
      BlendForMinContrastWithSelf(FromTransformInput(), kContrastRatio);
  const auto verify_blend = [&](SkColor input) {
    const SkColor target = color_utils::GetColorWithMaxContrast(input);
    EXPECT_LT(color_utils::GetContrastRatio(transform.Run(input, ColorMixer()),
                                            target),
              color_utils::GetContrastRatio(input, target));
  };
  verify_blend(SK_ColorBLACK);
  verify_blend(SK_ColorWHITE);
  verify_blend(SK_ColorRED);
}

// Tests that BlendTowardMaxContrast() produces a transform that blends its
// input color towards the color with max contrast.
TEST(ColorTransformTest, BlendTowardMaxContrast) {
  constexpr SkAlpha kAlpha = 0x20;
  const ColorTransform transform =
      BlendTowardMaxContrast(FromTransformInput(), kAlpha);
  const auto verify_blend = [&](SkColor input) {
    const SkColor target = color_utils::GetColorWithMaxContrast(input);
    EXPECT_LT(color_utils::GetContrastRatio(transform.Run(input, ColorMixer()),
                                            target),
              color_utils::GetContrastRatio(input, target));
  };
  verify_blend(SK_ColorBLACK);
  verify_blend(SK_ColorWHITE);
  verify_blend(SK_ColorRED);
}

// Tests that ContrastInvert() produces a transform that outputs a color with at
// least as much contrast, but against the opposite endpoint.
TEST(ColorTransformTest, ContrastInvert) {
  const ColorTransform transform = ContrastInvert(FromTransformInput());
  const auto verify_invert = [&](SkColor input) {
    const SkColor far_endpoint = color_utils::GetColorWithMaxContrast(input);
    const SkColor near_endpoint =
        color_utils::GetColorWithMaxContrast(far_endpoint);
    EXPECT_GE(color_utils::GetContrastRatio(transform.Run(input, ColorMixer()),
                                            near_endpoint),
              color_utils::GetContrastRatio(input, far_endpoint));
  };
  verify_invert(gfx::kGoogleGrey900);
  verify_invert(SK_ColorWHITE);
  verify_invert(SK_ColorRED);
  verify_invert(gfx::kGoogleBlue500);
}

// Tests that DeriveDefaultIconColor() produces a transform that changes its
// input color.
TEST(ColorTransformTest, DeriveDefaultIconColor) {
  const ColorTransform transform = DeriveDefaultIconColor(FromTransformInput());
  const auto verify_derive = [&](SkColor input) {
    EXPECT_NE(input, transform.Run(input, ColorMixer()));
  };
  verify_derive(SK_ColorBLACK);
  verify_derive(SK_ColorWHITE);
  verify_derive(SK_ColorRED);
}

// Tests that initializing a transform from a color produces a transform that
// ignores the input color and always outputs a specified SkColor.
TEST(ColorTransformTest, FromColor) {
  constexpr SkColor kOutput = SK_ColorGREEN;
  const ColorTransform transform = kOutput;
  const auto verify_color = [&](SkColor input) {
    EXPECT_EQ(kOutput, transform.Run(input, ColorMixer()));
  };
  verify_color(SK_ColorBLACK);
  verify_color(SK_ColorWHITE);
  verify_color(SK_ColorRED);
}

// Tests that a transform created from a ColorId produces a transform that
// ignores the input color and always outputs a specified result color.
TEST(ColorTransformTest, FromColorId) {
  const ColorTransform transform = {kColorTest0};
  constexpr SkColor kTest1Color = SK_ColorRED;
  ColorMixer mixer;
  mixer[kColorTest0] = {kColorTest1};
  mixer[kColorTest1] = {kTest1Color};
  const auto verify_color = [&](SkColor input) {
    EXPECT_EQ(kTest1Color, transform.Run(input, mixer));
  };
  verify_color(SK_ColorBLACK);
  verify_color(SK_ColorWHITE);
  verify_color(SK_ColorRED);
}  // namespace

// Tests that FromTransformInput() returns its input color unmodified.
TEST(ColorTransformTest, FromTransformInput) {
  const ColorTransform transform = FromTransformInput();
  const auto verify_color = [&](SkColor input) {
    EXPECT_EQ(input, transform.Run(input, ColorMixer()));
  };
  verify_color(SK_ColorBLACK);
  verify_color(SK_ColorWHITE);
  verify_color(SK_ColorRED);
}  // namespace

// Tests that GetColorWithMaxContrast() produces a transform that changes white
// to the darkest color.
TEST(ColorTransformTest, GetColorWithMaxContrast) {
  const ColorTransform transform =
      GetColorWithMaxContrast(FromTransformInput());
  constexpr SkColor kNewDarkestColor = gfx::kGoogleGrey500;
  const SkColor default_darkest_color =
      color_utils::SetDarkestColorForTesting(kNewDarkestColor);
  constexpr SkColor kLightestColor = SK_ColorWHITE;
  EXPECT_EQ(kNewDarkestColor, transform.Run(kLightestColor, ColorMixer()));
  color_utils::SetDarkestColorForTesting(default_darkest_color);
  EXPECT_EQ(default_darkest_color, transform.Run(kLightestColor, ColorMixer()));
}

// Tests that GetResultingPaintColor() produces a transform that composites
// opaquely.
TEST(ColorTransformTest, GetResultingPaintColor) {
  const ColorTransform transform =
      GetResultingPaintColor(FromTransformInput(), kColorTest0);
  constexpr SkColor kBackground = SK_ColorWHITE;
  ColorMixer mixer;
  mixer[kColorTest0] = {kBackground};
  EXPECT_EQ(SK_ColorBLACK, transform.Run(SK_ColorBLACK, mixer));
  EXPECT_EQ(kBackground, transform.Run(SK_ColorTRANSPARENT, mixer));
  EXPECT_EQ(color_utils::AlphaBlend(SK_ColorBLACK, kBackground, SkAlpha{0x80}),
            transform.Run(SkColorSetA(SK_ColorBLACK, 0x80), mixer));
}

// Tests that SelectBasedOnDarkInput() produces a transform that toggles between
// inputs based on whether the input color is dark.
TEST(ColorTransformTest, SelectBasedOnDarkInput) {
  constexpr SkColor kDarkOutput = SK_ColorGREEN;
  constexpr SkColor kLightOutput = SK_ColorRED;
  const ColorTransform transform =
      SelectBasedOnDarkInput(FromTransformInput(), kDarkOutput, kLightOutput);
  EXPECT_EQ(kDarkOutput, transform.Run(SK_ColorBLACK, ColorMixer()));
  EXPECT_EQ(kLightOutput, transform.Run(SK_ColorWHITE, ColorMixer()));
  EXPECT_EQ(kDarkOutput, transform.Run(SK_ColorBLUE, ColorMixer()));
  EXPECT_EQ(kLightOutput, transform.Run(SK_ColorRED, ColorMixer()));
}

// Tests that SetAlpha() produces a transform that sets its input's alpha.
TEST(ColorTransformTest, SetAlpha) {
  constexpr SkAlpha kAlpha = 0x20;
  const ColorTransform transform = SetAlpha(FromTransformInput(), kAlpha);
  for (auto color : {SK_ColorBLACK, SK_ColorRED, SK_ColorTRANSPARENT}) {
    EXPECT_EQ(SkColorSetA(color, kAlpha), transform.Run(color, ColorMixer()));
  }
}

// Tests that PickGoogleColor() produces a transform that picks a Google color
// with appropriate contrast against the specified background.
TEST(ColorTransformTest, PickGoogleColor) {
  constexpr SkColor kBackground = gfx::kGoogleGrey600;
  constexpr float kMinContrast = 2.1f;
  const ColorTransform transform =
      PickGoogleColor(FromTransformInput(), kBackground, kMinContrast);
  EXPECT_EQ(gfx::kGoogleRed900, transform.Run(SK_ColorRED, ColorMixer()));
  EXPECT_EQ(gfx::kGoogleGreen100, transform.Run(SK_ColorGREEN, ColorMixer()));
  EXPECT_EQ(gfx::kGoogleBlue900, transform.Run(SK_ColorBLUE, ColorMixer()));
}

// Tests that PickGoogleColorTwoBackgrounds() produces a transform that picks a
// Google color with appropriate contrast against both specified backgrounds.
TEST(ColorTransformTest, PickGoogleColorTwoBackgrounds) {
  constexpr SkColor kBackgroundA = gfx::kGoogleGrey800;
  constexpr SkColor kBackgroundB = gfx::kGoogleGrey400;
  constexpr float kMinContrast = 1.5f;
  const ColorTransform transform = PickGoogleColorTwoBackgrounds(
      FromTransformInput(), kBackgroundA, kBackgroundB, kMinContrast);
  EXPECT_EQ(gfx::kGoogleRed500, transform.Run(SK_ColorRED, ColorMixer()));
  EXPECT_EQ(gfx::kGoogleGreen050, transform.Run(SK_ColorGREEN, ColorMixer()));
  EXPECT_EQ(gfx::kGoogleBlue800, transform.Run(SK_ColorBLUE, ColorMixer()));
}

// Tests that HSLShift() produces a transform that applies the given HSL shift
// to the given input color.
TEST(ColorTransformTest, HSLShift) {
  constexpr color_utils::HSL kHsl = {0.2, 0.3, 0.4};
  const ColorTransform transform = HSLShift(FromTransformInput(), kHsl);
  const auto verify_color = [&](SkColor input) {
    EXPECT_EQ(color_utils::HSLShift(input, kHsl),
              transform.Run(input, ColorMixer()));
  };
  verify_color(SK_ColorBLACK);
  verify_color(gfx::kGoogleGrey600);
  verify_color(SK_ColorWHITE);
}

}  // namespace
}  // namespace ui
