// Copyright 2014 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 "ui/views/widget/native_widget_mac.h"

#import <Cocoa/Cocoa.h>

#import "base/mac/mac_util.h"
#import "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "ui/base/test/ui_controls.h"
#import "ui/base/test/windowed_nsnotification_observer.h"
#import "ui/events/test/cocoa_test_event_utils.h"
#include "ui/views/bubble/bubble_dialog_delegate.h"
#include "ui/views/test/test_widget_observer.h"
#include "ui/views/test/views_interactive_ui_test_base.h"
#include "ui/views/test/widget_test.h"

namespace views {
namespace test {

// Tests for NativeWidgetMac that rely on global window manager state, and can
// not be parallelized.
class NativeWidgetMacInteractiveUITest
    : public WidgetTest,
      public ::testing::WithParamInterface<bool> {
 public:
  class Observer;

  NativeWidgetMacInteractiveUITest() = default;

  // WidgetTest:
  void SetUp() override {
    ViewsInteractiveUITestBase::InteractiveSetUp();
    WidgetTest::SetUp();
  }

  Widget* MakeWidget() {
    return GetParam() ? CreateTopLevelFramelessPlatformWidget()
                      : CreateTopLevelPlatformWidget();
  }

 protected:
  std::unique_ptr<Observer> observer_;
  int activation_count_ = 0;
  int deactivation_count_ = 0;

 private:
  DISALLOW_COPY_AND_ASSIGN(NativeWidgetMacInteractiveUITest);
};

class NativeWidgetMacInteractiveUITest::Observer : public TestWidgetObserver {
 public:
  Observer(NativeWidgetMacInteractiveUITest* parent, Widget* widget)
      : TestWidgetObserver(widget), parent_(parent) {}

  void OnWidgetActivationChanged(Widget* widget, bool active) override {
    if (active)
      parent_->activation_count_++;
    else
      parent_->deactivation_count_++;
  }

 private:
  NativeWidgetMacInteractiveUITest* parent_;

  DISALLOW_COPY_AND_ASSIGN(Observer);
};

// Test that showing a window causes it to attain global keyWindow status.
TEST_P(NativeWidgetMacInteractiveUITest, ShowAttainsKeyStatus) {
  Widget* widget = MakeWidget();
  observer_.reset(new Observer(this, widget));

  EXPECT_FALSE(widget->IsActive());
  EXPECT_EQ(0, activation_count_);
  {
    WidgetActivationWaiter wait_for_first_active(widget, true);
    widget->Show();
    wait_for_first_active.Wait();
  }
  EXPECT_TRUE(widget->IsActive());
  EXPECT_TRUE([widget->GetNativeWindow() isKeyWindow]);
  EXPECT_EQ(1, activation_count_);
  EXPECT_EQ(0, deactivation_count_);

  // Now check that losing and gaining key status due events outside of Widget
  // works correctly.
  Widget* widget2 = MakeWidget();  // Note: not observed.
  EXPECT_EQ(0, deactivation_count_);
  {
    WidgetActivationWaiter wait_for_deactivate(widget, false);
    widget2->Show();
    wait_for_deactivate.Wait();
  }
  EXPECT_EQ(1, deactivation_count_);
  EXPECT_FALSE(widget->IsActive());
  EXPECT_EQ(1, activation_count_);

  {
    WidgetActivationWaiter wait_for_external_activate(widget, true);
    [widget->GetNativeWindow() makeKeyAndOrderFront:nil];
    wait_for_external_activate.Wait();
  }
  EXPECT_TRUE(widget->IsActive());
  EXPECT_EQ(1, deactivation_count_);
  EXPECT_EQ(2, activation_count_);

  widget2->CloseNow();
  widget->CloseNow();

  EXPECT_EQ(1, deactivation_count_);
  EXPECT_EQ(2, activation_count_);
}

// Test that ShowInactive does not take keyWindow status.
TEST_P(NativeWidgetMacInteractiveUITest, ShowInactiveIgnoresKeyStatus) {
  Widget* widget = MakeWidget();

  base::scoped_nsobject<WindowedNSNotificationObserver> waiter(
      [[WindowedNSNotificationObserver alloc]
          initForNotification:NSWindowDidBecomeKeyNotification
                       object:widget->GetNativeWindow()]);

  EXPECT_FALSE(widget->IsVisible());
  EXPECT_FALSE([widget->GetNativeWindow() isVisible]);
  EXPECT_FALSE(widget->IsActive());
  EXPECT_FALSE([widget->GetNativeWindow() isKeyWindow]);
  widget->ShowInactive();

  EXPECT_TRUE(widget->IsVisible());
  EXPECT_TRUE([widget->GetNativeWindow() isVisible]);
  EXPECT_FALSE(widget->IsActive());
  EXPECT_FALSE([widget->GetNativeWindow() isKeyWindow]);

  // If the window were to become active, this would activate it.
  RunPendingMessages();
  EXPECT_FALSE(widget->IsActive());
  EXPECT_FALSE([widget->GetNativeWindow() isKeyWindow]);
  EXPECT_EQ(0, [waiter notificationCount]);

  // Activating the inactive widget should make it key, asynchronously.
  widget->Activate();
  [waiter wait];
  EXPECT_EQ(1, [waiter notificationCount]);
  EXPECT_TRUE(widget->IsActive());
  EXPECT_TRUE([widget->GetNativeWindow() isKeyWindow]);

  widget->CloseNow();
}

namespace {

// Show |widget| and wait for it to become the key window.
void ShowKeyWindow(Widget* widget) {
  base::scoped_nsobject<WindowedNSNotificationObserver> waiter(
      [[WindowedNSNotificationObserver alloc]
          initForNotification:NSWindowDidBecomeKeyNotification
                       object:widget->GetNativeWindow()]);
  widget->Show();
  EXPECT_TRUE([waiter wait]);
  EXPECT_TRUE([widget->GetNativeWindow() isKeyWindow]);
}

NSData* ViewAsTIFF(NSView* view) {
  NSBitmapImageRep* bitmap =
      [view bitmapImageRepForCachingDisplayInRect:[view bounds]];
  [view cacheDisplayInRect:[view bounds] toBitmapImageRep:bitmap];
  return [bitmap TIFFRepresentation];
}

class TestBubbleView : public BubbleDialogDelegateView {
 public:
  explicit TestBubbleView(Widget* parent) {
    SetAnchorView(parent->GetContentsView());
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(TestBubbleView);
};

}  // namespace

// Test that parent windows keep their traffic lights enabled when showing
// dialogs.
TEST_F(NativeWidgetMacInteractiveUITest, ParentWindowTrafficLights) {
  Widget* parent_widget = CreateTopLevelPlatformWidget();
  parent_widget->SetBounds(gfx::Rect(100, 100, 100, 100));
  ShowKeyWindow(parent_widget);

  NSWindow* parent = parent_widget->GetNativeWindow();
  EXPECT_TRUE([parent isMainWindow]);

  NSButton* button = [parent standardWindowButton:NSWindowCloseButton];
  EXPECT_TRUE(button);
  NSData* active_button_image = ViewAsTIFF(button);
  EXPECT_TRUE(active_button_image);

  // Pop open a bubble on the parent Widget. When the visibility of Bubbles with
  // an anchor View changes, BubbleDialogDelegateView::HandleVisibilityChanged()
  // updates Widget::SetAlwaysRenderAsActive(..) accordingly.
  ShowKeyWindow(BubbleDialogDelegateView::CreateBubble(
      new TestBubbleView(parent_widget)));

  // Ensure the button instance is still valid.
  EXPECT_EQ(button, [parent standardWindowButton:NSWindowCloseButton]);

  // Parent window should still be main, and have its traffic lights active.
  EXPECT_TRUE([parent isMainWindow]);
  EXPECT_FALSE([parent isKeyWindow]);

  // Enabled status doesn't actually change, but check anyway.
  EXPECT_TRUE([button isEnabled]);
  NSData* button_image_with_child = ViewAsTIFF(button);
  EXPECT_TRUE([active_button_image isEqualToData:button_image_with_child]);

  // Verify that activating some other random window does change the button.
  // When the bubble loses activation, it will dismiss itself and update
  // Widget::SetAlwaysRenderAsActive().
  Widget* other_widget = CreateTopLevelPlatformWidget();
  other_widget->SetBounds(gfx::Rect(200, 200, 100, 100));
  ShowKeyWindow(other_widget);
  EXPECT_FALSE([parent isMainWindow]);
  EXPECT_FALSE([parent isKeyWindow]);
  EXPECT_TRUE([button isEnabled]);
  NSData* inactive_button_image = ViewAsTIFF(button);
  EXPECT_FALSE([active_button_image isEqualToData:inactive_button_image]);

  other_widget->CloseNow();
  parent_widget->CloseNow();
}

// Test that bubble widgets are dismissed on right mouse down.
TEST_F(NativeWidgetMacInteractiveUITest, BubbleDismiss) {
  Widget* parent_widget = CreateTopLevelPlatformWidget();
  parent_widget->SetBounds(gfx::Rect(100, 100, 100, 100));
  ShowKeyWindow(parent_widget);

  Widget* bubble_widget =
      BubbleDialogDelegateView::CreateBubble(new TestBubbleView(parent_widget));
  ShowKeyWindow(bubble_widget);

  // First, test with LeftMouseDown in the parent window.
  NSEvent* mouse_down = cocoa_test_event_utils::LeftMouseDownAtPointInWindow(
      NSMakePoint(50, 50), parent_widget->GetNativeWindow());
  [NSApp sendEvent:mouse_down];
  EXPECT_TRUE(bubble_widget->IsClosed());

  bubble_widget =
      BubbleDialogDelegateView::CreateBubble(new TestBubbleView(parent_widget));
  ShowKeyWindow(bubble_widget);

  // Test with RightMouseDown in the parent window.
  mouse_down = cocoa_test_event_utils::RightMouseDownAtPointInWindow(
      NSMakePoint(50, 50), parent_widget->GetNativeWindow());
  [NSApp sendEvent:mouse_down];
  EXPECT_TRUE(bubble_widget->IsClosed());

  bubble_widget =
      BubbleDialogDelegateView::CreateBubble(new TestBubbleView(parent_widget));
  ShowKeyWindow(bubble_widget);

  // Test with RightMouseDown in the bubble (bubble should stay open).
  mouse_down = cocoa_test_event_utils::RightMouseDownAtPointInWindow(
      NSMakePoint(50, 50), bubble_widget->GetNativeWindow());
  [NSApp sendEvent:mouse_down];
  EXPECT_FALSE(bubble_widget->IsClosed());
  bubble_widget->CloseNow();

  // Test with RightMouseDown when set_close_on_deactivate(false).
  TestBubbleView* bubble_view = new TestBubbleView(parent_widget);
  bubble_view->set_close_on_deactivate(false);
  bubble_widget = BubbleDialogDelegateView::CreateBubble(bubble_view);
  ShowKeyWindow(bubble_widget);

  mouse_down = cocoa_test_event_utils::RightMouseDownAtPointInWindow(
      NSMakePoint(50, 50), parent_widget->GetNativeWindow());
  [NSApp sendEvent:mouse_down];
  EXPECT_FALSE(bubble_widget->IsClosed());

  parent_widget->CloseNow();
}

INSTANTIATE_TEST_CASE_P(NativeWidgetMacInteractiveUITestInstance,
                        NativeWidgetMacInteractiveUITest,
                        ::testing::Bool());

}  // namespace test
}  // namespace views
