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

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/>.

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

//! # Extensions for `Path`

use {
    std::{
        env,
        path::{Path, PathBuf},
    },
    crate::Sealed,
};

#[cfg(unix)]
#[cfg(feature="libc")]
use {
    core::{
        ffi::CStr,
        mem,
    },
    alloc::ffi::CString,
    std::{
        io::Error,
        os::unix::prelude::OsStrExt,
    },
    crate::{FileSystem, Result},
};

/// # Extensions for `Path`
///
/// This (sealed) trait provides some extensions for [`Path`][struct:std/path/Path].
///
/// [struct:std/path/Path]: https://doc.rust-lang.org/std/path/struct.Path.html
pub trait PathExt: Sealed {

    /// # Shortens this path from a base
    ///
    /// If there's an error, the original path is returned.
    fn shorten<P>(&self, base: P) -> &Path where P: AsRef<Path>;

    /// # Shortens this path from current directory
    ///
    /// If there's an error, the original path is returned.
    fn shorten_here(&self) -> &Path;

    /// # Gets file system which this path resides on
    #[cfg(all(feature="libc", unix))]
    #[doc(cfg(all(feature="libc", unix)))]
    fn file_system(&self) -> Result<FileSystem>;

}

macro_rules! impl_path_ext { ($($ty: ty),+$(,)?) => {
    $(
        impl Sealed for $ty {}

        impl PathExt for $ty {

            fn shorten<P>(&self, base: P) -> &Path where P: AsRef<Path> {
                self.strip_prefix(base).unwrap_or(self)
            }

            fn shorten_here(&self) -> &Path {
                env::current_dir().map(|d| self.shorten(d)).unwrap_or(self)
            }

            #[cfg(all(feature="libc", unix))]
            #[doc(cfg(all(feature="libc", unix)))]
            fn file_system(&self) -> Result<FileSystem> {
                file_system(self)
            }

        }
    )+
}}

impl_path_ext!(&Path, PathBuf);

/// # Gets file system of a path
#[cfg(all(feature="libc", unix))]
#[doc(cfg(all(feature="libc", unix)))]
fn file_system(path: &Path) -> Result<FileSystem> {
    // Docs: statfs(2)
    match unsafe {
        let mut statfs = mem::zeroed::<libc::statfs>();
        match libc::statfs(CString::new(path.as_os_str().as_bytes())?.as_ptr(), &mut statfs) {
            0 => statfs.f_type,
            _ => return Err(fmt_err_code("statfs")),
        }
    } {
        libc::ADFS_SUPER_MAGIC => Ok(FileSystem::ADFSSuper),
        libc::AFFS_SUPER_MAGIC => Ok(FileSystem::AFFSSuper),
        libc::AFS_SUPER_MAGIC => Ok(FileSystem::AFSSuper),
        libc::AUTOFS_SUPER_MAGIC => Ok(FileSystem::AutoFSSuper),
        libc::BPF_FS_MAGIC => Ok(FileSystem::BpfFS),
        libc::BTRFS_SUPER_MAGIC => Ok(FileSystem::BtrFSSuper),
        libc::CGROUP_SUPER_MAGIC => Ok(FileSystem::CgroupSuper),
        libc::CGROUP2_SUPER_MAGIC => Ok(FileSystem::Cgroup2Super),
        libc::CODA_SUPER_MAGIC => Ok(FileSystem::CodaSuper),
        libc::CRAMFS_MAGIC => Ok(FileSystem::CramFS),
        libc::DEBUGFS_MAGIC => Ok(FileSystem::DebugFS),
        libc::DEVPTS_SUPER_MAGIC => Ok(FileSystem::DevPtsSuper),
        libc::ECRYPTFS_SUPER_MAGIC => Ok(FileSystem::EcryptFSSuper),
        libc::EFS_SUPER_MAGIC => Ok(FileSystem::EFSSuper),
        libc::EXT4_SUPER_MAGIC => Ok(FileSystem::Ext234Super),
        libc::F2FS_SUPER_MAGIC => Ok(FileSystem::F2FSSuper),
        libc::FUSE_SUPER_MAGIC => Ok(FileSystem::FuseSuper),
        libc::FUTEXFS_SUPER_MAGIC => Ok(FileSystem::FutexFSSuper),
        libc::HOSTFS_SUPER_MAGIC => Ok(FileSystem::HostFSSuper),
        libc::HPFS_SUPER_MAGIC => Ok(FileSystem::HPFSSuper),
        libc::HUGETLBFS_MAGIC => Ok(FileSystem::HugeTLBFS),
        libc::ISOFS_SUPER_MAGIC => Ok(FileSystem::IsoFSSuper),
        libc::JFFS2_SUPER_MAGIC => Ok(FileSystem::JFFS2Super),
        libc::MINIX_SUPER_MAGIC => Ok(FileSystem::MINIXSuper),
        libc::MINIX_SUPER_MAGIC2 => Ok(FileSystem::MINIXSuper2),
        libc::MINIX2_SUPER_MAGIC => Ok(FileSystem::MINIX2Super),
        libc::MINIX2_SUPER_MAGIC2 => Ok(FileSystem::MINIX2Super2),
        libc::MINIX3_SUPER_MAGIC => Ok(FileSystem::MINIX3Super),
        libc::MSDOS_SUPER_MAGIC => Ok(FileSystem::MSDOSSuper),
        libc::NCP_SUPER_MAGIC => Ok(FileSystem::NCPSuper),
        libc::NFS_SUPER_MAGIC => Ok(FileSystem::NFSSuper),
        libc::NILFS_SUPER_MAGIC => Ok(FileSystem::NILFSSuper),
        libc::NSFS_MAGIC => Ok(FileSystem::NSFS),
        libc::OCFS2_SUPER_MAGIC => Ok(FileSystem::OCFS2Super),
        libc::OPENPROM_SUPER_MAGIC => Ok(FileSystem::OpenPROMSuper),
        libc::OVERLAYFS_SUPER_MAGIC => Ok(FileSystem::OverlayFSSuper),
        libc::PROC_SUPER_MAGIC => Ok(FileSystem::ProcSuper),
        libc::QNX4_SUPER_MAGIC => Ok(FileSystem::QNX4Super),
        libc::QNX6_SUPER_MAGIC => Ok(FileSystem::QNX6Super),
        libc::REISERFS_SUPER_MAGIC => Ok(FileSystem::ReiserFSSuper),
        libc::SECURITYFS_MAGIC => Ok(FileSystem::SecurityFS),
        libc::SELINUX_MAGIC => Ok(FileSystem::SELinux),
        libc::SMACK_MAGIC => Ok(FileSystem::Smack),
        libc::SMB_SUPER_MAGIC => Ok(FileSystem::SMBSuper),
        libc::SYSFS_MAGIC => Ok(FileSystem::SysFS),
        libc::TMPFS_MAGIC => Ok(FileSystem::TmpFS),
        libc::TRACEFS_MAGIC => Ok(FileSystem::TraceFS),
        libc::UDF_SUPER_MAGIC => Ok(FileSystem::UDFSuper),
        libc::USBDEVICE_SUPER_MAGIC => Ok(FileSystem::USBDeviceSuper),
        libc::XENFS_SUPER_MAGIC => Ok(FileSystem::XENFSSuper),
        libc::XFS_SUPER_MAGIC => Ok(FileSystem::XFSSuper),
        _ => Ok(FileSystem::Unknown),
    }
}

#[cfg(feature="libc")]
#[doc(cfg(feature="libc"))]
fn fmt_err_code<S>(fn_name: S) -> Error where S: AsRef<str> {
    let err_code = unsafe {
        *libc::__errno_location()
    };
    err!(
        "libc::{fn_name}() failed: {err_code}: {err:?}",
        fn_name=fn_name.as_ref(),
        err=unsafe { CStr::from_ptr(libc::strerror(err_code)) },
    )
}
