// 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.

#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "components/input/native_web_keyboard_event.h"
#include "components/input/web_input_event_builders_mac.h"
#include "content/app_shim_remote_cocoa/render_widget_host_ns_view_bridge.h"
#include "content/app_shim_remote_cocoa/render_widget_host_ns_view_host_helper.h"
#include "content/app_shim_remote_cocoa/web_contents_ns_view_bridge.h"
#include "content/common/render_widget_host_ns_view.mojom.h"
#include "content/common/web_contents_ns_view_bridge.mojom.h"
#include "content/public/browser/remote_cocoa.h"
#include "content/public/browser/render_widget_host_view_mac_delegate.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
#include "third_party/blink/public/mojom/input/input_handler.mojom.h"
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#include "ui/base/cocoa/remote_accessibility_api.h"
#include "ui/events/cocoa/cocoa_event_utils.h"

namespace remote_cocoa {

namespace {

class RenderWidgetHostNSViewBridgeOwner
    : public RenderWidgetHostNSViewHostHelper {
 public:
  explicit RenderWidgetHostNSViewBridgeOwner(
      uint64_t view_id,
      mojo::PendingAssociatedRemote<mojom::RenderWidgetHostNSViewHost> client,
      mojo::PendingAssociatedReceiver<mojom::RenderWidgetHostNSView>
          bridge_receiver,
      remote_cocoa::RenderWidgetHostViewMacDelegateCallback
          responder_delegate_creation_callback)
      : host_(std::move(client),
              ui::WindowResizeHelperMac::Get()->task_runner()) {
    bridge_ = std::make_unique<remote_cocoa::RenderWidgetHostNSViewBridge>(
        host_.get(), this, view_id,
        base::BindOnce(&RenderWidgetHostNSViewBridgeOwner::OnMojoDisconnect,
                       base::Unretained(this)));
    bridge_->BindReceiver(std::move(bridge_receiver));
    host_.set_disconnect_handler(
        base::BindOnce(&RenderWidgetHostNSViewBridgeOwner::OnMojoDisconnect,
                       base::Unretained(this)));

    if (responder_delegate_creation_callback) {
      [bridge_->GetNSView()
          setResponderDelegate:std::move(responder_delegate_creation_callback)
                                   .Run()];
    }
  }

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

 private:
  NSAccessibilityRemoteUIElement* __strong remote_accessibility_element_;
  void OnMojoDisconnect() { delete this; }

  std::unique_ptr<blink::WebCoalescedInputEvent> TranslateEvent(
      const blink::WebInputEvent& web_event) {
    return std::make_unique<blink::WebCoalescedInputEvent>(
        web_event.Clone(), std::vector<std::unique_ptr<blink::WebInputEvent>>{},
        std::vector<std::unique_ptr<blink::WebInputEvent>>{},
        ui::LatencyInfo());
  }

  id GetAccessibilityElement() override {
    if (!remote_accessibility_element_) {
      base::ProcessId browser_pid = base::kNullProcessId;
      std::vector<uint8_t> element_token;
      host_->GetRenderWidgetAccessibilityToken(&browser_pid, &element_token);
      [NSAccessibilityRemoteUIElement
          registerRemoteUIProcessIdentifier:browser_pid];
      remote_accessibility_element_ =
          ui::RemoteAccessibility::GetRemoteElementFromToken(element_token);
    }
    return remote_accessibility_element_;
  }

  // RenderWidgetHostNSViewHostHelper implementation.
  id GetRootBrowserAccessibilityElement() override {
    // The RenderWidgetHostViewCocoa in the app shim process does not
    // participate in the accessibility tree. Only the instance in the browser
    // process does.
    return nil;
  }
  id GetFocusedBrowserAccessibilityElement() override {
    // Some ATs (e.g. Text To Speech) need to access the focused
    // element in the app shim process. We make these apps work by
    // returning the `accessibilityFocusedUIElement` of the BridgedContentView,
    // which is an NSAccessibilityRemoteUIElement in app shim process.
    NSView* bridgedContentView = [[bridge_->GetNSView() superview] superview];
    return [bridgedContentView accessibilityFocusedUIElement];
  }
  void SetAccessibilityWindow(NSWindow* window) override {
    host_->SetRemoteAccessibilityWindowToken(
        ui::RemoteAccessibility::GetTokenForLocalElement(window));
  }

  void ForwardKeyboardEvent(const input::NativeWebKeyboardEvent& key_event,
                            const ui::LatencyInfo& latency_info) override {
    ForwardKeyboardEventWithCommands(
        key_event, latency_info, std::vector<blink::mojom::EditCommandPtr>());
  }
  void ForwardKeyboardEventWithCommands(
      const input::NativeWebKeyboardEvent& key_event,
      const ui::LatencyInfo& latency_info,
      std::vector<blink::mojom::EditCommandPtr> edit_commands) override {
    const blink::WebKeyboardEvent* web_event =
        static_cast<const blink::WebKeyboardEvent*>(&key_event);
    std::unique_ptr<blink::WebCoalescedInputEvent> input_event =
        std::make_unique<blink::WebCoalescedInputEvent>(
            web_event->Clone(),
            std::vector<std::unique_ptr<blink::WebInputEvent>>{},
            std::vector<std::unique_ptr<blink::WebInputEvent>>{}, latency_info);
    std::vector<uint8_t> native_event_data =
        ui::EventToData(key_event.os_event.Get());
    host_->ForwardKeyboardEventWithCommands(
        std::move(input_event), native_event_data, key_event.skip_if_unhandled,
        std::move(edit_commands));
  }
  void RouteOrProcessMouseEvent(
      const blink::WebMouseEvent& web_event) override {
    host_->RouteOrProcessMouseEvent(TranslateEvent(web_event));
  }
  void RouteOrProcessTouchEvent(
      const blink::WebTouchEvent& web_event) override {
    host_->RouteOrProcessTouchEvent(TranslateEvent(web_event));
  }
  void RouteOrProcessWheelEvent(
      const blink::WebMouseWheelEvent& web_event) override {
    host_->RouteOrProcessWheelEvent(TranslateEvent(web_event));
  }
  void ForwardMouseEvent(const blink::WebMouseEvent& web_event) override {
    host_->ForwardMouseEvent(TranslateEvent(web_event));
  }
  void ForwardWheelEvent(const blink::WebMouseWheelEvent& web_event) override {
    host_->ForwardWheelEvent(TranslateEvent(web_event));
  }
  void PinchEvent(blink::WebGestureEvent pinch_event,
                  bool is_synthetically_injected) override {
    host_->PinchEvent(TranslateEvent(pinch_event), is_synthetically_injected);
  }
  void SmartMagnifyEvent(const blink::WebGestureEvent& web_event) override {
    host_->SmartMagnifyEvent(TranslateEvent(web_event));
  }

  mojo::AssociatedRemote<mojom::RenderWidgetHostNSViewHost> host_;
  std::unique_ptr<RenderWidgetHostNSViewBridge> bridge_;
};
}

void CreateRenderWidgetHostNSView(
    uint64_t view_id,
    mojo::ScopedInterfaceEndpointHandle host_handle,
    mojo::ScopedInterfaceEndpointHandle view_receiver_handle,
    RenderWidgetHostViewMacDelegateCallback
        responder_delegate_creation_callback) {
  // Cast from the stub interface to the mojom::RenderWidgetHostNSViewHost
  // and mojom::RenderWidgetHostNSView private interfaces.
  // TODO(ccameron): Remove the need for this cast.
  // https://crbug.com/888290
  mojo::PendingAssociatedRemote<mojom::RenderWidgetHostNSViewHost> host(
      std::move(host_handle), 0);

  // Create a RenderWidgetHostNSViewBridgeOwner. The resulting object will be
  // destroyed when its underlying pipe is closed.
  std::ignore = new RenderWidgetHostNSViewBridgeOwner(
      view_id, std::move(host),
      mojo::PendingAssociatedReceiver<mojom::RenderWidgetHostNSView>(
          std::move(view_receiver_handle)),
      std::move(responder_delegate_creation_callback));
}

void CreateWebContentsNSView(
    uint64_t view_id,
    mojo::ScopedInterfaceEndpointHandle host_handle,
    mojo::ScopedInterfaceEndpointHandle view_request_handle) {
  mojo::PendingAssociatedRemote<mojom::WebContentsNSViewHost> host(
      std::move(host_handle), 0);
  mojo::PendingAssociatedReceiver<mojom::WebContentsNSView> ns_view_receiver(
      std::move(view_request_handle));
  // Note that the resulting object will be destroyed when its underlying pipe
  // is closed.
  (new WebContentsNSViewBridge(view_id, std::move(host)))
      ->Bind(std::move(ns_view_receiver),
             ui::WindowResizeHelperMac::Get()->task_runner());
}

}  // namespace remote_cocoa
