//! Tools for inspecting and manipulating clients.

use {
    serde::{Deserialize, Serialize},
    std::ops::Deref,
};

/// A client connected to the compositor.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Client(pub u64);

impl Client {
    /// Returns whether the client exists.
    pub fn exists(self) -> bool {
        self.0 != 0 && get!(false).client_exists(self)
    }

    /// Returns whether the client does not exist.
    ///
    /// This is a shorthand for `!self.exists()`.
    pub fn does_not_exist(self) -> bool {
        !self.exists()
    }

    /// Returns whether this client is XWayland.
    pub fn is_xwayland(self) -> bool {
        get!(false).client_is_xwayland(self)
    }

    /// Disconnects the client.
    pub fn kill(self) {
        get!().client_kill(self)
    }
}

/// Returns all current clients.
pub fn clients() -> Vec<Client> {
    get!().clients()
}

/// A client matcher.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct ClientMatcher(pub u64);

/// A matched client.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct MatchedClient {
    pub(crate) matcher: ClientMatcher,
    pub(crate) client: Client,
}

/// A criterion for matching a client.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[non_exhaustive]
pub enum ClientCriterion<'a> {
    /// Matches if the contained matcher matches.
    Matcher(ClientMatcher),
    /// Matches if the contained criterion does not match.
    Not(&'a ClientCriterion<'a>),
    /// Matches if all of the contained criteria match.
    All(&'a [ClientCriterion<'a>]),
    /// Matches if any of the contained criteria match.
    Any(&'a [ClientCriterion<'a>]),
    /// Matches if an exact number of the contained criteria match.
    Exactly(usize, &'a [ClientCriterion<'a>]),
    /// Matches the engine name of the client's sandbox verbatim.
    SandboxEngine(&'a str),
    /// Matches the engine name of the client's sandbox with a regular expression.
    SandboxEngineRegex(&'a str),
    /// Matches the app id of the client's sandbox verbatim.
    SandboxAppId(&'a str),
    /// Matches the app id of the client's sandbox with a regular expression.
    SandboxAppIdRegex(&'a str),
    /// Matches the instance id of the client's sandbox verbatim.
    SandboxInstanceId(&'a str),
    /// Matches the instance id of the client's sandbox with a regular expression.
    SandboxInstanceIdRegex(&'a str),
    /// Matches if the client is sandboxed.
    Sandboxed,
    /// Matches the user ID of the client.
    Uid(i32),
    /// Matches the process ID of the client.
    Pid(i32),
    /// Matches if the client is Xwayland.
    IsXwayland,
    /// Matches the `/proc/pid/comm` of the client verbatim.
    Comm(&'a str),
    /// Matches the `/proc/pid/comm` of the client with a regular expression.
    CommRegex(&'a str),
    /// Matches the `/proc/pid/exe` of the client verbatim.
    Exe(&'a str),
    /// Matches the `/proc/pid/exe` of the client with a regular expression.
    ExeRegex(&'a str),
    /// Matches the tag of the client verbatim.
    Tag(&'a str),
    /// Matches the tag of the client with a regular expression.
    TagRegex(&'a str),
}

impl ClientCriterion<'_> {
    /// Converts the criterion to a matcher.
    pub fn to_matcher(self) -> ClientMatcher {
        get!(ClientMatcher(0)).create_client_matcher(self)
    }

    /// Binds a function to execute when the criterion matches a client.
    ///
    /// This leaks the matcher.
    pub fn bind<F: FnMut(MatchedClient) + 'static>(self, cb: F) {
        self.to_matcher().bind(cb);
    }

    /// Sets the capabilities granted to clients matching this matcher.
    ///
    /// This leaks the matcher.
    pub fn set_capabilities(self, caps: ClientCapabilities) {
        self.to_matcher().set_capabilities(caps);
    }

    /// Sets the upper capability bounds for clients in sandboxes created by this client.
    ///
    /// This leaks the matcher.
    pub fn set_sandbox_bounding_capabilities(self, caps: ClientCapabilities) {
        self.to_matcher().set_sandbox_bounding_capabilities(caps);
    }
}

impl ClientMatcher {
    /// Destroys the matcher.
    ///
    /// Any bound callback will no longer be executed.
    pub fn destroy(self) {
        get!().destroy_client_matcher(self);
    }

    /// Sets a function to execute when the criterion matches a client.
    ///
    /// Replaces any already bound callback.
    pub fn bind<F: FnMut(MatchedClient) + 'static>(self, cb: F) {
        get!().set_client_matcher_handler(self, cb);
    }

    /// Sets the capabilities granted to clients matching this matcher.
    ///
    /// If multiple matchers match a client, the capabilities are added.
    ///
    /// If no matcher matches a client, it is granted the default capabilities depending
    /// on whether it's sandboxed or not. If it is not sandboxed, it is granted the
    /// capabilities [`CC_LAYER_SHELL`] and [`CC_DRM_LEASE`]. Otherwise it is granted the
    /// capability [`CC_DRM_LEASE`].
    ///
    /// Regardless of any capabilities set through this function, the capabilities of the
    /// client can never exceed its bounding capabilities.
    pub fn set_capabilities(self, caps: ClientCapabilities) {
        get!().set_client_matcher_capabilities(self, caps);
    }

    /// Sets the upper capability bounds for clients in sandboxes created by this client.
    ///
    /// If multiple matchers match a client, the capabilities are added.
    ///
    /// If no matcher matches a client, the bounding capabilities for sandboxes depend on
    /// whether the client is itself sandboxed. If it is sandboxed, the bounding
    /// capabilities are the effective capabilities of the client. Otherwise the bounding
    /// capabilities are all capabilities.
    ///
    /// Regardless of any capabilities set through this function, the capabilities set
    /// through this function can never exceed the client's bounding capabilities.
    pub fn set_sandbox_bounding_capabilities(self, caps: ClientCapabilities) {
        get!().set_client_matcher_bounding_capabilities(self, caps);
    }
}

impl MatchedClient {
    /// Returns the client that matched.
    pub fn client(self) -> Client {
        self.client
    }

    /// Returns the matcher.
    pub fn matcher(self) -> ClientMatcher {
        self.matcher
    }

    /// Latches a function to be executed when the client no longer matches the criteria.
    pub fn latch<F: FnOnce() + 'static>(self, cb: F) {
        get!().set_client_matcher_latch_handler(self.matcher, self.client, cb);
    }
}

impl Deref for MatchedClient {
    type Target = Client;

    fn deref(&self) -> &Self::Target {
        &self.client
    }
}

bitflags! {
    /// Capabilities granted to a client.
    #[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq)]
    pub struct ClientCapabilities(pub u64) {
        /// Grants access to the `ext_data_control_manager_v1` and
        /// `zwlr_data_control_manager_v1` globals.
        pub const CC_DATA_CONTROL             = 1 << 0,
        /// Grants access to the `zwp_virtual_keyboard_manager_v1` global.
        pub const CC_VIRTUAL_KEYBOARD         = 1 << 1,
        /// Grants access to the `ext_foreign_toplevel_list_v1` global.
        pub const CC_FOREIGN_TOPLEVEL_LIST    = 1 << 2,
        /// Grants access to the `ext_idle_notifier_v1` global.
        pub const CC_IDLE_NOTIFIER            = 1 << 3,
        /// Grants access to the `ext_session_lock_manager_v1` global.
        pub const CC_SESSION_LOCK             = 1 << 4,
        /// Grants access to the `zwlr_layer_shell_v1` global.
        pub const CC_LAYER_SHELL              = 1 << 6,
        /// Grants access to the `ext_image_copy_capture_manager_v1` and
        /// `zwlr_screencopy_manager_v1` globals.
        pub const CC_SCREENCOPY               = 1 << 7,
        /// Grants access to the `ext_transient_seat_manager_v1` global.
        pub const CC_SEAT_MANAGER             = 1 << 8,
        /// Grants access to the `wp_drm_lease_device_v1` global.
        pub const CC_DRM_LEASE                = 1 << 9,
        /// Grants access to the `zwp_input_method_manager_v2` global.
        pub const CC_INPUT_METHOD             = 1 << 10,
        /// Grants access to the `ext_workspace_manager_v1` global.
        pub const CC_WORKSPACE_MANAGER        = 1 << 11,
        /// Grants access to the `zwlr_foreign_toplevel_manager_v1` global.
        pub const CC_FOREIGN_TOPLEVEL_MANAGER = 1 << 12,
        /// Grants access to the `jay_head_manager_v1` and `zwlr_output_manager_v1`
        /// globals.
        pub const CC_HEAD_MANAGER             = 1 << 13,
        /// Grants access to the `zwlr_gamma_control_manager_v1` global.
        pub const CC_GAMMA_CONTROL_MANAGER    = 1 << 14,
    }
}
