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

#include "ui/base/x/test/x11_ui_controls_test_helper.h"

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "ui/base/x/x11_util.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#include "ui/events/test/x11_event_waiter.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/x/keysyms/keysyms.h"
#include "ui/gfx/x/xproto.h"

namespace ui {
namespace {

using ui_controls::DOWN;
using ui_controls::LEFT;
using ui_controls::MIDDLE;
using ui_controls::MouseButton;
using ui_controls::RIGHT;
using ui_controls::UP;

// Mask of the buttons currently down.
unsigned button_down_mask = 0;

// The root and time fields of |xevent| may be modified.
template <typename T>
void PostEventToWindowTreeHost(gfx::AcceleratedWidget widget, T* xevent) {
  x11::Window xwindow = static_cast<x11::Window>(widget);
  xevent->event = xwindow;

  xevent->root = x11::Connection::Get()->default_root();
  xevent->time = x11::Time::CurrentTime;

  x11::Connection::Get()->SendEvent(*xevent, xwindow, x11::EventMask::NoEvent);
  x11::Connection::Get()->Flush();
}

}  // namespace

X11UIControlsTestHelper::X11UIControlsTestHelper()
    : connection_(*x11::Connection::Get()),
      x_root_window_(ui::GetX11RootWindow()),
      x_window_(connection_->CreateDummyWindow(
          "Chromium X11UIControlsTestHelper Window")) {}

X11UIControlsTestHelper::~X11UIControlsTestHelper() {
  connection_->DestroyWindow({x_window_});
}

unsigned X11UIControlsTestHelper::ButtonDownMask() const {
  return button_down_mask;
}

void X11UIControlsTestHelper::SendKeyEvents(gfx::AcceleratedWidget widget,
                                            ui::KeyboardCode key,
                                            int key_event_types,
                                            int accelerator_state,
                                            base::OnceClosure closure) {
  bool press = key_event_types & ui_controls::kKeyPress;
  bool release = key_event_types & ui_controls::kKeyRelease;

  bool control = accelerator_state & ui_controls::kControl;
  bool shift = accelerator_state & ui_controls::kShift;
  bool alt = accelerator_state & ui_controls::kAlt;

  x11::KeyEvent xevent;

  // Send key press events.
  if (press) {
    xevent.opcode = x11::KeyEvent::Press;
    if (control) {
      SetKeycodeAndSendThenMask(widget, &xevent, XK_Control_L,
                                x11::KeyButMask::Control);
    }
    if (shift) {
      SetKeycodeAndSendThenMask(widget, &xevent, XK_Shift_L,
                                x11::KeyButMask::Shift);
    }
    if (alt) {
      SetKeycodeAndSendThenMask(widget, &xevent, XK_Alt_L,
                                x11::KeyButMask::Mod1);
    }
#if BUILDFLAG(ARKWEB_INPUT_EVENTS)
    xevent.detail = x11::Connection::Get()->KeysymToKeycode(
        ui::XKeysymForWindowsKeyCode(key, shift, false));
#else
    xevent.detail = x11::Connection::Get()->KeysymToKeycode(
        ui::XKeysymForWindowsKeyCode(key, shift));
#endif
    PostEventToWindowTreeHost(widget, &xevent);
  }

  // Send key release events.
  if (release) {
    xevent.opcode = x11::KeyEvent::Release;
    PostEventToWindowTreeHost(widget, &xevent);
    if (alt) {
      UnmaskAndSetKeycodeThenSend(widget, &xevent, x11::KeyButMask::Mod1,
                                  XK_Alt_L);
    }
    if (shift) {
      UnmaskAndSetKeycodeThenSend(widget, &xevent, x11::KeyButMask::Shift,
                                  XK_Shift_L);
    }
    if (control) {
      UnmaskAndSetKeycodeThenSend(widget, &xevent, x11::KeyButMask::Control,
                                  XK_Control_L);
    }
  }

  RunClosureAfterAllPendingUIEvents(std::move(closure));
  return;
}

void X11UIControlsTestHelper::SendMouseMotionNotifyEvent(
    gfx::AcceleratedWidget widget,
    const gfx::Point& mouse_loc,
    const gfx::Point& mouse_loc_in_connection_px,
    base::OnceClosure closure) {
  x11::MotionNotifyEvent xevent{
      .root_x = static_cast<int16_t>(mouse_loc_in_connection_px.x()),
      .root_y = static_cast<int16_t>(mouse_loc_in_connection_px.y()),
      .event_x = static_cast<int16_t>(mouse_loc.x()),
      .event_y = static_cast<int16_t>(mouse_loc.y()),
      .state = static_cast<x11::KeyButMask>(button_down_mask),
      .same_screen = true,
  };
  // RootWindow will take care of other necessary fields.
  PostEventToWindowTreeHost(widget, &xevent);
  RunClosureAfterAllPendingUIEvents(std::move(closure));
  return;
}

void X11UIControlsTestHelper::SendMouseEvent(
    gfx::AcceleratedWidget widget,
    MouseButton type,
    int button_state,
    int accelerator_state,
    const gfx::Point& mouse_loc,
    const gfx::Point& mouse_loc_in_connection_px,
    base::OnceClosure closure) {
  x11::ButtonEvent xevent;
  xevent.event_x = mouse_loc.x();
  xevent.event_y = mouse_loc.y();
  xevent.root_x = mouse_loc_in_connection_px.x();
  xevent.root_y = mouse_loc_in_connection_px.y();
  switch (type) {
    case LEFT:
      xevent.detail = static_cast<x11::Button>(1);
      xevent.state = x11::KeyButMask::Button1;
      break;
    case MIDDLE:
      xevent.detail = static_cast<x11::Button>(2);
      xevent.state = x11::KeyButMask::Button2;
      break;
    case RIGHT:
      xevent.detail = static_cast<x11::Button>(3);
      xevent.state = x11::KeyButMask::Button3;
      break;
  }

  // Process accelerator key state.
  if (accelerator_state & ui_controls::kShift)
    xevent.state = xevent.state | x11::KeyButMask::Shift;
  if (accelerator_state & ui_controls::kControl)
    xevent.state = xevent.state | x11::KeyButMask::Control;
  if (accelerator_state & ui_controls::kAlt)
    xevent.state = xevent.state | x11::KeyButMask::Mod1;
  if (accelerator_state & ui_controls::kCommand)
    xevent.state = xevent.state | x11::KeyButMask::Mod4;

  // RootWindow will take care of other necessary fields.
  if (button_state & DOWN) {
    xevent.opcode = x11::ButtonEvent::Press;
    PostEventToWindowTreeHost(widget, &xevent);
    button_down_mask |= static_cast<int>(xevent.state);
  }
  if (button_state & UP) {
    xevent.opcode = x11::ButtonEvent::Release;
    PostEventToWindowTreeHost(widget, &xevent);
    int state = static_cast<int>(xevent.state);
    button_down_mask = (button_down_mask | state) ^ state;
  }

  RunClosureAfterAllPendingUIEvents(std::move(closure));
  return;
}

void X11UIControlsTestHelper::RunClosureAfterAllPendingUIEvents(
    base::OnceClosure closure) {
  if (closure.is_null())
    return;
  ui::XEventWaiter::Create(x_window_, std::move(closure));
}

void X11UIControlsTestHelper::SetKeycodeAndSendThenMask(
    gfx::AcceleratedWidget widget,
    x11::KeyEvent* xevent,
    uint32_t keysym,
    x11::KeyButMask mask) {
  xevent->detail = x11::Connection::Get()->KeysymToKeycode(keysym);
  PostEventToWindowTreeHost(widget, xevent);
  xevent->state = xevent->state | mask;
}

void X11UIControlsTestHelper::UnmaskAndSetKeycodeThenSend(
    gfx::AcceleratedWidget widget,
    x11::KeyEvent* xevent,
    x11::KeyButMask mask,
    uint32_t keysym) {
  xevent->state = static_cast<x11::KeyButMask>(
      static_cast<uint32_t>(xevent->state) ^ static_cast<uint32_t>(mask));
  xevent->detail = x11::Connection::Get()->KeysymToKeycode(keysym);
  PostEventToWindowTreeHost(widget, xevent);
}

}  // namespace ui