// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/web/js_messaging/web_frames_manager_impl.h"

#import "base/strings/string_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "ios/web/public/js_messaging/web_frame.h"

namespace web {

#pragma mark - WebFramesManagerImpl

WebFramesManagerImpl::WebFramesManagerImpl() : weak_factory_(this) {}

WebFramesManagerImpl::~WebFramesManagerImpl() = default;

bool WebFramesManagerImpl::AddFrame(std::unique_ptr<WebFrame> frame) {
  DCHECK(frame);
  DCHECK(!frame->GetFrameId().empty());
  if (frame->IsMainFrame()) {
    if (main_web_frame_) {
      // A main frame is already registered, ignore duplicate registration
      // message.
      return false;
    }
    main_web_frame_ = frame.get();
  }
  DCHECK(web_frames_.count(frame->GetFrameId()) == 0);
  std::string frame_id = frame->GetFrameId();
  WebFrame* added_frame = frame.get();
  web_frames_[frame_id] = std::move(frame);

  for (auto& observer : observers_) {
    observer.WebFrameBecameAvailable(this, added_frame);
  }
  return true;
}

void WebFramesManagerImpl::RemoveFrameWithId(const std::string& frame_id) {
  for (auto& observer : observers_) {
    observer.WebFrameBecameUnavailable(this, frame_id);
  }

  DCHECK(!frame_id.empty());
  // If the removed frame is a main frame, it should be the current one.
  DCHECK(web_frames_.count(frame_id) == 0 ||
         !web_frames_[frame_id]->IsMainFrame() ||
         main_web_frame_ == web_frames_[frame_id].get());
  if (web_frames_.count(frame_id) == 0) {
    return;
  }
  if (main_web_frame_ && main_web_frame_->GetFrameId() == frame_id) {
    main_web_frame_ = nullptr;
  }
  // The web::WebFrame destructor can call some callbacks that will try to
  // access the frame via GetFrameWithId. This can lead to a reentrancy issue
  // on `web_frames_`.
  // To avoid this issue, keep the frame alive during the map operation and
  // destroy it after.
  auto keep_frame_alive = std::move(web_frames_[frame_id]);
  web_frames_.erase(frame_id);
}

void WebFramesManagerImpl::RemoveAllWebFrames() {
  std::set<std::string> frame_ids;
  for (const auto& it : web_frames_) {
    frame_ids.insert(it.first);
  }
  for (std::string frame_id : frame_ids) {
    RemoveFrameWithId(frame_id);
  }
}

#pragma mark - WebFramesManager

void WebFramesManagerImpl::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void WebFramesManagerImpl::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

std::set<WebFrame*> WebFramesManagerImpl::GetAllWebFrames() {
  std::set<WebFrame*> frames;
  for (const auto& it : web_frames_) {
    frames.insert(it.second.get());
  }
  return frames;
}

WebFrame* WebFramesManagerImpl::GetMainWebFrame() {
  return main_web_frame_;
}

WebFrame* WebFramesManagerImpl::GetFrameWithId(const std::string& frame_id) {
  if (frame_id.empty()) {
    return nullptr;
  }

  auto web_frames_it = web_frames_.find(base::ToLowerASCII(frame_id));
  return web_frames_it == web_frames_.end() ? nullptr
                                            : web_frames_it->second.get();
}

}  // namespace web
