use core::fmt;

use abi_lib::SyscallRegister;

use crate::time::SystemTime;

use super::FileMode;

/// Describes an operation to perform when updating certain file metadata elements
#[derive(Debug, Clone)]
pub enum FileMetadataUpdateMode {
    /// Assign a new file mode
    Set(FileMode),
    /// Set and/or clear existing file mode bits
    Modify {
        /// Bits to set
        set: FileMode,
        /// Bits to clear
        clear: FileMode,
    },
}

/// Describes how file times are updated
#[derive(Debug, Clone)]
pub struct FileTimesUpdate {
    /// Last accessed time
    pub atime: Option<SystemTime>,
    /// Last modified time
    pub mtime: Option<SystemTime>,
    /// Created time
    pub ctime: Option<SystemTime>,
}

/// Describes a modification to a file's metadata
#[derive(Debug, Clone)]
pub enum FileMetadataUpdate {
    /// Changes file permissions
    Permissions(FileMetadataUpdateMode),
    /// Changes file mtime/atime/ctime
    Times(FileTimesUpdate),
}

/// Describes a seek operation for a regular file
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SeekFrom {
    /// Argument is an offset from the start of the file
    Start(u64),
    /// Argument is an offset from the end of the file
    End(i64),
    /// Argument is an offset from the current position
    Current(i64),
}

impl SyscallRegister for SeekFrom {
    fn into_syscall_register(self) -> usize {
        match self {
            SeekFrom::Start(value) => (value as usize) << 2,
            SeekFrom::End(value) => (value << 2) as usize | 1,
            SeekFrom::Current(value) => (value << 2) as usize | 2,
        }
    }

    fn from_syscall_register(value: usize) -> Self {
        let dir = value & 0x3;

        match dir {
            0 => Self::Start((value as u64) >> 2),
            1 => Self::End((value as i64) >> 2),
            2 => Self::Current((value as i64) >> 2),
            _ => unreachable!(),
        }
    }
}

impl fmt::Display for FileMode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        macro_rules! print_bit {
            ($res:expr, $val:expr, $self:expr, $bit:expr) => {
                if $self.contains($bit) {
                    $res = $val;
                }
            };
        }

        let mut buf = ['-'; 9];

        print_bit!(buf[0], 'r', self, Self::USER_READ);
        print_bit!(buf[1], 'w', self, Self::USER_WRITE);
        print_bit!(buf[2], 'x', self, Self::USER_EXEC);
        print_bit!(buf[3], 'r', self, Self::GROUP_READ);
        print_bit!(buf[4], 'w', self, Self::GROUP_WRITE);
        print_bit!(buf[5], 'x', self, Self::GROUP_EXEC);
        print_bit!(buf[6], 'r', self, Self::OTHER_READ);
        print_bit!(buf[7], 'w', self, Self::OTHER_WRITE);
        print_bit!(buf[8], 'x', self, Self::OTHER_EXEC);

        for ch in buf.iter() {
            fmt::Display::fmt(ch, f)?;
        }
        Ok(())
    }
}
