// 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 "base/memory/raw_ptr.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/ui_base_features.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/vector_icons.h"
#include "ui/message_center/views/message_view.h"
#include "ui/message_center/views/notification_control_buttons_view.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget.h"

namespace message_center {

namespace {

class TestMessageView : public MessageView {
 public:
  explicit TestMessageView(const Notification& notification)
      : MessageView(notification) {}

  NotificationControlButtonsView* GetControlButtonsView() const override {
    return buttons_view_;
  }

  void set_control_buttons_view(NotificationControlButtonsView* buttons_view) {
    buttons_view_ = buttons_view;
  }

 private:
  raw_ptr<NotificationControlButtonsView> buttons_view_ = nullptr;
};

}  // namespace

class NotificationControlButtonsTest : public views::ViewsTestBase {
 public:
  NotificationControlButtonsTest() = default;

  NotificationControlButtonsTest(const NotificationControlButtonsTest&) =
      delete;
  NotificationControlButtonsTest& operator=(
      const NotificationControlButtonsTest&) = delete;

  ~NotificationControlButtonsTest() override = default;

  // views::ViewsTestBase:
  void SetUp() override {
    views::ViewsTestBase::SetUp();
    widget_ =
        CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
    Notification notification(
        NOTIFICATION_TYPE_SIMPLE, "id", u"title", u"id", ui::ImageModel(),
        std::u16string(), GURL(),
        NotifierId(NotifierType::APPLICATION, "notifier_id"),
        RichNotificationData(), nullptr);
    message_view_ = std::make_unique<TestMessageView>(notification);
    message_view_->set_control_buttons_view(widget_->SetContentsView(
        std::make_unique<NotificationControlButtonsView>(message_view_.get())));
  }

  void TearDown() override {
    message_view_->set_control_buttons_view(nullptr);
    widget_.reset();
    views::ViewsTestBase::TearDown();
  }

  NotificationControlButtonsView* buttons_view() {
    return message_view_->GetControlButtonsView();
  }

  bool MatchesIcon(views::ImageButton* button,
                   const gfx::VectorIcon& icon,
                   SkColor color,
                   int size = 0) {
    SkBitmap expected = *gfx::CreateVectorIcon(icon, size, color).bitmap();
    SkBitmap actual = *button->GetImage(views::Button::STATE_NORMAL).bitmap();
    return gfx::test::AreBitmapsEqual(expected, actual);
  }

  void ExpectIconColor(SkColor color) {
    EXPECT_TRUE(MatchesIcon(buttons_view()->close_button(),
                            features::IsRoundedIconsEnabled()
                                ? kCloseIcon
                                : kNotificationCloseButtonOldIcon,
                            color));
    EXPECT_TRUE(MatchesIcon(buttons_view()->settings_button(),
                            features::IsRoundedIconsEnabled()
                                ? kSettingsFilledIcon
                                : kNotificationSettingsButtonOldIcon,
                            color));
    EXPECT_TRUE(MatchesIcon(buttons_view()->snooze_button(),
                            features::IsRoundedIconsEnabled()
                                ? kScheduleIcon
                                : kNotificationSnoozeButtonOldIcon,
                            color));
  }

 private:
  std::unique_ptr<views::Widget> widget_;
  std::unique_ptr<TestMessageView> message_view_;
};

TEST_F(NotificationControlButtonsTest, TestShowAndHideButtons) {
  EXPECT_EQ(nullptr, buttons_view()->close_button());
  EXPECT_EQ(nullptr, buttons_view()->settings_button());
  EXPECT_EQ(nullptr, buttons_view()->snooze_button());

  buttons_view()->ShowCloseButton(true);
  buttons_view()->ShowSettingsButton(true);
  buttons_view()->ShowSnoozeButton(true);

  EXPECT_NE(nullptr, buttons_view()->close_button());
  EXPECT_NE(nullptr, buttons_view()->settings_button());
  EXPECT_NE(nullptr, buttons_view()->snooze_button());

  buttons_view()->ShowCloseButton(false);
  buttons_view()->ShowSettingsButton(false);
  buttons_view()->ShowSnoozeButton(false);

  EXPECT_EQ(nullptr, buttons_view()->close_button());
  EXPECT_EQ(nullptr, buttons_view()->settings_button());
  EXPECT_EQ(nullptr, buttons_view()->snooze_button());
}

TEST_F(NotificationControlButtonsTest, IconColor_NoContrastEnforcement) {
  buttons_view()->ShowCloseButton(true);
  buttons_view()->ShowSettingsButton(true);
  buttons_view()->ShowSnoozeButton(true);

  // Default icon color.
  ExpectIconColor(buttons_view()->GetColorProvider()->GetColor(ui::kColorIcon));

  // Without setting a background color we won't enforce contrast ratios.
  buttons_view()->SetButtonIconColors(SK_ColorWHITE);
  ExpectIconColor(SK_ColorWHITE);
  buttons_view()->SetButtonIconColors(SK_ColorBLACK);
  ExpectIconColor(SK_ColorBLACK);
}

TEST_F(NotificationControlButtonsTest, IconColor_ContrastEnforcement) {
  buttons_view()->ShowCloseButton(true);
  buttons_view()->ShowSettingsButton(true);
  buttons_view()->ShowSnoozeButton(true);

  // A bright background should enforce dark enough icons.
  buttons_view()->SetBackgroundColor(SK_ColorWHITE);
  buttons_view()->SetButtonIconColors(SK_ColorWHITE);
  ExpectIconColor(
      color_utils::BlendForMinContrast(SK_ColorWHITE, SK_ColorWHITE).color);

  // A dark background should enforce bright enough icons.
  buttons_view()->SetBackgroundColor(SK_ColorBLACK);
  buttons_view()->SetButtonIconColors(SK_ColorBLACK);
  ExpectIconColor(
      color_utils::BlendForMinContrast(SK_ColorBLACK, SK_ColorBLACK).color);
}

// Tests default and custom control button icons.
TEST_F(NotificationControlButtonsTest, SetIcons) {
  // Show the default control buttons.
  buttons_view()->ShowCloseButton(true);
  buttons_view()->ShowSettingsButton(true);
  buttons_view()->ShowSnoozeButton(true);

  // Verify that the default control buttons are using their default icons.
  const SkColor default_icon_color =
      buttons_view()->GetColorProvider()->GetColor(ui::kColorIcon);
  EXPECT_TRUE(
      MatchesIcon(buttons_view()->close_button(),
                  features::IsRoundedIconsEnabled()
                      ? NotificationControlButtonsView::kDefaultCloseIcon
                      : NotificationControlButtonsView::kDefaultCloseOldIcon,
                  default_icon_color));
  EXPECT_TRUE(
      MatchesIcon(buttons_view()->settings_button(),
                  features::IsRoundedIconsEnabled()
                      ? NotificationControlButtonsView::kDefaultSettingsIcon
                      : NotificationControlButtonsView::kDefaultSettingsOldIcon,
                  default_icon_color));
  EXPECT_TRUE(
      MatchesIcon(buttons_view()->snooze_button(),
                  features::IsRoundedIconsEnabled()
                      ? NotificationControlButtonsView::kDefaultSnoozeIcon
                      : NotificationControlButtonsView::kDefaultSnoozeOldIcon,
                  default_icon_color));

  // Set the control buttons to have custom, non-default icons.
  buttons_view()->ShowCloseButton(false);
  buttons_view()->ShowSettingsButton(false);
  buttons_view()->ShowSnoozeButton(false);
  const gfx::VectorIcon& test_icon =
      features::IsRoundedIconsEnabled() ? kChromeProductIcon : kProductOldIcon;
  buttons_view()->SetCloseButtonIcon(test_icon);
  buttons_view()->SetSettingsButtonIcon(test_icon);
  buttons_view()->SetSnoozeButtonIcon(test_icon);
  buttons_view()->ShowCloseButton(true);
  buttons_view()->ShowSettingsButton(true);
  buttons_view()->ShowSnoozeButton(true);

  // Verify that the control buttons are now using the custom icons.
  EXPECT_TRUE(MatchesIcon(buttons_view()->close_button(), test_icon,
                          default_icon_color));
  EXPECT_TRUE(MatchesIcon(buttons_view()->settings_button(), test_icon,
                          default_icon_color));
  EXPECT_TRUE(MatchesIcon(buttons_view()->snooze_button(), test_icon,
                          default_icon_color));
}

// Tests that the icon size can be specified.
TEST_F(NotificationControlButtonsTest, IconSize) {
  // Set the control buttons to have a custom size.
  int custom_size = 8;
  buttons_view()->SetButtonIconSize(custom_size);

  // Show the control buttons and verify that they are using the custom size.
  buttons_view()->ShowCloseButton(true);
  buttons_view()->ShowSettingsButton(true);
  buttons_view()->ShowSnoozeButton(true);
  const SkColor default_icon_color =
      buttons_view()->GetColorProvider()->GetColor(ui::kColorIcon);
  EXPECT_TRUE(
      MatchesIcon(buttons_view()->close_button(),
                  features::IsRoundedIconsEnabled()
                      ? NotificationControlButtonsView::kDefaultCloseIcon
                      : NotificationControlButtonsView::kDefaultCloseOldIcon,
                  default_icon_color, custom_size));
  EXPECT_TRUE(
      MatchesIcon(buttons_view()->settings_button(),
                  features::IsRoundedIconsEnabled()
                      ? NotificationControlButtonsView::kDefaultSettingsIcon
                      : NotificationControlButtonsView::kDefaultSettingsOldIcon,
                  default_icon_color, custom_size));
  EXPECT_TRUE(
      MatchesIcon(buttons_view()->snooze_button(),
                  features::IsRoundedIconsEnabled()
                      ? NotificationControlButtonsView::kDefaultSnoozeIcon
                      : NotificationControlButtonsView::kDefaultSnoozeOldIcon,
                  default_icon_color, custom_size));

  // Show the same control buttons with a different custom size.
  buttons_view()->ShowCloseButton(false);
  buttons_view()->ShowSettingsButton(false);
  buttons_view()->ShowSnoozeButton(false);
  custom_size = 12;
  buttons_view()->SetButtonIconSize(custom_size);
  buttons_view()->ShowCloseButton(true);
  buttons_view()->ShowSettingsButton(true);
  buttons_view()->ShowSnoozeButton(true);

  // Verify that the control buttons are using the new custom size.
  EXPECT_TRUE(
      MatchesIcon(buttons_view()->close_button(),
                  features::IsRoundedIconsEnabled()
                      ? NotificationControlButtonsView::kDefaultCloseIcon
                      : NotificationControlButtonsView::kDefaultCloseOldIcon,
                  default_icon_color, custom_size));
  EXPECT_TRUE(
      MatchesIcon(buttons_view()->settings_button(),
                  features::IsRoundedIconsEnabled()
                      ? NotificationControlButtonsView::kDefaultSettingsIcon
                      : NotificationControlButtonsView::kDefaultSettingsOldIcon,
                  default_icon_color, custom_size));
  EXPECT_TRUE(
      MatchesIcon(buttons_view()->snooze_button(),
                  features::IsRoundedIconsEnabled()
                      ? NotificationControlButtonsView::kDefaultSnoozeIcon
                      : NotificationControlButtonsView::kDefaultSnoozeOldIcon,
                  default_icon_color, custom_size));
}

// Tests spacing between control buttons.
TEST_F(NotificationControlButtonsTest, BetweenButtonsSpacing) {
  // Show all the control buttons with default horizontal spacing.
  buttons_view()->ShowCloseButton(true);
  buttons_view()->ShowSettingsButton(true);
  buttons_view()->ShowSnoozeButton(true);

  // Verify that the control buttons use the default horizontal spacing.
  EXPECT_EQ(static_cast<views::BoxLayout*>(buttons_view()->GetLayoutManager())
                ->between_child_spacing(),
            NotificationControlButtonsView::kDefaultBetweenButtonSpacing);

  // Use a non-default amount of horizontal spacing.
  const int new_spacing =
      NotificationControlButtonsView::kDefaultBetweenButtonSpacing + 1;
  buttons_view()->SetBetweenButtonSpacing(new_spacing);

  // Verify that the control buttons use the new horizontal spacing, and that
  // the layout has been invalidated.
  EXPECT_EQ(static_cast<views::BoxLayout*>(buttons_view()->GetLayoutManager())
                ->between_child_spacing(),
            new_spacing);
  EXPECT_TRUE(buttons_view()->needs_layout());
}

}  // namespace message_center
