// Copyright 2017 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/aura/mus/system_input_injector_mus.h"

#include "ui/aura/env.h"
#include "ui/aura/mus/window_manager_delegate.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/gfx/geometry/point_conversions.h"

namespace aura {

namespace {

int KeyboardCodeToModifier(ui::KeyboardCode key) {
  switch (key) {
    case ui::VKEY_MENU:
    case ui::VKEY_LMENU:
    case ui::VKEY_RMENU:
      return ui::MODIFIER_ALT;
    case ui::VKEY_ALTGR:
      return ui::MODIFIER_ALTGR;
    case ui::VKEY_CAPITAL:
      return ui::MODIFIER_CAPS_LOCK;
    case ui::VKEY_CONTROL:
    case ui::VKEY_LCONTROL:
    case ui::VKEY_RCONTROL:
      return ui::MODIFIER_CONTROL;
    case ui::VKEY_LWIN:
    case ui::VKEY_RWIN:
      return ui::MODIFIER_COMMAND;
    case ui::VKEY_SHIFT:
    case ui::VKEY_LSHIFT:
    case ui::VKEY_RSHIFT:
      return ui::MODIFIER_SHIFT;
    default:
      return ui::MODIFIER_NONE;
  }
}

}  // namespace

SystemInputInjectorMus::SystemInputInjectorMus(WindowManagerClient* client)
    : client_(client) {}

SystemInputInjectorMus::~SystemInputInjectorMus() {}

void SystemInputInjectorMus::MoveCursorTo(const gfx::PointF& location) {
  ui::MouseEvent event(
      ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), ui::EventTimeForNow(),
      modifiers_.GetModifierFlags(),
      /* changed_button_flags */ 0,
      ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_MOUSE));
  event.set_location_f(location);
  event.set_root_location_f(location);

  InjectEventAt(ui::PointerEvent(event), gfx::ToRoundedPoint(location));
}

void SystemInputInjectorMus::InjectMouseButton(ui::EventFlags button,
                                               bool down) {
  gfx::Point location = aura::Env::GetInstance()->last_mouse_location();

  int modifier = ui::MODIFIER_NONE;
  switch (button) {
    case ui::EF_LEFT_MOUSE_BUTTON:
      modifier = ui::MODIFIER_LEFT_MOUSE_BUTTON;
      break;
    case ui::EF_RIGHT_MOUSE_BUTTON:
      modifier = ui::MODIFIER_RIGHT_MOUSE_BUTTON;
      break;
    case ui::EF_MIDDLE_MOUSE_BUTTON:
      modifier = ui::MODIFIER_MIDDLE_MOUSE_BUTTON;
    default:
      LOG(WARNING) << "Invalid flag: " << button << " for the button parameter";
      return;
  }

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

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

  ui::MouseEvent event(
      down ? ui::ET_MOUSE_PRESSED : ui::ET_MOUSE_RELEASED, location, location,
      ui::EventTimeForNow(), modifiers_.GetModifierFlags() | flag,
      /* changed_button_flags */ flag,
      ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_MOUSE));
  InjectEventAt(ui::PointerEvent(event), location);
}

void SystemInputInjectorMus::InjectMouseWheel(int delta_x, int delta_y) {
  gfx::Point location = aura::Env::GetInstance()->last_mouse_location();

  ui::MouseWheelEvent event(gfx::Vector2d(delta_x, delta_y), location, location,
                            ui::EventTimeForNow(),
                            modifiers_.GetModifierFlags(),
                            /* changed_button_flags */ 0);
  InjectEventAt(ui::PointerEvent(event), location);
}

void SystemInputInjectorMus::InjectKeyEvent(ui::DomCode dom_code,
                                            bool down,
                                            bool suppress_auto_repeat) {
  // |suppress_auto_repeat| is always true, and can be ignored.
  ui::KeyboardCode key_code = ui::DomCodeToUsLayoutKeyboardCode(dom_code);

  int modifier = KeyboardCodeToModifier(key_code);
  if (modifier)
    UpdateModifier(modifier, down);

  ui::KeyEvent e(down ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED, key_code,
                 dom_code, modifiers_.GetModifierFlags());

  InjectEventAt(e, display::Screen::GetScreen()->GetCursorScreenPoint());
}

void SystemInputInjectorMus::InjectEventAt(const ui::Event& event,
                                           const gfx::Point& location) {
  display::Screen* screen = display::Screen::GetScreen();
  display::Display display = screen->GetDisplayNearestPoint(location);
  client_->InjectEvent(event, display.id());
}

void SystemInputInjectorMus::UpdateModifier(unsigned int modifier, bool down) {
  if (modifier == ui::MODIFIER_NONE)
    return;

  // KeyboardEvdev performs a transformation here from MODIFIER_CAPS_LOCK to
  // MODIFIER_MOD3. That was needed to work around X11, we actually want to
  // ship this state across the wire without modification.
  modifiers_.UpdateModifier(modifier, down);
}

}  // namespace aura
