// Copyright 2014 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 "ui/events/ozone/evdev/event_factory_evdev.h"

#include <utility>

#include "base/bind.h"
#include "base/task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/worker_pool.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/event_utils.h"
#include "ui/events/ozone/device/device_event.h"
#include "ui/events/ozone/device/device_manager.h"
#include "ui/events/ozone/evdev/cursor_delegate_evdev.h"
#include "ui/events/ozone/evdev/input_controller_evdev.h"
#include "ui/events/ozone/evdev/input_device_factory_evdev.h"
#include "ui/events/ozone/evdev/input_device_factory_evdev_proxy.h"
#include "ui/events/ozone/evdev/input_injector_evdev.h"
#include "ui/events/ozone/evdev/touch_evdev_types.h"

namespace ui {

namespace {

// Thread safe dispatcher proxy for EventFactoryEvdev.
//
// This is used on the device I/O thread for dispatching to UI.
class ProxyDeviceEventDispatcher : public DeviceEventDispatcherEvdev {
 public:
  ProxyDeviceEventDispatcher(
      scoped_refptr<base::SingleThreadTaskRunner> ui_thread_runner,
      base::WeakPtr<EventFactoryEvdev> event_factory_evdev)
      : ui_thread_runner_(ui_thread_runner),
        event_factory_evdev_(event_factory_evdev) {}
  ~ProxyDeviceEventDispatcher() override {}

  // DeviceEventDispatcher:
  void DispatchKeyEvent(const KeyEventParams& params) override {
    ui_thread_runner_->PostTask(FROM_HERE,
                                base::Bind(&EventFactoryEvdev::DispatchKeyEvent,
                                           event_factory_evdev_, params));
  }

  void DispatchMouseMoveEvent(const MouseMoveEventParams& params) override {
    ui_thread_runner_->PostTask(
        FROM_HERE, base::Bind(&EventFactoryEvdev::DispatchMouseMoveEvent,
                              event_factory_evdev_, params));
  }

  void DispatchMouseButtonEvent(const MouseButtonEventParams& params) override {
    ui_thread_runner_->PostTask(
        FROM_HERE, base::Bind(&EventFactoryEvdev::DispatchMouseButtonEvent,
                              event_factory_evdev_, params));
  }

  void DispatchMouseWheelEvent(const MouseWheelEventParams& params) override {
    ui_thread_runner_->PostTask(
        FROM_HERE, base::Bind(&EventFactoryEvdev::DispatchMouseWheelEvent,
                              event_factory_evdev_, params));
  }

  void DispatchPinchEvent(const PinchEventParams& params) override {
    ui_thread_runner_->PostTask(
        FROM_HERE, base::Bind(&EventFactoryEvdev::DispatchPinchEvent,
                              event_factory_evdev_, params));
  }

  void DispatchScrollEvent(const ScrollEventParams& params) override {
    ui_thread_runner_->PostTask(
        FROM_HERE, base::Bind(&EventFactoryEvdev::DispatchScrollEvent,
                              event_factory_evdev_, params));
  }

  void DispatchTouchEvent(const TouchEventParams& params) override {
    ui_thread_runner_->PostTask(
        FROM_HERE, base::Bind(&EventFactoryEvdev::DispatchTouchEvent,
                              event_factory_evdev_, params));
  }

  void DispatchKeyboardDevicesUpdated(
      const std::vector<KeyboardDevice>& devices) override {
    ui_thread_runner_->PostTask(
        FROM_HERE,
        base::Bind(&EventFactoryEvdev::DispatchKeyboardDevicesUpdated,
                   event_factory_evdev_, devices));
  }
  void DispatchTouchscreenDevicesUpdated(
      const std::vector<TouchscreenDevice>& devices) override {
    ui_thread_runner_->PostTask(
        FROM_HERE,
        base::Bind(&EventFactoryEvdev::DispatchTouchscreenDevicesUpdated,
                   event_factory_evdev_, devices));
  }
  void DispatchMouseDevicesUpdated(
      const std::vector<InputDevice>& devices) override {
    ui_thread_runner_->PostTask(
        FROM_HERE, base::Bind(&EventFactoryEvdev::DispatchMouseDevicesUpdated,
                              event_factory_evdev_, devices));
  }
  void DispatchTouchpadDevicesUpdated(
      const std::vector<InputDevice>& devices) override {
    ui_thread_runner_->PostTask(
        FROM_HERE,
        base::Bind(&EventFactoryEvdev::DispatchTouchpadDevicesUpdated,
                   event_factory_evdev_, devices));
  }
  void DispatchDeviceListsComplete() override {
    ui_thread_runner_->PostTask(
        FROM_HERE, base::Bind(&EventFactoryEvdev::DispatchDeviceListsComplete,
                              event_factory_evdev_));
  }

 private:
  scoped_refptr<base::SingleThreadTaskRunner> ui_thread_runner_;
  base::WeakPtr<EventFactoryEvdev> event_factory_evdev_;
};

}  // namespace

EventFactoryEvdev::EventFactoryEvdev(CursorDelegateEvdev* cursor,
                                     DeviceManager* device_manager,
                                     KeyboardLayoutEngine* keyboard_layout)
    : device_manager_(device_manager),
      keyboard_(&modifiers_,
                keyboard_layout,
                base::Bind(&EventFactoryEvdev::DispatchUiEvent,
                           base::Unretained(this))),
      cursor_(cursor),
      input_controller_(&keyboard_, &button_map_),
      touch_id_generator_(0),
      weak_ptr_factory_(this) {
  DCHECK(device_manager_);
}

EventFactoryEvdev::~EventFactoryEvdev() {
}

void EventFactoryEvdev::Init() {
  DCHECK(!initialized_);

  StartThread();

  initialized_ = true;
}

scoped_ptr<SystemInputInjector> EventFactoryEvdev::CreateSystemInputInjector() {
  // Use forwarding dispatcher for the injector rather than dispatching
  // directly. We cannot assume it is safe to (re-)enter ui::Event dispatch
  // synchronously from the injection point.
  scoped_ptr<DeviceEventDispatcherEvdev> proxy_dispatcher(
      new ProxyDeviceEventDispatcher(base::ThreadTaskRunnerHandle::Get(),
                                     weak_ptr_factory_.GetWeakPtr()));
  return make_scoped_ptr(
      new InputInjectorEvdev(std::move(proxy_dispatcher), cursor_));
}

void EventFactoryEvdev::DispatchKeyEvent(const KeyEventParams& params) {
  TRACE_EVENT1("evdev", "EventFactoryEvdev::DispatchKeyEvent", "device",
               params.device_id);
  keyboard_.OnKeyChange(params.code, params.down, params.suppress_auto_repeat,
                        params.timestamp, params.device_id);
}

void EventFactoryEvdev::DispatchMouseMoveEvent(
    const MouseMoveEventParams& params) {
  TRACE_EVENT1("evdev", "EventFactoryEvdev::DispatchMouseMoveEvent", "device",
               params.device_id);
  MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(),
                   params.timestamp, modifiers_.GetModifierFlags(),
                   /* changed_button_flags */ 0);
  event.set_location_f(params.location);
  event.set_root_location_f(params.location);
  event.set_source_device_id(params.device_id);
  event.set_pointer_details(params.pointer_details);
  DispatchUiEvent(&event);
}

void EventFactoryEvdev::DispatchMouseButtonEvent(
    const MouseButtonEventParams& params) {
  TRACE_EVENT1("evdev", "EventFactoryEvdev::DispatchMouseButtonEvent", "device",
               params.device_id);

  // Mouse buttons can be remapped, touchpad taps & clicks cannot.
  unsigned int button = params.button;
  if (params.allow_remap)
    button = button_map_.GetMappedButton(button);

  int modifier = EVDEV_MODIFIER_NONE;
  switch (button) {
    case BTN_LEFT:
      modifier = EVDEV_MODIFIER_LEFT_MOUSE_BUTTON;
      break;
    case BTN_RIGHT:
      modifier = EVDEV_MODIFIER_RIGHT_MOUSE_BUTTON;
      break;
    case BTN_MIDDLE:
      modifier = EVDEV_MODIFIER_MIDDLE_MOUSE_BUTTON;
      break;
    case BTN_BACK:
      modifier = EVDEV_MODIFIER_BACK_MOUSE_BUTTON;
      break;
    case BTN_FORWARD:
      modifier = EVDEV_MODIFIER_FORWARD_MOUSE_BUTTON;
      break;
    default:
      return;
  }

  int flag = modifiers_.GetEventFlagFromModifier(modifier);
  bool was_down = modifiers_.GetModifierFlags() & flag;
  modifiers_.UpdateModifier(modifier, params.down);
  bool down = modifiers_.GetModifierFlags() & flag;

  // Suppress nested clicks. EventModifiersEvdev counts presses, we only
  // dispatch an event on 0-1 (first press) and 1-0 (last release) transitions.
  if (down == was_down)
    return;

  MouseEvent event(params.down ? ui::ET_MOUSE_PRESSED : ui::ET_MOUSE_RELEASED,
                   gfx::Point(), gfx::Point(), params.timestamp,
                   modifiers_.GetModifierFlags() | flag,
                   /* changed_button_flags */ flag);
  event.set_location_f(params.location);
  event.set_root_location_f(params.location);
  event.set_source_device_id(params.device_id);
  event.set_pointer_details(params.pointer_details);
  DispatchUiEvent(&event);
}

void EventFactoryEvdev::DispatchMouseWheelEvent(
    const MouseWheelEventParams& params) {
  TRACE_EVENT1("evdev", "EventFactoryEvdev::DispatchMouseWheelEvent", "device",
               params.device_id);
  MouseWheelEvent event(params.delta, gfx::Point(), gfx::Point(),
                        params.timestamp, modifiers_.GetModifierFlags(),
                        0 /* changed_button_flags */);
  event.set_location_f(params.location);
  event.set_root_location_f(params.location);
  event.set_source_device_id(params.device_id);
  DispatchUiEvent(&event);
}

void EventFactoryEvdev::DispatchPinchEvent(const PinchEventParams& params) {
  TRACE_EVENT1("evdev", "EventFactoryEvdev::DispatchPinchEvent", "device",
               params.device_id);
  GestureEventDetails details(params.type);
  details.set_scale(params.scale);
  GestureEvent event(params.location.x(), params.location.y(), 0,
                     params.timestamp, details);
  event.set_source_device_id(params.device_id);
  DispatchUiEvent(&event);
}

void EventFactoryEvdev::DispatchScrollEvent(const ScrollEventParams& params) {
  TRACE_EVENT1("evdev", "EventFactoryEvdev::DispatchScrollEvent", "device",
               params.device_id);
  ScrollEvent event(params.type, gfx::Point(), params.timestamp,
                    modifiers_.GetModifierFlags(), params.delta.x(),
                    params.delta.y(), params.ordinal_delta.x(),
                    params.ordinal_delta.y(), params.finger_count);
  event.set_location_f(params.location);
  event.set_root_location_f(params.location);
  event.set_source_device_id(params.device_id);
  DispatchUiEvent(&event);
}

void EventFactoryEvdev::DispatchTouchEvent(const TouchEventParams& params) {
  TRACE_EVENT1("evdev", "EventFactoryEvdev::DispatchTouchEvent", "device",
               params.device_id);

  float x = params.location.x();
  float y = params.location.y();
  double radius_x = params.pointer_details.radius_x;
  double radius_y = params.pointer_details.radius_y;

  // Transform the event to align touches to the image based on display mode.
  DeviceDataManager::GetInstance()->ApplyTouchTransformer(params.device_id, &x,
                                                          &y);
  DeviceDataManager::GetInstance()->ApplyTouchRadiusScale(params.device_id,
                                                          &radius_x);
  DeviceDataManager::GetInstance()->ApplyTouchRadiusScale(params.device_id,
                                                          &radius_y);

  PointerDetails details = params.pointer_details;
  details.radius_x = radius_x;
  details.radius_y = radius_y;

  // params.slot is guaranteed to be < kNumTouchEvdevSlots.
  int touch_id = touch_id_generator_.GetGeneratedID(
      params.device_id * kNumTouchEvdevSlots + params.slot);
  TouchEvent touch_event(
      params.type, gfx::Point(), modifiers_.GetModifierFlags(), touch_id,
      params.timestamp, /* radius_x */ 0.f, /* radius_y */ 0.f,
      /* angle */ 0.f, /* force */ 0.f);
  touch_event.set_location_f(gfx::PointF(x, y));
  touch_event.set_root_location_f(gfx::PointF(x, y));
  touch_event.set_source_device_id(params.device_id);
  touch_event.set_pointer_details(details);
  DispatchUiEvent(&touch_event);

  if (params.type == ET_TOUCH_RELEASED || params.type == ET_TOUCH_CANCELLED) {
    touch_id_generator_.ReleaseGeneratedID(touch_event.touch_id());
  }
}

void EventFactoryEvdev::DispatchUiEvent(Event* event) {
  // DispatchEvent takes PlatformEvent which is void*. This function
  // wraps it with the real type.
  DispatchEvent(event);
}

void EventFactoryEvdev::DispatchKeyboardDevicesUpdated(
    const std::vector<KeyboardDevice>& devices) {
  TRACE_EVENT0("evdev", "EventFactoryEvdev::DispatchKeyboardDevicesUpdated");
  DeviceHotplugEventObserver* observer = DeviceDataManager::GetInstance();
  observer->OnKeyboardDevicesUpdated(devices);
}

void EventFactoryEvdev::DispatchTouchscreenDevicesUpdated(
    const std::vector<TouchscreenDevice>& devices) {
  TRACE_EVENT0("evdev", "EventFactoryEvdev::DispatchTouchscreenDevicesUpdated");
  DeviceHotplugEventObserver* observer = DeviceDataManager::GetInstance();
  observer->OnTouchscreenDevicesUpdated(devices);
}

void EventFactoryEvdev::DispatchMouseDevicesUpdated(
    const std::vector<InputDevice>& devices) {
  TRACE_EVENT0("evdev", "EventFactoryEvdev::DispatchMouseDevicesUpdated");

  // There's no list of mice in DeviceDataManager.
  input_controller_.set_has_mouse(devices.size() != 0);
  DeviceHotplugEventObserver* observer = DeviceDataManager::GetInstance();
  observer->OnMouseDevicesUpdated(devices);
}

void EventFactoryEvdev::DispatchTouchpadDevicesUpdated(
    const std::vector<InputDevice>& devices) {
  TRACE_EVENT0("evdev", "EventFactoryEvdev::DispatchTouchpadDevicesUpdated");

  // There's no list of touchpads in DeviceDataManager.
  input_controller_.set_has_touchpad(devices.size() != 0);
  DeviceHotplugEventObserver* observer = DeviceDataManager::GetInstance();
  observer->OnTouchpadDevicesUpdated(devices);
}

void EventFactoryEvdev::DispatchDeviceListsComplete() {
  TRACE_EVENT0("evdev", "EventFactoryEvdev::DispatchDeviceListsComplete");
  DeviceHotplugEventObserver* observer = DeviceDataManager::GetInstance();
  observer->OnDeviceListsComplete();
}

void EventFactoryEvdev::OnDeviceEvent(const DeviceEvent& event) {
  if (event.device_type() != DeviceEvent::INPUT)
    return;

  switch (event.action_type()) {
    case DeviceEvent::ADD:
    case DeviceEvent::CHANGE: {
      TRACE_EVENT1("evdev", "EventFactoryEvdev::OnDeviceAdded", "path",
                   event.path().value());
      input_device_factory_proxy_->AddInputDevice(NextDeviceId(), event.path());
      break;
    }
    case DeviceEvent::REMOVE: {
      TRACE_EVENT1("evdev", "EventFactoryEvdev::OnDeviceRemoved", "path",
                   event.path().value());
      input_device_factory_proxy_->RemoveInputDevice(event.path());
      break;
    }
  }
}

void EventFactoryEvdev::OnDispatcherListChanged() {
  if (!initialized_)
    Init();
}

void EventFactoryEvdev::WarpCursorTo(gfx::AcceleratedWidget widget,
                                     const gfx::PointF& location) {
  if (!cursor_)
    return;

  cursor_->MoveCursorTo(widget, location);

  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE,
      base::Bind(&EventFactoryEvdev::DispatchMouseMoveEvent,
                 weak_ptr_factory_.GetWeakPtr(),
                 MouseMoveEventParams(
                     -1 /* device_id */, cursor_->GetLocation(),
                     PointerDetails(EventPointerType::POINTER_TYPE_MOUSE),
                     EventTimeForNow())));
}

int EventFactoryEvdev::NextDeviceId() {
  return ++last_device_id_;
}

void EventFactoryEvdev::StartThread() {
  // Set up device factory.
  scoped_ptr<DeviceEventDispatcherEvdev> proxy_dispatcher(
      new ProxyDeviceEventDispatcher(base::ThreadTaskRunnerHandle::Get(),
                                     weak_ptr_factory_.GetWeakPtr()));
  thread_.Start(std::move(proxy_dispatcher), cursor_,
                base::Bind(&EventFactoryEvdev::OnThreadStarted,
                           weak_ptr_factory_.GetWeakPtr()));
}

void EventFactoryEvdev::OnThreadStarted(
    scoped_ptr<InputDeviceFactoryEvdevProxy> input_device_factory) {
  TRACE_EVENT0("evdev", "EventFactoryEvdev::OnThreadStarted");
  input_device_factory_proxy_ = std::move(input_device_factory);

  // Hook up device configuration.
  input_controller_.SetInputDeviceFactory(input_device_factory_proxy_.get());

  // Scan & monitor devices.
  device_manager_->AddObserver(this);
  device_manager_->ScanDevices(this);

  // Notify device thread that initial scan is done.
  input_device_factory_proxy_->OnStartupScanComplete();
}

}  // namespace ui