#![allow(dead_code)]

use crate::io::{self, IoVec, IoVecMut, SeekFrom};
use crate::mem;
use crate::net::Shutdown;
use crate::sys::cvt_wasi;
use libc::{self, c_char, c_void};

#[derive(Debug)]
pub struct WasiFd {
    fd: libc::__wasi_fd_t,
}

// FIXME: these should probably all be fancier structs, builders, enums, etc
pub type LookupFlags = u32;
pub type FdFlags = u16;
pub type Advice = u8;
pub type Rights = u64;
pub type Oflags = u16;
pub type DirCookie = u64;
pub type Timestamp = u64;
pub type FstFlags = u16;
pub type RiFlags = u16;
pub type RoFlags = u16;
pub type SiFlags = u16;

fn iovec(a: &mut [IoVecMut<'_>]) -> (*const libc::__wasi_iovec_t, usize) {
    assert_eq!(
        mem::size_of::<IoVecMut<'_>>(),
        mem::size_of::<libc::__wasi_iovec_t>()
    );
    assert_eq!(
        mem::align_of::<IoVecMut<'_>>(),
        mem::align_of::<libc::__wasi_iovec_t>()
    );
    (a.as_ptr() as *const libc::__wasi_iovec_t, a.len())
}

fn ciovec(a: &[IoVec<'_>]) -> (*const libc::__wasi_ciovec_t, usize) {
    assert_eq!(
        mem::size_of::<IoVec<'_>>(),
        mem::size_of::<libc::__wasi_ciovec_t>()
    );
    assert_eq!(
        mem::align_of::<IoVec<'_>>(),
        mem::align_of::<libc::__wasi_ciovec_t>()
    );
    (a.as_ptr() as *const libc::__wasi_ciovec_t, a.len())
}

impl WasiFd {
    pub unsafe fn from_raw(fd: libc::__wasi_fd_t) -> WasiFd {
        WasiFd { fd }
    }

    pub fn into_raw(self) -> libc::__wasi_fd_t {
        let ret = self.fd;
        mem::forget(self);
        ret
    }

    pub fn as_raw(&self) -> libc::__wasi_fd_t {
        self.fd
    }

    pub fn datasync(&self) -> io::Result<()> {
        cvt_wasi(unsafe { libc::__wasi_fd_datasync(self.fd) })
    }

    pub fn pread(&self, bufs: &mut [IoVecMut<'_>], offset: u64) -> io::Result<usize> {
        let mut read = 0;
        let (ptr, len) = iovec(bufs);
        cvt_wasi(unsafe { libc::__wasi_fd_pread(self.fd, ptr, len, offset, &mut read) })?;
        Ok(read)
    }

    pub fn pwrite(&self, bufs: &[IoVec<'_>], offset: u64) -> io::Result<usize> {
        let mut read = 0;
        let (ptr, len) = ciovec(bufs);
        cvt_wasi(unsafe { libc::__wasi_fd_pwrite(self.fd, ptr, len, offset, &mut read) })?;
        Ok(read)
    }

    pub fn read(&self, bufs: &mut [IoVecMut<'_>]) -> io::Result<usize> {
        let mut read = 0;
        let (ptr, len) = iovec(bufs);
        cvt_wasi(unsafe { libc::__wasi_fd_read(self.fd, ptr, len, &mut read) })?;
        Ok(read)
    }

    pub fn write(&self, bufs: &[IoVec<'_>]) -> io::Result<usize> {
        let mut read = 0;
        let (ptr, len) = ciovec(bufs);
        cvt_wasi(unsafe { libc::__wasi_fd_write(self.fd, ptr, len, &mut read) })?;
        Ok(read)
    }

    pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
        let (whence, offset) = match pos {
            SeekFrom::Start(pos) => (libc::__WASI_WHENCE_SET, pos as i64),
            SeekFrom::End(pos) => (libc::__WASI_WHENCE_END, pos),
            SeekFrom::Current(pos) => (libc::__WASI_WHENCE_CUR, pos),
        };
        let mut pos = 0;
        cvt_wasi(unsafe { libc::__wasi_fd_seek(self.fd, offset, whence, &mut pos) })?;
        Ok(pos)
    }

    pub fn tell(&self) -> io::Result<u64> {
        let mut pos = 0;
        cvt_wasi(unsafe { libc::__wasi_fd_tell(self.fd, &mut pos) })?;
        Ok(pos)
    }

    // FIXME: __wasi_fd_fdstat_get

    pub fn set_flags(&self, flags: FdFlags) -> io::Result<()> {
        cvt_wasi(unsafe { libc::__wasi_fd_fdstat_set_flags(self.fd, flags) })
    }

    pub fn set_rights(&self, base: Rights, inheriting: Rights) -> io::Result<()> {
        cvt_wasi(unsafe { libc::__wasi_fd_fdstat_set_rights(self.fd, base, inheriting) })
    }

    pub fn sync(&self) -> io::Result<()> {
        cvt_wasi(unsafe { libc::__wasi_fd_sync(self.fd) })
    }

    pub fn advise(&self, offset: u64, len: u64, advice: Advice) -> io::Result<()> {
        cvt_wasi(unsafe { libc::__wasi_fd_advise(self.fd, offset, len, advice as u8) })
    }

    pub fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
        cvt_wasi(unsafe { libc::__wasi_fd_allocate(self.fd, offset, len) })
    }

    pub fn create_directory(&self, path: &[u8]) -> io::Result<()> {
        cvt_wasi(unsafe {
            libc::__wasi_path_create_directory(self.fd, path.as_ptr() as *const c_char, path.len())
        })
    }

    pub fn link(
        &self,
        old_flags: LookupFlags,
        old_path: &[u8],
        new_fd: &WasiFd,
        new_path: &[u8],
    ) -> io::Result<()> {
        cvt_wasi(unsafe {
            libc::__wasi_path_link(
                self.fd,
                old_flags,
                old_path.as_ptr() as *const c_char,
                old_path.len(),
                new_fd.fd,
                new_path.as_ptr() as *const c_char,
                new_path.len(),
            )
        })
    }

    pub fn open(
        &self,
        dirflags: LookupFlags,
        path: &[u8],
        oflags: Oflags,
        fs_rights_base: Rights,
        fs_rights_inheriting: Rights,
        fs_flags: FdFlags,
    ) -> io::Result<WasiFd> {
        unsafe {
            let mut fd = 0;
            cvt_wasi(libc::__wasi_path_open(
                self.fd,
                dirflags,
                path.as_ptr() as *const c_char,
                path.len(),
                oflags,
                fs_rights_base,
                fs_rights_inheriting,
                fs_flags,
                &mut fd,
            ))?;
            Ok(WasiFd::from_raw(fd))
        }
    }

    pub fn readdir(&self, buf: &mut [u8], cookie: DirCookie) -> io::Result<usize> {
        let mut used = 0;
        cvt_wasi(unsafe {
            libc::__wasi_fd_readdir(
                self.fd,
                buf.as_mut_ptr() as *mut c_void,
                buf.len(),
                cookie,
                &mut used,
            )
        })?;
        Ok(used)
    }

    pub fn readlink(&self, path: &[u8], buf: &mut [u8]) -> io::Result<usize> {
        let mut used = 0;
        cvt_wasi(unsafe {
            libc::__wasi_path_readlink(
                self.fd,
                path.as_ptr() as *const c_char,
                path.len(),
                buf.as_mut_ptr() as *mut c_char,
                buf.len(),
                &mut used,
            )
        })?;
        Ok(used)
    }

    pub fn rename(&self, old_path: &[u8], new_fd: &WasiFd, new_path: &[u8]) -> io::Result<()> {
        cvt_wasi(unsafe {
            libc::__wasi_path_rename(
                self.fd,
                old_path.as_ptr() as *const c_char,
                old_path.len(),
                new_fd.fd,
                new_path.as_ptr() as *const c_char,
                new_path.len(),
            )
        })
    }

    pub fn filestat_get(&self, buf: *mut libc::__wasi_filestat_t) -> io::Result<()> {
        cvt_wasi(unsafe { libc::__wasi_fd_filestat_get(self.fd, buf) })
    }

    pub fn filestat_set_times(
        &self,
        atim: Timestamp,
        mtim: Timestamp,
        fstflags: FstFlags,
    ) -> io::Result<()> {
        cvt_wasi(unsafe { libc::__wasi_fd_filestat_set_times(self.fd, atim, mtim, fstflags) })
    }

    pub fn filestat_set_size(&self, size: u64) -> io::Result<()> {
        cvt_wasi(unsafe { libc::__wasi_fd_filestat_set_size(self.fd, size) })
    }

    pub fn path_filestat_get(
        &self,
        flags: LookupFlags,
        path: &[u8],
        buf: *mut libc::__wasi_filestat_t,
    ) -> io::Result<()> {
        cvt_wasi(unsafe {
            libc::__wasi_path_filestat_get(
                self.fd,
                flags,
                path.as_ptr() as *const c_char,
                path.len(),
                buf,
            )
        })
    }

    pub fn path_filestat_set_times(
        &self,
        flags: LookupFlags,
        path: &[u8],
        atim: Timestamp,
        mtim: Timestamp,
        fstflags: FstFlags,
    ) -> io::Result<()> {
        cvt_wasi(unsafe {
            libc::__wasi_path_filestat_set_times(
                self.fd,
                flags,
                path.as_ptr() as *const c_char,
                path.len(),
                atim,
                mtim,
                fstflags,
            )
        })
    }

    pub fn symlink(&self, old_path: &[u8], new_path: &[u8]) -> io::Result<()> {
        cvt_wasi(unsafe {
            libc::__wasi_path_symlink(
                old_path.as_ptr() as *const c_char,
                old_path.len(),
                self.fd,
                new_path.as_ptr() as *const c_char,
                new_path.len(),
            )
        })
    }

    pub fn unlink_file(&self, path: &[u8]) -> io::Result<()> {
        cvt_wasi(unsafe {
            libc::__wasi_path_unlink_file(self.fd, path.as_ptr() as *const c_char, path.len())
        })
    }

    pub fn remove_directory(&self, path: &[u8]) -> io::Result<()> {
        cvt_wasi(unsafe {
            libc::__wasi_path_remove_directory(self.fd, path.as_ptr() as *const c_char, path.len())
        })
    }

    pub fn sock_recv(
        &self,
        ri_data: &mut [IoVecMut<'_>],
        ri_flags: RiFlags,
    ) -> io::Result<(usize, RoFlags)> {
        let mut ro_datalen = 0;
        let mut ro_flags = 0;
        let (ptr, len) = iovec(ri_data);
        cvt_wasi(unsafe {
            libc::__wasi_sock_recv(self.fd, ptr, len, ri_flags, &mut ro_datalen, &mut ro_flags)
        })?;
        Ok((ro_datalen, ro_flags))
    }

    pub fn sock_send(&self, si_data: &[IoVec<'_>], si_flags: SiFlags) -> io::Result<usize> {
        let mut so_datalen = 0;
        let (ptr, len) = ciovec(si_data);
        cvt_wasi(unsafe { libc::__wasi_sock_send(self.fd, ptr, len, si_flags, &mut so_datalen) })?;
        Ok(so_datalen)
    }

    pub fn sock_shutdown(&self, how: Shutdown) -> io::Result<()> {
        let how = match how {
            Shutdown::Read => libc::__WASI_SHUT_RD,
            Shutdown::Write => libc::__WASI_SHUT_WR,
            Shutdown::Both => libc::__WASI_SHUT_WR | libc::__WASI_SHUT_RD,
        };
        cvt_wasi(unsafe { libc::__wasi_sock_shutdown(self.fd, how) })?;
        Ok(())
    }
}

impl Drop for WasiFd {
    fn drop(&mut self) {
        unsafe {
            // FIXME: can we handle the return code here even though we can't on
            // unix?
            libc::__wasi_fd_close(self.fd);
        }
    }
}
