use clap;
use std::str::FromStr;
use log::{error,info};
use crate::bios::dpb;
use crate::fs::{DiskFS,cpm,dos3x,prodos,pascal};
use crate::img;
use crate::img::{DiskKind,DiskImage,DiskImageType,names};
use super::CommandError;
use crate::{STDRESULT,DYNERR};
const RCH: &str = "unreachable was reached";
const BOOT_MESS: &str = "omit boot flag; for this OS you will need to copy boot files after formatting";
const BOOT_MESS_CPM: &str = "omit boot flag; for this OS you will need to copy reserved tracks after formatting";
fn mkimage(img_typ: &DiskImageType,kind: &DiskKind,maybe_vol: Option<&String>,maybe_wrap: Option<&String>) -> Result<Box<dyn DiskImage>,DYNERR> {
let vol = match maybe_vol {
Some(vstr) => match u8::from_str_radix(vstr,10) {
Ok(v) => v,
_ => 254
},
_ => 254
};
match (img_typ,maybe_wrap) {
(DiskImageType::DOT2MG,None) => {
error!("selected image type requires the `--wrap` option");
return Err(Box::new(CommandError::InvalidCommand))
},
(DiskImageType::DOT2MG,Some(_)) => {},
(_,None) => {},
_ => {
error!("omit the `--wrap` option for this image type");
return Err(Box::new(CommandError::InvalidCommand))
}
}
return match (img_typ,*kind) {
(DiskImageType::D13,names::A2_DOS32_KIND) => Ok(Box::new(img::dsk_d13::D13::create(35))),
(DiskImageType::DO,names::A2_DOS33_KIND) => Ok(Box::new(img::dsk_do::DO::create(35,16))),
(DiskImageType::WOZ1,names::A2_DOS32_KIND) => Ok(Box::new(img::woz1::Woz1::create(vol,*kind))),
(DiskImageType::WOZ1,names::A2_DOS33_KIND) => Ok(Box::new(img::woz1::Woz1::create(vol,*kind))),
(DiskImageType::WOZ2,names::A2_DOS32_KIND) => Ok(Box::new(img::woz2::Woz2::create(vol,*kind))),
(DiskImageType::WOZ2,names::A2_DOS33_KIND) => Ok(Box::new(img::woz2::Woz2::create(vol,*kind))),
(DiskImageType::WOZ2,names::A2_400_KIND) => Ok(Box::new(img::woz2::Woz2::create(vol,*kind))),
(DiskImageType::WOZ2,names::A2_800_KIND) => Ok(Box::new(img::woz2::Woz2::create(vol,*kind))),
(DiskImageType::PO,names::A2_DOS33_KIND) => Ok(Box::new(img::dsk_po::PO::create(280))),
(DiskImageType::PO,names::A2_400_KIND) => Ok(Box::new(img::dsk_po::PO::create(800))),
(DiskImageType::PO,names::A2_800_KIND) => Ok(Box::new(img::dsk_po::PO::create(1600))),
(DiskImageType::PO,names::A2_HD_MAX) => Ok(Box::new(img::dsk_po::PO::create(65535))),
(DiskImageType::DOT2MG,names::A2_DOS33_KIND) => img::dot2mg::Dot2mg::create(vol,*kind,maybe_wrap),
(DiskImageType::DOT2MG,names::A2_400_KIND) => img::dot2mg::Dot2mg::create(vol,*kind,maybe_wrap),
(DiskImageType::DOT2MG,names::A2_800_KIND) => img::dot2mg::Dot2mg::create(vol,*kind,maybe_wrap),
(DiskImageType::DOT2MG,names::A2_HD_MAX) => img::dot2mg::Dot2mg::create(vol,*kind,maybe_wrap),
(DiskImageType::NIB,names::A2_DOS32_KIND) => Ok(Box::new(img::nib::Nib::create(vol,*kind))),
(DiskImageType::NIB,names::A2_DOS33_KIND) => Ok(Box::new(img::nib::Nib::create(vol,*kind))),
(DiskImageType::IMD,names::IBM_CPM1_KIND) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::IMD,names::OSBORNE1_SD_KIND) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::IMD,names::OSBORNE1_DD_KIND) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::IMD,names::KAYPROII_KIND) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::IMD,names::KAYPRO4_KIND) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::IMD,names::TRS80_M2_CPM_KIND) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::IMD,names::NABU_CPM_KIND) => Ok(Box::new(img::imd::Imd::create(*kind))),
_ => {
error!("pairing of image type and disk kind is not supported");
Err(Box::new(CommandError::UnsupportedItemType))
}
};
}
fn mkdos3x(vol: Option<&String>,boot: bool,img: Box<dyn DiskImage>) -> Result<Vec<u8>,DYNERR> {
if img.byte_capacity()!=35*13*256 && img.byte_capacity()!=35*16*256 {
error!("disk image capacity {} not consistent with DOS 3.x",img.byte_capacity());
return Err(Box::new(CommandError::OutOfRange));
}
let kind = img.kind(); match (kind,img.what_am_i()) {
(_,DiskImageType::PO) => {
error!("attempt to create ProDOS ordered DOS disk");
return Err(Box::new(CommandError::UnsupportedFormat));
},
(DiskKind::LogicalSectors(img::names::A2_DOS32),DiskImageType::DO) |
(DiskKind::D525(img::names::A2_DOS32),DiskImageType::DO) => {
error!("DOS 3.2 cannot use DO image type, use D13");
return Err(Box::new(CommandError::UnsupportedFormat))
},
_ => {}
}
if vol==None {
error!("DOS 3.x requires volume number");
return Err(Box::new(CommandError::InvalidCommand));
}
match u8::from_str_radix(vol.unwrap(), 10) {
Ok(v) if v>=1 || v<=254 => {
if boot && v!=254 {
error!("we can only add the boot tracks if volume number is 254");
return Err(Box::new(CommandError::UnsupportedItemType));
}
let mut disk = Box::new(dos3x::Disk::from_img(img));
match kind {
DiskKind::LogicalSectors(img::names::A2_DOS32) => disk.init32(v,boot)?,
DiskKind::D525(img::names::A2_DOS32) => disk.init32(v,boot)?,
DiskKind::LogicalSectors(img::names::A2_DOS33) => disk.init33(v,boot)?,
DiskKind::D525(img::names::A2_DOS33) => disk.init33(v,boot)?,
_ => {
error!("disk incompatible with DOS 3.x");
return Err(Box::new(CommandError::UnsupportedFormat));
}
}
return Ok(disk.get_img().to_bytes());
},
_ => {
error!("volume must be from 1 to 254");
return Err(Box::new(CommandError::OutOfRange));
}
}
}
fn mkprodos(vol: Option<&String>,boot: bool,img: Box<dyn DiskImage>) -> Result<Vec<u8>,DYNERR> {
if boot {
error!("{}",BOOT_MESS);
return Err(Box::new(CommandError::UnsupportedItemType));
}
let floppy = match img.kind() {
DiskKind::D35(_) => true,
DiskKind::D525(_) => true,
DiskKind::D8(_) => true,
_ => false
};
if let Some(vol_name) = vol {
let mut disk = Box::new(prodos::Disk::from_img(img));
disk.format(vol_name,floppy,None)?;
return Ok(disk.get_img().to_bytes());
} else {
error!("prodos fs requires volume name");
return Err(Box::new(CommandError::InvalidCommand));
}
}
fn mkpascal(vol: Option<&String>,boot: bool,img: Box<dyn DiskImage>) -> Result<Vec<u8>,DYNERR> {
if boot {
error!("{}",BOOT_MESS);
return Err(Box::new(CommandError::UnsupportedItemType));
}
if let Some(vol_name) = vol {
let mut disk = Box::new(pascal::Disk::from_img(img));
match disk.format(vol_name,0xee,None) {
Ok(()) => Ok(disk.get_img().to_bytes()),
Err(e) => return Err(e)
}
} else {
error!("pascal fs requires volume name");
return Err(Box::new(CommandError::InvalidCommand));
}
}
fn mkcpm(vol: Option<&String>,boot: bool,kind: &DiskKind,img: Box<dyn DiskImage>) -> Result<Vec<u8>,DYNERR> {
if boot {
error!("{}",BOOT_MESS_CPM);
return Err(Box::new(CommandError::UnsupportedItemType));
}
let mut disk = Box::new(cpm::Disk::from_img(img,dpb::DiskParameterBlock::create(&kind),[2,2,3]));
let vol_name = match vol {
Some(nm) => nm,
None => "A"
};
match disk.format(vol_name,None) {
Ok(()) => Ok(disk.get_img().to_bytes()),
Err(e) => return Err(e)
}
}
pub fn mkdsk(cmd: &clap::ArgMatches) -> STDRESULT {
let dest_path= cmd.get_one::<String>("dimg").expect(RCH);
let which_fs = cmd.get_one::<String>("os").expect(RCH);
if !["cpm2","dos32","dos33","prodos","pascal"].contains(&which_fs.as_str()) {
return Err(Box::new(CommandError::UnknownItemType));
}
let dest_path_abstract = std::path::Path::new(dest_path);
if let Some(parent) = std::path::Path::parent(dest_path_abstract) {
if parent.to_string_lossy().len()>0 {
match std::path::Path::try_exists(parent) {
Ok(true) => {},
Ok(false) => {
error!("destination directory does not exist ({})",parent.to_string_lossy());
return Err(Box::new(CommandError::InvalidCommand));
},
Err(e) => {
error!("problem with this destination path");
return Err(Box::new(e))
}
}
}
}
match std::path::Path::try_exists(dest_path_abstract) {
Ok(true) => {
error!("cannot overwrite existing disk image");
return Err(Box::new(CommandError::InvalidCommand));
},
Ok(false) => info!("destination path OK, preparing to write"),
Err(e) => {
error!("problem with this destination path");
return Err(Box::new(e))
}
}
let maybe_vol = cmd.get_one::<String>("volume");
let mut kind = DiskKind::from_str(cmd.get_one::<String>("kind").expect(RCH)).unwrap();
let img_typ = DiskImageType::from_str(cmd.get_one::<String>("type").expect(RCH)).unwrap();
let maybe_wrap = cmd.get_one::<String>("wrap");
if kind==names::A2_DOS33_KIND && which_fs=="dos32" {
kind = names::A2_DOS32_KIND;
}
let boot = cmd.get_flag("bootable");
if boot {
info!("bootable requested");
}
match mkimage(&img_typ,&kind,maybe_vol,maybe_wrap) {
Ok(img) => {
if let Some(fext) = dest_path.split(".").last() {
if !img.file_extensions().contains(&fext.to_string().to_lowercase()) {
error!("Extension was {}, should be {:?}",fext,img.file_extensions());
return Err(Box::new(CommandError::InvalidCommand));
}
} else {
error!("Extension missing, should be {:?}",img.file_extensions());
return Err(Box::new(CommandError::InvalidCommand));
}
let result = match which_fs.as_str() {
"cpm2" => mkcpm(maybe_vol,boot,&kind,img),
"dos32" => mkdos3x(maybe_vol,boot,img),
"dos33" => mkdos3x(maybe_vol,boot,img),
"prodos" => mkprodos(maybe_vol,boot,img),
"pascal" => mkpascal(maybe_vol,boot,img),
_ => panic!("unreachable")
};
match result {
Ok(buf) => {
eprintln!("writing {} bytes",buf.len());
std::fs::write(&dest_path,&buf).expect("could not write data to disk");
Ok(())
},
Err(e) => Err(e)
}
},
Err(e) => Err(e)
}
}