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

#include "remoting/host/linux/x11_keyboard_impl.h"

#include "remoting/host/linux/unicode_to_keysym.h"
#include "ui/gfx/x/future.h"
#include "ui/gfx/x/xkb.h"
#include "ui/gfx/x/xproto.h"
#include "ui/gfx/x/xtest.h"

namespace {

bool FindKeycodeForKeySym(x11::Connection* connection,
                          uint32_t key_sym,
                          uint32_t* keycode,
                          uint32_t* modifiers) {
  auto found_keycode = connection->KeysymToKeycode(key_sym);

  const x11::KeyButMask kModifiersToTry[] = {
      {},
      x11::KeyButMask::Shift,
      x11::KeyButMask::Mod2,
      x11::KeyButMask::Mod3,
      x11::KeyButMask::Mod4,
      x11::KeyButMask::Shift | x11::KeyButMask::Mod2,
      x11::KeyButMask::Shift | x11::KeyButMask::Mod3,
      x11::KeyButMask::Shift | x11::KeyButMask::Mod4,
  };

  // TODO(sergeyu): Is there a better way to find modifiers state?
  for (auto i : kModifiersToTry) {
    auto mods = static_cast<uint32_t>(i);
    if (connection->KeycodeToKeysym(found_keycode, mods) == key_sym) {
      *modifiers = mods;
      *keycode = static_cast<uint8_t>(found_keycode);
      return true;
    }
  }
  return false;
}

// This is ported from XStringToKeysym
// https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/2b7598221d87049d03e9a95fcb541c37c8728184/src/StrKeysym.c#L147-154
uint32_t UnicodeToKeysym(uint32_t u) {
  if (u > 0x10ffff || u < 0x20 || (u > 0x7e && u < 0xa0)) {
    return 0;
  }
  if (u < 0x100) {
    return u;
  }
  return u | 0x01000000;
}

}  // namespace

namespace remoting {

X11KeyboardImpl::X11KeyboardImpl(x11::Connection* connection)
    : connection_(connection) {}

X11KeyboardImpl::~X11KeyboardImpl() = default;

std::vector<uint32_t> X11KeyboardImpl::GetUnusedKeycodes() {
  std::vector<uint32_t> unused_keycodes_;
  uint8_t min_keycode = static_cast<uint8_t>(connection_->setup().min_keycode);
  uint8_t max_keycode = static_cast<uint8_t>(connection_->setup().max_keycode);
  uint8_t keycode_count = max_keycode - min_keycode + 1;

  auto req = connection_->GetKeyboardMapping(
      {connection_->setup().min_keycode, keycode_count});
  if (auto reply = req.Sync()) {
    for (int keycode = max_keycode; keycode >= min_keycode; keycode--) {
      bool used = false;
      int offset = (keycode - min_keycode) * reply->keysyms_per_keycode;
      for (int level = 0; level < reply->keysyms_per_keycode; level++) {
        if (reply->keysyms[offset + level] != x11::KeySym{}) {
          used = true;
          break;
        }
      }
      if (!used) {
        unused_keycodes_.push_back(keycode);
      }
    }
  }
  return unused_keycodes_;
}

void X11KeyboardImpl::PressKey(uint32_t keycode, uint32_t modifiers) {
  connection_->xkb().LatchLockState(
      {static_cast<x11::Xkb::DeviceSpec>(x11::Xkb::Id::UseCoreKbd),
       static_cast<x11::ModMask>(modifiers),
       static_cast<x11::ModMask>(modifiers)});

  connection_->xtest().FakeInput(
      {x11::KeyEvent::Press, static_cast<uint8_t>(keycode)});
  connection_->xtest().FakeInput(
      {x11::KeyEvent::Release, static_cast<uint8_t>(keycode)});

  connection_->xkb().LatchLockState(
      {static_cast<x11::Xkb::DeviceSpec>(x11::Xkb::Id::UseCoreKbd),
       static_cast<x11::ModMask>(modifiers), x11::ModMask{}});
}

bool X11KeyboardImpl::FindKeycode(uint32_t code_point,
                                  uint32_t* keycode,
                                  uint32_t* modifiers) {
  for (uint32_t keysym : GetKeySymsForUnicode(code_point)) {
    if (FindKeycodeForKeySym(connection_, keysym, keycode, modifiers)) {
      return true;
    }
  }
  return false;
}

bool X11KeyboardImpl::ChangeKeyMapping(uint32_t keycode, uint32_t code_point) {
  if (!code_point) {
    return false;
  }
  auto keysym = UnicodeToKeysym(code_point);
  if (!keysym) {
    return false;
  }
  connection_->ChangeKeyboardMapping({
      .keycode_count = 1,
      .first_keycode = static_cast<x11::KeyCode>(keycode),
      .keysyms_per_keycode = 2,
      .keysyms = {static_cast<x11::KeySym>(keysym) /* lower-case */,
                  static_cast<x11::KeySym>(keysym) /* upper-case */},
  });
  return true;
}

void X11KeyboardImpl::Flush() {
  connection_->Flush();
}

void X11KeyboardImpl::Sync() {
  connection_->Sync();
}

}  // namespace remoting