use yggdrasil_abi::{error::Error, io::FileType};

#[repr(C)]
pub(crate) struct OctalField<const N: usize> {
    data: [u8; N],
}

#[repr(C)]
pub(crate) struct TarString<const N: usize> {
    data: [u8; N],
}

pub(crate) struct TarIterator<'a> {
    data: &'a [u8],
    offset: usize,
    zero_blocks: usize,
}

#[repr(C, packed)]
pub(crate) struct TarEntry {
    pub name: TarString<100>,
    pub mode: OctalField<8>,
    pub uid: OctalField<8>,
    pub gid: OctalField<8>,
    pub size: OctalField<12>,
    pub mtime: OctalField<12>,
    _checksum: OctalField<8>,
    type_: u8,
    link_name: TarString<100>,
    _magic: [u8; 8],
    _user: TarString<32>,
    _group: TarString<32>,
    _dev_major: OctalField<8>,
    _dev_minor: OctalField<8>,
    _prefix: TarString<155>,
    __pad: [u8; 12],
}

impl<'a> TarIterator<'a> {
    pub const fn new(data: &'a [u8]) -> Self {
        Self {
            data,
            offset: 0,
            zero_blocks: 0,
        }
    }
}

impl<'a> Iterator for TarIterator<'a> {
    type Item = Result<(&'a TarEntry, Option<&'a [u8]>), Error>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if self.offset + 512 > self.data.len() {
                break None;
            }

            let hdr_ptr = &self.data[self.offset..];
            let hdr = unsafe { &*(hdr_ptr.as_ptr() as *const TarEntry) };

            if hdr.is_empty() {
                if self.zero_blocks == 1 {
                    self.offset = self.data.len();
                    return None;
                }
                self.zero_blocks += 1;
                continue;
            }

            let size = usize::from(&hdr.size);
            let size_aligned = (size + 511) & !511;

            let (data, size_aligned) = match hdr.type_ {
                0 | b'0' => {
                    if self.offset + 512 + size > self.data.len() {
                        return Some(Err(Error::InvalidArgument));
                    }

                    let data = &self.data[self.offset + 512..self.offset + 512 + size];

                    (Some(data), size_aligned)
                }
                // Directory
                b'5' => (None, 0),
                // Symlink (TODO long symlinks)
                b'2' => (None, 0),
                // Symlink
                _ => {
                    todo!("Unhandled entry type: {}", hdr.type_)
                }
            };
            self.offset += size_aligned + 512;

            break Some(Ok((hdr, data)));
        }
    }
}

impl<const N: usize> From<&OctalField<N>> for usize {
    fn from(value: &OctalField<N>) -> Self {
        let mut acc = 0;
        for i in 0..N {
            if !(b'0'..b'8').contains(&value.data[i]) {
                break;
            }
            acc <<= 3;
            acc |= (value.data[i] - b'0') as usize;
        }
        acc
    }
}

impl<const N: usize> TarString<N> {
    pub fn as_str(&self) -> Result<&str, Error> {
        core::str::from_utf8(&self.data[..self.len()]).map_err(|_| Error::InvalidArgument)
    }

    pub fn len(&self) -> usize {
        for i in 0..N {
            if self.data[i] == 0 {
                return i;
            }
        }
        N
    }
}

impl TarEntry {
    pub fn is_empty(&self) -> bool {
        self.name.data[0] == 0
    }

    // TODO long links
    pub fn symlink_target(&self) -> Result<&str, Error> {
        self.link_name.as_str()
    }

    pub fn node_kind(&self) -> FileType {
        match self.type_ {
            0 | b'0' => FileType::File,
            b'5' => FileType::Directory,
            b'2' => FileType::Symlink,
            _ => todo!(),
        }
    }
}
