// Stolen from https://github.com/Peltoche/lsd/blob/master/src/meta/permissions.rs

use serde::{Deserialize, Serialize};
use std::{fmt::Display, fs::Metadata};

pub type RWX = (char, char, char, char, char, char, char, char, char);
pub type Octal = (u8, u8, u8, u8);

#[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Serialize, Deserialize, Hash)]
pub struct Permissions {
    #[serde(default)]
    pub user_read: bool,

    #[serde(default)]
    pub user_write: bool,

    #[serde(default)]
    pub user_execute: bool,

    #[serde(default)]
    pub group_read: bool,

    #[serde(default)]
    pub group_write: bool,

    #[serde(default)]
    pub group_execute: bool,

    #[serde(default)]
    pub other_read: bool,

    #[serde(default)]
    pub other_write: bool,

    #[serde(default)]
    pub other_execute: bool,

    #[serde(default)]
    pub sticky: bool,

    #[serde(default)]
    pub setgid: bool,

    #[serde(default)]
    pub setuid: bool,
}

impl Permissions {}

impl<'a> From<&'a Metadata> for Permissions {
    #[cfg(unix)]
    fn from(meta: &Metadata) -> Self {
        use std::os::unix::fs::PermissionsExt;

        let bits = meta.permissions().mode();
        let has_bit = |bit| bits & bit == bit;

        Self {
            user_read: has_bit(modes::USER_READ),
            user_write: has_bit(modes::USER_WRITE),
            user_execute: has_bit(modes::USER_EXECUTE),

            group_read: has_bit(modes::GROUP_READ),
            group_write: has_bit(modes::GROUP_WRITE),
            group_execute: has_bit(modes::GROUP_EXECUTE),

            other_read: has_bit(modes::OTHER_READ),
            other_write: has_bit(modes::OTHER_WRITE),
            other_execute: has_bit(modes::OTHER_EXECUTE),

            sticky: has_bit(modes::STICKY),
            setgid: has_bit(modes::SETGID),
            setuid: has_bit(modes::SETUID),
        }
    }

    #[cfg(windows)]
    fn from(_: &Metadata) -> Self {
        panic!("Cannot get permissions from metadata on Windows")
    }
}

impl Display for Permissions {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let (ur, uw, ux, gr, gw, gx, or, ow, ox) = (*self).into();
        write!(f, "{ur}{uw}{ux}{gr}{gw}{gx}{or}{ow}{ox}")
    }
}

impl Into<RWX> for Permissions {
    fn into(self) -> RWX {
        let bit = |bit: bool, chr: char| {
            if bit {
                chr
            } else {
                '-'
            }
        };

        let ur = bit(self.user_read, 'r');
        let uw = bit(self.user_write, 'w');
        let ux = match (self.user_execute, self.setuid) {
            (true, true) => 's',
            (true, false) => 'x',
            (false, true) => 'S',
            (false, false) => '-',
        };

        let gr = bit(self.group_read, 'r');
        let gw = bit(self.group_write, 'w');
        let gx = match (self.group_execute, self.setgid) {
            (true, true) => 's',
            (true, false) => 'x',
            (false, true) => 'S',
            (false, false) => '-',
        };

        let or = bit(self.other_read, 'r');
        let ow = bit(self.other_write, 'w');
        let ox = match (self.other_execute, self.sticky) {
            (true, true) => 't',
            (true, false) => 'x',
            (false, true) => 'T',
            (false, false) => '-',
        };

        (ur, uw, ux, gr, gw, gx, or, ow, ox)
    }
}

impl Into<Octal> for Permissions {
    fn into(self) -> Octal {
        let bits_to_octal =
            |r: bool, w: bool, x: bool| (r as u8) * 4 + (w as u8) * 2 + (x as u8);

        (
            bits_to_octal(self.setuid, self.setgid, self.sticky),
            bits_to_octal(self.user_read, self.user_write, self.user_execute),
            bits_to_octal(self.group_read, self.group_write, self.group_execute),
            bits_to_octal(self.other_read, self.other_write, self.other_execute),
        )
    }
}

// More readable aliases for the permission bits exposed by libc.
#[allow(trivial_numeric_casts)]
#[cfg(unix)]
mod modes {
    pub type Mode = u32;
    // The `libc::mode_t` type’s actual type varies, but the value returned
    // from `metadata.permissions().mode()` is always `u32`.

    pub const USER_READ: Mode = libc::S_IRUSR as Mode;
    pub const USER_WRITE: Mode = libc::S_IWUSR as Mode;
    pub const USER_EXECUTE: Mode = libc::S_IXUSR as Mode;

    pub const GROUP_READ: Mode = libc::S_IRGRP as Mode;
    pub const GROUP_WRITE: Mode = libc::S_IWGRP as Mode;
    pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode;

    pub const OTHER_READ: Mode = libc::S_IROTH as Mode;
    pub const OTHER_WRITE: Mode = libc::S_IWOTH as Mode;
    pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode;

    pub const STICKY: Mode = libc::S_ISVTX as Mode;
    pub const SETGID: Mode = libc::S_ISGID as Mode;
    pub const SETUID: Mode = libc::S_ISUID as Mode;
}
