/**
 * @file src/platform/common.h
 * @brief Declarations for common platform specific utilities.
 */
#pragma once

// standard includes
#include <bitset>
#include <filesystem>
#include <functional>
#include <mutex>
#include <optional>
#include <string>

// lib includes
#include <boost/core/noncopyable.hpp>
#ifndef _WIN32
  #include <boost/asio.hpp>
  #include <boost/process/v1.hpp>
#endif

// local includes
#include "src/config.h"
#include "src/logging.h"
#include "src/thread_safe.h"
#include "src/utility.h"
#include "src/video_colorspace.h"

extern "C" {
#include <moonlight-common-c/src/Limelight.h>
}

using namespace std::literals;

struct sockaddr;
struct AVFrame;
struct AVBufferRef;
struct AVHWFramesContext;
struct AVCodecContext;
struct AVDictionary;

#ifdef _WIN32
// Forward declarations of boost classes to avoid having to include boost headers
// here, which results in issues with Windows.h and WinSock2.h include order.
namespace boost {
  namespace asio {
    namespace ip {
      class address;
    }  // namespace ip
  }  // namespace asio

  namespace filesystem {
    class path;
  }

  namespace process::v1 {
    class child;
    class group;
    template<typename Char>
    class basic_environment;
    /**
     * @brief Map of environment variable names to values.
     */
    typedef basic_environment<char> environment;
  }  // namespace process::v1
}  // namespace boost
#endif
namespace video {
  struct config_t;
}  // namespace video

namespace nvenc {
  class nvenc_encoder;
}

namespace platf {
  // Limited by bits in activeGamepadMask
  constexpr auto MAX_GAMEPADS = 16;  ///< Maximum number of simultaneously tracked gamepads.

  constexpr std::uint32_t DPAD_UP = 0x0001;  ///< Moonlight gamepad button mask bit for D-pad up.
  constexpr std::uint32_t DPAD_DOWN = 0x0002;  ///< Moonlight gamepad button mask bit for D-pad down.
  constexpr std::uint32_t DPAD_LEFT = 0x0004;  ///< Moonlight gamepad button mask bit for D-pad left.
  constexpr std::uint32_t DPAD_RIGHT = 0x0008;  ///< Moonlight gamepad button mask bit for D-pad right.
  constexpr std::uint32_t START = 0x0010;  ///< Moonlight gamepad button mask bit for Start.
  constexpr std::uint32_t BACK = 0x0020;  ///< Moonlight gamepad button mask bit for Back.
  constexpr std::uint32_t LEFT_STICK = 0x0040;  ///< Moonlight gamepad button mask bit for left stick press.
  constexpr std::uint32_t RIGHT_STICK = 0x0080;  ///< Moonlight gamepad button mask bit for right stick press.
  constexpr std::uint32_t LEFT_BUTTON = 0x0100;  ///< Moonlight gamepad button mask bit for left shoulder.
  constexpr std::uint32_t RIGHT_BUTTON = 0x0200;  ///< Moonlight gamepad button mask bit for right shoulder.
  constexpr std::uint32_t HOME = 0x0400;  ///< Moonlight gamepad button mask bit for Home.
  constexpr std::uint32_t A = 0x1000;  ///< Moonlight gamepad button mask bit for A.
  constexpr std::uint32_t B = 0x2000;  ///< Moonlight gamepad button mask bit for B.
  constexpr std::uint32_t X = 0x4000;  ///< Moonlight gamepad button mask bit for X.
  constexpr std::uint32_t Y = 0x8000;  ///< Moonlight gamepad button mask bit for Y.
  constexpr std::uint32_t PADDLE1 = 0x010000;  ///< Moonlight gamepad button mask bit for paddle 1.
  constexpr std::uint32_t PADDLE2 = 0x020000;  ///< Moonlight gamepad button mask bit for paddle 2.
  constexpr std::uint32_t PADDLE3 = 0x040000;  ///< Moonlight gamepad button mask bit for paddle 3.
  constexpr std::uint32_t PADDLE4 = 0x080000;  ///< Moonlight gamepad button mask bit for paddle 4.
  constexpr std::uint32_t TOUCHPAD_BUTTON = 0x100000;  ///< Moonlight gamepad button mask bit for touchpad click.
  constexpr std::uint32_t MISC_BUTTON = 0x200000;  ///< Moonlight gamepad button mask bit for the miscellaneous button.

  /**
   * @brief Gamepad type exposed to clients and why it may be disabled.
   */
  struct supported_gamepad_t {
    std::string name;  ///< Human-readable name for this item.
    bool is_enabled;  ///< Whether this gamepad type is currently available.
    std::string reason_disabled;  ///< Human-readable reason the gamepad type is disabled.
  };

  /**
   * @brief Enumerates supported gamepad feedback options.
   */
  enum class gamepad_feedback_e {
    rumble,  ///< Rumble
    rumble_triggers,  ///< Rumble triggers
    set_motion_event_state,  ///< Set motion event state
    set_rgb_led,  ///< Set RGB LED
    set_adaptive_triggers,  ///< Set adaptive triggers
  };

  /**
   * @brief Feedback command sent from Sunshine to a virtual gamepad.
   */
  struct gamepad_feedback_msg_t {
    /**
     * @brief Create a rumble object or message.
     *
     * @param id Identifier for the controller, session, display, or resource.
     * @param lowfreq Low-frequency rumble motor intensity.
     * @param highfreq High-frequency rumble motor intensity.
     * @return Constructed rumble object.
     */
    static gamepad_feedback_msg_t make_rumble(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) {
      gamepad_feedback_msg_t msg;
      msg.type = gamepad_feedback_e::rumble;
      msg.id = id;
      msg.data.rumble = {lowfreq, highfreq};
      return msg;
    }

    /**
     * @brief Create rumble triggers.
     *
     * @param id Identifier for the controller, session, display, or resource.
     * @param left Left trigger or motor payload for the feedback command.
     * @param right Right trigger or motor payload for the feedback command.
     * @return Constructed rumble triggers object.
     */
    static gamepad_feedback_msg_t make_rumble_triggers(std::uint16_t id, std::uint16_t left, std::uint16_t right) {
      gamepad_feedback_msg_t msg;
      msg.type = gamepad_feedback_e::rumble_triggers;
      msg.id = id;
      msg.data.rumble_triggers = {left, right};
      return msg;
    }

    /**
     * @brief Motion-event feedback command payload for a controller.
     *
     * @param id Identifier for the controller, session, display, or resource.
     * @param motion_type Motion type.
     * @param report_rate Report rate.
     * @return Constructed motion event state object.
     */
    static gamepad_feedback_msg_t make_motion_event_state(std::uint16_t id, std::uint8_t motion_type, std::uint16_t report_rate) {
      gamepad_feedback_msg_t msg;
      msg.type = gamepad_feedback_e::set_motion_event_state;
      msg.id = id;
      msg.data.motion_event_state.motion_type = motion_type;
      msg.data.motion_event_state.report_rate = report_rate;
      return msg;
    }

    /**
     * @brief Create RGB led.
     *
     * @param id Identifier for the controller, session, display, or resource.
     * @param r Red color channel value.
     * @param g Green color channel value.
     * @param b Blue color channel value.
     * @return Constructed RGB led object.
     */
    static gamepad_feedback_msg_t make_rgb_led(std::uint16_t id, std::uint8_t r, std::uint8_t g, std::uint8_t b) {
      gamepad_feedback_msg_t msg;
      msg.type = gamepad_feedback_e::set_rgb_led;
      msg.id = id;
      msg.data.rgb_led = {r, g, b};
      return msg;
    }

    /**
     * @brief Create adaptive triggers.
     *
     * @param id Identifier for the controller, session, display, or resource.
     * @param event_flags Event flags.
     * @param type_left Type left.
     * @param type_right Type right.
     * @param left Left trigger or motor payload for the feedback command.
     * @param right Right trigger or motor payload for the feedback command.
     * @return Constructed adaptive triggers object.
     */
    static gamepad_feedback_msg_t make_adaptive_triggers(std::uint16_t id, uint8_t event_flags, uint8_t type_left, uint8_t type_right, const std::array<uint8_t, 10> &left, const std::array<uint8_t, 10> &right) {
      gamepad_feedback_msg_t msg;
      msg.type = gamepad_feedback_e::set_adaptive_triggers;
      msg.id = id;
      msg.data.adaptive_triggers = {.event_flags = event_flags, .type_left = type_left, .type_right = type_right, .left = left, .right = right};
      return msg;
    }

    gamepad_feedback_e type;  ///< Feedback command type stored in the union payload.
    std::uint16_t id;  ///< Controller identifier associated with this message.

    union {
      struct {
        std::uint16_t lowfreq;
        std::uint16_t highfreq;
      } rumble;

      struct {
        std::uint16_t left_trigger;
        std::uint16_t right_trigger;
      } rumble_triggers;

      struct {
        std::uint16_t report_rate;
        std::uint8_t motion_type;
      } motion_event_state;

      struct {
        std::uint8_t r;
        std::uint8_t g;
        std::uint8_t b;
      } rgb_led;

      struct {
        uint16_t controllerNumber;
        uint8_t event_flags;
        uint8_t type_left;
        uint8_t type_right;
        std::array<uint8_t, 10> left;
        std::array<uint8_t, 10> right;
      } adaptive_triggers;
    } data;  ///< Controller feedback payload for the selected feedback type.
  };

  /**
   * @brief Queue used to deliver controller feedback commands to the platform backend.
   */
  using feedback_queue_t = safe::mail_raw_t::queue_t<gamepad_feedback_msg_t>;

  namespace speaker {
    /**
     * @brief Enumerates supported speaker options.
     */
    enum speaker_e {
      FRONT_LEFT,  ///< Front left
      FRONT_RIGHT,  ///< Front right
      FRONT_CENTER,  ///< Front center
      LOW_FREQUENCY,  ///< Low frequency
      BACK_LEFT,  ///< Back left
      BACK_RIGHT,  ///< Back right
      SIDE_LEFT,  ///< Side left
      SIDE_RIGHT,  ///< Side right
      MAX_SPEAKERS,  ///< Maximum number of speakers
    };

    /**
     * @brief Moonlight speaker order for stereo audio.
     */
    constexpr std::array<std::uint8_t, 2> map_stereo {
      FRONT_LEFT,
      FRONT_RIGHT
    };
    /**
     * @brief Moonlight speaker order for 5.1 surround audio.
     */
    constexpr std::array<std::uint8_t, 6> map_surround51 {
      FRONT_LEFT,
      FRONT_RIGHT,
      FRONT_CENTER,
      LOW_FREQUENCY,
      BACK_LEFT,
      BACK_RIGHT,
    };
    /**
     * @brief Moonlight speaker order for 7.1 surround audio.
     */
    constexpr std::array<std::uint8_t, 8> map_surround71 {
      FRONT_LEFT,
      FRONT_RIGHT,
      FRONT_CENTER,
      LOW_FREQUENCY,
      BACK_LEFT,
      BACK_RIGHT,
      SIDE_LEFT,
      SIDE_RIGHT,
    };
  }  // namespace speaker

  /**
   * @brief Enumerates supported mem type options.
   */
  enum class mem_type_e {
    system,  ///< System memory
    vaapi,  ///< VAAPI
    dxgi,  ///< DXGI
    cuda,  ///< CUDA
    videotoolbox,  ///< VideoToolbox
    vulkan,  ///< Vulkan
    unknown  ///< Unknown
  };

  /**
   * @brief Enumerates supported pix fmt options.
   */
  enum class pix_fmt_e {
    yuv420p,  ///< YUV 4:2:0
    yuv420p10,  ///< YUV 4:2:0 10-bit
    nv12,  ///< NV12
    p010,  ///< P010
    ayuv,  ///< AYUV
    yuv444p16,  ///< Planar 10-bit (shifted to 16-bit) YUV 4:4:4
    yuv444p,  ///< Planar 8-bit YUV 4:4:4
    y410,  ///< Y410
    unknown  ///< Unknown
  };

  /**
   * @brief Convert a Sunshine pixel format enum to its string name.
   *
   * @param pix_fmt Sunshine pixel format to convert or allocate for.
   * @return Value converted from pix fmt.
   */
  inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) {
    using namespace std::literals;
#ifndef DOXYGEN
  #define _CONVERT(x) \
    case pix_fmt_e::x: \
      return #x##sv
#endif
    switch (pix_fmt) {
      _CONVERT(yuv420p);
      _CONVERT(yuv420p10);
      _CONVERT(nv12);
      _CONVERT(p010);
      _CONVERT(ayuv);
      _CONVERT(yuv444p16);
      _CONVERT(yuv444p);
      _CONVERT(y410);
      _CONVERT(unknown);
    }
#undef _CONVERT

    return "unknown"sv;
  }

  // Dimensions for touchscreen input
  /**
   * @brief Touchscreen coordinate bounds used to scale absolute input.
   */
  struct touch_port_t {
    int offset_x;  ///< Horizontal offset in physical pixels.
    int offset_y;  ///< Vertical offset in physical pixels.
    int width;  ///< Frame or display width in pixels.
    int height;  ///< Frame or display height in pixels.
    int logical_width;  ///< Logical width after display scaling.
    int logical_height;  ///< Logical height after display scaling.
  };

  // These values must match Limelight-internal.h's SS_FF_* constants!
  namespace platform_caps {
    /**
     * @brief Bitset containing platform capability flags.
     */
    typedef uint32_t caps_t;

    /**
     * @brief Capability bit indicating native pen and touch support.
     */
    constexpr caps_t pen_touch = 0x01;  // Pen and touch events
    /**
     * @brief Capability bit indicating controller touchpad support.
     */
    constexpr caps_t controller_touch = 0x02;  // Controller touch events
  };  // namespace platform_caps

  /**
   * @brief Button and axis state received for a virtual gamepad.
   */
  struct gamepad_state_t {
    std::uint32_t buttonFlags;  ///< Moonlight button mask for the current gamepad state.
    std::uint8_t lt;  ///< Left trigger value.
    std::uint8_t rt;  ///< Right trigger value.
    std::int16_t lsX;  ///< Left stick X-axis value.
    std::int16_t lsY;  ///< Left stick Y-axis value.
    std::int16_t rsX;  ///< Right stick X-axis value.
    std::int16_t rsY;  ///< Right stick Y-axis value.
  };

  /**
   * @brief Global and client-relative identifiers for a virtual gamepad.
   */
  struct gamepad_id_t {
    // The global index is used when looking up gamepads in the platform's
    // gamepad array. It identifies gamepads uniquely among all clients.
    int globalIndex;  ///< Index unique across all connected clients.

    // The client-relative index is the controller number as reported by the
    // client. It must be used when communicating back to the client via
    // the input feedback queue.
    std::uint8_t clientRelativeIndex;  ///< Client relative index.
  };

  /**
   * @brief Capabilities reported when a controller is connected.
   */
  struct gamepad_arrival_t {
    std::uint8_t type;  ///< Protocol or controller type discriminator.
    std::uint16_t capabilities;  ///< Capability flags advertised by the controller.
    std::uint32_t supportedButtons;  ///< Button mask supported by the connected controller.
  };

  /**
   * @brief Touchpad contact data reported by a controller.
   */
  struct gamepad_touch_t {
    gamepad_id_t id;  ///< Gamepad identifier for the event.
    std::uint8_t eventType;  ///< Moonlight event type for the input packet.
    std::uint32_t pointerId;  ///< Client-provided pointer identifier for a touch contact.
    float x;  ///< Horizontal coordinate or vector component.
    float y;  ///< Vertical coordinate or vector component.
    float pressure;  ///< Contact pressure reported by the client.
  };

  /**
   * @brief Accelerometer or gyroscope sample from a controller.
   */
  struct gamepad_motion_t {
    gamepad_id_t id;  ///< Gamepad identifier for the event.
    std::uint8_t motionType;  ///< Motion type.

    // Accel: m/s^2
    // Gyro: deg/s
    float x;  ///< Horizontal coordinate or vector component.
    float y;  ///< Vertical coordinate or vector component.
    float z;  ///< Depth or Z-axis vector component.
  };

  /**
   * @brief Battery state reported by a virtual gamepad.
   */
  struct gamepad_battery_t {
    gamepad_id_t id;  ///< Gamepad identifier for the event.
    std::uint8_t state;  ///< Battery state reported by the client.
    std::uint8_t percentage;  ///< Battery charge percentage.
  };

  /**
   * @brief Absolute touchscreen event data from the client.
   */
  struct touch_input_t {
    std::uint8_t eventType;  ///< Moonlight event type for the input packet.
    std::uint16_t rotation;  ///< Degrees (0..360) or LI_ROT_UNKNOWN.
    std::uint32_t pointerId;  ///< Client-provided pointer identifier for a touch contact.
    float x;  ///< Horizontal coordinate or vector component.
    float y;  ///< Vertical coordinate or vector component.
    float pressureOrDistance;  ///< Distance for hover and pressure for contact.
    float contactAreaMajor;  ///< Major axis of the reported contact area.
    float contactAreaMinor;  ///< Minor axis of the reported contact area.
  };

  /**
   * @brief Pen tablet event data from the client.
   */
  struct pen_input_t {
    std::uint8_t eventType;  ///< Moonlight event type for the input packet.
    std::uint8_t toolType;  ///< Pen tool type reported by the client.
    std::uint8_t penButtons;  ///< Button mask for the active pen tool.
    std::uint8_t tilt;  ///< Degrees (0..90) or LI_TILT_UNKNOWN.
    std::uint16_t rotation;  ///< Degrees (0..360) or LI_ROT_UNKNOWN.
    float x;  ///< Horizontal coordinate or vector component.
    float y;  ///< Vertical coordinate or vector component.
    float pressureOrDistance;  ///< Distance for hover and pressure for contact.
    float contactAreaMajor;  ///< Major axis of the reported contact area.
    float contactAreaMinor;  ///< Minor axis of the reported contact area.
  };

  /**
   * @brief RAII helper that runs shutdown cleanup when destroyed.
   */
  class deinit_t {
  public:
    /**
     * @brief Destroy the deinitializer.
     */
    virtual ~deinit_t() = default;
  };

  /**
   * @brief Captured frame buffer shared between capture and encode stages.
   */
  struct img_t: std::enable_shared_from_this<img_t> {
  public:
    img_t() = default;

    img_t(img_t &&) = delete;
    img_t(const img_t &) = delete;
    img_t &operator=(img_t &&) = delete;
    img_t &operator=(const img_t &) = delete;

    std::uint8_t *data {};  ///< Pointer to the captured image buffer.
    std::int32_t width {};  ///< Image width in pixels.
    std::int32_t height {};  ///< Image height in pixels.
    std::int32_t pixel_pitch {};  ///< Bytes per pixel in the image buffer.
    std::int32_t row_pitch {};  ///< Bytes between consecutive image rows.

    std::optional<std::chrono::steady_clock::time_point> frame_timestamp;  ///< Capture timestamp associated with the frame.

    /**
     * @brief Destroy the image.
     */
    virtual ~img_t() = default;
  };

  /**
   * @brief Host and virtual audio sink names for audio routing.
   */
  struct sink_t {
    // Play on host PC
    std::string host;  ///< Host playback sink name.

    // On macOS and Windows, it is not possible to create a virtual sink
    // Therefore, it is optional
    /**
     * @brief Optional virtual sink names for each supported channel layout.
     */
    struct null_t {
      std::string stereo;  ///< Virtual sink name for stereo audio.
      std::string surround51;  ///< Virtual sink name for 5.1 surround audio.
      std::string surround71;  ///< Virtual sink name for 7.1 surround audio.
    };

    std::optional<null_t> null;  ///< Optional virtual sink names for monitor capture.
  };

  /**
   * @brief Base interface for hardware or software frame conversion.
   */
  struct encode_device_t {
    virtual ~encode_device_t() = default;

    /**
     * @brief Convert a captured image into the encoder input representation.
     *
     * @param img Image or frame object to read from or populate.
     * @return Conversion status.
     */
    virtual int convert(platf::img_t &img) = 0;

    video::sunshine_colorspace_t colorspace;  ///< Colorspace metadata expected by the encoder.
  };

  /**
   * @brief AVCodec-backed encode device and frame state.
   */
  struct avcodec_encode_device_t: encode_device_t {
    void *data {};  ///< Backend-specific conversion state.
    AVFrame *frame {};  ///< FFmpeg frame currently owned by the encode device.

    /**
     * @brief Convert a captured image into the encoder input representation.
     *
     * @param img Image or frame object to read from or populate.
     * @return Conversion status.
     */
    int convert(platf::img_t &img) override {
      return -1;
    }

    /**
     * @brief Apply the configured colorspace metadata to the active frame.
     */
    virtual void apply_colorspace() {
    }

    /**
     * @brief Set the frame to be encoded.
     * @note Implementations must take ownership of 'frame'.
     *
     * @param frame Video or graphics frame being processed.
     * @param hw_frames_ctx FFmpeg hardware frames context associated with the frame.
     * @return Status from updating frame.
     */
    virtual int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
      BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?";
      return -1;
    };

    /**
     * @brief Initialize the hwframes context.
     * @note Implementations may set parameters during initialization of the hwframes context.
     *
     * @param frames FFmpeg hardware frames context to initialize.
     */
    virtual void init_hwframes(AVHWFramesContext *frames) {};

    /**
     * @brief Provides a hook for allow platform-specific code to adjust codec options.
     * @note Implementations may set or modify codec options prior to codec initialization.
     *
     * @param ctx Native context object used by the operation or callback.
     * @param options Request options or socket options to apply.
     */
    virtual void init_codec_options(AVCodecContext *ctx, AVDictionary **options) {};

    /**
     * @brief Prepare to derive a context.
     * @note Implementations may make modifications required before context derivation
     *
     * @param hw_device_type FFmpeg hardware device type requested for context derivation.
     * @return 0 when context derivation may continue; nonzero to abort.
     */
    virtual int prepare_to_derive_context(int hw_device_type) {
      return 0;
    };
  };

  /**
   * @brief NVENC-backed encode device state.
   */
  struct nvenc_encode_device_t: encode_device_t {
    /**
     * @brief Initialize the platform encoder for the client stream configuration.
     *
     * @param client_config Client stream configuration negotiated for this session.
     * @param colorspace Colorimetry information used for conversion or encoding.
     * @return True when the backend successfully completes the requested action.
     */
    virtual bool init_encoder(const video::config_t &client_config, const video::sunshine_colorspace_t &colorspace) = 0;

    nvenc::nvenc_encoder *nvenc = nullptr;  ///< NVENC encoder instance owned by the encode device.
  };

  /**
   * @brief Enumerates supported capture options.
   */
  enum class capture_e : int {
    ok,  ///< Success
    reinit,  ///< Need to reinitialize
    timeout,  ///< Timeout
    interrupted,  ///< Capture was interrupted
    error  ///< Error
  };

  /**
   * @brief Abstract display capture backend used by the streaming pipeline.
   */
  class display_t {
  public:
    /**
     * @brief Callback for when a new image is ready.
     * When display has a new image ready or a timeout occurs, this callback will be called with the image.
     * If a frame was captured, frame_captured will be true. If a timeout occurred, it will be false.
     * @retval true On success
     * @retval false On break request
     */
    using push_captured_image_cb_t = std::function<bool(std::shared_ptr<img_t> &&img, bool frame_captured)>;

    /**
     * @brief Get free image from pool.
     * Calls must be synchronized.
     * Blocks until there is free image in the pool or capture is interrupted.
     * @retval true On success, img_out contains free image
     * @retval false When capture has been interrupted, img_out contains nullptr
     */
    using pull_free_image_cb_t = std::function<bool(std::shared_ptr<img_t> &img_out)>;

    display_t() noexcept = default;

    /**
     * @brief Capture a frame.
     * @param push_captured_image_cb The callback that is called with captured image,
     * must be called from the same thread as capture()
     * @param pull_free_image_cb Capture backends call this callback to get empty image from the pool.
     * If backend uses multiple threads, calls to this callback must be synchronized.
     * Calls to this callback and push_captured_image_cb must be synchronized as well.
     * @param cursor A pointer to the flag that indicates whether the cursor should be captured as well.
     * @retval capture_e::ok When stopping
     * @retval capture_e::error On error
     * @retval capture_e::reinit When need of reinitialization
     */
    virtual capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) = 0;

    /**
     * @brief Allocate an image buffer compatible with this display backend.
     *
     * @return Allocated img object, or null when unavailable.
     */
    virtual std::shared_ptr<img_t> alloc_img() = 0;

    /**
     * @brief Populate a fallback image when real capture data is unavailable.
     *
     * @param img Image or frame object to read from or populate.
     * @return Capture status reported to the streaming pipeline.
     */
    virtual int dummy_img(img_t *img) = 0;

    /**
     * @brief Create AVCodec encode device.
     *
     * @param pix_fmt Sunshine pixel format to convert or allocate for.
     * @return Constructed AVCodec encode device object.
     */
    virtual std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) {
      return nullptr;
    }

    /**
     * @brief Create NVENC encode device.
     *
     * @param pix_fmt Sunshine pixel format to convert or allocate for.
     * @return Constructed NVENC encode device object.
     */
    virtual std::unique_ptr<nvenc_encode_device_t> make_nvenc_encode_device(pix_fmt_e pix_fmt) {
      return nullptr;
    }

    /**
     * @brief Report whether the active display mode is HDR.
     *
     * @return True when the active display mode is HDR.
     */
    virtual bool is_hdr() {
      return false;
    }

    /**
     * @brief Read HDR metadata for the active display mode.
     *
     * @param metadata Output structure populated with HDR metadata.
     * @return True when HDR metadata was written to `metadata`.
     */
    virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) {
      std::memset(&metadata, 0, sizeof(metadata));
      return false;
    }

    /**
     * @brief Check that a given codec is supported by the display device.
     * @param name The FFmpeg codec name (or similar for non-FFmpeg codecs).
     * @param config The codec configuration.
     * @return `true` if supported, `false` otherwise.
     */
    virtual bool is_codec_supported(std::string_view name, const ::video::config_t &config) {
      return true;
    }

    virtual ~display_t() = default;

    // Offsets for when streaming a specific monitor. By default, they are 0.
    int offset_x {0};  ///< Horizontal capture offset in physical pixels.
    int offset_y {0};  ///< Vertical capture offset in physical pixels.
    int env_width {0};  ///< Width of the full capture environment in physical pixels.
    int env_height {0};  ///< Height of the full capture environment in physical pixels.
    int env_logical_width {0};  ///< Width of the full capture environment after display scaling.
    int env_logical_height {0};  ///< Height of the full capture environment after display scaling.
    int width {0};  ///< Width of the captured display in physical pixels.
    int height {0};  ///< Height of the captured display in physical pixels.
    int logical_width {0};  ///< Width of the captured display after display scaling.
    int logical_height {0};  ///< Height of the captured display after display scaling.

  protected:
    // collect capture timing data (at loglevel debug)
    logging::time_delta_periodic_logger sleep_overshoot_logger = {debug, "Frame capture sleep overshoot"};  ///< Periodic logger for capture sleep overshoot measurements.
  };

  /**
   * @brief Audio capture source used by the streaming pipeline.
   */
  class mic_t {
  public:
    /**
     * @brief Deliver a captured audio sample to Sunshine's audio pipeline.
     *
     * @param frame_buffer Destination for captured floating-point PCM samples.
     * @return Capture status reported to the streaming pipeline.
     */
    virtual capture_e sample(std::vector<float> &frame_buffer) = 0;

    virtual ~mic_t() = default;
  };

  /**
   * @brief Platform audio controller that manages sinks and microphone capture.
   */
  class audio_control_t {
  public:
    /**
     * @brief Update the sink value on the backend.
     *
     * @param sink Audio sink name to route or capture.
     * @return Status from updating sink.
     */
    virtual int set_sink(const std::string &sink) = 0;

    /**
     * @brief Create a microphone capture stream for the requested layout.
     *
     * @param mapping Opus channel mapping table for the requested layout.
     * @param channels Number of audio channels in the stream.
     * @param sample_rate Audio sample rate in hertz.
     * @param frame_size Number of samples captured per audio frame.
     * @param continuous Whether silent audio should continue to be emitted.
     * @param host_audio_enabled Whether host playback should remain enabled during capture.
     * @return Microphone capture object for the requested audio layout.
     */
    virtual std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous, [[maybe_unused]] bool host_audio_enabled) = 0;

    /**
     * @brief Check if the audio sink is available in the system.
     * @param sink Sink to be checked.
     * @returns True if available, false otherwise.
     */
    virtual bool is_sink_available(const std::string &sink) = 0;

    /**
     * @brief Query host and virtual sink names available to Sunshine.
     *
     * @return Host and virtual sink names when the backend can report them.
     */
    virtual std::optional<sink_t> sink_info() = 0;

    /**
     * @brief Destroy the audio control.
     */
    virtual ~audio_control_t() = default;
  };

  /**
   * @brief Platform-specific input backend context.
   */
  struct input_raw_t;

  /**
   * @brief Release a platform input backend created by input().
   *
   * @param input Platform input backend to release.
   */
  void freeInput(input_raw_t *input);

  /**
   * @brief Owning pointer for a platform input backend.
   */
  using input_t = util::safe_ptr<input_raw_t, &freeInput>;

  std::filesystem::path appdata();

  /**
   * @brief Return the hardware MAC address associated with a network address.
   *
   * @param address Network address being parsed or filtered.
   * @return Hardware MAC address string, or an empty string when it cannot be resolved.
   */
  std::string get_mac_address(const std::string_view &address);

  /**
   * @brief Convert a socket address to a printable IP address.
   *
   * @param ip_addr Socket address to format.
   * @return Value converted from sockaddr.
   */
  std::string from_sockaddr(const sockaddr *const);
  /**
   * @brief Convert a socket address to a port and printable IP address.
   *
   * @param ip_addr Socket address to format.
   * @return Value converted from sockaddr ex.
   */
  std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const);

  /**
   * @brief Create the platform audio controller.
   *
   * @return Platform audio controller, or nullptr when audio control is unavailable.
   */
  std::unique_ptr<audio_control_t> audio_control();

  /**
   * @brief Get the display_t instance for the given hwdevice_type.
   * If display_name is empty, use the first monitor that's compatible you can find
   * If you require to use this parameter in a separate thread, make a copy of it.
   * @param display_name The name of the monitor that SHOULD be displayed
   * @param config Stream configuration
   * @return The display_t instance based on hwdevice_type.
   */
  std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);

  // A list of names of displays accepted as display_name with the mem_type_e
  /**
   * @brief List display names accepted by the selected capture backend.
   *
   * @param hwdevice_type Hardware device type requested for capture or encode.
   * @return Display names accepted by the selected capture backend.
   */
  std::vector<std::string> display_names(mem_type_e hwdevice_type);

  /**
   * @brief Check if GPUs/drivers have changed since the last call to this function.
   * @return `true` if a change has occurred or if it is unknown whether a change occurred.
   */
  bool needs_encoder_reenumeration();

  /**
   * @brief Launch a configured preparation or application command.
   *
   * @param elevated Whether the command should run with elevated privileges.
   * @param interactive Whether the command should run in an interactive session.
   * @param cmd Command line to execute or inspect.
   * @param working_dir Working directory for the child process.
   * @param env Environment variables for the child process.
   * @param file Optional stdio file handle connected to the child process.
   * @param ec Error code returned by the asynchronous operation.
   * @param group Process group used when launching the command.
   * @return Child process handle for the launched command.
   */
  boost::process::v1::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const boost::process::v1::environment &env, FILE *file, std::error_code &ec, boost::process::v1::group *group);

  /**
   * @brief Enumerates supported thread priority options.
   */
  enum class thread_priority_e : int {
    low,  ///< Low priority
    normal,  ///< Normal priority
    high,  ///< High priority
    critical  ///< Critical priority
  };
  /**
   * @brief Apply the requested scheduling priority to the current thread.
   *
   * @param priority Thread priority requested from the platform backend.
   */
  void adjust_thread_priority(thread_priority_e priority);

  /**
   * @brief Name the current thread for use with development tools.
   * @note On Linux this will be truncated after 15 characters.
   *
   * @param name Human-readable name to assign.
   */
  void set_thread_name(const std::string &name);

  void enable_mouse_keys();

  // Allow OS-specific actions to be taken to prepare for streaming
  void streaming_will_start();
  void streaming_will_stop();

  void restart();

  /**
   * @brief Platform buffer pointer and size for batched socket sends.
   */
  struct buffer_descriptor_t {
    const char *buffer;  ///< Pointer to the payload buffer.
    size_t size;  ///< Size of the buffer in bytes.

    // Constructors required for emplace_back() prior to C++20
    /**
     * @brief Describe a contiguous byte buffer for batched socket sending.
     *
     * @param buffer Serialized byte buffer to read from or write to.
     * @param size Number of bytes or elements requested.
     */
    buffer_descriptor_t(const char *buffer, size_t size):
        buffer(buffer),
        size(size) {
    }

    buffer_descriptor_t():
        buffer(nullptr),
        size(0) {
    }
  };

  /**
   * @brief Buffers and native metadata for one batched send operation.
   */
  struct batched_send_info_t {
    // Optional headers to be prepended to each packet
    const char *headers;  ///< Optional header bytes prepended to each payload.
    size_t header_size;  ///< Header size in bytes.

    // One or more data buffers to use for the payloads
    //
    // NB: Data buffers must be aligned to payload size!
    std::vector<buffer_descriptor_t> &payload_buffers;  ///< Payload buffers containing one or more packet blocks.
    size_t payload_size;  ///< Payload size in bytes for each packet block.

    // The offset (in header+payload message blocks) in the header and payload
    // buffers to begin sending messages from
    size_t block_offset;  ///< First packet block index to send.

    // The number of header+payload message blocks to send
    size_t block_count;  ///< Number of packet blocks to send.

    std::uintptr_t native_socket;  ///< Platform socket handle used for the send operation.
    boost::asio::ip::address &target_address;  ///< Destination IP address for outgoing packets.
    uint16_t target_port;  ///< Destination UDP port for outgoing packets.
    boost::asio::ip::address &source_address;  ///< Local source IP address for outgoing packets.

    /**
     * @brief Returns a payload buffer descriptor for the given payload offset.
     * @param offset The offset in the total payload data (bytes).
     * @return Buffer descriptor describing the region at the given offset.
     */
    buffer_descriptor_t buffer_for_payload_offset(ptrdiff_t offset) {
      for (const auto &desc : payload_buffers) {
        if (offset < desc.size) {
          return {
            desc.buffer + offset,
            desc.size - offset,
          };
        } else {
          offset -= desc.size;
        }
      }
      return {};
    }
  };

  /**
   * @brief Send multiple fixed-size UDP payload blocks using the platform backend.
   *
   * @param send_info Socket addresses, buffers, and sizes for the send operation.
   * @return True when all requested packet blocks are submitted to the socket.
   */
  bool send_batch(batched_send_info_t &send_info);

  /**
   * @brief Destination address and payload data for one UDP send.
   */
  struct send_info_t {
    const char *header;  ///< Optional header bytes prepended to the payload.
    size_t header_size;  ///< Header size in bytes.
    const char *payload;  ///< Payload bytes to send after the header.
    size_t payload_size;  ///< Payload size in bytes for each packet block.

    std::uintptr_t native_socket;  ///< Platform socket handle used for the send operation.
    boost::asio::ip::address &target_address;  ///< Destination IP address for outgoing packets.
    uint16_t target_port;  ///< Destination UDP port for outgoing packets.
    boost::asio::ip::address &source_address;  ///< Local source IP address for outgoing packets.
  };

  /**
   * @brief Send the serialized response over the active socket.
   *
   * @param send_info Socket addresses, buffers, and sizes for the send operation.
   * @return True when the packet is submitted to the socket.
   */
  bool send(send_info_t &send_info);

  /**
   * @brief Identifies traffic classes used for socket QoS tagging.
   */
  enum class qos_data_type_e : int {
    audio,  ///< Audio
    video  ///< Video
  };

  /**
   * @brief Enable QoS on the given socket for traffic to the specified destination.
   * @param native_socket The native socket handle.
   * @param address The destination address for traffic sent on this socket.
   * @param port The destination port for traffic sent on this socket.
   * @param data_type The type of traffic sent on this socket.
   * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic.
   *
   * @return Cleanup handle that restores or releases QoS state when destroyed.
   */
  std::unique_ptr<deinit_t> enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging);

  /**
   * @brief Open a url in the default web browser.
   * @param url The url to open.
   */
  void open_url(const std::string &url);

  /**
   * @brief Attempt to gracefully terminate a process group.
   * @param native_handle The native handle of the process group.
   * @return `true` if termination was successfully requested.
   */
  bool request_process_group_exit(std::uintptr_t native_handle);

  /**
   * @brief Check if a process group still has running children.
   * @param native_handle The native handle of the process group.
   * @return `true` if processes are still running.
   */
  bool process_group_running(std::uintptr_t native_handle);

  /**
   * @brief Create the platform input backend for a stream.
   *
   * @return Platform-specific input backend for the active stream.
   */
  input_t input();
  /**
   * @brief Get the current mouse position for platform input tests.
   *
   * @param input The input_t instance to use.
   * @return Screen coordinates of the mouse, or `std::nullopt` when the platform cannot observe the cursor.
   *
   * @note This helper exists only so tests can observe virtual mouse movement. Production input paths should submit
   * mouse events through `move_mouse()` or `abs_mouse()` instead of reading the host cursor location.
   *
   * @examples
   * if (auto location = get_mouse_loc(input)) {
   *   auto [x, y] = *location;
   * }
   * @examples_end
   */
  std::optional<util::point_t> get_mouse_loc(input_t &input);
  /**
   * @brief Move mouse using the backend coordinate system.
   *
   * @param input Platform input backend that receives the event.
   * @param deltaX Delta x.
   * @param deltaY Delta y.
   */
  void move_mouse(input_t &input, int deltaX, int deltaY);
  /**
   * @brief Move the pointer to an absolute client-provided touch coordinate.
   *
   * @param input Platform input backend that receives the event.
   * @param touch_port Touch coordinate bounds used for scaling.
   * @param x Horizontal absolute coordinate from the client.
   * @param y Vertical absolute coordinate from the client.
   */
  void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
  /**
   * @brief Press or release a virtual mouse button.
   *
   * @param input Platform input backend that receives the event.
   * @param button Mouse button identifier to press or release.
   * @param release Whether the key or button event is a release.
   */
  void button_mouse(input_t &input, int button, bool release);
  /**
   * @brief Apply a vertical scroll event to the virtual mouse.
   *
   * @param input Platform input backend that receives the event.
   * @param distance High-resolution scroll distance reported by the client.
   */
  void scroll(input_t &input, int distance);
  /**
   * @brief Apply a horizontal scroll event to the virtual mouse.
   *
   * @param input Platform input backend that receives the event.
   * @param distance High-resolution scroll distance reported by the client.
   */
  void hscroll(input_t &input, int distance);
  /**
   * @brief Press or release a virtual keyboard key.
   *
   * @param input Platform input backend that receives the event.
   * @param modcode Modifier key code to update.
   * @param release Whether the key or button event is a release.
   * @param flags Bit flags that modify the requested operation.
   */
  void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags);
  void gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state);
  /**
   * @brief Submit UTF-8 text input to the keyboard backend.
   *
   * @param input Platform input backend that receives the event.
   * @param utf8 UTF-8 text submitted by the client.
   * @param size Number of bytes or elements requested.
   */
  void unicode(input_t &input, const char *utf8, int size);

  /**
   * @brief Per-client input context allocated by a platform backend.
   */
  typedef deinit_t client_input_t;

  /**
   * @brief Allocate a context to store per-client input data.
   * @param input The global input context.
   * @return A unique pointer to a per-client input data context.
   */
  std::unique_ptr<client_input_t> allocate_client_input_context(input_t &input);

  /**
   * @brief Send a touch event to the OS.
   * @param input The client-specific input context.
   * @param touch_port The current viewport for translating to screen coordinates.
   * @param touch The touch event.
   */
  void touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch);

  /**
   * @brief Send a pen event to the OS.
   * @param input The client-specific input context.
   * @param touch_port The current viewport for translating to screen coordinates.
   * @param pen The pen event.
   */
  void pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen);

  /**
   * @brief Send a gamepad touch event to the OS.
   * @param input The global input context.
   * @param touch The touch event.
   */
  void gamepad_touch(input_t &input, const gamepad_touch_t &touch);

  /**
   * @brief Send a gamepad motion event to the OS.
   * @param input The global input context.
   * @param motion The motion event.
   */
  void gamepad_motion(input_t &input, const gamepad_motion_t &motion);

  /**
   * @brief Send a gamepad battery event to the OS.
   * @param input The global input context.
   * @param battery The battery event.
   */
  void gamepad_battery(input_t &input, const gamepad_battery_t &battery);

  /**
   * @brief Create a new virtual gamepad.
   * @param input The global input context.
   * @param id The gamepad ID.
   * @param metadata Controller metadata from client (empty if none provided).
   * @param feedback_queue The queue for posting messages back to the client.
   * @return 0 on success.
   */
  int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
  /**
   * @brief Rebind an existing virtual gamepad to a resumed client session.
   *
   * @param input Platform input backend that owns the virtual gamepad.
   * @param id Global and client-relative identifiers for the resumed controller.
   * @param feedback_queue Queue used to return gamepad feedback to the resumed client.
   * @return 0 when the gamepad exists and was rebound; otherwise -1.
   */
  int rebind_gamepad(input_t &input, const gamepad_id_t &id, feedback_queue_t feedback_queue);
  /**
   * @brief Release gamepad resources.
   *
   * @param input Platform input backend that receives the event.
   * @param nr Controller index assigned by the client.
   */
  void free_gamepad(input_t &input, int nr);

  /**
   * @brief Get the supported platform capabilities to advertise to the client.
   * @return Capability flags.
   */
  platform_caps::caps_t get_capabilities();

  constexpr auto SERVICE_NAME = "Sunshine";  ///< mDNS service instance name advertised for GameStream discovery.
  constexpr auto SERVICE_TYPE = "_nvstream._tcp";  ///< mDNS service type advertised for GameStream discovery.

  namespace publish {
    [[nodiscard]] std::unique_ptr<deinit_t> start();
  }

  /**
   * @brief Initialize the platform-specific high precision timer.
   *
   * @return Cleanup handle for initialized platform resources, or null if none are needed.
   */
  [[nodiscard]] std::unique_ptr<deinit_t> init();

  /**
   * @brief Returns the current computer name in UTF-8.
   * @return Computer name or a placeholder upon failure.
   */
  std::string get_host_name();

  /**
   * @brief Resolves the render device path to use for hardware encoding.
   * @details If `config::video.adapter_name` is set, returns that.
   *          Otherwise, auto-detects the GPU with a connected display via `find_render_node_with_display()`.
   *          Falls back to `/dev/dri/renderD128` if detection fails.
   * @return Resolved render device path (may be empty on non-Linux platforms).
   */
  std::string resolve_render_device();

  /**
   * @brief Gets the supported gamepads for this platform backend.
   * @details This may be called prior to `platf::input()`!
   * @param input Pointer to the platform's `input_t` or `nullptr`.
   * @return Vector of gamepad options and status.
   */
  std::vector<supported_gamepad_t> &supported_gamepads(input_t *input);

  /**
   * @brief Platform timer object used for precise frame pacing.
   */
  struct high_precision_timer: private boost::noncopyable {
    virtual ~high_precision_timer() = default;

    /**
     * @brief Sleep for the duration
     * @param duration Sleep duration
     */
    virtual void sleep_for(const std::chrono::nanoseconds &duration) = 0;

    /**
     * @brief Check if platform-specific timer backend has been initialized successfully
     * @return `true` on success, `false` on error
     */
    virtual operator bool() = 0;
  };

  /**
   * @brief Create platform-specific timer capable of high-precision sleep
   * @return A unique pointer to timer
   */
  std::unique_ptr<high_precision_timer> create_high_precision_timer();

  /**
   * @brief Check is the current process is running with elevated privileges (e.g. system admin/etc.)
   * @param all_caps Bool that specifies whether to check all caps or only CAP_SYS_ADMIN
   * @return True if capabilities specified to be checked are present.
   */
  bool has_elevated_privileges(bool all_caps);

  /**
   * @brief Drop elevated privileges (e.g. system admin/nice etc.)
   * @param all_caps Bool that specifies whether to drop all caps or only CAP_SYS_ADMIN
   */
  void drop_elevated_privileges(bool all_caps);

}  // namespace platf
