// 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 <utility>
#include <vector>

#include "components/bubble/bubble_controller.h"
#include "components/bubble/bubble_delegate.h"

BubbleManager::BubbleManager() : manager_state_(SHOW_BUBBLES) {}

BubbleManager::~BubbleManager() {
  FinalizePendingRequests();
}

BubbleReference BubbleManager::ShowBubble(scoped_ptr<BubbleDelegate> bubble) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK_NE(manager_state_, ITERATING_BUBBLES);
  DCHECK(bubble);

  scoped_ptr<BubbleController> controller(
      new BubbleController(this, std::move(bubble)));

  BubbleReference bubble_ref = controller->AsWeakPtr();

  switch (manager_state_) {
    case SHOW_BUBBLES:
      controller->Show();
      controllers_.push_back(std::move(controller));
      break;
    case NO_MORE_BUBBLES:
      FOR_EACH_OBSERVER(BubbleManagerObserver, observers_,
                        OnBubbleNeverShown(controller->AsWeakPtr()));
      break;
    default:
      NOTREACHED();
      break;
  }

  return bubble_ref;
}

bool BubbleManager::CloseBubble(BubbleReference bubble,
                                BubbleCloseReason reason) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK_NE(manager_state_, ITERATING_BUBBLES);
  return CloseAllMatchingBubbles(bubble.get(), nullptr, reason);
}

void BubbleManager::CloseAllBubbles(BubbleCloseReason reason) {
  // The following close reasons don't make sense for multiple bubbles:
  DCHECK_NE(reason, BUBBLE_CLOSE_ACCEPTED);
  DCHECK_NE(reason, BUBBLE_CLOSE_CANCELED);
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK_NE(manager_state_, ITERATING_BUBBLES);
  CloseAllMatchingBubbles(nullptr, nullptr, reason);
}

void BubbleManager::UpdateAllBubbleAnchors() {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK_NE(manager_state_, ITERATING_BUBBLES);

  // Guard against bubbles being added or removed while iterating the bubbles.
  ManagerState original_state = manager_state_;
  manager_state_ = ITERATING_BUBBLES;
  for (auto controller : controllers_)
    controller->UpdateAnchorPosition();
  manager_state_ = original_state;
}

void BubbleManager::AddBubbleManagerObserver(BubbleManagerObserver* observer) {
  observers_.AddObserver(observer);
}

void BubbleManager::RemoveBubbleManagerObserver(
    BubbleManagerObserver* observer) {
  observers_.RemoveObserver(observer);
}

void BubbleManager::FinalizePendingRequests() {
  // Return if already "Finalized".
  if (manager_state_ == NO_MORE_BUBBLES)
    return;

  manager_state_ = NO_MORE_BUBBLES;
  CloseAllBubbles(BUBBLE_CLOSE_FORCED);
}

void BubbleManager::CloseBubblesOwnedBy(const content::RenderFrameHost* frame) {
  CloseAllMatchingBubbles(nullptr, frame, BUBBLE_CLOSE_FRAME_DESTROYED);
}

bool BubbleManager::CloseAllMatchingBubbles(
    BubbleController* bubble,
    const content::RenderFrameHost* owner,
    BubbleCloseReason reason) {
  // Specifying both an owning frame and a particular bubble to close doesn't
  // make sense. If we have a frame, all bubbles owned by that frame need to
  // have the opportunity to close. If we want to close a specific bubble, then
  // it should get the close event regardless of which frame owns it. On the
  // other hand, OR'ing the conditions needs a special case in order to be able
  // to close all bubbles, so we disallow passing both until a need appears.
  DCHECK(!bubble || !owner);

  ScopedVector<BubbleController> close_queue;

  // Guard against bubbles being added or removed while iterating the bubbles.
  ManagerState original_state = manager_state_;
  manager_state_ = ITERATING_BUBBLES;
  for (auto i = controllers_.begin(); i != controllers_.end();) {
    if ((!bubble || bubble == *i) && (!owner || (*i)->OwningFrameIs(owner)) &&
        (*i)->ShouldClose(reason)) {
      close_queue.push_back(*i);
      i = controllers_.weak_erase(i);
    } else {
      ++i;
    }
  }
  manager_state_ = original_state;

  for (auto controller : close_queue) {
    controller->DoClose(reason);

    FOR_EACH_OBSERVER(BubbleManagerObserver, observers_,
                      OnBubbleClosed(controller->AsWeakPtr(), reason));
  }

  return !close_queue.empty();
}