// Take a look at the license at the top of the repository in the LICENSE file.

use std::cmp::Ordering;

use crate::{Gid, Uid, UserInner};

/// Type containing user information.
///
/// It is returned by [`Users`][crate::Users].
///
/// ```no_run
/// use sysinfo::Users;
///
/// let users = Users::new_with_refreshed_list();
/// for user in users.list() {
///     println!("{:?}", user);
/// }
/// ```
pub struct User {
    pub(crate) inner: UserInner,
}

impl PartialEq for User {
    fn eq(&self, other: &Self) -> bool {
        self.id() == other.id()
            && self.group_id() == other.group_id()
            && self.name() == other.name()
    }
}

impl Eq for User {}

impl PartialOrd for User {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for User {
    fn cmp(&self, other: &Self) -> Ordering {
        self.name().cmp(other.name())
    }
}

impl User {
    /// Returns the ID of the user.
    ///
    /// ```no_run
    /// use sysinfo::Users;
    ///
    /// let users = Users::new_with_refreshed_list();
    /// for user in users.list() {
    ///     println!("{:?}", *user.id());
    /// }
    /// ```
    pub fn id(&self) -> &Uid {
        self.inner.id()
    }

    /// Returns the group ID of the user.
    ///
    /// ⚠️ This information is not set on Windows.  Windows doesn't have a `username` specific
    /// group assigned to the user. They do however have unique
    /// [Security Identifiers](https://docs.microsoft.com/en-us/windows/win32/secauthz/security-identifiers)
    /// made up of various [Components](https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-components).
    /// Pieces of the SID may be a candidate for this field, but it doesn't map well to a single
    /// group ID.
    ///
    /// ```no_run
    /// use sysinfo::Users;
    ///
    /// let users = Users::new_with_refreshed_list();
    /// for user in users.list() {
    ///     println!("{}", *user.group_id());
    /// }
    /// ```
    pub fn group_id(&self) -> Gid {
        self.inner.group_id()
    }

    /// Returns the name of the user.
    ///
    /// ```no_run
    /// use sysinfo::Users;
    ///
    /// let users = Users::new_with_refreshed_list();
    /// for user in users.list() {
    ///     println!("{}", user.name());
    /// }
    /// ```
    pub fn name(&self) -> &str {
        self.inner.name()
    }

    /// Returns the groups of the user.
    ///
    /// ⚠️ This is computed every time this method is called.
    ///
    /// ```no_run
    /// use sysinfo::Users;
    ///
    /// let users = Users::new_with_refreshed_list();
    /// for user in users.list() {
    ///     println!("{} is in {:?}", user.name(), user.groups());
    /// }
    /// ```
    pub fn groups(&self) -> Vec<Group> {
        self.inner.groups()
    }
}

#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
pub(crate) struct GroupInner {
    pub(crate) id: Gid,
    pub(crate) name: String,
}

/// Type containing group information.
///
/// It is returned by [`User::groups`] or [`Groups::list`].
///
/// ```no_run
/// use sysinfo::Users;
///
/// let mut users = Users::new_with_refreshed_list();
///
/// for user in users.list() {
///     println!(
///         "user: (ID: {:?}, group ID: {:?}, name: {:?})",
///         user.id(),
///         user.group_id(),
///         user.name(),
///     );
///     for group in user.groups() {
///         println!("group: (ID: {:?}, name: {:?})", group.id(), group.name());
///     }
/// }
/// ```
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Group {
    pub(crate) inner: GroupInner,
}

impl Group {
    /// Returns the ID of the group.
    ///
    /// ⚠️ This information is not set on Windows.
    ///
    /// ```no_run
    /// use sysinfo::Users;
    ///
    /// let mut users = Users::new_with_refreshed_list();
    ///
    /// for user in users.list() {
    ///     for group in user.groups() {
    ///         println!("{:?}", group.id());
    ///     }
    /// }
    /// ```
    pub fn id(&self) -> &Gid {
        self.inner.id()
    }

    /// Returns the name of the group.
    ///
    /// ```no_run
    /// use sysinfo::Users;
    ///
    /// let mut users = Users::new_with_refreshed_list();
    ///
    /// for user in users.list() {
    ///     for group in user.groups() {
    ///         println!("{}", group.name());
    ///     }
    /// }
    /// ```
    pub fn name(&self) -> &str {
        self.inner.name()
    }
}

/// Interacting with users.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let mut users = Users::new();
/// for user in users.list() {
///     println!("{} is in {} groups", user.name(), user.groups().len());
/// }
/// ```
pub struct Users {
    users: Vec<User>,
}

impl Default for Users {
    fn default() -> Self {
        Self::new()
    }
}

impl From<Users> for Vec<User> {
    fn from(users: Users) -> Self {
        users.users
    }
}

impl From<Vec<User>> for Users {
    fn from(users: Vec<User>) -> Self {
        Self { users }
    }
}

impl std::ops::Deref for Users {
    type Target = [User];

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

impl std::ops::DerefMut for Users {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.list_mut()
    }
}

impl<'a> IntoIterator for &'a Users {
    type Item = &'a User;
    type IntoIter = std::slice::Iter<'a, User>;

    fn into_iter(self) -> Self::IntoIter {
        self.list().iter()
    }
}

impl<'a> IntoIterator for &'a mut Users {
    type Item = &'a mut User;
    type IntoIter = std::slice::IterMut<'a, User>;

    fn into_iter(self) -> Self::IntoIter {
        self.list_mut().iter_mut()
    }
}

impl Users {
    /// Creates a new empty [`Users`][crate::Users] type.
    ///
    /// If you want it to be filled directly, take a look at [`Users::new_with_refreshed_list`].
    ///
    /// ```no_run
    /// use sysinfo::Users;
    ///
    /// let mut users = Users::new();
    /// users.refresh();
    /// for user in users.list() {
    ///     println!("{user:?}");
    /// }
    /// ```
    pub fn new() -> Self {
        Self { users: Vec::new() }
    }

    /// Creates a new [`Users`][crate::Users] type with the user list loaded.
    ///
    /// ```no_run
    /// use sysinfo::Users;
    ///
    /// let mut users = Users::new_with_refreshed_list();
    /// for user in users.list() {
    ///     println!("{user:?}");
    /// }
    /// ```
    pub fn new_with_refreshed_list() -> Self {
        let mut users = Self::new();
        users.refresh();
        users
    }

    /// Returns the users list.
    ///
    /// ```no_run
    /// use sysinfo::Users;
    ///
    /// let users = Users::new_with_refreshed_list();
    /// for user in users.list() {
    ///     println!("{user:?}");
    /// }
    /// ```
    pub fn list(&self) -> &[User] {
        &self.users
    }

    /// Returns the users list.
    ///
    /// ```no_run
    /// use sysinfo::Users;
    ///
    /// let mut users = Users::new_with_refreshed_list();
    /// users.list_mut().sort_by(|user1, user2| {
    ///     user1.name().partial_cmp(user2.name()).unwrap()
    /// });
    /// ```
    pub fn list_mut(&mut self) -> &mut [User] {
        &mut self.users
    }

    /// The user list will be emptied then completely recomputed.
    ///
    /// ```no_run
    /// use sysinfo::Users;
    ///
    /// let mut users = Users::new();
    /// users.refresh();
    /// ```
    pub fn refresh(&mut self) {
        crate::sys::get_users(&mut self.users);
    }

    /// Returns the [`User`] matching the given `user_id`.
    ///
    /// **Important**: The user list must be filled before using this method, otherwise it will
    /// always return `None` (through the `refresh_*` methods).
    ///
    /// It is a shorthand for:
    ///
    /// ```ignore
    /// # use sysinfo::Users;
    /// let users = Users::new_with_refreshed_list();
    /// users.list().find(|user| user.id() == user_id);
    /// ```
    ///
    /// Full example:
    ///
    #[cfg_attr(feature = "system", doc = "```no_run")]
    #[cfg_attr(not(feature = "system"), doc = "```ignore")]
    /// use sysinfo::{Pid, System, Users};
    ///
    /// let mut s = System::new_all();
    /// let users = Users::new_with_refreshed_list();
    ///
    /// if let Some(process) = s.process(Pid::from(1337)) {
    ///     if let Some(user_id) = process.user_id() {
    ///         println!("User for process 1337: {:?}", users.get_user_by_id(user_id));
    ///     }
    /// }
    /// ```
    pub fn get_user_by_id(&self, user_id: &Uid) -> Option<&User> {
        self.users.iter().find(|user| user.id() == user_id)
    }
}

/// Interacting with groups.
///
/// ```no_run
/// use sysinfo::Groups;
///
/// let mut groups = Groups::new();
/// for group in groups.list() {
///     println!("{}", group.name());
/// }
/// ```
pub struct Groups {
    groups: Vec<Group>,
}

impl Default for Groups {
    fn default() -> Self {
        Self::new()
    }
}

impl From<Groups> for Vec<Group> {
    fn from(groups: Groups) -> Self {
        groups.groups
    }
}

impl From<Vec<Group>> for Groups {
    fn from(groups: Vec<Group>) -> Self {
        Self { groups }
    }
}

impl std::ops::Deref for Groups {
    type Target = [Group];

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

impl std::ops::DerefMut for Groups {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.list_mut()
    }
}

impl<'a> IntoIterator for &'a Groups {
    type Item = &'a Group;
    type IntoIter = std::slice::Iter<'a, Group>;

    fn into_iter(self) -> Self::IntoIter {
        self.list().iter()
    }
}

impl<'a> IntoIterator for &'a mut Groups {
    type Item = &'a mut Group;
    type IntoIter = std::slice::IterMut<'a, Group>;

    fn into_iter(self) -> Self::IntoIter {
        self.list_mut().iter_mut()
    }
}

impl Groups {
    /// Creates a new empty [`Groups`][crate::Groups] type.
    ///
    /// If you want it to be filled directly, take a look at [`Groups::new_with_refreshed_list`].
    ///
    /// ```no_run
    /// use sysinfo::Groups;
    ///
    /// let mut groups = Groups::new();
    /// groups.refresh();
    /// for group in groups.list() {
    ///     println!("{group:?}");
    /// }
    /// ```
    pub fn new() -> Self {
        Self { groups: Vec::new() }
    }

    /// Creates a new [`Groups`][crate::Groups] type with the group list loaded.
    ///
    /// ```no_run
    /// use sysinfo::Groups;
    ///
    /// let mut groups = Groups::new_with_refreshed_list();
    /// for group in groups.list() {
    ///     println!("{group:?}");
    /// }
    /// ```
    pub fn new_with_refreshed_list() -> Self {
        let mut groups = Self::new();
        groups.refresh();
        groups
    }

    /// Returns the groups list.
    ///
    /// ```no_run
    /// use sysinfo::Groups;
    ///
    /// let groups = Groups::new_with_refreshed_list();
    /// for group in groups.list() {
    ///     println!("{group:?}");
    /// }
    /// ```
    pub fn list(&self) -> &[Group] {
        &self.groups
    }

    /// Returns the groups list.
    ///
    /// ```no_run
    /// use sysinfo::Groups;
    ///
    /// let mut groups = Groups::new_with_refreshed_list();
    /// groups.list_mut().sort_by(|user1, user2| {
    ///     user1.name().partial_cmp(user2.name()).unwrap()
    /// });
    /// ```
    pub fn list_mut(&mut self) -> &mut [Group] {
        &mut self.groups
    }

    /// The group list will be emptied then completely recomputed.
    ///
    /// ```no_run
    /// use sysinfo::Groups;
    ///
    /// let mut groups = Groups::new();
    /// groups.refresh();
    /// ```
    pub fn refresh(&mut self) {
        crate::sys::get_groups(&mut self.groups);
    }
}

#[cfg(test)]
mod tests {
    use crate::*;

    #[test]
    fn check_list() {
        let mut users = Users::new();
        assert!(users.list().is_empty());
        users.refresh();
        assert!(users.list().len() >= MIN_USERS);
    }

    // This test exists to ensure that the `TryFrom<usize>` and `FromStr` traits are implemented
    // on `Uid`, `Gid` and `Pid`.
    #[allow(clippy::unnecessary_fallible_conversions)]
    #[test]
    fn check_uid_gid_from_impls() {
        use std::convert::TryFrom;
        use std::str::FromStr;

        #[cfg(not(windows))]
        {
            assert!(crate::Uid::try_from(0usize).is_ok());
            assert!(crate::Uid::from_str("0").is_ok());
        }
        #[cfg(windows)]
        {
            assert!(crate::Uid::from_str("S-1-5-18").is_ok()); // SECURITY_LOCAL_SYSTEM_RID
            assert!(crate::Uid::from_str("0").is_err());
        }

        assert!(crate::Gid::try_from(0usize).is_ok());
        assert!(crate::Gid::from_str("0").is_ok());
    }

    #[test]
    fn check_groups() {
        if !crate::IS_SUPPORTED_SYSTEM {
            return;
        }
        assert!(!Groups::new_with_refreshed_list().is_empty());
    }
}
