//!

use crate::sys::inode::TypePerm;

use {
    super::Ext2,
    alloc::{
        sync::Arc,
        {string::String, vec::Vec},
    },
    core::{
        fmt::{self, Debug},
        iter::Iterator,
        num::NonZeroU32,
    },
    error::Error,
    genfs::*,
    sector::{Address, SectorSize},
    spin::{Mutex, MutexGuard},
    sys::inode::Inode as RawInode,
    volume::Volume,
};
/// DOCME: what is this?
pub struct Synced<T> {
    inner: Arc<Mutex<T>>,
}

impl<T> Synced<T> {
    /// DOCME: what is this?
    pub fn with_inner(inner: T) -> Synced<T> {
        Synced {
            inner: Arc::new(Mutex::new(inner)),
        }
    }

    /// DOCME: what is this?
    pub fn inner<'a>(&'a self) -> MutexGuard<'a, T> {
        self.inner.lock()
    }
}

impl<T> Clone for Synced<T> {
    fn clone(&self) -> Self {
        Synced {
            inner: self.inner.clone(),
        }
    }
}

impl<S: SectorSize, V: Volume<u8, S>> Synced<Ext2<S, V>> {
    /// DOCME: what is this?
    pub fn new(volume: V) -> Result<Synced<Ext2<S, V>>, Error> {
        Ext2::new(volume).map(|inner| Synced::with_inner(inner))
    }
    /// Get the root inode.
    pub fn root_inode(&self) -> Inode<S, V> {
        self.inode_nth(2).unwrap()
    }
    /// Get the inode at the given index.
    pub fn inode_nth(&self, index: usize) -> Option<Inode<S, V>> {
        self.inodes_nth(index).next()
    }
    /// DOCME: what is this?
    pub fn inodes(&self) -> Inodes<S, V> {
        self.inodes_nth(1)
    }
    /// DOCME: what is this?
    pub fn inodes_nth(&self, index: usize) -> Inodes<S, V> {
        assert!(index > 0, "inodes are 1-indexed");
        let inner = self.inner();
        Inodes {
            fs: self.clone(),
            log_block_size: inner.log_block_size(),
            inode_size: inner.inode_size(),
            inodes_per_group: inner.inodes_count(),
            inodes_count: inner.total_inodes_count(),
            index,
        }
    }
    /// DOCME: what is this?
    pub fn sector_size(&self) -> usize {
        S::SIZE
    }
    /// DOCME: what is this?
    pub fn log_sector_size(&self) -> u32 {
        S::LOG_SIZE
    }
}

impl<S: SectorSize, V: Volume<u8, S>> Fs for Synced<Ext2<S, V>> {
    type Path = [u8];
    type PathOwned = Vec<u8>;
    type File = Inode<S, V>;
    type Dir = Directory<S, V>;
    type DirEntry = DirectoryEntry;
    type Metadata = (); // TODO
    type Permissions = (); // TODO
    type Error = Error;

    fn open(
        &self,
        abs_path: &Self::Path,
        _options: &OpenOptions<Self::Permissions>,
    ) -> Result<Self::File, Self::Error> {
        fn inner<'a, S, V, I>(
            fs: &Synced<Ext2<S, V>>,
            inode: Inode<S, V>,
            mut path: I,
            abs_path: &[u8],
        ) -> Result<Inode<S, V>, Error>
        where
            S: SectorSize,
            V: Volume<u8, S>,
            I: Iterator<Item = &'a [u8]>,
        {
            let name = match path.next() {
                Some(name) => name,
                None => return Ok(inode),
            };

            let mut dir =
                inode.directory().ok_or_else(|| Error::NotADirectory {
                    inode: inode.num,
                    name: String::from_utf8_lossy(abs_path).into_owned(),
                })?;

            let entry = dir
                .find(|entry| {
                    entry.is_err() || entry.as_ref().unwrap().name == name
                })
                .ok_or_else(|| Error::NotFound {
                    name: String::from_utf8_lossy(abs_path).into_owned(),
                })??;

            let inode = fs
                .inode_nth(entry.inode)
                .ok_or(Error::InodeNotFound { inode: inode.num })?;

            inner(fs, inode, path, abs_path)
        }

        if abs_path.len() == 0 || abs_path[0] != b'/' {
            return Err(Error::NotAbsolute {
                name: String::from_utf8_lossy(abs_path).into_owned(),
            });
        }

        if abs_path == b"/" {
            return Ok(self.root_inode());
        }

        let mut path = abs_path.split(|byte| *byte == b'/');
        path.next();
        let root = self.root_inode();

        inner(self, root, path, abs_path)
    }

    fn remove_file(&mut self, _path: &Self::Path) -> Result<(), Self::Error> {
        unimplemented!()
    }

    fn metadata(
        &self,
        _path: &Self::Path,
    ) -> Result<Self::Metadata, Self::Error> {
        unimplemented!()
    }

    fn symlink_metadata(
        &self,
        _path: &Self::Path,
    ) -> Result<Self::Metadata, Self::Error> {
        unimplemented!()
    }

    fn rename(
        &mut self,
        _from: &Self::Path,
        _to: &Self::Path,
    ) -> Result<(), Self::Error> {
        unimplemented!()
    }

    fn copy(
        &mut self,
        _from: &Self::Path,
        _to: &Self::Path,
    ) -> Result<u64, Self::Error> {
        unimplemented!()
    }

    fn hard_link(
        &mut self,
        _src: &Self::Path,
        _dst: &Self::Path,
    ) -> Result<(), Self::Error> {
        unimplemented!()
    }

    fn symlink(
        &mut self,
        _src: &Self::Path,
        _dst: &Self::Path,
    ) -> Result<(), Self::Error> {
        unimplemented!()
    }

    fn read_link(
        &self,
        _path: &Self::Path,
    ) -> Result<Self::PathOwned, Self::Error> {
        unimplemented!()
    }

    fn canonicalize(
        &self,
        _path: &Self::Path,
    ) -> Result<Self::PathOwned, Self::Error> {
        unimplemented!()
    }

    fn create_dir(
        &mut self,
        _path: &Self::Path,
        _options: &DirOptions<Self::Permissions>,
    ) -> Result<(), Self::Error> {
        unimplemented!()
    }

    fn remove_dir(&mut self, _path: &Self::Path) -> Result<(), Self::Error> {
        unimplemented!()
    }

    fn remove_dir_all(
        &mut self,
        _path: &Self::Path,
    ) -> Result<(), Self::Error> {
        unimplemented!()
    }

    fn read_dir(&self, path: &Self::Path) -> Result<Self::Dir, Self::Error> {
        let inode = self.open(path, OpenOptions::new().read(true))?;
        inode.directory().ok_or(Error::NotADirectory {
            inode: inode.num,
            name: String::from_utf8_lossy(path).into_owned(),
        })
    }

    fn set_permissions(
        &mut self,
        _path: &Self::Path,
        _perm: Self::Permissions,
    ) -> Result<(), Self::Error> {
        unimplemented!()
    }
}

impl<S: SectorSize, V: Volume<u8, S>> Debug for Synced<Ext2<S, V>> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Synced<Ext2<{}>>", S::SIZE)
    }
}

#[derive(Debug, Clone)]
/// A collection of inodes.
pub struct Inodes<S: SectorSize, V: Volume<u8, S>> {
    fs: Synced<Ext2<S, V>>,
    log_block_size: u32,
    inode_size: usize,
    inodes_per_group: usize,
    inodes_count: usize,
    index: usize,
}

impl<S: SectorSize, V: Volume<u8, S>> Iterator for Inodes<S, V> {
    type Item = Inode<S, V>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.index < self.inodes_count {
            let block_group = (self.index - 1) / self.inodes_per_group;
            let index = (self.index - 1) % self.inodes_per_group;
            self.index += 1;

            let fs = self.fs.inner();

            let inodes_block =
                fs.block_groups.inner[block_group].inode_table_block;

            let offset = Address::with_block_size(
                inodes_block,
                (index * self.inode_size) as i32,
                self.log_block_size,
            );
            let raw = unsafe {
                RawInode::find_inode(&fs.volume, offset, self.inode_size).ok()
            };
            raw.map(|(raw, offset)| {
                Inode::new(
                    self.fs.clone(),
                    raw,
                    offset,
                    (self.index - 1) as u32,
                )
            })
        } else {
            None
        }
    }
}

#[derive(Debug)]
/// A single inode in an ext2 filesystem.
pub struct Inode<S: SectorSize, V: Volume<u8, S>> {
    fs: Synced<Ext2<S, V>>,
    inner: RawInode,
    addr: Address<S>,
    num: u32,
}

impl<S: SectorSize, V: Volume<u8, S>> Clone for Inode<S, V> {
    fn clone(&self) -> Self {
        Inode {
            fs: self.fs.clone(),
            inner: self.inner,
            addr: self.addr,
            num: self.num,
        }
    }
}

impl<S: SectorSize, V: Volume<u8, S>> Inode<S, V> {
    ///
    pub fn new(
        fs: Synced<Ext2<S, V>>,
        inner: RawInode,
        addr: Address<S>,
        num: u32,
    ) -> Inode<S, V> {
        Inode {
            fs,
            inner,
            addr,
            num,
        }
    }
    /// Read to the end of a buffer.
    pub fn read_to_end(&self, buf: &mut Vec<u8>) -> Result<usize, Error> {
        let total_size = self.size();
        let capacity = buf.capacity();
        if capacity < total_size {
            buf.reserve_exact(total_size - capacity);
        }
        unsafe {
            buf.set_len(total_size);
        }
        let size = self.read(&mut buf[..]);
        size.and_then(|size| {
            unsafe {
                buf.set_len(size);
            }
            Ok(size)
        })
        .or_else(|err| {
            unsafe {
                buf.set_len(0);
            }
            Err(err)
        })
    }
    /// Return blocks on a sector
    pub fn blocks(&self) -> InodeBlocks<S, V> {
        InodeBlocks {
            inode: self.clone(),
            index: 0,
        }
    }
    /// return a directory iterator
    pub fn directory(&self) -> Option<Directory<S, V>> {
        if self.is_dir() {
            Some(Directory {
                blocks: self.blocks(),
                offset: 0,
                buffer: None,
                block_size: {
                    let fs = self.fs.inner();
                    fs.block_size()
                },
            })
        } else {
            None
        }
    }
    /// Determine if an inode is a directory
    pub fn is_dir(&self) -> bool {
        { self.inner.type_perm }.contains(TypePerm::DIRECTORY)
        // self.inner.type_perm.contains(TypePerm::DIRECTORY)
    }
    ///
    pub fn block(&self, index: usize) -> Option<NonZeroU32> {
        self.try_block(index).ok().and_then(|block| block)
    }
    /// Try to get a block
    pub fn try_block(
        &self,
        mut index: usize,
    ) -> Result<Option<NonZeroU32>, Error> {
        // number of blocks in direct table: 12
        // number of blocks in indirect table: block_size/4
        //   why?
        //     - a block is n bytes long
        //     - a block address occupies 32 bits, or 4 bytes
        //     - thus, n/4
        // number of blocks in doubly table: (block_size/4)^2
        //   why?
        //     - every entry in the doubly table points to another block
        //     - that's n/4 blocks, where n is the block size
        //     - every block contains n/4 block pointers
        //     - that's n/4 blocks with n/4 pointers each = (n/4)^2
        // number of blocks in triply table: (block_size/4)^3

        fn block_index<S: SectorSize, V: Volume<u8, S>>(
            volume: &V,
            block: u32,
            index: usize,
            log_block_size: u32,
        ) -> Result<Option<NonZeroU32>, Error> {
            let offset = (index * 4) as i32;
            let end = offset + 4;
            let addr = Address::with_block_size(block, offset, log_block_size);
            let end = Address::with_block_size(block, end, log_block_size);
            let block = volume.slice(addr..end);
            match block {
                Ok(block) => unsafe {
                    Ok(NonZeroU32::new(block.dynamic_cast::<u32>().0))
                },
                Err(err) => Err(err.into()),
            }
        }

        let fs = self.fs.inner();

        let bs4 = fs.block_size() / 4;
        let log_block_size = fs.log_block_size();

        if index < 12 {
            return Ok(NonZeroU32::new(self.inner.direct_pointer[index]));
        }

        index -= 12;

        if index < bs4 {
            let block = self.inner.indirect_pointer;
            return block_index(&fs.volume, block, index, log_block_size);
        }

        index -= bs4;

        if index < bs4 * bs4 {
            let indirect_index = index >> (log_block_size + 2);
            let block = match block_index(
                &fs.volume,
                self.inner.doubly_indirect,
                indirect_index,
                log_block_size,
            ) {
                Ok(Some(block)) => block.get(),
                Ok(None) => return Ok(None),
                Err(err) => return Err(err),
            };
            return block_index(
                &fs.volume,
                block,
                index & (bs4 - 1),
                log_block_size,
            );
        }

        index -= bs4 * bs4;

        if index < bs4 * bs4 * bs4 {
            let doubly_index = index >> (2 * log_block_size + 4);
            let indirect = match block_index(
                &fs.volume,
                self.inner.triply_indirect,
                doubly_index,
                log_block_size,
            ) {
                Ok(Some(block)) => block.get(),
                Ok(None) => return Ok(None),
                Err(err) => return Err(err),
            };
            let indirect_index = (index >> (log_block_size + 2)) & (bs4 - 1);
            let block = match block_index(
                &fs.volume,
                indirect as u32,
                indirect_index,
                log_block_size,
            ) {
                Ok(Some(block)) => block.get(),
                Ok(None) => return Ok(None),
                Err(err) => return Err(err),
            };
            return block_index(
                &fs.volume,
                block,
                index & (bs4 - 1),
                log_block_size,
            );
        }

        Ok(None)
    }
    ///
    pub fn in_use(&self) -> bool {
        self.inner.hard_links > 0
    }
    /// return the uid
    pub fn uid(&self) -> u16 {
        self.inner.uid
    }
    ///
    pub fn sectors(&self) -> usize {
        self.inner.sectors_count as usize
    }
    ///
    pub fn size32(&self) -> u32 {
        self.inner.size_low
    }
    ///
    pub fn size64(&self) -> u64 {
        self.inner.size_low as u64 | (self.inner.size_high as u64) << 32
    }
    ///
    #[cfg(target_pointer_width = "64")]
    #[inline]
    pub fn size(&self) -> usize {
        self.size64() as usize
    }

    #[cfg(target_pointer_width = "32")]
    #[inline]
    pub fn size(&self) -> usize {
        self.size32() as usize
    }

    /// ableOS: expose type_perm
    pub fn type_perm(&self) -> TypePerm {
        self.inner.type_perm
    }
}

impl<S: SectorSize, V: Volume<u8, S>> File for Inode<S, V> {
    type Error = Error;

    fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
        let total_size = self.size();
        let block_size = {
            let fs = self.fs.inner();
            fs.block_size()
        };
        let mut offset = 0;

        for block in self.blocks() {
            match block {
                Ok((data, _)) => {
                    let data_size = block_size
                        .min(total_size - offset)
                        .min(buf.len() - offset);
                    let end = offset + data_size;
                    buf[offset..end].copy_from_slice(&data[..data_size]);
                    offset += data_size;
                }
                Err(err) => return Err(err.into()),
            }
        }

        Ok(offset)
    }

    fn write(&mut self, _buf: &[u8]) -> Result<usize, Self::Error> {
        unimplemented!()
    }

    fn flush(&mut self) -> Result<(), Self::Error> {
        unimplemented!()
    }

    fn seek(&mut self, _pos: SeekFrom) -> Result<u64, Self::Error> {
        unimplemented!()
    }
}

///
#[derive(Debug, Clone)]
pub struct InodeBlocks<S: SectorSize, V: Volume<u8, S>> {
    inode: Inode<S, V>,
    index: usize,
}

impl<S: SectorSize, V: Volume<u8, S>> Iterator for InodeBlocks<S, V> {
    type Item = Result<(Vec<u8>, Address<S>), Error>;

    fn next(&mut self) -> Option<Self::Item> {
        let block = self.inode.try_block(self.index);
        let block = match block {
            Ok(Some(ok)) => ok,
            Ok(None) => return None,
            Err(err) => return Some(Err(err)),
        };

        self.index += 1;
        let fs = self.inode.fs.inner();

        let block = block.get();
        let log_block_size = fs.log_block_size();
        let offset = Address::with_block_size(block, 0, log_block_size);
        let end = Address::with_block_size(block + 1, 0, log_block_size);

        let slice = fs
            .volume
            .slice(offset..end)
            .map(|slice| (slice.to_vec(), offset))
            .map_err(|err| err.into());
        Some(slice)
    }
}

#[derive(Debug, Clone)]
/// A directory structure
pub struct Directory<S: SectorSize, V: Volume<u8, S>> {
    blocks: InodeBlocks<S, V>,
    offset: usize,
    buffer: Option<Vec<u8>>,
    block_size: usize,
}

impl<S: SectorSize, V: Volume<u8, S>> Dir<DirectoryEntry, Error>
    for Directory<S, V>
{
}

impl<S: SectorSize, V: Volume<u8, S>> Iterator for Directory<S, V> {
    type Item = Result<DirectoryEntry, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.buffer.is_none() || self.offset >= self.block_size {
            self.buffer = match self.blocks.next() {
                None => return None,
                Some(Ok((block, _))) => Some(block),
                Some(Err(err)) => return Some(Err(err)),
            };

            self.offset = 0;
        }

        let buffer = &self.buffer.as_ref().unwrap()[self.offset..];

        let inode = buffer[0] as u32
            | (buffer[1] as u32) << 8
            | (buffer[2] as u32) << 16
            | (buffer[3] as u32) << 24;
        if inode == 0 {
            return None;
        }

        let size = buffer[4] as u16 | (buffer[5] as u16) << 8;
        let len = buffer[6];
        let ty = buffer[7];

        let name = buffer[8..8 + len as usize].to_vec();

        self.offset += size as usize;

        Some(Ok(DirectoryEntry {
            name: name,
            inode: inode as usize,
            ty: ty,
        }))
    }
}

#[derive(Clone)]
/// A directory entry
pub struct DirectoryEntry {
    /// The name of the entry
    pub name: Vec<u8>,
    /// The inode of the entry
    pub inode: usize,
    ///
    pub ty: u8,
}

impl DirEntry for DirectoryEntry {
    type Path = [u8];
    type PathOwned = Vec<u8>;
    type Metadata = (); // TODO
    type FileType = u8; // TODO: enum FileType
    type Error = Error;

    fn path(&self) -> Self::PathOwned {
        unimplemented!()
    }

    fn metadata(&self) -> Result<Self::Metadata, Self::Error> {
        unimplemented!()
    }

    fn file_type(&self) -> Result<Self::FileType, Self::Error> {
        Ok(self.ty)
    }

    fn file_name(&self) -> &Self::Path {
        &self.name
    }
}

impl DirectoryEntry {
    /// Turns a filename into a string for display
    pub fn file_name_string(&self) -> String {
        let mut filename = String::new();
        for ch in &self.name {
            filename.push(*ch as char);
        }
        filename
    }
}

#[cfg(test)]
mod tests {
    use std::cell::RefCell;
    use std::fs::File;

    use genfs::{File as GenFile, Fs, OpenOptions};

    use sector::{SectorSize, Size512};
    use volume::Volume;

    use super::{Ext2, Inode, Synced};

    #[test]
    fn file() {
        let file = RefCell::new(File::open("ext2.img").unwrap());
        let fs = Synced::<Ext2<Size512, _>>::new(file);

        assert!(
            fs.is_ok(),
            "Err({:?})",
            fs.err().unwrap_or_else(|| unreachable!()),
        );

        let fs = fs.unwrap();
        let inner = fs.inner();

        let vers = inner.version();
        println!("version: {}.{}", vers.0, vers.1);
        assert_eq!(128, inner.inode_size());
    }

    #[test]
    fn inodes() {
        let file = RefCell::new(File::open("ext2.img").unwrap());
        let fs = Synced::<Ext2<Size512, _>>::new(file);

        assert!(
            fs.is_ok(),
            "Err({:?})",
            fs.err().unwrap_or_else(|| unreachable!()),
        );

        let fs = fs.unwrap();

        let inodes = fs.inodes().filter(|inode| inode.in_use());
        for inode in inodes {
            println!("{:?}", inode);
        }
    }

    #[test]
    fn inode_blocks() {
        use std::str;
        let file = RefCell::new(File::open("ext2.img").unwrap());
        let fs = Synced::<Ext2<Size512, _>>::new(file).unwrap();

        let inodes = fs.inodes().filter(|inode| {
            inode.in_use() && inode.uid() == 1000 && inode.size() < 1024
        });
        for inode in inodes {
            println!("{:?}", inode);
            let size = inode.size();
            for block in inode.blocks() {
                let (data, _) = block.unwrap();
                assert_eq!(data.len(), {
                    let fs = fs.inner();
                    fs.block_size()
                });
                println!("{:?}", &data[..size]);
                let _ = str::from_utf8(&data[..size])
                    .map(|string| println!("{}", string));
            }
        }
    }

    #[test]
    fn read_inode() {
        let file = RefCell::new(File::open("ext2.img").unwrap());
        let fs = Synced::<Ext2<Size512, _>>::new(file).unwrap();

        let inodes = fs.inodes().filter(|inode| {
            inode.in_use() && inode.uid() == 1000 && inode.size() < 1024
        });
        for inode in inodes {
            let mut buf = Vec::with_capacity(inode.size());
            unsafe {
                buf.set_len(inode.size());
            }
            let size = inode.read(&mut buf[..]);
            assert!(size.is_ok());
            let size = size.unwrap();
            assert_eq!(size, inode.size());
            unsafe {
                buf.set_len(size);
            }
        }
    }

    #[test]
    fn read_big() {
        let file = RefCell::new(File::open("ext2.img").unwrap());
        let fs = Synced::<Ext2<Size512, _>>::new(file).unwrap();

        let inodes = fs.inodes().filter(|inode| {
            inode.in_use() && inode.uid() == 1000 && inode.size() == 537600
        });
        for inode in inodes {
            let mut buf = Vec::with_capacity(inode.size());
            unsafe {
                buf.set_len(inode.size());
            }
            let size = inode.read(&mut buf[..]);
            assert!(size.is_ok());
            let size = size.unwrap();
            assert_eq!(size, inode.size());
            unsafe {
                buf.set_len(size);
            }

            for (i, &x) in buf.iter().enumerate() {
                if i & 1 == 0 {
                    assert_eq!(x, b'u', "{}", i);
                } else {
                    assert_eq!(x, b'\n', "{}", i);
                }
            }
        }
    }

    #[test]
    fn walkdir() {
        use std::str;

        fn walk<'vol, S: SectorSize, V: Volume<u8, S>>(
            fs: &'vol Synced<Ext2<S, V>>,
            inode: Inode<S, V>,
            name: String,
        ) {
            inode.directory().map(|dir| {
                for entry in dir {
                    assert!(entry.is_ok());
                    let entry = entry.unwrap();
                    let entry_name = str::from_utf8(&entry.name).unwrap_or("?");
                    println!("{}/{} => {}", name, entry_name, entry.inode,);
                    if entry_name != "." && entry_name != ".." {
                        walk(
                            fs,
                            fs.inode_nth(entry.inode).unwrap(),
                            format!("{}/{}", name, entry_name),
                        );
                    }
                }
            });
        }

        let file = RefCell::new(File::open("ext2.img").unwrap());
        let fs = Synced::<Ext2<Size512, _>>::new(file).unwrap();

        let root = fs.root_inode();
        walk(&fs, root, String::new());
    }

    #[test]
    fn find() {
        use std::str;
        let file = RefCell::new(File::open("ext2.img").unwrap());
        let fs = Synced::<Ext2<Size512, _>>::new(file).unwrap();

        let found = fs.open(b"/home/funky/README.md", &OpenOptions::new());

        assert!(found.is_ok());
        let inode = found.unwrap();
        let mut vec = Vec::new();
        assert!(inode.read_to_end(&mut vec).is_ok());
        println!("{}", str::from_utf8(&vec).unwrap());
    }
}
