use crate::ffi::OsString;
use crate::hash::Hash;
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom};
use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
use crate::path::{Path, PathBuf};
use crate::sys::fd::FileDesc;
pub use crate::sys::fs::common::{Dir, exists};
use crate::sys::time::SystemTime;
use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner, map_motor_error, unsupported};

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FileType {
    rt_filetype: u8,
}

impl FileType {
    pub fn is_dir(&self) -> bool {
        self.rt_filetype == moto_rt::fs::FILETYPE_DIRECTORY
    }

    pub fn is_file(&self) -> bool {
        self.rt_filetype == moto_rt::fs::FILETYPE_FILE
    }

    pub fn is_symlink(&self) -> bool {
        false
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct FilePermissions {
    rt_perm: u64,
}

impl FilePermissions {
    pub fn readonly(&self) -> bool {
        (self.rt_perm & moto_rt::fs::PERM_WRITE == 0)
            && (self.rt_perm & moto_rt::fs::PERM_READ != 0)
    }

    pub fn set_readonly(&mut self, readonly: bool) {
        if readonly {
            self.rt_perm = moto_rt::fs::PERM_READ;
        } else {
            self.rt_perm = moto_rt::fs::PERM_READ | moto_rt::fs::PERM_WRITE;
        }
    }
}

#[derive(Copy, Clone, Debug, Default)]
pub struct FileTimes {
    modified: u128,
    accessed: u128,
}

impl FileTimes {
    pub fn set_accessed(&mut self, t: SystemTime) {
        self.accessed = t.as_u128();
    }

    pub fn set_modified(&mut self, t: SystemTime) {
        self.modified = t.as_u128();
    }
}

#[derive(Copy, Clone, PartialEq, Eq)]
pub struct FileAttr {
    inner: moto_rt::fs::FileAttr,
}

impl FileAttr {
    pub fn size(&self) -> u64 {
        self.inner.size
    }

    pub fn perm(&self) -> FilePermissions {
        FilePermissions { rt_perm: self.inner.perm }
    }

    pub fn file_type(&self) -> FileType {
        FileType { rt_filetype: self.inner.file_type }
    }

    pub fn modified(&self) -> io::Result<SystemTime> {
        match self.inner.modified {
            0 => Err(crate::io::Error::from(crate::io::ErrorKind::Other)),
            x => Ok(SystemTime::from_u128(x)),
        }
    }

    pub fn accessed(&self) -> io::Result<SystemTime> {
        match self.inner.accessed {
            0 => Err(crate::io::Error::from(crate::io::ErrorKind::Other)),
            x => Ok(SystemTime::from_u128(x)),
        }
    }

    pub fn created(&self) -> io::Result<SystemTime> {
        match self.inner.created {
            0 => Err(crate::io::Error::from(crate::io::ErrorKind::Other)),
            x => Ok(SystemTime::from_u128(x)),
        }
    }
}

#[derive(Clone, Debug)]
pub struct OpenOptions {
    rt_open_options: u32,
}

impl OpenOptions {
    pub fn new() -> OpenOptions {
        OpenOptions { rt_open_options: 0 }
    }

    pub fn read(&mut self, read: bool) {
        if read {
            self.rt_open_options |= moto_rt::fs::O_READ;
        } else {
            self.rt_open_options &= !moto_rt::fs::O_READ;
        }
    }

    pub fn write(&mut self, write: bool) {
        if write {
            self.rt_open_options |= moto_rt::fs::O_WRITE;
        } else {
            self.rt_open_options &= !moto_rt::fs::O_WRITE;
        }
    }

    pub fn append(&mut self, append: bool) {
        if append {
            self.rt_open_options |= moto_rt::fs::O_APPEND;
        } else {
            self.rt_open_options &= !moto_rt::fs::O_APPEND;
        }
    }

    pub fn truncate(&mut self, truncate: bool) {
        if truncate {
            self.rt_open_options |= moto_rt::fs::O_TRUNCATE;
        } else {
            self.rt_open_options &= !moto_rt::fs::O_TRUNCATE;
        }
    }

    pub fn create(&mut self, create: bool) {
        if create {
            self.rt_open_options |= moto_rt::fs::O_CREATE;
        } else {
            self.rt_open_options &= !moto_rt::fs::O_CREATE;
        }
    }

    pub fn create_new(&mut self, create_new: bool) {
        if create_new {
            self.rt_open_options |= moto_rt::fs::O_CREATE_NEW;
        } else {
            self.rt_open_options &= !moto_rt::fs::O_CREATE_NEW;
        }
    }
}

#[derive(Debug)]
pub struct File(FileDesc);

impl File {
    pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
        let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
        moto_rt::fs::open(path, opts.rt_open_options)
            .map(|fd| unsafe { Self::from_raw_fd(fd) })
            .map_err(map_motor_error)
    }

    pub fn file_attr(&self) -> io::Result<FileAttr> {
        moto_rt::fs::get_file_attr(self.as_raw_fd())
            .map(|inner| -> FileAttr { FileAttr { inner } })
            .map_err(map_motor_error)
    }

    pub fn fsync(&self) -> io::Result<()> {
        moto_rt::fs::fsync(self.as_raw_fd()).map_err(map_motor_error)
    }

    pub fn datasync(&self) -> io::Result<()> {
        moto_rt::fs::datasync(self.as_raw_fd()).map_err(map_motor_error)
    }

    pub fn truncate(&self, size: u64) -> io::Result<()> {
        moto_rt::fs::truncate(self.as_raw_fd(), size).map_err(map_motor_error)
    }

    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
        moto_rt::fs::read(self.as_raw_fd(), buf).map_err(map_motor_error)
    }

    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
        crate::io::default_read_vectored(|b| self.read(b), bufs)
    }

    pub fn is_read_vectored(&self) -> bool {
        false
    }

    pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
        crate::io::default_read_buf(|buf| self.read(buf), cursor)
    }

    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
        moto_rt::fs::write(self.as_raw_fd(), buf).map_err(map_motor_error)
    }

    pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
        crate::io::default_write_vectored(|b| self.write(b), bufs)
    }

    pub fn is_write_vectored(&self) -> bool {
        false
    }

    pub fn flush(&self) -> io::Result<()> {
        moto_rt::fs::flush(self.as_raw_fd()).map_err(map_motor_error)
    }

    pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
        match pos {
            SeekFrom::Start(offset) => {
                moto_rt::fs::seek(self.as_raw_fd(), offset as i64, moto_rt::fs::SEEK_SET)
                    .map_err(map_motor_error)
            }
            SeekFrom::End(offset) => {
                moto_rt::fs::seek(self.as_raw_fd(), offset, moto_rt::fs::SEEK_END)
                    .map_err(map_motor_error)
            }
            SeekFrom::Current(offset) => {
                moto_rt::fs::seek(self.as_raw_fd(), offset, moto_rt::fs::SEEK_CUR)
                    .map_err(map_motor_error)
            }
        }
    }

    pub fn tell(&self) -> io::Result<u64> {
        self.seek(SeekFrom::Current(0))
    }

    pub fn duplicate(&self) -> io::Result<File> {
        moto_rt::fs::duplicate(self.as_raw_fd())
            .map(|fd| unsafe { Self::from_raw_fd(fd) })
            .map_err(map_motor_error)
    }

    pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> {
        moto_rt::fs::set_file_perm(self.as_raw_fd(), perm.rt_perm).map_err(map_motor_error)
    }

    pub fn set_times(&self, _times: FileTimes) -> io::Result<()> {
        unsupported() // Let's not do that.
    }

    pub fn lock(&self) -> io::Result<()> {
        unsupported()
    }

    pub fn lock_shared(&self) -> io::Result<()> {
        unsupported()
    }

    pub fn try_lock(&self) -> Result<(), crate::fs::TryLockError> {
        Err(crate::fs::TryLockError::Error(io::Error::from(io::ErrorKind::Unsupported)))
    }

    pub fn try_lock_shared(&self) -> Result<(), crate::fs::TryLockError> {
        Err(crate::fs::TryLockError::Error(io::Error::from(io::ErrorKind::Unsupported)))
    }

    pub fn unlock(&self) -> io::Result<()> {
        unsupported()
    }

    pub fn size(&self) -> Option<io::Result<u64>> {
        None
    }
}

#[derive(Debug)]
pub struct DirBuilder {}

impl DirBuilder {
    pub fn new() -> DirBuilder {
        DirBuilder {}
    }

    pub fn mkdir(&self, path: &Path) -> io::Result<()> {
        let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
        moto_rt::fs::mkdir(path).map_err(map_motor_error)
    }
}

pub fn unlink(path: &Path) -> io::Result<()> {
    let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
    moto_rt::fs::unlink(path).map_err(map_motor_error)
}

pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
    let old = old.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
    let new = new.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
    moto_rt::fs::rename(old, new).map_err(map_motor_error)
}

pub fn rmdir(path: &Path) -> io::Result<()> {
    let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
    moto_rt::fs::rmdir(path).map_err(map_motor_error)
}

pub fn remove_dir_all(path: &Path) -> io::Result<()> {
    let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
    moto_rt::fs::rmdir_all(path).map_err(map_motor_error)
}

pub fn set_perm(path: &Path, perm: FilePermissions) -> io::Result<()> {
    let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
    moto_rt::fs::set_perm(path, perm.rt_perm).map_err(map_motor_error)
}

pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> {
    unsupported()
}

pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> {
    unsupported()
}

pub fn readlink(_p: &Path) -> io::Result<PathBuf> {
    unsupported()
}

pub fn symlink(_original: &Path, _link: &Path) -> io::Result<()> {
    unsupported()
}

pub fn link(_src: &Path, _dst: &Path) -> io::Result<()> {
    unsupported()
}

pub fn stat(path: &Path) -> io::Result<FileAttr> {
    let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
    let inner = moto_rt::fs::stat(path).map_err(map_motor_error)?;
    Ok(FileAttr { inner })
}

pub fn lstat(path: &Path) -> io::Result<FileAttr> {
    stat(path)
}

pub fn canonicalize(path: &Path) -> io::Result<PathBuf> {
    let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
    let path = moto_rt::fs::canonicalize(path).map_err(map_motor_error)?;
    Ok(path.into())
}

pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
    let from = from.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
    let to = to.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
    moto_rt::fs::copy(from, to).map_err(map_motor_error)
}

#[derive(Debug)]
pub struct ReadDir {
    rt_fd: moto_rt::RtFd,
    path: String,
}

impl Drop for ReadDir {
    fn drop(&mut self) {
        moto_rt::fs::closedir(self.rt_fd).unwrap();
    }
}

pub fn readdir(path: &Path) -> io::Result<ReadDir> {
    let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
    Ok(ReadDir {
        rt_fd: moto_rt::fs::opendir(path).map_err(map_motor_error)?,
        path: path.to_owned(),
    })
}

impl Iterator for ReadDir {
    type Item = io::Result<DirEntry>;

    fn next(&mut self) -> Option<io::Result<DirEntry>> {
        match moto_rt::fs::readdir(self.rt_fd).map_err(map_motor_error) {
            Ok(maybe_item) => match maybe_item {
                Some(inner) => Some(Ok(DirEntry { inner, parent_path: self.path.clone() })),
                None => None,
            },
            Err(err) => Some(Err(err)),
        }
    }
}

pub struct DirEntry {
    parent_path: String,
    inner: moto_rt::fs::DirEntry,
}

impl DirEntry {
    fn filename(&self) -> &str {
        core::str::from_utf8(unsafe {
            core::slice::from_raw_parts(self.inner.fname.as_ptr(), self.inner.fname_size as usize)
        })
        .unwrap()
    }

    pub fn path(&self) -> PathBuf {
        let mut path = self.parent_path.clone();
        path.push_str("/");
        path.push_str(self.filename());
        path.into()
    }

    pub fn file_name(&self) -> OsString {
        self.filename().to_owned().into()
    }

    pub fn metadata(&self) -> io::Result<FileAttr> {
        Ok(FileAttr { inner: self.inner.attr })
    }

    pub fn file_type(&self) -> io::Result<FileType> {
        Ok(FileType { rt_filetype: self.inner.attr.file_type })
    }
}

impl AsInner<FileDesc> for File {
    #[inline]
    fn as_inner(&self) -> &FileDesc {
        &self.0
    }
}

impl AsInnerMut<FileDesc> for File {
    #[inline]
    fn as_inner_mut(&mut self) -> &mut FileDesc {
        &mut self.0
    }
}

impl IntoInner<FileDesc> for File {
    fn into_inner(self) -> FileDesc {
        self.0
    }
}

impl FromInner<FileDesc> for File {
    fn from_inner(file_desc: FileDesc) -> Self {
        Self(file_desc)
    }
}

impl AsFd for File {
    #[inline]
    fn as_fd(&self) -> BorrowedFd<'_> {
        self.0.as_fd()
    }
}

impl AsRawFd for File {
    #[inline]
    fn as_raw_fd(&self) -> RawFd {
        self.0.as_raw_fd()
    }
}

impl IntoRawFd for File {
    fn into_raw_fd(self) -> RawFd {
        self.0.into_raw_fd()
    }
}

impl FromRawFd for File {
    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
        unsafe { Self(FromRawFd::from_raw_fd(raw_fd)) }
    }
}
