// Copyright (c) 2013 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 "chrome/browser/ui/libgtk2ui/native_theme_gtk2.h"

#include <gtk/gtk.h>

#include "chrome/browser/ui/libgtk2ui/chrome_gtk_frame.h"
#include "chrome/browser/ui/libgtk2ui/chrome_gtk_menu_subclasses.h"
#include "chrome/browser/ui/libgtk2ui/gtk2_ui.h"
#include "chrome/browser/ui/libgtk2ui/gtk2_util.h"
#include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/path.h"
#include "ui/gfx/skia_util.h"
#include "ui/native_theme/common_theme.h"

namespace libgtk2ui {

namespace {

// Theme colors returned by GetSystemColor().
const SkColor kInvalidColorIdColor = SkColorSetRGB(255, 0, 128);
const SkColor kURLTextColor = SkColorSetRGB(0x0b, 0x80, 0x43);

// Generates the normal URL color, a green color used in unhighlighted URL
// text. It is a mix of |kURLTextColor| and the current text color.  Unlike the
// selected text color, it is more important to match the qualities of the
// foreground typeface color instead of taking the background into account.
SkColor NormalURLColor(SkColor foreground) {
  color_utils::HSL fg_hsl, hue_hsl;
  color_utils::SkColorToHSL(foreground, &fg_hsl);
  color_utils::SkColorToHSL(kURLTextColor, &hue_hsl);

  // Only allow colors that have a fair amount of saturation in them (color vs
  // white). This means that our output color will always be fairly green.
  double s = std::max(0.5, fg_hsl.s);

  // Make sure the luminance is at least as bright as the |kURLTextColor| green
  // would be if we were to use that.
  double l;
  if (fg_hsl.l < hue_hsl.l)
    l = hue_hsl.l;
  else
    l = (fg_hsl.l + hue_hsl.l) / 2;

  color_utils::HSL output = { hue_hsl.h, s, l };
  return color_utils::HSLToSkColor(output, 255);
}

// Generates the selected URL color, a green color used on URL text in the
// currently highlighted entry in the autocomplete popup. It's a mix of
// |kURLTextColor|, the current text color, and the background color (the
// select highlight). It is more important to contrast with the background
// saturation than to look exactly like the foreground color.
SkColor SelectedURLColor(SkColor foreground, SkColor background) {
  color_utils::HSL fg_hsl, bg_hsl, hue_hsl;
  color_utils::SkColorToHSL(foreground, &fg_hsl);
  color_utils::SkColorToHSL(background, &bg_hsl);
  color_utils::SkColorToHSL(kURLTextColor, &hue_hsl);

  // The saturation of the text should be opposite of the background, clamped
  // to 0.2-0.8. We make sure it's greater than 0.2 so there's some color, but
  // less than 0.8 so it's not the oversaturated neon-color.
  double opposite_s = 1 - bg_hsl.s;
  double s = std::max(0.2, std::min(0.8, opposite_s));

  // The luminance should match the luminance of the foreground text.  Again,
  // we clamp so as to have at some amount of color (green) in the text.
  double opposite_l = fg_hsl.l;
  double l = std::max(0.1, std::min(0.9, opposite_l));

  color_utils::HSL output = { hue_hsl.h, s, l };
  return color_utils::HSLToSkColor(output, 255);
}

enum WidgetState {
  NORMAL = 0,
  ACTIVE = 1,
  PRELIGHT = 2,
  SELECTED = 3,
  INSENSITIVE = 4,
};

#if GTK_MAJOR_VERSION == 2
// Same order as enum WidgetState above
const GtkStateType stateMap[] = {
  GTK_STATE_NORMAL,
  GTK_STATE_ACTIVE,
  GTK_STATE_PRELIGHT,
  GTK_STATE_SELECTED,
  GTK_STATE_INSENSITIVE,
};

SkColor GetFGColor(GtkWidget* widget, WidgetState state) {
  return GdkColorToSkColor(gtk_rc_get_style(widget)->fg[stateMap[state]]);
}
SkColor GetBGColor(GtkWidget* widget, WidgetState state) {
  return GdkColorToSkColor(gtk_rc_get_style(widget)->bg[stateMap[state]]);
}

SkColor GetTextColor(GtkWidget* widget, WidgetState state) {
  return GdkColorToSkColor(gtk_rc_get_style(widget)->text[stateMap[state]]);
}
SkColor GetTextAAColor(GtkWidget* widget, WidgetState state) {
  return GdkColorToSkColor(gtk_rc_get_style(widget)->text_aa[stateMap[state]]);
}
SkColor GetBaseColor(GtkWidget* widget, WidgetState state) {
  return GdkColorToSkColor(gtk_rc_get_style(widget)->base[stateMap[state]]);
}

#else
// Same order as enum WidgetState above
const GtkStateFlags stateMap[] = {
  GTK_STATE_FLAG_NORMAL,
  GTK_STATE_FLAG_ACTIVE,
  GTK_STATE_FLAG_PRELIGHT,
  GTK_STATE_FLAG_SELECTED,
  GTK_STATE_FLAG_INSENSITIVE,
};

SkColor GetFGColor(GtkWidget* widget, WidgetState state) {
  GdkRGBA color;
  gtk_style_context_get_color(
      gtk_widget_get_style_context(widget), stateMap[state], &color);
  return SkColorSetRGB(color.red * 255, color.green * 255, color.blue * 255);
}
SkColor GetBGColor(GtkWidget* widget, WidgetState state) {
  GdkRGBA color;

  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
  gtk_style_context_get_background_color(
      gtk_widget_get_style_context(widget), stateMap[state], &color);
  G_GNUC_END_IGNORE_DEPRECATIONS

  // Hack for default color
  if (color.alpha == 0.0)
    color = {1, 1, 1, 1};

  return SkColorSetRGB(color.red * 255, color.green * 255, color.blue * 255);
}

SkColor GetTextColor(GtkWidget* widget, WidgetState state) {
  return GetFGColor(widget, state);
}
SkColor GetTextAAColor(GtkWidget* widget, WidgetState state) {
  return GetFGColor(widget, state);
}
SkColor GetBaseColor(GtkWidget* widget, WidgetState state) {
  return GetBGColor(widget, state);
}

#endif

}  // namespace

// static
NativeThemeGtk2* NativeThemeGtk2::instance() {
  CR_DEFINE_STATIC_LOCAL(NativeThemeGtk2, s_native_theme, ());
  return &s_native_theme;
}

// Constructors automatically called
NativeThemeGtk2::NativeThemeGtk2() {}
// This doesn't actually get called
NativeThemeGtk2::~NativeThemeGtk2() {}

void NativeThemeGtk2::PaintMenuPopupBackground(
    SkCanvas* canvas,
    const gfx::Size& size,
    const MenuBackgroundExtraParams& menu_background) const {
  if (menu_background.corner_radius > 0) {
    SkPaint paint;
    paint.setStyle(SkPaint::kFill_Style);
    paint.setFlags(SkPaint::kAntiAlias_Flag);
    paint.setColor(GetSystemColor(kColorId_MenuBackgroundColor));

    gfx::Path path;
    SkRect rect = SkRect::MakeWH(SkIntToScalar(size.width()),
                                 SkIntToScalar(size.height()));
    SkScalar radius = SkIntToScalar(menu_background.corner_radius);
    SkScalar radii[8] = {radius, radius, radius, radius,
                         radius, radius, radius, radius};
    path.addRoundRect(rect, radii);

    canvas->drawPath(path, paint);
  } else {
    canvas->drawColor(GetSystemColor(kColorId_MenuBackgroundColor),
                      SkXfermode::kSrc_Mode);
  }
}

void NativeThemeGtk2::PaintMenuItemBackground(
    SkCanvas* canvas,
    State state,
    const gfx::Rect& rect,
    const MenuItemExtraParams& menu_item) const {
  SkColor color;
  SkPaint paint;
  switch (state) {
    case NativeTheme::kNormal:
    case NativeTheme::kDisabled:
      color = GetSystemColor(NativeTheme::kColorId_MenuBackgroundColor);
      paint.setColor(color);
      break;
    case NativeTheme::kHovered:
      color = GetSystemColor(
          NativeTheme::kColorId_FocusedMenuItemBackgroundColor);
      paint.setColor(color);
      break;
    default:
      NOTREACHED() << "Invalid state " << state;
      break;
  }
  if (menu_item.corner_radius > 0) {
    const SkScalar radius = SkIntToScalar(menu_item.corner_radius);
    canvas->drawRoundRect(gfx::RectToSkRect(rect), radius, radius, paint);
    return;
  }
  canvas->drawRect(gfx::RectToSkRect(rect), paint);
}

SkColor NativeThemeGtk2::GetSystemColor(ColorId color_id) const {
  const SkColor kPositiveTextColor = SkColorSetRGB(0x0b, 0x80, 0x43);
  const SkColor kNegativeTextColor = SkColorSetRGB(0xc5, 0x39, 0x29);

  switch (color_id) {
    // Windows
    case kColorId_WindowBackground:
      return GetBGColor(GetWindow(), SELECTED);

    // Dialogs
    case kColorId_DialogBackground:
    case kColorId_BubbleBackground:
      return GetBGColor(GetWindow(), NORMAL);

    // FocusableBorder
    case kColorId_FocusedBorderColor:
      return GetBGColor(GetEntry(), SELECTED);
    case kColorId_UnfocusedBorderColor:
      return GetTextAAColor(GetEntry(), NORMAL);

    // MenuItem
#if GTK_MAJOR_VERSION == 2
    case kColorId_SelectedMenuItemForegroundColor:
      return GetTextColor(GetMenuItem(), SELECTED);
    case kColorId_FocusedMenuItemBackgroundColor:
      return GetBGColor(GetMenuItem(), SELECTED);
#else
    case kColorId_SelectedMenuItemForegroundColor:
      return GetTextColor(GetMenuItem(), PRELIGHT);
    case kColorId_FocusedMenuItemBackgroundColor:
      return GetBGColor(GetMenuItem(), PRELIGHT);
#endif

    case kColorId_EnabledMenuItemForegroundColor:
    case kColorId_DisabledEmphasizedMenuItemForegroundColor:
      return GetTextColor(GetMenuItem(), NORMAL);
    case kColorId_DisabledMenuItemForegroundColor:
      return GetTextColor(GetMenuItem(), INSENSITIVE);
    case kColorId_HoverMenuItemBackgroundColor:
      return GetBGColor(GetMenuItem(), PRELIGHT);
    case kColorId_FocusedMenuButtonBorderColor:
      return GetBGColor(GetEntry(), NORMAL);
    case kColorId_HoverMenuButtonBorderColor:
      return GetTextAAColor(GetEntry(), PRELIGHT);
    case kColorId_MenuBorderColor:
    case kColorId_EnabledMenuButtonBorderColor:
    case kColorId_MenuSeparatorColor: {
      return GetTextColor(GetMenuItem(), INSENSITIVE);
    }
    case kColorId_MenuBackgroundColor:
      return GetBGColor(GetMenu(), NORMAL);

    // Label
    case kColorId_LabelEnabledColor:
      return GetTextColor(GetEntry(), NORMAL);
    case kColorId_LabelDisabledColor:
      return GetTextColor(GetLabel(), INSENSITIVE);
    case kColorId_LabelBackgroundColor:
      return GetBGColor(GetWindow(), NORMAL);

    // Link
    case kColorId_LinkDisabled:
      return SkColorSetA(GetSystemColor(kColorId_LinkEnabled), 0xBB);
    case kColorId_LinkEnabled: {
      SkColor link_color = SK_ColorTRANSPARENT;
      GetChromeStyleColor("link-color", &link_color);
      if (link_color != SK_ColorTRANSPARENT)
        return link_color;
      // Default color comes from gtklinkbutton.c.
      return SkColorSetRGB(0x00, 0x00, 0xEE);
    }
    case kColorId_LinkPressed:
      return SK_ColorRED;

    // Button
    case kColorId_ButtonBackgroundColor:
      return GetBGColor(GetButton(), NORMAL);
    case kColorId_ButtonEnabledColor:
      return GetTextColor(GetButton(), NORMAL);
    case kColorId_BlueButtonEnabledColor:
      return GetTextColor(GetBlueButton(), NORMAL);
    case kColorId_ButtonDisabledColor:
      return GetTextColor(GetButton(), INSENSITIVE);
    case kColorId_BlueButtonDisabledColor:
      return GetTextColor(GetBlueButton(), INSENSITIVE);
    case kColorId_ButtonHighlightColor:
      return GetBaseColor(GetButton(), SELECTED);
    case kColorId_ButtonHoverColor:
      return GetTextColor(GetButton(), PRELIGHT);
    case kColorId_BlueButtonHoverColor:
      return GetTextColor(GetBlueButton(), PRELIGHT);
    case kColorId_ButtonHoverBackgroundColor:
      return GetBGColor(GetButton(), PRELIGHT);
    case kColorId_BlueButtonPressedColor:
      return GetTextColor(GetBlueButton(), ACTIVE);
    case kColorId_BlueButtonShadowColor:
      return SK_ColorTRANSPARENT;
      // return GetTextColor(GetButton(), NORMAL);
    case kColorId_CallToActionColor:
      return GetSystemColor(kColorId_LinkEnabled);
    case kColorId_TextOnCallToActionColor:
      return GetTextAAColor(GetLabel(), PRELIGHT);

    // Textfield
    case kColorId_TextfieldDefaultColor:
      return GetTextColor(GetEntry(), NORMAL);
    case kColorId_TextfieldDefaultBackground:
      return GetBaseColor(GetEntry(), NORMAL);

#if GTK_MAJOR_VERSION == 2
    case kColorId_TextfieldReadOnlyColor:
      return GetTextColor(GetEntry(), ACTIVE);
    case kColorId_TextfieldReadOnlyBackground:
      return GetBaseColor(GetEntry(), ACTIVE);
    case kColorId_TextfieldSelectionColor:
      return GetTextColor(GetEntry(), SELECTED);
    case kColorId_TextfieldSelectionBackgroundFocused:
      return GetBaseColor(GetEntry(), SELECTED);
#else
    case kColorId_TextfieldReadOnlyColor:
      return GetTextColor(GetEntry(), SELECTED);
    case kColorId_TextfieldReadOnlyBackground:
      return GetBaseColor(GetEntry(), SELECTED);
    case kColorId_TextfieldSelectionColor:
      return GetTextColor(GetLabel(), SELECTED);
    case kColorId_TextfieldSelectionBackgroundFocused:
      return GetBaseColor(GetLabel(), SELECTED);
#endif


    // Tooltips
    case kColorId_TooltipBackground:
      return GetBGColor(GetTooltip(), NORMAL);
    case kColorId_TooltipText:
      return GetFGColor(GetTooltip(), NORMAL);

    // Trees and Tables (implemented on GTK using the same class)
    case kColorId_TableBackground:
    case kColorId_TreeBackground:
      return GetBGColor(GetTree(), NORMAL);
    case kColorId_TableText:
    case kColorId_TreeText:
      return GetTextColor(GetTree(), NORMAL);
    case kColorId_TableSelectedText:
    case kColorId_TableSelectedTextUnfocused:
    case kColorId_TreeSelectedText:
    case kColorId_TreeSelectedTextUnfocused:
      return GetTextColor(GetTree(), SELECTED);
    case kColorId_TableSelectionBackgroundFocused:
    case kColorId_TableSelectionBackgroundUnfocused:
    case kColorId_TreeSelectionBackgroundFocused:
    case kColorId_TreeSelectionBackgroundUnfocused:
      return GetBGColor(GetTree(), SELECTED);
    case kColorId_TreeArrow:
      return GetFGColor(GetTree(), NORMAL);
    case kColorId_TableGroupingIndicatorColor:
      return GetTextAAColor(GetTree(), NORMAL);

      // Results Table
    case kColorId_ResultsTableNormalBackground:
      return GetSystemColor(kColorId_TextfieldDefaultBackground);
    case kColorId_ResultsTableHoveredBackground:
      return color_utils::AlphaBlend(
          GetSystemColor(kColorId_TextfieldDefaultBackground),
          GetSystemColor(kColorId_TextfieldSelectionBackgroundFocused),
          0x80);
    case kColorId_ResultsTableSelectedBackground:
      return GetSystemColor(kColorId_TextfieldSelectionBackgroundFocused);
    case kColorId_ResultsTableNormalText:
    case kColorId_ResultsTableHoveredText:
      return GetSystemColor(kColorId_TextfieldDefaultColor);
    case kColorId_ResultsTableSelectedText:
      return GetSystemColor(kColorId_TextfieldSelectionColor);
    case kColorId_ResultsTableNormalDimmedText:
    case kColorId_ResultsTableHoveredDimmedText:
    case kColorId_ResultsTableNormalHeadline:
    case kColorId_ResultsTableHoveredHeadline:
      return color_utils::AlphaBlend(
          GetSystemColor(kColorId_TextfieldDefaultColor),
          GetSystemColor(kColorId_TextfieldDefaultBackground),
          0x80);
    case kColorId_ResultsTableSelectedDimmedText:
    case kColorId_ResultsTableSelectedHeadline:
      return color_utils::AlphaBlend(
          GetSystemColor(kColorId_TextfieldSelectionColor),
          GetSystemColor(kColorId_TextfieldDefaultBackground),
          0x80);
    case kColorId_ResultsTableNormalUrl:
    case kColorId_ResultsTableHoveredUrl:
      return NormalURLColor(GetSystemColor(kColorId_TextfieldDefaultColor));

    case kColorId_ResultsTableSelectedUrl:
      return SelectedURLColor(
          GetSystemColor(kColorId_TextfieldSelectionColor),
          GetSystemColor(kColorId_TextfieldSelectionBackgroundFocused));

    case kColorId_ResultsTablePositiveText: {
      return color_utils::GetReadableColor(kPositiveTextColor,
                                           GetBaseColor(GetEntry(), NORMAL));
    }
    case kColorId_ResultsTablePositiveHoveredText: {
      return color_utils::GetReadableColor(kPositiveTextColor,
                                           GetBaseColor(GetEntry(), PRELIGHT));
    }
    case kColorId_ResultsTablePositiveSelectedText: {
      return color_utils::GetReadableColor(kPositiveTextColor,
                                           GetBaseColor(GetEntry(), SELECTED));
    }
    case kColorId_ResultsTableNegativeText: {
      return color_utils::GetReadableColor(kNegativeTextColor,
                                           GetBaseColor(GetEntry(), NORMAL));
    }
    case kColorId_ResultsTableNegativeHoveredText: {
      return color_utils::GetReadableColor(kNegativeTextColor,
                                           GetBaseColor(GetEntry(), PRELIGHT));
    }
    case kColorId_ResultsTableNegativeSelectedText: {
      return color_utils::GetReadableColor(kNegativeTextColor,
                                           GetBaseColor(GetEntry(), SELECTED));
    }

    // Throbber
    case kColorId_ThrobberSpinningColor:
    case kColorId_ThrobberLightColor:
      return GetSystemColor(kColorId_TextfieldSelectionBackgroundFocused);

    case kColorId_ThrobberWaitingColor:
      return color_utils::AlphaBlend(
          GetSystemColor(kColorId_TextfieldSelectionBackgroundFocused),
          GetBGColor(GetWindow(), NORMAL),
          0x80);

    case kColorId_NumColors:
      NOTREACHED();
      break;
  }

  return kInvalidColorIdColor;
}

// Get ChromeGtkFrame theme colors. No-op in GTK3.
bool NativeThemeGtk2::GetChromeStyleColor(const char* style_property,
                                          SkColor* ret_color) const {
#if GTK_MAJOR_VERSION == 2
  GdkColor* style_color = nullptr;
  gtk_widget_style_get(GetWindow(), style_property, &style_color, nullptr);
  if (style_color) {
    *ret_color = GdkColorToSkColor(*style_color);
    gdk_color_free(style_color);
    return true;
  }
#endif

  return false;
}

GtkWidget* NativeThemeGtk2::GetWindow() const {
  static GtkWidget* fake_window = NULL;

  if (!fake_window) {
    fake_window = chrome_gtk_frame_new();
    gtk_widget_realize(fake_window);
  }

  return fake_window;
}

GtkWidget* NativeThemeGtk2::GetEntry() const {
  static GtkWidget* fake_entry = NULL;

  if (!fake_entry) {
    fake_entry = gtk_entry_new();

    // The fake entry needs to be in the window so it can be realized so we can
    // use the computed parts of the style.
    gtk_container_add(GTK_CONTAINER(GetWindow()), fake_entry);
    gtk_widget_realize(fake_entry);
  }

  return fake_entry;
}

GtkWidget* NativeThemeGtk2::GetLabel() const {
  static GtkWidget* fake_label = NULL;

  if (!fake_label)
    fake_label = gtk_label_new("");

  return fake_label;
}

GtkWidget* NativeThemeGtk2::GetButton() const {
  static GtkWidget* fake_button = NULL;

  if (!fake_button)
    fake_button = gtk_button_new();

  return fake_button;
}

GtkWidget* NativeThemeGtk2::GetBlueButton() const {
  static GtkWidget* fake_bluebutton = NULL;

  if (!fake_bluebutton) {
    fake_bluebutton = gtk_button_new();
    TurnButtonBlue(fake_bluebutton);
  }

  return fake_bluebutton;
}

GtkWidget* NativeThemeGtk2::GetTree() const {
  static GtkWidget* fake_tree = NULL;

  if (!fake_tree)
    fake_tree = gtk_tree_view_new();

  return fake_tree;
}

GtkWidget* NativeThemeGtk2::GetTooltip() const {
  static GtkWidget* fake_tooltip = NULL;

  if (!fake_tooltip) {
    fake_tooltip = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_name(fake_tooltip, "gtk-tooltip");
    gtk_widget_realize(fake_tooltip);
  }

  return fake_tooltip;
}

GtkWidget* NativeThemeGtk2::GetMenu() const {
  static GtkWidget* fake_menu = NULL;

  if (!fake_menu)
    fake_menu = gtk_custom_menu_new();

  return fake_menu;
}

GtkWidget* NativeThemeGtk2::GetMenuItem() const {
  static GtkWidget* fake_menu_item = NULL;

  if (!fake_menu_item) {
    fake_menu_item = gtk_custom_menu_item_new();
    gtk_menu_shell_append(GTK_MENU_SHELL(GetMenu()), fake_menu_item);
  }

  return fake_menu_item;
}

}  // namespace libgtk2ui