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

#include "content/public/test/text_input_test_utils.h"
#include "base/memory/raw_ptr.h"

#include <memory>
#include <unordered_set>

#include "base/strings/utf_string_conversions.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/renderer_host/render_widget_host_view_base_observer.h"
#include "content/browser/renderer_host/text_input_manager.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/test_utils.h"
#include "ui/base/ime/ime_text_span.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/input_method_observer.h"
#include "ui/base/ime/mojom/text_input_state.mojom.h"

#if defined(USE_AURA)
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#endif

namespace ui {
class TextInputClient;
}

namespace content {

// This class is an observer of TextInputManager associated with the provided
// WebContents. An instance of this class is used in TextInputManagerTester to
// expose the required API for testing outside of content/.
class TextInputManagerTester::InternalObserver
    : public TextInputManager::Observer,
      public WebContentsObserver {
 public:
  InternalObserver(WebContents* web_contents, TextInputManagerTester* tester)
      : WebContentsObserver(web_contents),
        updated_view_(nullptr),
        text_input_state_changed_(false) {
    text_input_manager_ =
        static_cast<WebContentsImpl*>(web_contents)->GetTextInputManager();
    DCHECK(!!text_input_manager_);
    text_input_manager_->AddObserver(this);
  }

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

  ~InternalObserver() override {
    if (text_input_manager_)
      text_input_manager_->RemoveObserver(this);
  }

  void set_update_text_input_state_called_callback(
      base::RepeatingClosure callback) {
    update_text_input_state_callback_ = std::move(callback);
  }

  void set_on_selection_bounds_changed_callback(
      base::RepeatingClosure callback) {
    on_selection_bounds_changed_callback_ = std::move(callback);
  }

  void set_on_ime_composition_range_changed_callback(
      base::RepeatingClosure callback) {
    on_ime_composition_range_changed_callback_ = std::move(callback);
  }

  void set_on_text_selection_changed_callback(base::RepeatingClosure callback) {
    on_text_selection_changed_callback_ = std::move(callback);
  }

  const gfx::Range* last_composition_range() const {
    return last_composition_range_.get();
  }

  RenderWidgetHostView* GetUpdatedView() const { return updated_view_; }

  bool text_input_state_changed() const { return text_input_state_changed_; }

  TextInputManager* text_input_manager() const { return text_input_manager_; }

  // TextInputManager::Observer implementations.
  void OnUpdateTextInputStateCalled(TextInputManager* text_input_manager,
                                    RenderWidgetHostViewBase* updated_view,
                                    bool did_change_state) override {
    if (text_input_manager_ != text_input_manager)
      return;
    text_input_state_changed_ = did_change_state;
    updated_view_ = updated_view;
    if (!update_text_input_state_callback_.is_null())
      update_text_input_state_callback_.Run();
  }

  void OnSelectionBoundsChanged(
      TextInputManager* text_input_manager,
      RenderWidgetHostViewBase* updated_view) override {
    updated_view_ = updated_view;
    if (!on_selection_bounds_changed_callback_.is_null())
      on_selection_bounds_changed_callback_.Run();
  }

  void OnImeCompositionRangeChanged(
      TextInputManager* text_input_manager,
      RenderWidgetHostViewBase* updated_view) override {
    updated_view_ = updated_view;
    const gfx::Range* range =
        text_input_manager_->GetCompositionRangeForTesting();
    DCHECK(range);
    last_composition_range_ =
        std::make_unique<gfx::Range>(range->start(), range->end());
    if (!on_ime_composition_range_changed_callback_.is_null())
      on_ime_composition_range_changed_callback_.Run();
  }

  void OnTextSelectionChanged(TextInputManager* text_input_manager,
                              RenderWidgetHostViewBase* updated_view) override {
    updated_view_ = updated_view;
    if (!on_text_selection_changed_callback_.is_null())
      on_text_selection_changed_callback_.Run();
  }

  // WebContentsObserver implementation.
  void WebContentsDestroyed() override {
    DCHECK(text_input_manager_);
    text_input_manager_->RemoveObserver(this);
    text_input_manager_ = nullptr;
  }

 private:
  raw_ptr<TextInputManager> text_input_manager_;
  raw_ptr<RenderWidgetHostViewBase> updated_view_;
  bool text_input_state_changed_;
  std::unique_ptr<gfx::Range> last_composition_range_;
  base::RepeatingClosure update_text_input_state_callback_;
  base::RepeatingClosure on_selection_bounds_changed_callback_;
  base::RepeatingClosure on_ime_composition_range_changed_callback_;
  base::RepeatingClosure on_text_selection_changed_callback_;
};

// This class observes the lifetime of a RenderWidgetHostView. An instance of
// this class is used in TestRenderWidgetHostViewDestructionObserver to expose
// the required observer API for testing outside of content/.
class TestRenderWidgetHostViewDestructionObserver::InternalObserver
    : public RenderWidgetHostViewBaseObserver {
 public:
  InternalObserver(RenderWidgetHostViewBase* view)
      : view_(view), destroyed_(false) {
    view->AddObserver(this);
  }

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

  ~InternalObserver() override {
    if (view_)
      view_->RemoveObserver(this);
  }

  void Wait() {
    if (destroyed_)
      return;
    message_loop_runner_ = new content::MessageLoopRunner();
    message_loop_runner_->Run();
  }

 private:
  void OnRenderWidgetHostViewBaseDestroyed(
      RenderWidgetHostViewBase* view) override {
    DCHECK_EQ(view_, view);
    destroyed_ = true;
    view->RemoveObserver(this);
    view_ = nullptr;
    if (message_loop_runner_)
      message_loop_runner_->Quit();
  }

  raw_ptr<RenderWidgetHostViewBase> view_;
  bool destroyed_;
  scoped_refptr<MessageLoopRunner> message_loop_runner_;
};

#if defined(USE_AURA)
class InputMethodObserverAura : public TestInputMethodObserver,
                                public ui::InputMethodObserver {
 public:
  explicit InputMethodObserverAura(ui::InputMethod* input_method)
      : input_method_(input_method), text_input_client_(nullptr) {
    input_method_->AddObserver(this);
  }

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

  ~InputMethodObserverAura() override {
    if (input_method_)
      input_method_->RemoveObserver(this);
  }

  // TestInputMethodObserver implementations.
  ui::TextInputType GetTextInputTypeFromClient() override {
    if (text_input_client_)
      return text_input_client_->GetTextInputType();

    return ui::TEXT_INPUT_TYPE_NONE;
  }

  void SetOnVirtualKeyboardVisibilityChangedIfEnabledCallback(
      const base::RepeatingCallback<void(bool)>& callback) override {
    on_virtual_keyboard_visibility_changed_if_enabled_callback_ = callback;
  }

 private:
  void OnFocus() override {}
  void OnBlur() override {}
  void OnCaretBoundsChanged(const ui::TextInputClient* client) override {}
  void OnTextInputStateChanged(const ui::TextInputClient* client) override {}
  void OnInputMethodDestroyed(const ui::InputMethod* input_method) override {}

  void OnVirtualKeyboardVisibilityChangedIfEnabled(bool should_show) override {
    on_virtual_keyboard_visibility_changed_if_enabled_callback_.Run(
        should_show);
  }

  raw_ptr<ui::InputMethod> input_method_;
  raw_ptr<const ui::TextInputClient> text_input_client_;
  base::RepeatingCallback<void(bool)>
      on_virtual_keyboard_visibility_changed_if_enabled_callback_;
};
#endif

ui::TextInputType GetTextInputTypeFromWebContents(WebContents* web_contents) {
  const ui::mojom::TextInputState* state =
      static_cast<WebContentsImpl*>(web_contents)
          ->GetTextInputManager()
          ->GetTextInputState();
  return !!state ? state->type : ui::TEXT_INPUT_TYPE_NONE;
}

bool GetTextInputTypeForView(WebContents* web_contents,
                             RenderWidgetHostView* view,
                             ui::TextInputType* type) {
  TextInputManager* manager =
      static_cast<WebContentsImpl*>(web_contents)->GetTextInputManager();

  RenderWidgetHostViewBase* view_base =
      static_cast<RenderWidgetHostViewBase*>(view);
  if (!manager || !manager->IsRegistered(view_base))
    return false;

  *type = manager->GetTextInputTypeForViewForTesting(view_base);

  return true;
}

bool RequestCompositionInfoFromActiveWidget(WebContents* web_contents) {
  TextInputManager* manager =
      static_cast<WebContentsImpl*>(web_contents)->GetTextInputManager();
  if (!manager || !manager->GetActiveWidget())
    return false;

  manager->GetActiveWidget()->RequestCompositionUpdates(
      true /* immediate_request */, false /* monitor_updates */);
  return true;
}

bool DoesFrameHaveFocusedEditableElement(RenderFrameHost* frame) {
  return static_cast<RenderFrameHostImpl*>(frame)
      ->has_focused_editable_element();
}

void SendImeCommitTextToWidget(
    RenderWidgetHost* rwh,
    const std::u16string& text,
    const std::vector<ui::ImeTextSpan>& ime_text_spans,
    const gfx::Range& replacement_range,
    int relative_cursor_pos) {
  RenderWidgetHostImpl::From(rwh)->ImeCommitText(
      text, ime_text_spans, replacement_range, relative_cursor_pos);
}

void SendImeSetCompositionTextToWidget(
    RenderWidgetHost* rwh,
    const std::u16string& text,
    const std::vector<ui::ImeTextSpan>& ime_text_spans,
    const gfx::Range& replacement_range,
    int selection_start,
    int selection_end) {
  RenderWidgetHostImpl::From(rwh)->ImeSetComposition(
      text, ime_text_spans, replacement_range, selection_start, selection_end);
}

bool DestroyRenderWidgetHost(int32_t process_id,
                             int32_t local_root_routing_id) {
  RenderFrameHostImpl* rfh =
      RenderFrameHostImpl::FromID(process_id, local_root_routing_id);
  if (!rfh)
    return false;

  while (!rfh->is_local_root())
    rfh = rfh->GetParent();

  DCHECK(rfh->GetPage().IsPrimary())
      << "Only implemented for frames in a primary page";
  FrameTreeNode* ftn = rfh->frame_tree_node();
  if (rfh->IsOutermostMainFrame()) {
    WebContents::FromRenderFrameHost(rfh)->Close();
  } else {
    ftn->frame_tree().RemoveFrame(ftn);
  }
  return true;
}

size_t GetRegisteredViewsCountFromTextInputManager(WebContents* web_contents) {
  std::unordered_set<RenderWidgetHostView*> views;
  TextInputManager* manager =
      static_cast<WebContentsImpl*>(web_contents)->GetTextInputManager();
  return !!manager ? manager->GetRegisteredViewsCountForTesting() : 0;
}

RenderWidgetHostView* GetActiveViewFromWebContents(WebContents* web_contents) {
  return static_cast<WebContentsImpl*>(web_contents)
      ->GetTextInputManager()
      ->active_view_for_testing();
}

TextInputManagerTester::TextInputManagerTester(WebContents* web_contents)
    : observer_(new InternalObserver(web_contents, this)) {}

TextInputManagerTester::~TextInputManagerTester() {}

void TextInputManagerTester::SetUpdateTextInputStateCalledCallback(
    base::RepeatingClosure callback) {
  observer_->set_update_text_input_state_called_callback(std::move(callback));
}

void TextInputManagerTester::SetOnSelectionBoundsChangedCallback(
    base::RepeatingClosure callback) {
  observer_->set_on_selection_bounds_changed_callback(std::move(callback));
}

void TextInputManagerTester::SetOnImeCompositionRangeChangedCallback(
    base::RepeatingClosure callback) {
  observer_->set_on_ime_composition_range_changed_callback(std::move(callback));
}

void TextInputManagerTester::SetOnTextSelectionChangedCallback(
    base::RepeatingClosure callback) {
  observer_->set_on_text_selection_changed_callback(std::move(callback));
}

bool TextInputManagerTester::GetTextInputType(ui::TextInputType* type) {
  DCHECK(observer_->text_input_manager());
  const ui::mojom::TextInputState* state =
      observer_->text_input_manager()->GetTextInputState();
  if (!state)
    return false;
  *type = state->type;
  return true;
}

bool TextInputManagerTester::GetTextInputValue(std::string* value) {
  DCHECK(observer_->text_input_manager());
  const ui::mojom::TextInputState* state =
      observer_->text_input_manager()->GetTextInputState();
  if (!state)
    return false;
  if (state->value)
    *value = base::UTF16ToUTF8(*state->value);
  else
    *value = std::string();
  return true;
}

bool TextInputManagerTester::GetTextInputVkPolicy(
    ui::mojom::VirtualKeyboardPolicy* vk_policy) {
  DCHECK(observer_->text_input_manager());
  const ui::mojom::TextInputState* state =
      observer_->text_input_manager()->GetTextInputState();
  if (!state)
    return false;
  *vk_policy = state->vk_policy;
  return true;
}

bool TextInputManagerTester::GetTextInputVkVisibilityRequest(
    ui::mojom::VirtualKeyboardVisibilityRequest* last_vk_visibility_request) {
  DCHECK(observer_->text_input_manager());
  const ui::mojom::TextInputState* state =
      observer_->text_input_manager()->GetTextInputState();
  if (!state)
    return false;
  *last_vk_visibility_request = state->last_vk_visibility_request;
  return true;
}

bool TextInputManagerTester::GetTextInputShowImeIfNeeded(
    bool* show_ime_if_needed) {
  DCHECK(observer_->text_input_manager());
  const ui::mojom::TextInputState* state =
      observer_->text_input_manager()->GetTextInputState();
  if (!state)
    return false;
  *show_ime_if_needed = state->show_ime_if_needed;
  return true;
}

const RenderWidgetHostView* TextInputManagerTester::GetActiveView() {
  DCHECK(observer_->text_input_manager());
  return observer_->text_input_manager()->active_view_for_testing();
}

RenderWidgetHostView* TextInputManagerTester::GetUpdatedView() {
  return observer_->GetUpdatedView();
}

bool TextInputManagerTester::GetCurrentTextSelectionLength(size_t* length) {
  DCHECK(observer_->text_input_manager());

  if (!observer_->text_input_manager()->GetActiveWidget())
    return false;

  *length = observer_->text_input_manager()->GetTextSelection()->text().size();
  return true;
}

bool TextInputManagerTester::GetLastCompositionRangeLength(uint32_t* length) {
  if (!observer_->last_composition_range())
    return false;
  *length = observer_->last_composition_range()->length();
  return true;
}

bool TextInputManagerTester::IsTextInputStateChanged() {
  return observer_->text_input_state_changed();
}

TextInputManagerObserverBase::TextInputManagerObserverBase(
    content::WebContents* web_contents)
    : tester_(std::make_unique<TextInputManagerTester>(web_contents)),
      success_(false) {}

TextInputManagerObserverBase::~TextInputManagerObserverBase() = default;

void TextInputManagerObserverBase::Wait() {
  if (success_)
    return;

  base::RunLoop loop;
  quit_ = loop.QuitClosure();
  loop.Run();
}

void TextInputManagerObserverBase::OnSuccess() {
  success_ = true;
  if (quit_)
    std::move(quit_).Run();

  // By deleting |tester_| we make sure that the internal observer used in
  // content/ is removed from the observer list of TextInputManager.
  tester_.reset();
}

TextInputManagerValueObserver::TextInputManagerValueObserver(
    content::WebContents* web_contents,
    const std::string& expected_value)
    : TextInputManagerObserverBase(web_contents),
      expected_value_(expected_value) {
  tester()->SetUpdateTextInputStateCalledCallback(base::BindRepeating(
      &TextInputManagerValueObserver::VerifyValue, base::Unretained(this)));
}

void TextInputManagerValueObserver::VerifyValue() {
  std::string value;
  if (tester()->GetTextInputValue(&value) && expected_value_ == value)
    OnSuccess();
}

TextInputManagerTypeObserver::TextInputManagerTypeObserver(
    content::WebContents* web_contents,
    ui::TextInputType expected_type)
    : TextInputManagerObserverBase(web_contents),
      expected_type_(expected_type) {
  tester()->SetUpdateTextInputStateCalledCallback(base::BindRepeating(
      &TextInputManagerTypeObserver::VerifyType, base::Unretained(this)));
}

void TextInputManagerTypeObserver::VerifyType() {
  ui::TextInputType type =
      tester()->GetTextInputType(&type) ? type : ui::TEXT_INPUT_TYPE_NONE;
  if (expected_type_ == type)
    OnSuccess();
}

TestRenderWidgetHostViewDestructionObserver::
    TestRenderWidgetHostViewDestructionObserver(RenderWidgetHostView* view)
    : observer_(
          new InternalObserver(static_cast<RenderWidgetHostViewBase*>(view))) {}

TestRenderWidgetHostViewDestructionObserver::
    ~TestRenderWidgetHostViewDestructionObserver() {}

void TestRenderWidgetHostViewDestructionObserver::Wait() {
  observer_->Wait();
}

TextInputStateSender::TextInputStateSender(RenderWidgetHostView* view)
    : text_input_state_(ui::mojom::TextInputState::New()),
      view_(static_cast<RenderWidgetHostViewBase*>(view)) {}

TextInputStateSender::~TextInputStateSender() {}

void TextInputStateSender::Send() {
  if (view_)
    view_->TextInputStateChanged(*text_input_state_);
}

void TextInputStateSender::SetFromCurrentState() {
  if (view_) {
    const ui::mojom::TextInputState* state =
        RenderWidgetHostImpl::From(view_->GetRenderWidgetHost())
            ->delegate()
            ->GetTextInputManager()
            ->GetTextInputState();
    text_input_state_ = state->Clone();
  }
}

void TextInputStateSender::SetType(ui::TextInputType type) {
  text_input_state_->type = type;
}

void TextInputStateSender::SetMode(ui::TextInputMode mode) {
  text_input_state_->mode = mode;
}

void TextInputStateSender::SetFlags(int flags) {
  text_input_state_->flags = flags;
}

void TextInputStateSender::SetCanComposeInline(bool can_compose_inline) {
  text_input_state_->can_compose_inline = can_compose_inline;
}

void TextInputStateSender::SetShowVirtualKeyboardIfEnabled(
    bool show_ime_if_needed) {
  text_input_state_->show_ime_if_needed = show_ime_if_needed;
}

#if defined(USE_AURA)
void TextInputStateSender::SetLastPointerType(
    ui::EventPointerType last_pointer_type) {
  RenderWidgetHostViewAura* rwhva =
      static_cast<RenderWidgetHostViewAura*>(view_);
  rwhva->SetLastPointerType(last_pointer_type);
}
#endif

TestInputMethodObserver::TestInputMethodObserver() {}

TestInputMethodObserver::~TestInputMethodObserver() {}

// static
std::unique_ptr<TestInputMethodObserver> TestInputMethodObserver::Create(
    WebContents* web_contents) {
  std::unique_ptr<TestInputMethodObserver> observer;

#if defined(USE_AURA)
  RenderWidgetHostViewAura* view = static_cast<RenderWidgetHostViewAura*>(
      web_contents->GetRenderWidgetHostView());
  observer = std::make_unique<InputMethodObserverAura>(view->GetInputMethod());
#endif
  return observer;
}

}  // namespace content