use std::env;
use std::error::Error;
use std::fs;
use std::fs::File;
use std::process;

use mkfs_xfs::image::{
    diff_manifest, parse_manifest, summarize_reader_extents, ManifestDiff, MetadataExtentSummary,
};

fn main() -> Result<(), Box<dyn Error>> {
    let args: Vec<String> = env::args().collect();
    if args.len() != 3 {
        eprintln!("usage: {} <manifest.csv> <image>", args[0]);
        process::exit(2);
    }

    let manifest = fs::read_to_string(&args[1])?;
    let expected = parse_manifest(&manifest)?;
    let mut image = File::open(&args[2])?;
    let actual = summarize_reader_extents(&mut image, &expected)?;
    let diffs = diff_manifest(&expected, &actual);

    if diffs.is_empty() {
        println!("manifest matches");
        return Ok(());
    }

    for diff in diffs {
        print_diff(&diff);
    }
    process::exit(1);
}

fn print_diff(diff: &ManifestDiff) {
    match diff {
        ManifestDiff::Missing { expected } => {
            println!("missing {}", summary(expected));
        }
        ManifestDiff::Extra { actual } => {
            println!("extra {}", summary(actual));
        }
        ManifestDiff::Mismatch { expected, actual } => {
            println!("mismatch offset {}", expected.offset);
            println!("  expected {}", summary(expected));
            println!("  actual   {}", summary(actual));
        }
    }
}

fn summary(summary: &MetadataExtentSummary) -> String {
    format!(
        "offset={} len={} crc32c={:08x} magic={}",
        summary.offset,
        summary.len,
        summary.crc32c,
        magic(summary.magic)
    )
}

fn magic(bytes: [u8; 4]) -> String {
    let mut out = String::new();
    for byte in bytes {
        if byte.is_ascii_graphic() || byte == b' ' {
            out.push(char::from(byte));
        } else {
            out.push_str(&format!("\\x{byte:02x}"));
        }
    }
    out
}
