#![warn(clippy::as_conversions)]
mod entry;
pub mod file_time;
mod pak;
pub(crate) mod reader;
pub(crate) mod writer;
pub use crate::entry::Entry;
pub use crate::file_time::FileTime;
pub use crate::pak::Pak;
pub(crate) const MAGIC: &[u8] = &[0xc0, 0x4a, 0xc0, 0xba];
pub(crate) const VERSION: &[u8] = &[0; 4];
const FILEFLAGS_DEFAULT: u8 = 0x00;
const FILEFLAGS_END: u8 = 0x80;
#[derive(Debug)]
pub enum PakError {
    Io(std::io::Error),
    InvalidMagic([u8; 4]),
    InvalidVersion([u8; 4]),
    InvalidFileNameLength {
        length: usize,
        error: std::num::TryFromIntError,
    },
    InvalidFileDataLength {
        length: usize,
        error: std::num::TryFromIntError,
    },
    InvalidRecordFileSize {
        file_size: u32,
        error: std::num::TryFromIntError,
    },
}
impl From<std::io::Error> for PakError {
    fn from(e: std::io::Error) -> Self {
        Self::Io(e)
    }
}
impl std::fmt::Display for PakError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Io(e) => e.fmt(f),
            Self::InvalidMagic(magic) => {
                write!(f, "invalid magic number '{magic:?}', expected '{MAGIC:?}'",)
            }
            Self::InvalidVersion(version) => {
                write!(f, "invalid version '{version:?}', expected '{VERSION:?}'",)
            }
            Self::InvalidFileNameLength { length, .. } => {
                write!(f, "invalid file name length '{length}'")
            }
            Self::InvalidFileDataLength { length, .. } => {
                write!(f, "invalid file data length '{length}'")
            }
            Self::InvalidRecordFileSize { file_size, .. } => {
                write!(f, "invalid record file size '{file_size}'")
            }
        }
    }
}
impl std::error::Error for PakError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Io(error) => Some(error),
            Self::InvalidFileNameLength { error, .. } => Some(error),
            Self::InvalidFileDataLength { error, .. } => Some(error),
            Self::InvalidRecordFileSize { error, .. } => Some(error),
            _ => None,
        }
    }
}
#[derive(Debug)]
struct Record {
    pub name: Vec<u8>,
    pub file_size: u32,
    pub file_time: FileTime,
}
#[cfg(test)]
mod tests {
    use super::*;
    use std::path::Path;
    const EXTRACT_PATH: &str = "test-extract";
    const PAK_PATH: &str = "test_data/Simple Building.pak";
    fn extract(pak: &mut Pak, extract_dir: &Path) {
        let _ = std::fs::remove_dir_all(extract_dir);
        for entry in pak.entries.iter_mut() {
            let entry_path = entry.path_str().unwrap();
            let entry_dir = entry.dir_str().transpose().unwrap();
            let entry_name = entry.name_str().unwrap();
            let (expected_entry_dir, expected_entry_name) = entry_path
                .rsplit_once(['/', '\\'])
                .map(|(dir, name)| (Some(dir), name))
                .unwrap_or((None, entry_path));
            assert!(
                expected_entry_name == entry_name,
                "{expected_entry_name} != {entry_name}"
            );
            assert!(
                expected_entry_dir == entry_dir,
                "{expected_entry_dir:?} != {entry_dir:?}"
            );
            println!("Extracting '{}'...", entry_path);
            if let Some(dir) = entry_dir {
                let entry_extract_dir = extract_dir.join(dir);
                std::fs::create_dir_all(&entry_extract_dir).unwrap();
            }
            let entry_extract_path = extract_dir.join(entry_path);
            let mut f = std::fs::File::create(&entry_extract_path).unwrap();
            std::io::copy(entry, &mut f).unwrap();
        }
    }
    #[test]
    fn extract_read() {
        let f = std::fs::File::open(PAK_PATH).unwrap();
        let mut p = Pak::from_read(f).unwrap();
        let extract_dir = Path::new(EXTRACT_PATH).join("read");
        extract(&mut p, &extract_dir);
    }
    #[test]
    fn extract_bytes() {
        let data = std::fs::read(PAK_PATH).unwrap();
        let mut p = Pak::from_bytes(&data).unwrap();
        let extract_dir = Path::new(EXTRACT_PATH).join("bytes");
        extract(&mut p, &extract_dir);
    }
    #[test]
    fn bytes_vs_read() {
        let data = std::fs::read(PAK_PATH).unwrap();
        let read = Pak::from_read(std::io::Cursor::new(&data)).unwrap();
        let bytes = Pak::from_bytes(&data).unwrap();
        assert_eq!(bytes, read);
    }
    #[test]
    fn round_trip() {
        let original = std::fs::read(PAK_PATH).unwrap();
        let mut pak = Pak::from_read(std::io::Cursor::new(&original)).unwrap();
        let mut round = Vec::new();
        pak.write_to(&mut round).unwrap();
        assert_eq!(&round, &original);
        let pak2 = Pak::from_read(std::io::Cursor::new(&round)).unwrap();
        assert_eq!(pak, pak2);
    }
}