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

#ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_EVENT_SOURCE_H_
#define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_EVENT_SOURCE_H_

#include <deque>
#include <memory>
#include <optional>
#include <ostream>

#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/events/pointer_details.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/ozone/platform/wayland/common/wayland_util.h"
#include "ui/ozone/platform/wayland/host/wayland_input_method_context.h"
#include "ui/ozone/platform/wayland/host/wayland_keyboard.h"
#include "ui/ozone/platform/wayland/host/wayland_pointer.h"
#include "ui/ozone/platform/wayland/host/wayland_tablet_tool.h"
#include "ui/ozone/platform/wayland/host/wayland_touch.h"
#include "ui/ozone/platform/wayland/host/wayland_window_observer.h"
#include "ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures.h"
#include "ui/ozone/platform/wayland/host/wayland_zwp_relative_pointer_manager.h"

struct wl_display;

namespace gfx {
class Vector2dF;
}

namespace ui {

class WaylandConnection;
class WaylandWindow;
class WaylandWindowManager;
class WaylandEventWatcher;

// Wayland implementation of ui::PlatformEventSource. It polls for events
// through WaylandEventWatcher and centralizes the input and focus handling
// logic within Ozone Wayland backend. In order to do so, it also implements the
// input objects' delegate interfaces, which are the entry point of event data
// coming from input devices, e.g: wl_{keyboard,pointer,touch}, which are then
// pre-processed, translated into ui::Event instances and dispatched to the
// PlatformEvent system.
class WaylandEventSource : public PlatformEventSource,
                           public WaylandWindowObserver,
                           public WaylandKeyboard::Delegate,
                           public WaylandPointer::Delegate,
                           public WaylandTabletTool::Delegate,
                           public WaylandTouch::Delegate,
                           public WaylandZwpPointerGestures::Delegate,
                           public WaylandZwpRelativePointerManager::Delegate {
 public:
  static void ConvertEventToTarget(EventTarget* new_target,
                                   LocatedEvent* event);

  WaylandEventSource(wl_display* display,
                     wl_event_queue* event_queue,
                     WaylandWindowManager* window_manager,
                     WaylandConnection* connection,
                     bool use_threaded_polling = false);
  WaylandEventSource(const WaylandEventSource&) = delete;
  WaylandEventSource& operator=(const WaylandEventSource&) = delete;
  ~WaylandEventSource() override;

  int last_pointer_button_pressed() const {
    return last_pointer_button_pressed_;
  }

  int keyboard_modifiers() const { return keyboard_modifiers_; }

  // Sets a callback that that shutdowns the browser in case of unrecoverable
  // error. Called by WaylandEventWatcher.
  void SetShutdownCb(base::OnceCallback<void()> shutdown_cb);

  // Starts polling for events from the wayland connection file descriptor.
  // This method assumes connection is already established and input objects
  // are already bound and properly initialized.
  void StartProcessingEvents();

  // Forwards the call to WaylandEventWatcher, which calls
  // wl_display_roundtrip_queue.
  void RoundTripQueue();

  void DumpState(std::ostream& out) const;

  void ResetStateForTesting() override;

  // WaylandKeyboard::Delegate
  void OnKeyboardFocusChanged(WaylandWindow* window, bool focused) override;
  void OnKeyboardModifiersChanged(int modifiers) override;
  uint32_t OnKeyboardKeyEvent(EventType type,
                              DomCode dom_code,
                              bool repeat,
                              std::optional<uint32_t> serial,
                              base::TimeTicks timestamp,
                              int device_id,
                              WaylandKeyboard::KeyEventKind kind) override;
  void OnSynthesizedKeyPressEvent(WaylandWindow* window,
                                  DomCode dom_code,
                                  base::TimeTicks timestamp) override;

  // WaylandPointer::Delegate
  void OnPointerFocusChanged(WaylandWindow* window,
                             const gfx::PointF& location,
                             base::TimeTicks timestamp,
                             wl::EventDispatchPolicy dispatch_policy) override;
  void OnPointerButtonEvent(EventType evtype,
                            int changed_button,
                            base::TimeTicks timestamp,
                            WaylandWindow* window,
                            wl::EventDispatchPolicy dispatch_policy,
                            bool allow_release_of_unpressed_button,
                            bool is_synthesized) override;
  void OnPointerMotionEvent(const gfx::PointF& location,
                            base::TimeTicks timestamp,
                            wl::EventDispatchPolicy dispatch_policy,
                            bool is_synthesized) override;
  void OnPointerAxisEvent(const gfx::Vector2dF& offset,
                          std::optional<base::TimeTicks> timestamp,
                          bool is_high_resolution) override;
  void OnPointerFrameEvent() override;
  void OnPointerAxisSourceEvent(uint32_t axis_source) override;
  void OnPointerAxisStopEvent(uint32_t axis,
                              base::TimeTicks timestamp) override;
  const gfx::PointF& GetPointerLocation() const override;
  bool IsPointerButtonPressed(EventFlags button) const override;
  void ReleasePressedPointerButtons(WaylandWindow* window,
                                    base::TimeTicks timestamp) override;

  // WaylandTabletTool::Delegate:
  void OnTabletToolProximityIn(WaylandWindow* window,
                               const gfx::PointF& location,
                               const PointerDetails& details,
                               base::TimeTicks time) override;
  void OnTabletToolProximityOut(const PointerDetails& details,
                                base::TimeTicks time) override;
  void OnTabletToolMotion(const gfx::PointF& location,
                          const PointerDetails& details,
                          base::TimeTicks time) override;
  void OnTabletToolButton(int32_t button,
                          bool pressed,
                          const PointerDetails& details,
                          base::TimeTicks time) override;

  // WaylandTouch::Delegate
  void OnTouchPressEvent(WaylandWindow* window,
                         const gfx::PointF& location,
                         base::TimeTicks timestamp,
                         PointerId id,
                         wl::EventDispatchPolicy dispatch_policy) override;
  void OnTouchReleaseEvent(base::TimeTicks timestamp,
                           PointerId id,
                           wl::EventDispatchPolicy dispatch_policy,
                           bool is_synthesized) override;
  void OnTouchMotionEvent(const gfx::PointF& location,
                          base::TimeTicks timestamp,
                          PointerId id,
                          wl::EventDispatchPolicy dispatch_policy,
                          bool is_synthesized) override;
  void OnTouchCancelEvent() override;
  void OnTouchFrame() override;
  void OnTouchFocusChanged(WaylandWindow* window) override;
  std::vector<PointerId> GetActiveTouchPointIds() override;
  const WaylandWindow* GetTouchTarget(PointerId id) const override;

  // WaylandZwpPointerGesture::Delegate:
  void OnPinchEvent(EventType event_type,
                    const gfx::Vector2dF& delta,
                    base::TimeTicks timestamp,
                    int device_id,
                    std::optional<float> scale_delta) override;
  void OnHoldEvent(EventType event_type,
                   uint32_t finger_count,
                   base::TimeTicks timestamp,
                   int device_id,
                   wl::EventDispatchPolicy dispatch_policy) override;

  // WaylandZwpRelativePointerManager::Delegate:
  void SetRelativePointerMotionEnabled(bool enabled) override;
  void OnRelativePointerMotion(const gfx::Vector2dF& delta,
                               base::TimeTicks timestamp) override;

 private:
  struct PointerScrollData {
    PointerScrollData();
    PointerScrollData(const PointerScrollData& other);
    PointerScrollData(PointerScrollData&&);
    ~PointerScrollData();

    PointerScrollData& operator=(const PointerScrollData&);
    PointerScrollData& operator=(PointerScrollData&&);

    std::optional<uint32_t> axis_source;
    float dx = 0.0f;
    float dy = 0.0f;
    base::TimeDelta dt;
    bool is_axis_stop = false;
    bool is_high_resolution = false;
    std::optional<base::TimeTicks> timestamp;

    void DumpState(std::ostream& out) const;
  };

  struct FrameData {
    FrameData(const Event& event, base::OnceCallback<void()> completion_cb);
    FrameData(const FrameData& other) = delete;
    FrameData(FrameData&&) = delete;
    ~FrameData();

    std::unique_ptr<Event> event;
    base::OnceCallback<void()> completion_cb;

    void DumpState(std::ostream& out) const;
  };

  // PlatformEventSource:
  void OnDispatcherListChanged() override;

  // WaylandWindowObserver:
  void OnWindowRemoved(WaylandWindow* window) override;

  void HandleTouchFocusChange(WaylandWindow* window,
                              bool focused,
                              std::optional<PointerId> id = std::nullopt);
  bool ShouldUnsetTouchFocus(WaylandWindow* window, PointerId id);

  // Computes initial velocity of fling scroll based on recent frames.
  // The fling velocity is computed the same way as in libgestures.
  gfx::Vector2dF ComputeFlingVelocity();

  // Wrap up method to support async pointer down/up event processing.
  void OnPointerButtonEventInternal(base::WeakPtr<WaylandWindow> window,
                                    EventType type);

  // Wrap up method to support async touch release processing.
  void OnTouchReleaseInternal(PointerId id);

  // Ensure a valid instance of the PointerScrollData class member.
  void EnsurePointerScrollData(const std::optional<base::TimeTicks>& timestamp);

  void ProcessPointerScrollData();

  // Set the target to the event, then dispatch the event.
  void SetTargetAndDispatchEvent(Event* event, EventTarget* target);

  // Find and set the target for the touch event, then dispatch the event.
  void SetTouchTargetAndDispatchTouchEvent(TouchEvent* event);

  const raw_ptr<WaylandWindowManager> window_manager_;

  const raw_ptr<WaylandConnection> connection_;

  // Bitmask of EventFlags used to keep track of the the pointer state.
  int pointer_flags_ = 0;

  // Bitmask of EventFlags used to keep track of the last changed button.
  int last_pointer_button_pressed_ = 0;

  // Bitmask of EventFlags used to keep track of the the keyboard state.
  // See ui/events/event_constants.h for examples and details.
  int keyboard_modifiers_ = 0;

  // Last known pointer location.
  gfx::PointF pointer_location_;

  // Last known relative pointer location (used for pointer lock).
  std::optional<gfx::PointF> relative_pointer_location_;

  // Accumulates the scroll data within a pointer frame internal.
  std::optional<PointerScrollData> pointer_scroll_data_;

  // Latest set of pointer scroll data to compute fling scroll.
  // Front is newer, and back is older.
  std::deque<PointerScrollData> pointer_scroll_data_set_;

  // Time of the last pointer frame event.
  base::TimeTicks last_pointer_frame_time_;

  // Order set of touch events to be dispatching on the next
  // wl_touch::frame event.
  std::deque<std::unique_ptr<FrameData>> touch_frames_;

  // Order set of pointer events to be dispatching on the next
  // wl_pointer::frame event.
  std::deque<std::unique_ptr<FrameData>> pointer_frames_;

  // Set when pointer focus is lost and pressed buttons should be released at
  // the next frame event, unless focus is regained (e.g. moving between Chrome
  // surfaces like a window and its context menu popup).
  bool pending_focus_loss_release_ = false;

  // Status of fling.
  bool is_fling_active_ = false;

  // Map that keeps track of the current touch points, associating touch IDs to
  // to the surface/location where they happened.
  struct TouchPoint;
  base::flat_map<PointerId, std::unique_ptr<TouchPoint>> touch_points_;

  // The window that currently has tablet tool focus.
  base::WeakPtr<WaylandWindow> tablet_tool_focused_window_;

  // Last known tablet tool location.
  gfx::PointF tablet_tool_location_;

  // Bitmask of EventFlags used to keep track of the the tablet tool state.
  int tablet_tool_buttons_ = 0;

  // The window wl_pointer last entered. A tablet tool in proximity takes over
  // WaylandWindowManager's pointer focus, so that is not a reliable record of
  // where the mouse actually is; this is what proximity out hands back.
  base::WeakPtr<WaylandWindow> wl_pointer_focused_window_;

  std::unique_ptr<WaylandEventWatcher> event_watcher_;
};

}  // namespace ui
#endif  // UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_EVENT_SOURCE_H_
