use std::str::Utf8Error;
use snafu::prelude::*;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::naif::Endian;
use core::fmt;
use log::error;
use super::NAIFRecord;
#[derive(Debug, Snafu, PartialEq)]
#[snafu(visibility(pub(crate)))]
#[non_exhaustive]
pub enum FileRecordError {
#[snafu(display("issue: endian of file does not match the endian order of the machine"))]
WrongEndian,
#[snafu(display("endian flag or internal filename is not a valid UTF8 string: {source:?}"))]
ParsingError {
source: Utf8Error,
},
#[snafu(display("endian flag is `{read}` but it should be either `BIG-IEEE` or `LTL-IEEE`"))]
InvalidEndian {
read: String,
},
UnsupportedIdentifier {
loci: String,
},
#[snafu(display("indicates this is not a SPICE DAF file"))]
NotDAF,
#[snafu(display("has no identifier"))]
NoIdentifier,
#[snafu(display("is empty (ensure file is valid, e.g. do you need to run git-lfs)"))]
EmptyRecord,
}
#[derive(Debug, Clone, FromBytes, KnownLayout, Immutable, IntoBytes, PartialEq)]
#[repr(C)]
pub struct FileRecord {
pub id_str: [u8; 8],
pub nd: u32,
pub ni: u32,
pub internal_filename: [u8; 60],
pub forward: u32,
pub backward: u32,
pub free_addr: u32,
pub endian_str: [u8; 8],
pub pre_null: [u8; 603],
pub ftp_str: [u8; 28],
pub pst_null: [u8; 297],
}
impl Default for FileRecord {
fn default() -> Self {
Self {
id_str: [0; 8],
nd: Default::default(),
ni: Default::default(),
internal_filename: [0; 60],
forward: Default::default(),
backward: Default::default(),
free_addr: Default::default(),
endian_str: [0; 8],
pre_null: [0; 603],
ftp_str: [0; 28],
pst_null: [0; 297],
}
}
}
impl NAIFRecord for FileRecord {}
impl FileRecord {
pub fn ni(&self) -> usize {
self.ni as usize
}
pub fn nd(&self) -> usize {
self.nd as usize
}
pub fn fwrd_idx(&self) -> usize {
self.forward as usize
}
pub fn summary_size(&self) -> usize {
(self.nd + self.ni.div_ceil(2)) as usize
}
pub fn identification(&self) -> Result<&str, FileRecordError> {
let str_locidw =
core::str::from_utf8(&self.id_str).map_err(|_| FileRecordError::NoIdentifier)?;
if &str_locidw[0..3] != "DAF" || str_locidw.chars().nth(3) != Some('/') {
Err(FileRecordError::NotDAF)
} else {
let loci = str_locidw[4..].trim();
match loci {
"SPK" => Ok("SPK"),
"PCK" => Ok("PCK"),
_ => {
error!("DAF of type `{}` is not yet supported", &str_locidw[4..]);
Err(FileRecordError::UnsupportedIdentifier {
loci: loci.to_string(),
})
}
}
}
}
pub fn endianness(&self) -> Result<Endian, FileRecordError> {
let str_endianness = core::str::from_utf8(&self.endian_str).context(ParsingSnafu)?;
let file_endian = if str_endianness == "LTL-IEEE" {
Endian::Little
} else if str_endianness == "BIG-IEEE" {
Endian::Big
} else {
return Err(FileRecordError::InvalidEndian {
read: str_endianness.to_string(),
});
};
if file_endian != Endian::f64_native() || file_endian != Endian::u64_native() {
Err(FileRecordError::WrongEndian)
} else {
Ok(file_endian)
}
}
pub fn internal_filename(&self) -> Result<&str, FileRecordError> {
Ok(core::str::from_utf8(&self.internal_filename)
.context(ParsingSnafu)?
.trim())
}
pub fn is_empty(&self) -> bool {
self == &Self::default()
}
pub(crate) fn spk(filename: &str) -> Self {
let mut internal_filename = [0u8; 60];
for (dest, src) in internal_filename.iter_mut().zip(filename.as_bytes()) {
*dest = *src;
}
Self {
id_str: *b"DAF/SPK ",
nd: 2,
ni: 6,
internal_filename,
forward: 2,
backward: 2,
free_addr: 0,
endian_str: Endian::daf_endian_str(),
..Default::default()
}
}
}
impl fmt::Display for FileRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let id = self.identification().unwrap(); let endian = if let Ok(endian) = self.endianness() {
format!("{endian:?}")
} else {
"invalid endian".to_string()
};
write!(f, "FileRecord for {id} in {endian}",)
}
}