// 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 "chrome/common/themes/autogenerated_theme_util.h"

#include "ui/gfx/color_utils.h"

SkColor GetContrastingColor(SkColor color, float luminosity_change) {
  color_utils::HSL hsl;
  SkColorToHSL(color, &hsl);

  // If luminosity is 0, it means `color` is black. Use white for black
  // backgrounds.
  if (hsl.l == 0)
    return SK_ColorWHITE;

  // Decrease luminosity, unless color is already dark.
  if (hsl.l > 0.15)
    luminosity_change *= -1;

  hsl.l *= 1 + luminosity_change;

  return (hsl.l >= 0.0f && hsl.l <= 1.0f) ? HSLToSkColor(hsl, 255) : color;
}

// Decreases the lightness of the given color.
SkColor DarkenColor(SkColor color, float change) {
  color_utils::HSL hsl;
  SkColorToHSL(color, &hsl);
  hsl.l -= change;

  return (hsl.l >= 0.0f) ? HSLToSkColor(hsl, 255) : color;
}

// Increases the lightness of `source` until it reaches `contrast_ratio` with
// `base` or reaches `white_contrast` with white. This avoids decreasing
// saturation, as the alternative contrast-guaranteeing functions in color_utils
// would do.
SkColor LightenUntilContrast(SkColor source,
                             SkColor base,
                             float contrast_ratio,
                             float white_contrast) {
  const float base_luminance = color_utils::GetRelativeLuminance(base);
  constexpr float kWhiteLuminance = 1.0f;

  color_utils::HSL hsl;
  SkColorToHSL(source, &hsl);
  float min_l = hsl.l;
  float max_l = 1.0f;

  // Need only precision of 2 digits.
  while (max_l - min_l > 0.01) {
    hsl.l = min_l + (max_l - min_l) / 2;
    float luminance = color_utils::GetRelativeLuminance(HSLToSkColor(hsl, 255));
    if (color_utils::GetContrastRatio(base_luminance, luminance) >=
            contrast_ratio ||
        (color_utils::GetContrastRatio(kWhiteLuminance, luminance) <
         white_contrast)) {
      max_l = hsl.l;
    } else {
      min_l = hsl.l;
    }
  }

  hsl.l = max_l;
  return HSLToSkColor(hsl, 255);
}

AutogeneratedThemeColors GetAutogeneratedThemeColors(SkColor color) {
  SkColor frame_color = color;
  SkColor frame_text_color;
  SkColor active_tab_color = color;
  SkColor active_tab_text_color;

  constexpr float kDarkenStep = 0.03f;
  constexpr float kMinWhiteContrast = 1.3f;
  constexpr float kNoWhiteContrast = 0.0f;

  // Used to determine what we consider a very dark color.
  constexpr float kMaxLuminosityForDark = 0.05f;

  // Increasingly darken frame color and calculate the rest until colors with
  // sufficient contrast are found.
  while (true) {
    // Calculate frame color to have sufficient contrast with white or dark grey
    // text.
    frame_text_color = color_utils::GetColorWithMaxContrast(frame_color);
    SkColor blend_target =
        color_utils::GetColorWithMaxContrast(frame_text_color);
    frame_color = color_utils::BlendForMinContrast(
                      frame_color, frame_text_color, blend_target,
                      kAutogeneratedThemeTextPreferredContrast)
                      .color;

    // Generate active tab color so that it has enough contrast with the
    // |frame_color| to avoid the isolation line in the tab strip.
    active_tab_color = LightenUntilContrast(
        frame_color, frame_color, kAutogeneratedThemeActiveTabMinContrast,
        kNoWhiteContrast);

    // We want more contrast between frame and active tab for dark colors.
    color_utils::HSL hsl;
    SkColorToHSL(frame_color, &hsl);
    float preferred_contrast =
        hsl.l <= kMaxLuminosityForDark
            ? kAutogeneratedThemeActiveTabPreferredContrastForDark
            : kAutogeneratedThemeActiveTabPreferredContrast;

    // Try lightening the color to get more contrast with frame without getting
    // too close to white.
    active_tab_color = LightenUntilContrast(
        active_tab_color, frame_color, preferred_contrast, kMinWhiteContrast);

    // If we didn't succeed in generating active tab color with minimum
    // contrast with frame, then darken the frame color and try again.
    if (color_utils::GetContrastRatio(frame_color, active_tab_color) <
        kAutogeneratedThemeActiveTabMinContrast) {
      frame_color = DarkenColor(frame_color, kDarkenStep);
      continue;
    }

    // Select active tab text color, if possible.
    active_tab_text_color =
        color_utils::GetColorWithMaxContrast(active_tab_color);

    if (!color_utils::IsDark(active_tab_color)) {
      // If active tab is light color then continue lightening it until enough
      // contrast with dark text is reached.
      active_tab_text_color =
          color_utils::GetColorWithMaxContrast(active_tab_color);
      active_tab_color = LightenUntilContrast(
          active_tab_color, active_tab_text_color,
          kAutogeneratedThemeTextPreferredContrast, kNoWhiteContrast);
      break;
    }

    // If the active tab color is dark and has enough contrast with white text.
    // Then we are all set.
    if (color_utils::GetContrastRatio(active_tab_color, SK_ColorWHITE) >=
        kAutogeneratedThemeTextPreferredContrast)
      break;

    // If the active tab color is a dark color but the contrast with white is
    // not enough then we should darken the active tab color to reach the
    // contrast with white. But to keep the contrast with the frame we should
    // also darken the frame color. Therefore, just darken the frame color and
    // try again.
    frame_color = DarkenColor(frame_color, kDarkenStep);
  }
  return {frame_color, frame_text_color, active_tab_color,
          active_tab_text_color, active_tab_color};
}
