use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use std::str::FromStr;
use std::fmt;
use a2kit_macro::{DiskStruct, DiskStructError};
use crate::fs::TextConversion;
pub const BLOCK_SIZE: usize = 512;
pub const TEXT_PAGE: usize = 1024;
pub const VOL_HEADER_BLOCK: usize = 2;
pub const ENTRY_SIZE: usize = 26;
pub const MAX_CAT_REPS: usize = 100;
pub const INVALID_CHARS: &str = " $=?,[#:";
#[derive(thiserror::Error,Debug)]
pub enum Error {
#[error("parity error (CRC)")]
BadBlock,
#[error("bad device number")]
BadDevNum,
#[error("illegal operation")]
BadMode,
#[error("undefined hardware error")]
Hardware,
#[error("lost device")]
LostDev,
#[error("lost file")]
LostFile,
#[error("illegal filename")]
BadTitle,
#[error("insufficient space")]
NoRoom,
#[error("no device")]
NoDev,
#[error("no file")]
NoFile,
#[error("duplicate file")]
DuplicateFilename,
#[error("attempt to open already-open file")]
NotClosed,
#[error("attempt to access closed file")]
NotOpen,
#[error("error reading real or integer")]
BadFormat,
#[error("characters arriving too fast")]
BufferOverflow,
#[error("disk is write protected")]
WriteProtected,
#[error("failed to complete read or write")]
DevErr
}
pub const TYPE_MAP_DISP: [(u8,&str);9] = [
(0x00, "NONE"),
(0x01, "BAD"),
(0x02, "CODE"),
(0x03, "TEXT"),
(0x04, "INFO"),
(0x05, "DATA"),
(0x06, "GRAF"),
(0x07, "FOTO"),
(0x08, "SECURE")
];
#[derive(FromPrimitive)]
pub enum FileType {
Non = 0x00,
Bad = 0x01,
Code = 0x02,
Text = 0x03,
Info = 0x04,
Data = 0x05,
Graf = 0x06,
Foto = 0x07,
Secure = 0x08
}
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) {
Some(typ) => Ok(typ),
_ => Err(Error::BadMode)
};
}
match s {
"bin" => Ok(Self::Data),
"txt" => Ok(Self::Text),
"pcode" => Ok(Self::Code),
_ => Err(Error::BadMode)
}
}
}
pub struct TextConverter {
line_terminator: Vec<u8>
}
fn paginate(ans: &mut Vec<u8>,page: usize,count_on_page: usize) -> Result<usize,Error> {
if count_on_page >= TEXT_PAGE {
let offset = page*TEXT_PAGE;
for i in (0..TEXT_PAGE).rev() {
if ans[offset+i]==0x0d {
for _j in 0..1023-i {
ans.insert(offset+i+1,0);
}
return Ok(page+1);
}
}
return Err(Error::BadFormat);
}
return Ok(page);
}
impl TextConversion for TextConverter {
fn new(line_terminator: Vec<u8>) -> Self {
Self {
line_terminator
}
}
fn from_utf8(&self,txt: &str) -> Option<Vec<u8>> {
log::debug!("encoding text");
let src: Vec<u8> = txt.as_bytes().to_vec();
let mut ans: Vec<u8> = Vec::new();
let mut starting_line = true;
let mut indenting = 0;
let mut page = 0;
let mut count_on_page: usize = 0;
for i in 0..src.len() {
if i+1 < src.len() && src[i]==0x0d && src[i+1]==0x0a {
continue;
}
if starting_line {
if i>0 && src[i]==0x20 {
indenting += 1;
starting_line = false;
} else {
if i>0 {
ans.push(0x10);
ans.push(0x20);
count_on_page += 2;
}
if src[i]!=0x0a && src[i]!=0x0d {
starting_line = false;
ans.push(src[i]);
} else {
ans.push(0x0d);
}
count_on_page += 1;
}
} else if indenting>0 {
if src[i]==0x20 && indenting+0x20<0xff{
indenting += 1;
} else {
ans.push(0x10);
ans.push(0x20 + indenting);
if src[i]!=0x0a && src[i]!=0x0d {
ans.push(src[i]);
} else {
ans.push(0x0d);
starting_line = true;
}
indenting = 0;
count_on_page += 3;
}
} else if src[i]==0x0a || src[i]==0x0d {
ans.push(0x0d);
count_on_page += 1;
starting_line = true;
} else if src[i]<128 {
ans.push(src[i]);
count_on_page += 1;
starting_line = false;
} else {
return None;
}
match paginate(&mut ans,page,count_on_page) {
Ok(new_page) => page = new_page,
Err(e) => {
log::error!("{}",e);
return None
}
}
count_on_page = count_on_page % TEXT_PAGE;
}
if !Self::is_terminated(&ans, &self.line_terminator) { if self.line_terminator.len()>0 { ans.append(&mut self.line_terminator.clone());
count_on_page += 1;
}
}
match paginate(&mut ans,page,count_on_page) {
Ok(_new_page) => {}, Err(e) => {
log::error!("{}",e);
return None
}
}
while ans.len()%TEXT_PAGE>0 {
ans.push(0);
}
return Some(ans);
}
fn to_utf8(&self,src: &[u8]) -> Option<String> {
let mut ans: Vec<u8> = Vec::new();
let mut await_indent = false;
for i in 0..src.len() {
if await_indent {
for _rep in 0..src[i]-32 {
ans.push(0x20);
}
await_indent = false;
} else if src[i]==0x0d {
ans.push(0x0a);
} else if src[i]==0x10 {
await_indent = true;
} else if src[i]<127 && src[i]>0 {
ans.push(src[i]);
}
}
let res = String::from_utf8(ans);
match res {
Ok(s) => Some(s),
Err(_) => None
}
}
}
pub struct SequentialText {
pub header: Vec<u8>,
pub text: Vec<u8>
}
impl SequentialText {
fn create_header() -> [u8;TEXT_PAGE] {
let mut ans: [u8;TEXT_PAGE] = [0;TEXT_PAGE];
ans[0] = 1;
ans[0x70..0x80].copy_from_slice(&[0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x05, 0x00, 0x5E, 0x00]);
ans[0x80..0x90].copy_from_slice(&[0x13, 0xA3, 0x13, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
ans
}
}
impl FromStr for SequentialText {
type Err = std::fmt::Error;
fn from_str(s: &str) -> Result<Self,Self::Err> {
let encoder = TextConverter::new(vec![0x0d]);
if let Some(dat) = encoder.from_utf8(s) {
return Ok(Self {
header: Self::create_header().to_vec(),
text: dat.clone()
});
}
Err(std::fmt::Error)
}
}
impl fmt::Display for SequentialText {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
let encoder = TextConverter::new(vec![0x0d]);
if let Some(ans) = encoder.to_utf8(&self.text) {
return write!(f,"{}",ans);
}
write!(f,"err")
}
}
impl DiskStruct for SequentialText {
fn new() -> Self {
Self {
header: Vec::new(),
text: Vec::new()
}
}
fn from_bytes(dat: &[u8]) -> Result<Self,DiskStructError> {
if dat.len() < TEXT_PAGE + 1 {
return Err(DiskStructError::OutOfData);
}
Ok(Self {
header: dat[0..TEXT_PAGE].to_vec(),
text: dat[TEXT_PAGE..].to_vec()
})
}
fn to_bytes(&self) -> Vec<u8> {
let mut ans: Vec<u8> = Vec::new();
log::debug!("to_bytes: header {} text {}",self.header.len(),self.text.len());
ans.append(&mut self.header.clone());
ans.append(&mut self.text.clone());
return ans;
}
fn update_from_bytes(&mut self,dat: &[u8]) -> Result<(),DiskStructError> {
let temp = SequentialText::from_bytes(&dat)?;
self.text = temp.text.clone();
Ok(())
}
fn len(&self) -> usize {
return self.header.len() + self.text.len();
}
}