/*
 * Deskflow -- mouse and keyboard sharing utility
 * SPDX-FileCopyrightText: (C) 2024 Symless Ltd.
 * SPDX-FileCopyrightText: (C) 2022 Red Hat, Inc.
 * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
 */

#pragma once

#include "deskflow/IScreen.h"
#include "deskflow/PlatformScreen.h"
#include "platform/XDGPowerManager.h"

#include <libei.h>
#include <map>
#include <mutex>
#include <vector>

struct ei;
struct ei_event;
struct ei_seat;
struct ei_device;

namespace deskflow {

class WlClipboardCollection;
class EiKeyState;
class PortalRemoteDesktop;
class PortalInputCapture;

using ClipboardInfo = IScreen::ClipboardInfo;

//! Implementation of IPlatformScreen for X11
class EiScreen : public PlatformScreen
{
public:
  EiScreen(bool isPrimary, IEventQueue *events, bool usePortal);
  ~EiScreen() override;

  // IScreen overrides
  void *getEventTarget() const final;
  bool getClipboard(ClipboardID id, IClipboard *) const override;
  void getShape(std::int32_t &x, std::int32_t &y, std::int32_t &width, std::int32_t &height) const override;
  void getCursorPos(std::int32_t &x, std::int32_t &y) const override;

  // IPrimaryScreen overrides
  void reconfigure(std::uint32_t activeSides) override;
  std::uint32_t activeSides() override;
  void warpCursor(std::int32_t x, std::int32_t y) override;
  std::uint32_t registerHotKey(KeyID key, KeyModifierMask mask) override;
  void unregisterHotKey(std::uint32_t id) override;
  void fakeInputBegin() override;
  void fakeInputEnd() override;
  std::int32_t getJumpZoneSize() const override;
  bool isAnyMouseButtonDown(std::uint32_t &buttonID) const override;
  void getCursorCenter(std::int32_t &x, std::int32_t &y) const override;

  // ISecondaryScreen overrides
  void fakeMouseButton(ButtonID id, bool press) override;
  void fakeMouseMove(std::int32_t x, std::int32_t y) override;
  void fakeMouseRelativeMove(std::int32_t dx, std::int32_t dy) const override;
  void fakeMouseWheel(ScrollDelta delta) const override;
  void fakeKey(std::uint32_t keycode, bool isDown) const;

  // IPlatformScreen overrides
  void enable() override;
  void disable() override;
  void enter() override;
  bool canLeave() override;
  void leave() override;
  bool setClipboard(ClipboardID, const IClipboard *) override;
  void checkClipboards() override;
  void openScreensaver(bool notify) override;
  void closeScreensaver() override;
  void screensaver(bool activate) override;
  void resetOptions() override;
  void setOptions(const OptionsList &options) override;
  void setSequenceNumber(std::uint32_t) override;
  bool isPrimary() const override;

protected:
  // IPlatformScreen overrides
  void handleSystemEvent(const Event &event) override;
  void updateButtons() override;
  IKeyState *getKeyState() const override;
  std::string getSecureInputApp() const override;

  void updateShape();
  void addDevice(ei_device *device);
  void removeDevice(ei_device *device);

private:
  void initEi();
  void cleanupEi();
  void sendEvent(EventTypes type, void *data);
  void sendClipboardEvent(EventTypes type, ClipboardID id) const;
  ButtonID mapButtonFromEvdev(ei_event *event) const;
  void onKeyEvent(ei_event *event);
  void onButtonEvent(ei_event *event);
  void sendWheelEvents(ei_device *device, const int threshold, double dx, double dy, bool is_discrete);
  void onPointerScrollEvent(ei_event *event);
  void onPointerScrollDiscreteEvent(ei_event *event);
  void onMotionEvent(ei_event *event);
  void onAbsMotionEvent(const ei_event *) const;
  bool onHotkey(KeyID key, bool isPressed, KeyModifierMask mask);
  void eiLogEvent(ei_log_priority priority, const char *message) const;

  void handleConnectedToEisEvent(const Event &event);
  void handlePortalSessionClosed();

  static void handleEiLogEvent(ei *ei, const ei_log_priority priority, const char *message, ei_log_context *)
  {
    auto screen = static_cast<EiScreen *>(ei_get_user_data(ei));
    screen->eiLogEvent(priority, message);
  }

private:
  // true if screen is being used as a primary screen, false otherwise
  bool m_isPrimary = false;
  IEventQueue *m_events = nullptr;

  // keyboard stuff
  EiKeyState *m_keyState = nullptr;

  KeyID m_lastPressed = kKeyNone;

  // clipboard stuff
  WlClipboardCollection *m_clipboard = nullptr;

  std::vector<ei_device *> m_eiDevices;

  ei *m_ei = nullptr;
  ei_seat *m_eiSeat = nullptr;
  ei_device *m_eiPointer = nullptr;
  ei_device *m_eiKeyboard = nullptr;
  ei_device *m_eiAbs = nullptr;

  std::uint32_t m_sequenceNumber = 0;

  std::uint32_t m_activeSides = 0;
  std::uint32_t m_x = 0;
  std::uint32_t m_y = 0;
  std::uint32_t m_w = 0;
  std::uint32_t m_h = 0;

  // true if mouse has entered the screen
  bool m_isOnScreen;

  // server: last pointer position
  // client: position sent before enter()
  std::int32_t m_cursorX = 0;
  std::int32_t m_cursorY = 0;

  double m_bufferDX = 0;
  double m_bufferDY = 0;

  mutable std::mutex m_mutex;

  PortalRemoteDesktop *m_portalRemoteDesktop = nullptr;
  PortalInputCapture *m_portalInputCapture = nullptr;

  struct HotKeyItem
  {
  public:
    HotKeyItem(std::uint32_t mask, std::uint32_t id);
    auto operator<=>(const HotKeyItem &other) const
    {
      return mask <=> other.mask;
    }

  public:
    std::uint32_t mask = 0;
    std::uint32_t id = 0; // for registering the hotkey
  };

  class HotKeySet
  {
  public:
    explicit HotKeySet(KeyID keyid);
    KeyID keyid() const
    {
      return m_id;
    }
    bool removeById(std::uint32_t id);
    void addItem(HotKeyItem item);
    std::uint32_t findByMask(std::uint32_t mask) const;

  private:
    KeyID m_id = 0;
    std::vector<HotKeyItem> m_set;
  };

  using HotKeyMap = std::map<KeyID, HotKeySet>;

  HotKeyMap m_hotkeys;
  [[no_unique_address]] XDGPowerManager m_powerManager;
};

} // namespace deskflow
