/*
==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--

Dia-Files

Copyright (C) 2019-2024  Anonymous

There are several releases over multiple years,
they are listed as ranges, such as: "2019-2024".

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
*/

//! # Permissions

#[cfg(unix)]
use {
    core::cmp::Ordering,
    std::{
        fs::{self, Metadata},
        io::Error,
        os::unix::fs::PermissionsExt,
        path::{Path, PathBuf},
    },
    crate::Result,
};

mod impls;

#[cfg(test)]
mod tests;

/// # Raw Permission
#[cfg(unix)]
#[doc(cfg(unix))]
pub type RawPermission = u32;

/// # Permissions
///
/// You can cast this enum to [`RawPermission`][type:RawPermission] directly via `as`.
///
/// [type:RawPermission]: type.RawPermission.html
#[cfg(unix)]
#[doc(cfg(unix))]
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, PartialOrd)]
pub enum Permissions {

    /// # No permissions
    None = 0,

    /// # Read
    Read = 4,

    /// # Write
    Write = 2,

    /// # Execute
    Execute = 1,

    /// # Read/Write
    ReadWrite = Self::Read as isize | Self::Write as isize,

    /// # Read/Execute
    ReadExecute = Self::Read as isize | Self::Execute as isize,

    /// # Write/Execute
    WriteExecute = Self::Write as isize | Self::Execute as isize,

    /// # Read/Write/Execute
    ReadWriteExecute = Self::Read as isize | Self::Write as isize | Self::Execute as isize,

}

#[cfg(unix)]
#[doc(cfg(unix))]
impl Ord for Permissions {

    fn cmp(&self, other: &Self) -> Ordering {
        (*self as RawPermission).cmp(&(*other as RawPermission))
    }

}

/// # File Permissions
///
/// ## Notes
///
/// Currently, only Unix is supported.
///
/// ## Examples
///
/// ```
/// use dia_files::{FilePermissions, Permissions};
///
/// assert!(
///     FilePermissions::new(Permissions::Read, Permissions::None, Permissions::None)
///     < FilePermissions::new(Permissions::Read, Permissions::Read, Permissions::Read)
/// );
/// ```
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, PartialOrd)]
pub struct FilePermissions {

    #[cfg(unix)]
    user: Permissions,
    #[cfg(unix)]
    group: Permissions,
    #[cfg(unix)]
    others: Permissions,

    #[cfg(not(unix))]
    lock: (),

}

impl FilePermissions {

    /// # Makes new instance
    #[cfg(unix)]
    #[doc(cfg(unix))]
    pub const fn new(user: Permissions, group: Permissions, others: Permissions) -> Self {
        Self {
            user,
            group,
            others,
        }
    }

    /// # Makes new instance
    #[cfg(not(unix))]
    #[doc(cfg(not(unix)))]
    pub const fn new() -> Self {
        Self {
            lock: (),
        }
    }

    /// # User's permissions
    #[cfg(unix)]
    #[doc(cfg(unix))]
    pub const fn user(&self) -> &Permissions {
        &self.user
    }

    /// # Group's permissions
    #[cfg(unix)]
    #[doc(cfg(unix))]
    pub const fn group(&self) -> &Permissions {
        &self.group
    }

    /// # Others' permissions
    #[cfg(unix)]
    #[doc(cfg(unix))]
    pub const fn others(&self) -> &Permissions {
        &self.others
    }

    /// # Sets these permissions to a file
    #[cfg(all(not(feature="async-std"), unix))]
    #[doc(cfg(all(not(feature="async-std"), unix)))]
    pub fn set<P>(&self, file: P) -> Result<()> where P: AsRef<Path> {
        fs::set_permissions(file, self.into())
    }

    /// # Sets these permissions to a file
    #[cfg(all(feature="async-std", unix))]
    #[doc(cfg(all(feature="async-std", unix)))]
    pub async fn set<P>(&self, file: P) -> Result<()> where P: AsRef<async_std::path::Path> {
        async_std::fs::set_permissions(file, self.into()).await
    }

}

#[cfg(unix)]
#[doc(cfg(unix))]
impl Ord for FilePermissions {

    fn cmp(&self, other: &Self) -> Ordering {
        RawPermission::from(self).cmp(&other.into())
    }

}

#[cfg(unix)]
macro_rules! impl_from_file_permissions_for_fs_permissions { ($($file_permissions: ty,)+) => {
    $(
        #[cfg(unix)]
        #[doc(cfg(unix))]
        impl From<$file_permissions> for fs::Permissions {

            fn from(file_permissions: $file_permissions) -> Self {
                Self::from_mode(file_permissions.into())
            }

        }
    )+
}}

#[cfg(unix)]
impl_from_file_permissions_for_fs_permissions! { &FilePermissions, FilePermissions, }

#[cfg(unix)]
#[doc(cfg(unix))]
impl TryFrom<&fs::Permissions> for FilePermissions {

    type Error = Error;

    fn try_from(permissions: &fs::Permissions) -> Result<Self> {
        let mode = permissions.mode();
        Ok(Self::new(
            Permissions::try_from(mode << 23 >> 29)?,
            Permissions::try_from(mode << 26 >> 29)?,
            Permissions::try_from(mode << 29 >> 29)?,
        ))
    }

}

#[cfg(unix)]
#[doc(cfg(unix))]
impl TryFrom<fs::Permissions> for FilePermissions {

    type Error = Error;

    fn try_from(permissions: fs::Permissions) -> Result<Self> {
        Self::try_from(&permissions)
    }

}

#[cfg(unix)]
macro_rules! impl_try_from_metadata_for_file_permissions { ($($metadata: ty,)+) => {
    $(
        #[cfg(unix)]
        #[doc(cfg(unix))]
        impl TryFrom<$metadata> for FilePermissions {

            type Error = Error;

            fn try_from(metadata: $metadata) -> Result<Self> {
                Self::try_from(&metadata.permissions())
            }

        }
    )+
}}

#[cfg(unix)]
impl_try_from_metadata_for_file_permissions! { &Metadata, Metadata, }

#[cfg(unix)]
macro_rules! impl_try_from_paths_for_file_permissions { ($($path: ty,)+) => {
    $(
        #[cfg(unix)]
        #[doc(cfg(unix))]
        impl TryFrom<$path> for FilePermissions {

            type Error = Error;

            fn try_from(path: $path) -> Result<Self> {
                Self::try_from(&path.metadata()?.permissions())
            }

        }
    )+
}}

#[cfg(unix)]
impl_try_from_paths_for_file_permissions! { &Path, &PathBuf, PathBuf, }
