use std::fmt;

use bytemuck::{Pod, Zeroable};

mod reader;
mod writer;

pub use reader::{TarReader, TarReaderEntry};
pub use writer::{TarFileWriter, TarNewFileMetadata, TarWriter};

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct TarString<const N: usize>([u8; N]);

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct TarOctal<const N: usize>([u8; N]);

#[derive(Zeroable, Pod, Clone, Copy)]
#[repr(C)]
pub struct TarHeader {
    pub name: TarString<100>,
    pub mode: TarOctal<8>,
    pub uid: TarOctal<8>,
    pub gid: TarOctal<8>,
    pub size: TarOctal<12>,
    pub mtime: TarOctal<12>,
    pub checksum: TarOctal<8>,
    pub file_type: u8,
    pub link: TarString<100>,
    pub ustar_magic: [u8; 6],
    pub ustar_version: [u8; 2],
    pub uid_name: TarString<32>,
    pub gid_name: TarString<32>,
    pub dev_major: TarOctal<8>,
    pub dev_minor: TarOctal<8>,
    pub filename_prefix: TarString<155>,
    _0: [u8; 12],
}

unsafe impl<const N: usize> Pod for TarString<N> {}
unsafe impl<const N: usize> Zeroable for TarString<N> {}

unsafe impl<const N: usize> Pod for TarOctal<N> {}
unsafe impl<const N: usize> Zeroable for TarOctal<N> {}

impl<const N: usize> TarOctal<N> {
    pub const fn zero() -> Self {
        let mut bytes = [b'0'; N];
        bytes[N - 1] = 0;
        Self(bytes)
    }

    pub fn from_u64(value: u64) -> Option<Self> {
        // Limit check
        if (N - 1) * 3 < 64 && value >= (1 << ((N - 1) * 3)) {
            return None;
        }

        let mut bytes = [0; N];
        let mut j = N - 2;
        for i in 0..N - 1 {
            let v = ((value >> (j * 3)) & 0x7) as u8 + b'0';
            bytes[i] = v;
            j -= 1;
        }

        Some(Self(bytes))
    }

    pub fn from_u32(value: u32) -> Option<Self> {
        Self::from_u64(value as u64)
    }

    pub fn as_usize(&self) -> Option<usize> {
        let mut value = 0;

        for i in 0..N {
            let ch = self.0[i];
            if ch == b'\0' || ch == b' ' {
                break;
            }

            if ch < b'0' || ch > b'7' {
                return None;
            }

            value <<= 3;
            value |= (ch - b'0') as usize;
        }

        Some(value)
    }
}

impl<const N: usize> TarString<N> {
    pub const EMPTY: Self = Self([0; N]);

    pub fn new(s: &str) -> Option<Self> {
        if s.len() < N {
            let mut bytes = [0; N];
            bytes[..s.len()].copy_from_slice(s.as_bytes());
            Some(Self(bytes))
        } else {
            None
        }
    }

    pub fn is_empty(&self) -> bool {
        self.0[0] == b'\0'
    }

    pub fn len(&self) -> usize {
        self.0.iter().position(|&p| p == b'\0').unwrap_or(N)
    }

    pub fn as_str(&self) -> Option<&str> {
        core::str::from_utf8(&self.0[..self.len()]).ok()
    }
}

impl<const N: usize> fmt::Display for TarString<N> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use fmt::Write;

        for ch in self.0 {
            if ch == b'\0' {
                break;
            }
            f.write_char(ch as char)?;
        }

        Ok(())
    }
}

impl TarHeader {
    pub fn is_empty(&self) -> bool {
        self.name.is_empty()
    }

    pub fn is_dir(&self) -> bool {
        self.file_type == b'5'
    }

    pub fn is_file(&self) -> bool {
        self.file_type == b'\0' || self.file_type == b'0'
    }
}
