use std::str::FromStr;
use std::fmt;
use a2kit_macro::{DiskStructError,DiskStruct};
use crate::fs::TextConversion;
pub const DELETED: u8 = 0xe5;
pub const LABEL: u8 = 0x20;
pub const TIMESTAMP: u8 = 0x21;
pub const USER_END: u8 = 0x10;
pub const RECORD_SIZE: usize = 128;
pub const DIR_ENTRY_SIZE: usize = 32;
pub const MAX_LOGICAL_EXTENTS: [usize;4] = [32,512,2048,2048];
pub const LOGICAL_EXTENT_SIZE: usize = 16384;
pub const INVALID_CHARS: &str = " <>.,;:=?*[]";
#[derive(thiserror::Error,Debug)]
pub enum Error {
#[error("bad disk format")]
BadSector,
#[error("bad data format")]
BadFormat,
#[error("file is read only")]
FileReadOnly,
#[error("disk is read only")]
DiskReadOnly,
#[error("drive not found")]
Select,
#[error("directory full")]
DirectoryFull,
#[error("disk full")]
DiskFull,
#[error("cannot read")]
ReadError,
#[error("cannot write")]
WriteError,
#[error("file exists")]
FileExists,
#[error("file not found")]
FileNotFound
}
#[derive(PartialEq)]
pub enum EntryType {
FileExtent,
Label,
Password,
Timestamp,
Deleted,
Unknown
}
#[derive(PartialEq,Eq,Copy,Clone)]
pub enum Ptr {
ExtentEntry(usize),
ExtentData(usize),
Block(usize)
}
impl Ptr {
pub fn unwrap(&self) -> usize {
match self {
Self::Block(i) => *i,
Self::ExtentData(i) => *i,
Self::ExtentEntry(i) => *i
}
}
}
impl PartialOrd for Ptr {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self,other) {
(Self::Block(x),Self::Block(y)) => x.partial_cmp(y),
(Self::ExtentData(x),Self::ExtentData(y)) => x.partial_cmp(y),
(Self::ExtentEntry(x),Self::ExtentEntry(y)) => x.partial_cmp(y),
_ => None
}
}
}
impl Ord for Ptr {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.unwrap().cmp(&other.unwrap())
}
}
pub struct TextConverter {
line_terminator: Vec<u8>
}
impl TextConversion for TextConverter {
fn new(line_terminator: Vec<u8>) -> Self {
Self {
line_terminator
}
}
fn from_utf8(&self,txt: &str) -> Option<Vec<u8>> {
let src: Vec<u8> = txt.as_bytes().to_vec();
let mut ans: Vec<u8> = Vec::new();
for i in 0..src.len() {
if i+1<src.len() && src[i]==0x0d && src[i+1]==0x0a {
continue;
}
if src[i]==0x0a || src[i]==0x0d {
ans.push(0x0d);
ans.push(0x0a);
} else if src[i]<128 {
ans.push(src[i]);
} else {
return None;
}
}
if !Self::is_terminated(&ans, &self.line_terminator) {
ans.append(&mut self.line_terminator.clone());
}
return Some(ans);
}
fn to_utf8(&self,src: &[u8]) -> Option<String> {
let mut ans: Vec<u8> = Vec::new();
for i in 0..src.len() {
if src[i]==0x0d {
continue;
} else if src[i]>127 {
ans.push(0);
} else if src[i]==0x1a {
break;
} else {
ans.push(src[i]);
}
}
let res = String::from_utf8(ans);
match res {
Ok(s) => Some(s),
Err(_) => None
}
}
}
pub struct SequentialText {
pub text: Vec<u8>,
terminator: u8
}
impl FromStr for SequentialText {
type Err = std::fmt::Error;
fn from_str(s: &str) -> Result<Self,Self::Err> {
let encoder = TextConverter::new(vec![]);
if let Some(dat) = encoder.from_utf8(s) {
return Ok(Self {
text: dat.clone(),
terminator: 0x1a
});
}
Err(std::fmt::Error)
}
}
impl fmt::Display for SequentialText {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
let encoder = TextConverter::new(vec![]);
if let Some(ans) = encoder.to_utf8(&self.text) {
return write!(f,"{}",ans);
}
write!(f,"err")
}
}
impl DiskStruct for SequentialText {
fn new() -> Self {
Self {
text: Vec::new(),
terminator: 0x1a
}
}
fn from_bytes(dat: &[u8]) -> Result<Self,DiskStructError> {
Ok(Self {
text: match dat.split(|x| *x==0x1a).next() {
Some(v) => v.to_vec(),
_ => dat.to_vec()
},
terminator: 0x1a
})
}
fn to_bytes(&self) -> Vec<u8> {
let mut ans: Vec<u8> = Vec::new();
ans.append(&mut self.text.clone());
ans.push(self.terminator);
while ans.len()%128>0 {
ans.push(self.terminator);
}
return ans;
}
fn update_from_bytes(&mut self,dat: &[u8]) -> Result<(),DiskStructError> {
let temp = SequentialText::from_bytes(&dat)?;
self.text = temp.text.clone();
self.terminator = 0x1a;
Ok(())
}
fn len(&self) -> usize {
let unpadded = self.text.len() + 1;
match unpadded%128 {
0 => unpadded,
remainder => unpadded + 128 - remainder
}
}
}