use maraiah::{durandal::{err::*, file::*, image::*, sound::*},
              marathon::{machdr, map, ppm, shp, snd, tga, wav}};
use std::{fs, io, slice::from_ref};

fn make_tga(_opt: &Options, fname: &str, im: &impl Image) -> ResultS<()>
{
   let mut out = io::BufWriter::new(fs::File::create(fname)?);

   tga::write_tga(&mut out, im)
}

fn make_ppm(_opt: &Options, fname: &str, im: &impl Image) -> ResultS<()>
{
   let mut out = io::BufWriter::new(fs::File::create(fname)?);

   ppm::write_ppm(&mut out, im)
}

fn make_yaml<T>(opt: &Options, data: &T) -> ResultS<()>
   where T: serde::Serialize + std::fmt::Debug
{
   if opt.out_debug {
      println!("{:#?}", data);
   } else {
      serde_yaml::to_writer(io::stdout(), &data)?;
      println!();
   }

   Ok(())
}

fn make_wav(fname: &str, snd: &impl Sound) -> ResultS<()>
{
   let mut out = io::BufWriter::new(fs::File::create(fname)?);
   wav::write_wav(&mut out, snd)
}

fn process_wad(opt: &Options, b: &[u8]) -> ResultS<()>
{
   let wad = map::read(b)?;

   if opt.wad_wrt_all {
      make_yaml(opt, &wad)?;
   }

   Ok(())
}

fn dump_bitmaps(opt: &Options, c: &shp::coll::Collection, i: usize)
   -> ResultS<()>
{
   if !opt.shp_bmp {
      return Ok(());
   }

   for (j, bmp) in c.bmps.iter().enumerate() {
      if opt.shp_bmp_all {
         for (k, tab) in c.tabs.iter().enumerate() {
            let fname = format!("{}/shape{}_{}_{}.tga", opt.out_dir, i, j, k);
            make_tga(opt, &fname, &shp::bmap::ImageShp::new(bmp, &tab))?;
         }
      } else {
         let fname = format!("{}/shape{}_{}.tga", opt.out_dir, i, j);
         make_tga(opt, &fname, &shp::bmap::ImageShp::new(bmp, &c.tabs[0]))?;
      }
   }

   Ok(())
}

fn write_shp_objs(opt: &Options, cl: &shp::coll::Collection) -> ResultS<()>
{
   if opt.shp_tab {make_yaml(opt, &cl.tabs)?;}
   if opt.shp_frm {make_yaml(opt, &cl.frms)?;}
   if opt.shp_seq {make_yaml(opt, &cl.seqs)?;}

   Ok(())
}

fn process_shp(opt: &Options, b: &[u8]) -> ResultS<()>
{
   for (i, cl) in shp::read(b)?.iter().enumerate() {
      if let Some(cl) = &cl.0 {
         dump_bitmaps(opt, cl, i)?;
         write_shp_objs(opt, cl)?;
      }

      if let Some(cl) = &cl.1 {
         dump_bitmaps(opt, cl, i + 100)?;
         write_shp_objs(opt, cl)?;
      }
   }

   Ok(())
}

fn dump_sounds(opt: &Options, st: &snd::SoundTable, c: usize) -> ResultS<()>
{
   if !opt.snd_dump {
      return Ok(());
   }

   for (k, sd) in st {
      for (i, snd) in sd.sounds.iter().enumerate() {
         let fname = format!("{}/snd{}_{}_{}.wav", opt.out_dir, c, k, i);
         make_wav(&fname, snd)?;
      }
   }

   Ok(())
}

fn process_snd(opt: &Options, b: &[u8]) -> ResultS<()>
{
   for (c, st) in snd::read(b)?.iter().enumerate() {
      dump_sounds(opt, st, c)?;
   }

   Ok(())
}

fn process_ppm(opt: &Options, b: &[u8]) -> ResultS<()>
{
   let im = ppm::read_ppm(b)?;

   let fname = format!("{}/out.tga", opt.out_dir);
   make_tga(opt, &fname, &im)?;

   let fname = format!("{}/out.ppm", opt.out_dir);
   make_ppm(opt, &fname, &im)
}

fn main() -> ResultS<()>
{
   use argparse::*;

   let mut opt = Options::default();
   {
      let mut ap = ArgumentParser::new();

      macro_rules! arg {
         ($name:expr, $ref:expr, $type:expr, $desc:expr) => {
            ap.refer(&mut $ref).add_option(from_ref(&$name), $type, $desc);
         };
      }

      ap.set_description(env!("CARGO_PKG_DESCRIPTION"));

      ap.add_option(&["-v", "--version"],
                    Print(concat!(env!("CARGO_PKG_NAME"),
                                  " ",
                                  env!("CARGO_PKG_VERSION")).to_string()),
                    "Show the version");

      ap.refer(&mut opt.inputs)
        .add_argument("inputs", Collect, "Input files");

      arg!("--shp-write-tab",
           opt.shp_tab,
           StoreTrue,
           "shp: Dump all CLUTs as YAML to standard output");

      arg!("--shp-dump-bitmaps",
           opt.shp_bmp,
           StoreTrue,
           "shp: Dump bitmaps into a folder");

      arg!("--shp-dump-more-bitmaps",
           opt.shp_bmp_all,
           StoreTrue,
           "shp: Dump all color variations of each bitmap");

      arg!("--shp-write-frm",
           opt.shp_frm,
           StoreTrue,
           "shp: Dump all frames as YAML to standard output");

      arg!("--shp-write-seq",
           opt.shp_seq,
           StoreTrue,
           "shp: Dump all sequences as YAML to standard output");

      arg!("--snd-dump",
           opt.snd_dump,
           StoreTrue,
           "snd: Dump all sounds to WAVE files");

      arg!("--wad-write-all",
           opt.wad_wrt_all,
           StoreTrue,
           "wad: Dump all known chunks");

      arg!("--out-dir",
           opt.out_dir,
           Store,
           "Sets output directory for dump options");

      arg!("--out-debug",
           opt.out_debug,
           StoreTrue,
           "Writes debugging output rather than YAML");

      ap.parse_args_or_exit();
   }

   if opt.shp_bmp_all {
      opt.shp_bmp = true;
   }

   if opt.out_dir.is_empty() {
      opt.out_dir = ".".to_string();
   }

   validate_folder_path(&opt.out_dir)?;

   for arg in &opt.inputs {
      let (typ, fna) = if let Some(st) = arg.find(':') {
         arg.split_at(st + 1)
      } else {
         ("wad:", arg.as_str())
      };

      let fp = fs::File::open(fna)?;
      let mm = unsafe {memmap::Mmap::map(&fp)?};
      let b  = &mm[machdr::try_mac_header(&mm)..];

      match typ {
         "wad:" => process_wad(&opt, b),
         "shp:" => process_shp(&opt, b),
         "snd:" => process_snd(&opt, b),
         "ppm:" => process_ppm(&opt, b),
         _      => Err(err_msg("invalid file type specified on commandline")),
      }?;
   }

   Ok(())
}

#[derive(Default)]
struct Options
{
   inputs:      Vec<String>,
   out_dir:     String,
   out_debug:   bool,
   shp_tab:     bool,
   shp_bmp:     bool,
   shp_bmp_all: bool,
   shp_frm:     bool,
   shp_seq:     bool,
   snd_dump:    bool,
   wad_wrt_all: bool,
}

// EOF
