use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use std::str::FromStr;
use std::fmt;
use a2kit_macro::{DiskStructError,DiskStruct};
use crate::fs::TextConversion;
pub const VTOC_TRACK: u8 = 17;
pub const MAX_DIRECTORY_REPS: usize = 100;
pub const MAX_TSLIST_REPS: usize = 1000;
#[derive(thiserror::Error,Debug)]
pub enum Error {
#[error("RANGE ERROR")]
Range,
#[error("END OF DATA")]
EndOfData,
#[error("FILE NOT FOUND")]
FileNotFound,
#[error("VOLUME MISMATCH")]
VolumeMismatch,
#[error("I/O ERROR")]
IOError,
#[error("DISK FULL")]
DiskFull,
#[error("FILE LOCKED")]
FileLocked,
#[error("FILE TYPE MISMATCH")]
FileTypeMismatch,
#[error("WRITE PROTECTED")]
WriteProtected,
#[error("SYNTAX ERROR")]
SyntaxError
}
#[derive(FromPrimitive)]
pub enum FileType {
Text = 0x00,
Integer = 0x01,
Applesoft = 0x02,
Binary = 0x04
}
impl FromStr for FileType {
type Err = Error;
fn from_str(s: &str) -> Result<Self,Self::Err> {
if let Ok(num) = u8::from_str(s) {
return match FileType::from_u8(num & 0x7f) {
Some(typ) => Ok(typ),
_ => Err(Error::FileTypeMismatch)
};
}
match s {
"bin" => Ok(Self::Binary),
"txt" => Ok(Self::Text),
"atok" => Ok(Self::Applesoft),
"itok" => Ok(Self::Integer),
_ => Err(Error::FileTypeMismatch)
}
}
}
fn append_junk(dat: &[u8],trailing: Option<&[u8]>) -> Vec<u8> {
match trailing {
Some(v) => [dat.to_vec(),v.to_vec()].concat(),
None => dat.to_vec()
}
}
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(0x8d);
} else if src[i]<128 {
ans.push(src[i]+0x80);
} 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]==0x8d {
ans.push(0x0a);
} else if src[i]>127 {
ans.push(src[i]-0x80);
} else {
ans.push(0);
}
}
let res = String::from_utf8(ans);
match res {
Ok(s) => Some(s),
Err(_) => None
}
}
}
pub struct TokenizedProgram {
length: [u8;2],
pub program: Vec<u8>
}
impl TokenizedProgram {
pub fn pack(prog: &[u8],trailing: Option<&[u8]>) -> Self {
let padded = append_junk(prog,trailing);
Self {
length: u16::to_le_bytes(prog.len() as u16),
program: padded.clone()
}
}
}
impl DiskStruct for TokenizedProgram {
fn new() -> Self
{
Self {
length: [0;2],
program: Vec::new()
}
}
fn from_bytes(dat: &[u8]) -> Result<Self,DiskStructError> {
if dat.len() < 2 {
return Err(DiskStructError::OutOfData);
}
let end_byte = 2 + u16::from_le_bytes([dat[0],dat[1]]) as usize;
if end_byte > dat.len() {
return Err(DiskStructError::OutOfData);
}
return Ok(Self {
length: [dat[0],dat[1]],
program: dat[2..end_byte].to_vec().clone()
})
}
fn to_bytes(&self) -> Vec<u8> {
let mut ans: Vec<u8> = Vec::new();
ans.append(&mut self.length.to_vec());
ans.append(&mut self.program.clone());
return ans;
}
fn update_from_bytes(&mut self,dat: &[u8]) -> Result<(),DiskStructError> {
let temp = TokenizedProgram::from_bytes(&dat)?;
self.length = temp.length;
self.program = temp.program.clone();
Ok(())
}
fn len(&self) -> usize {
return 2 + self.program.len();
}
}
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![0x8d]);
if let Some(dat) = encoder.from_utf8(s) {
return Ok(Self {
text: dat.clone(),
terminator: 0
});
}
Err(std::fmt::Error)
}
}
impl fmt::Display for SequentialText {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
let encoder = TextConverter::new(vec![0x8d]);
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: 0
}
}
fn from_bytes(dat: &[u8]) -> Result<Self,DiskStructError> {
Ok(Self {
text: match dat.split(|x| *x==0).next() {
Some(v) => v.to_vec(),
_ => dat.to_vec()
},
terminator: 0
})
}
fn to_bytes(&self) -> Vec<u8> {
let mut ans: Vec<u8> = Vec::new();
ans.append(&mut self.text.clone());
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 = 0;
Ok(())
}
fn len(&self) -> usize {
return self.text.len() + 1;
}
}
pub struct BinaryData {
pub start: [u8;2],
length: [u8;2],
pub data: Vec<u8>
}
impl BinaryData {
pub fn pack(bin: &[u8], addr: u16) -> Self {
Self {
start: u16::to_le_bytes(addr),
length: u16::to_le_bytes(bin.len() as u16),
data: bin.to_vec()
}
}
}
impl DiskStruct for BinaryData {
fn new() -> Self
{
Self {
start: [0;2],
length: [0;2],
data: Vec::new()
}
}
fn from_bytes(dat: &[u8]) -> Result<Self,DiskStructError> {
if dat.len() < 4 {
return Err(DiskStructError::OutOfData);
}
let end_byte = 4 + u16::from_le_bytes([dat[2],dat[3]]) as usize;
if end_byte > dat.len() {
return Err(DiskStructError::OutOfData);
}
Ok(Self {
start: [dat[0],dat[1]],
length: [dat[2],dat[3]],
data: dat[4..end_byte].to_vec()
})
}
fn to_bytes(&self) -> Vec<u8> {
let mut ans: Vec<u8> = Vec::new();
ans.append(&mut self.start.to_vec());
ans.append(&mut self.length.to_vec());
ans.append(&mut self.data.clone());
return ans;
}
fn update_from_bytes(&mut self,dat: &[u8]) -> Result<(),DiskStructError> {
let temp = BinaryData::from_bytes(&dat)?;
self.start = temp.start;
self.length = temp.length;
self.data = temp.data.clone();
Ok(())
}
fn len(&self) -> usize {
return 4 + self.data.len();
}
}