// Copyright 2015 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 "components/bubble/bubble_manager.h"

#include <memory>
#include <utility>

#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "components/bubble/bubble_controller.h"
#include "components/bubble/bubble_manager_mocks.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

// A "show chain" happens when a bubble decides to show another bubble on close.
// All bubbles must be iterated to handle a close event. If a bubble shows
// another bubble while it's being closed, the iterator can get messed up.
class ChainShowBubbleDelegate : public MockBubbleDelegate {
 public:
  // |chained_bubble| can be nullptr if not interested in getting a reference to
  // the chained bubble.
  ChainShowBubbleDelegate(BubbleManager* manager,
                          std::unique_ptr<BubbleDelegate> delegate,
                          BubbleReference* chained_bubble)
      : manager_(manager),
        delegate_(std::move(delegate)),
        chained_bubble_(chained_bubble),
        closed_(false) {
    EXPECT_CALL(*this, ShouldClose(testing::_)).WillOnce(testing::Return(true));
  }

  ~ChainShowBubbleDelegate() override { EXPECT_TRUE(closed_); }

  void DidClose(BubbleCloseReason reason) override {
    MockBubbleDelegate::DidClose(reason);
    BubbleReference ref = manager_->ShowBubble(std::move(delegate_));
    if (chained_bubble_)
      *chained_bubble_ = ref;
    closed_ = true;
  }

 private:
  BubbleManager* manager_;
  std::unique_ptr<BubbleDelegate> delegate_;
  BubbleReference* chained_bubble_;
  bool closed_;

  DISALLOW_COPY_AND_ASSIGN(ChainShowBubbleDelegate);
};

// A "close chain" happens when a close event is received while another close
// event is in progress. Ex: Closing the BubbleUi will hide the bubble, causing
// it to lose focus, which causes another close event. This test simulates this
// by sending a close event during the |DidClose| method of a BubbleDelegate.
// Similarly to the show chain, a close chain can mess up the iterator.
class ChainCloseBubbleDelegate : public MockBubbleDelegate {
 public:
  ChainCloseBubbleDelegate(BubbleManager* manager) : manager_(manager) {}

  ~ChainCloseBubbleDelegate() override {}

  void DidClose(BubbleCloseReason reason) override {
    manager_->CloseAllBubbles(BUBBLE_CLOSE_FOCUS_LOST);
  }

 private:
  BubbleManager* manager_;

  DISALLOW_COPY_AND_ASSIGN(ChainCloseBubbleDelegate);
};

class MockBubbleManagerObserver : public BubbleManager::BubbleManagerObserver {
 public:
  MockBubbleManagerObserver() {}
  ~MockBubbleManagerObserver() override {}

  MOCK_METHOD1(OnBubbleNeverShown, void(BubbleReference));
  MOCK_METHOD2(OnBubbleClosed, void(BubbleReference, BubbleCloseReason));

 private:
  DISALLOW_COPY_AND_ASSIGN(MockBubbleManagerObserver);
};

class BubbleManagerSubclass : public BubbleManager {
 public:
  using BubbleManager::CloseBubblesOwnedBy;
};

class BubbleManagerTest : public testing::Test {
 public:
  BubbleManagerTest();
  ~BubbleManagerTest() override {}

  void SetUp() override;
  void TearDown() override;

 protected:
  std::unique_ptr<BubbleManagerSubclass> manager_;

 private:
  DISALLOW_COPY_AND_ASSIGN(BubbleManagerTest);
};

BubbleManagerTest::BubbleManagerTest() {}

void BubbleManagerTest::SetUp() {
  testing::Test::SetUp();
  manager_.reset(new BubbleManagerSubclass);
}

void BubbleManagerTest::TearDown() {
  manager_.reset();
  testing::Test::TearDown();
}

TEST_F(BubbleManagerTest, ManagerShowsBubbleUi) {
  std::unique_ptr<MockBubbleDelegate> delegate = MockBubbleDelegate::Default();

  MockBubbleUi* bubble_ui = delegate->bubble_ui();
  EXPECT_CALL(*bubble_ui, Destroyed());
  EXPECT_CALL(*bubble_ui, Show(testing::_));
  EXPECT_CALL(*bubble_ui, Close());
  EXPECT_CALL(*bubble_ui, UpdateAnchorPosition()).Times(0);

  manager_->ShowBubble(std::move(delegate));
}

TEST_F(BubbleManagerTest, ManagerUpdatesBubbleUiAnchor) {
  std::unique_ptr<MockBubbleDelegate> delegate = MockBubbleDelegate::Default();

  MockBubbleUi* bubble_ui = delegate->bubble_ui();
  EXPECT_CALL(*bubble_ui, Destroyed());
  EXPECT_CALL(*bubble_ui, Show(testing::_));
  EXPECT_CALL(*bubble_ui, Close());
  EXPECT_CALL(*bubble_ui, UpdateAnchorPosition());

  manager_->ShowBubble(std::move(delegate));
  manager_->UpdateAllBubbleAnchors();
}

TEST_F(BubbleManagerTest, CloseOnReferenceInvalidatesReference) {
  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default());

  ASSERT_TRUE(ref->CloseBubble(BUBBLE_CLOSE_FOCUS_LOST));

  ASSERT_FALSE(ref);
}

TEST_F(BubbleManagerTest, CloseOnStubbornReferenceDoesNotInvalidate) {
  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Stubborn());

  ASSERT_FALSE(ref->CloseBubble(BUBBLE_CLOSE_FOCUS_LOST));

  ASSERT_TRUE(ref);
}

TEST_F(BubbleManagerTest, CloseInvalidatesReference) {
  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default());

  ASSERT_TRUE(manager_->CloseBubble(ref, BUBBLE_CLOSE_FOCUS_LOST));

  ASSERT_FALSE(ref);
}

TEST_F(BubbleManagerTest, CloseAllInvalidatesReference) {
  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default());

  manager_->CloseAllBubbles(BUBBLE_CLOSE_FOCUS_LOST);

  ASSERT_FALSE(ref);
}

TEST_F(BubbleManagerTest, DestroyInvalidatesReference) {
  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default());

  manager_.reset();

  ASSERT_FALSE(ref);
}

TEST_F(BubbleManagerTest, CloseInvalidatesStubbornReference) {
  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Stubborn());

  ASSERT_TRUE(manager_->CloseBubble(ref, BUBBLE_CLOSE_FORCED));

  ASSERT_FALSE(ref);
}

TEST_F(BubbleManagerTest, CloseAllInvalidatesStubbornReference) {
  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Stubborn());

  manager_->CloseAllBubbles(BUBBLE_CLOSE_FORCED);

  ASSERT_FALSE(ref);
}

TEST_F(BubbleManagerTest, DestroyInvalidatesStubbornReference) {
  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Stubborn());

  manager_.reset();

  ASSERT_FALSE(ref);
}

TEST_F(BubbleManagerTest, CloseDoesNotInvalidateStubbornReference) {
  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Stubborn());

  ASSERT_FALSE(manager_->CloseBubble(ref, BUBBLE_CLOSE_FOCUS_LOST));

  ASSERT_TRUE(ref);
}

TEST_F(BubbleManagerTest, CloseAllDoesNotInvalidateStubbornReference) {
  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Stubborn());

  manager_->CloseAllBubbles(BUBBLE_CLOSE_FOCUS_LOST);

  ASSERT_TRUE(ref);
}

TEST_F(BubbleManagerTest, CloseAllInvalidatesMixAppropriately) {
  BubbleReference stubborn_ref1 =
      manager_->ShowBubble(MockBubbleDelegate::Stubborn());
  BubbleReference normal_ref1 =
      manager_->ShowBubble(MockBubbleDelegate::Default());
  BubbleReference stubborn_ref2 =
      manager_->ShowBubble(MockBubbleDelegate::Stubborn());
  BubbleReference normal_ref2 =
      manager_->ShowBubble(MockBubbleDelegate::Default());
  BubbleReference stubborn_ref3 =
      manager_->ShowBubble(MockBubbleDelegate::Stubborn());
  BubbleReference normal_ref3 =
      manager_->ShowBubble(MockBubbleDelegate::Default());

  manager_->CloseAllBubbles(BUBBLE_CLOSE_FOCUS_LOST);

  ASSERT_TRUE(stubborn_ref1);
  ASSERT_TRUE(stubborn_ref2);
  ASSERT_TRUE(stubborn_ref3);
  ASSERT_FALSE(normal_ref1);
  ASSERT_FALSE(normal_ref2);
  ASSERT_FALSE(normal_ref3);
}

TEST_F(BubbleManagerTest, CloseBubbleShouldOnlylCloseSelf) {
  BubbleReference ref1 = manager_->ShowBubble(MockBubbleDelegate::Default());
  BubbleReference ref2 = manager_->ShowBubble(MockBubbleDelegate::Default());
  BubbleReference ref3 = manager_->ShowBubble(MockBubbleDelegate::Default());

  EXPECT_TRUE(ref1);
  EXPECT_TRUE(ref2);
  EXPECT_TRUE(ref3);

  ref2->CloseBubble(BUBBLE_CLOSE_FOCUS_LOST);

  EXPECT_TRUE(ref1);
  EXPECT_FALSE(ref2);
  EXPECT_TRUE(ref3);
}

TEST_F(BubbleManagerTest, CloseOwnedByShouldLeaveUnowned) {
  std::unique_ptr<MockBubbleDelegate> delegate1 = MockBubbleDelegate::Default();
  std::unique_ptr<MockBubbleDelegate> delegate2 = MockBubbleDelegate::Default();
  std::unique_ptr<MockBubbleDelegate> delegate3 = MockBubbleDelegate::Default();
  MockBubbleDelegate& delegate1_ref = *delegate1;
  MockBubbleDelegate& delegate2_ref = *delegate2;
  MockBubbleDelegate& delegate3_ref = *delegate3;
  BubbleReference ref1 = manager_->ShowBubble(std::move(delegate1));
  BubbleReference ref2 = manager_->ShowBubble(std::move(delegate2));
  BubbleReference ref3 = manager_->ShowBubble(std::move(delegate3));

  // These pointers are only compared for equality, not dereferenced.
  const content::RenderFrameHost* const frame1 =
      reinterpret_cast<const content::RenderFrameHost*>(&ref1);
  const content::RenderFrameHost* const frame2 =
      reinterpret_cast<const content::RenderFrameHost*>(&ref2);

  EXPECT_CALL(delegate1_ref, OwningFrame())
      .WillRepeatedly(testing::Return(frame1));
  EXPECT_CALL(delegate2_ref, OwningFrame())
      .WillRepeatedly(testing::Return(frame2));
  EXPECT_CALL(delegate3_ref, OwningFrame())
      .WillRepeatedly(testing::Return(nullptr));
  EXPECT_CALL(delegate1_ref, ShouldClose(BUBBLE_CLOSE_FRAME_DESTROYED))
      .WillOnce(testing::Return(true));

  manager_->CloseBubblesOwnedBy(frame1);
  EXPECT_FALSE(ref1);
  EXPECT_TRUE(ref2);
  EXPECT_TRUE(ref3);
}

TEST_F(BubbleManagerTest, UpdateAllShouldWorkWithoutBubbles) {
  // Manager shouldn't crash if bubbles have never been added.
  manager_->UpdateAllBubbleAnchors();

  // Add a bubble and close it.
  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default());
  ASSERT_TRUE(manager_->CloseBubble(ref, BUBBLE_CLOSE_FORCED));

  // Bubble should NOT get an update event because it's already closed.
  manager_->UpdateAllBubbleAnchors();
}

TEST_F(BubbleManagerTest, CloseAllShouldWorkWithoutBubbles) {
  // Manager shouldn't crash if bubbles have never been added.
  manager_->CloseAllBubbles(BUBBLE_CLOSE_FOCUS_LOST);

  // Add a bubble and close it.
  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default());
  ASSERT_TRUE(manager_->CloseBubble(ref, BUBBLE_CLOSE_FORCED));

  // Bubble should NOT get a close event because it's already closed.
  manager_->CloseAllBubbles(BUBBLE_CLOSE_FOCUS_LOST);
}

// This test validates that it's possible to show another bubble when
// |CloseBubble| is called.
TEST_F(BubbleManagerTest, AllowBubbleChainingOnClose) {
  BubbleReference chained_bubble;
  BubbleReference ref =
      manager_->ShowBubble(base::WrapUnique(new ChainShowBubbleDelegate(
          manager_.get(), MockBubbleDelegate::Default(), &chained_bubble)));
  ASSERT_FALSE(chained_bubble);  // Bubble not yet visible.
  ASSERT_TRUE(manager_->CloseBubble(ref, BUBBLE_CLOSE_FORCED));
  ASSERT_TRUE(chained_bubble);  // Bubble is now visible.
}

// This test validates that it's possible to show another bubble when
// |CloseAllBubbles| is called.
TEST_F(BubbleManagerTest, AllowBubbleChainingOnCloseAll) {
  BubbleReference chained_bubble;
  BubbleReference ref =
      manager_->ShowBubble(base::WrapUnique(new ChainShowBubbleDelegate(
          manager_.get(), MockBubbleDelegate::Default(), &chained_bubble)));
  ASSERT_FALSE(chained_bubble);  // Bubble not yet visible.
  manager_->CloseAllBubbles(BUBBLE_CLOSE_FORCED);
  ASSERT_TRUE(chained_bubble);  // Bubble is now visible.
}

// This test validates that a show chain will not happen in the destructor.
// While chaining is during the normal life span of the manager, it should NOT
// happen when the manager is being destroyed.
TEST_F(BubbleManagerTest, BubblesDoNotChainOnDestroy) {
  MockBubbleManagerObserver metrics;
  // |chained_delegate| should never be shown.
  EXPECT_CALL(metrics, OnBubbleNeverShown(testing::_));
  // The ChainShowBubbleDelegate should be closed when the manager is destroyed.
  EXPECT_CALL(metrics, OnBubbleClosed(testing::_, BUBBLE_CLOSE_FORCED));
  manager_->AddBubbleManagerObserver(&metrics);

  std::unique_ptr<MockBubbleDelegate> chained_delegate(new MockBubbleDelegate);
  EXPECT_CALL(*chained_delegate->bubble_ui(), Show(testing::_)).Times(0);
  EXPECT_CALL(*chained_delegate, ShouldClose(testing::_)).Times(0);
  EXPECT_CALL(*chained_delegate, DidClose(testing::_)).Times(0);

  manager_->ShowBubble(base::WrapUnique(new ChainShowBubbleDelegate(
      manager_.get(), std::move(chained_delegate), nullptr)));
  manager_.reset();
}

TEST_F(BubbleManagerTest, BubbleCloseReasonIsCalled) {
  MockBubbleManagerObserver metrics;
  EXPECT_CALL(metrics, OnBubbleNeverShown(testing::_)).Times(0);
  EXPECT_CALL(metrics, OnBubbleClosed(testing::_, BUBBLE_CLOSE_ACCEPTED));
  manager_->AddBubbleManagerObserver(&metrics);

  BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default());
  ref->CloseBubble(BUBBLE_CLOSE_ACCEPTED);

  // Destroy to verify no events are sent to |metrics| in destructor.
  manager_.reset();
}

// In a close chain, it should be possible for the bubble in the second close
// event to close.
TEST_F(BubbleManagerTest, BubbleCloseChainCloseClose) {
  std::unique_ptr<ChainCloseBubbleDelegate> closing_bubble(
      new ChainCloseBubbleDelegate(manager_.get()));
  EXPECT_CALL(*closing_bubble, ShouldClose(testing::_))
      .WillOnce(testing::Return(true));

  BubbleReference other_bubble_ref =
      manager_->ShowBubble(MockBubbleDelegate::Default());

  BubbleReference closing_bubble_ref =
      manager_->ShowBubble(std::move(closing_bubble));

  EXPECT_TRUE(other_bubble_ref);
  EXPECT_TRUE(closing_bubble_ref);

  closing_bubble_ref->CloseBubble(BUBBLE_CLOSE_ACCEPTED);

  EXPECT_FALSE(other_bubble_ref);
  EXPECT_FALSE(closing_bubble_ref);
}

// In a close chain, it should be possible for the bubble in the second close
// event to remain open because close is a request.
TEST_F(BubbleManagerTest, BubbleCloseChainCloseNoClose) {
  std::unique_ptr<ChainCloseBubbleDelegate> closing_bubble(
      new ChainCloseBubbleDelegate(manager_.get()));
  EXPECT_CALL(*closing_bubble, ShouldClose(testing::_))
      .WillOnce(testing::Return(true));

  BubbleReference other_bubble_ref =
      manager_->ShowBubble(MockBubbleDelegate::Stubborn());

  BubbleReference closing_bubble_ref =
      manager_->ShowBubble(std::move(closing_bubble));

  EXPECT_TRUE(other_bubble_ref);
  EXPECT_TRUE(closing_bubble_ref);

  closing_bubble_ref->CloseBubble(BUBBLE_CLOSE_ACCEPTED);

  EXPECT_TRUE(other_bubble_ref);
  EXPECT_FALSE(closing_bubble_ref);
}

// This test is a sanity check. |closing_bubble| will attempt to close all other
// bubbles if it's closed, but it doesn't want to close. Sending a close request
// should keep it open without starting a close chain.
TEST_F(BubbleManagerTest, BubbleCloseChainNoCloseNoClose) {
  std::unique_ptr<ChainCloseBubbleDelegate> closing_bubble(
      new ChainCloseBubbleDelegate(manager_.get()));
  EXPECT_CALL(*closing_bubble, ShouldClose(testing::_))
      .WillRepeatedly(testing::Return(false));

  BubbleReference other_bubble_ref =
      manager_->ShowBubble(MockBubbleDelegate::Default());

  BubbleReference closing_bubble_ref =
      manager_->ShowBubble(std::move(closing_bubble));

  EXPECT_TRUE(other_bubble_ref);
  EXPECT_TRUE(closing_bubble_ref);

  closing_bubble_ref->CloseBubble(BUBBLE_CLOSE_ACCEPTED);

  EXPECT_TRUE(other_bubble_ref);
  EXPECT_TRUE(closing_bubble_ref);
}

}  // namespace
