//! Utilities for using QEMU.

use std::{
    path::{Path, PathBuf},
    process::Command,
};

use clingwrap::runner::{CommandError, CommandRunner};

/// Convert a disk image to the QCOW2 format.
pub fn convert_image(original: &Path, new: &Path) -> Result<(), QemuUtilError> {
    let mut cmd = Command::new("qemu-img");
    cmd.arg("convert")
        .arg(original)
        .args(["-O", "qcow2"])
        .arg(new);

    let mut runner = CommandRunner::new(cmd);
    runner.capture_stdout();
    runner.capture_stderr();
    runner
        .execute()
        .map_err(|err| QemuUtilError::convert(original, new, err))?;

    Ok(())
}

/// Create a copy-on-write QCOW2 disk image using a backing image,
/// which is also a QEMU2 disk image.
pub fn create_cow_image(backing_file: &Path, new_file: &Path) -> Result<(), QemuUtilError> {
    let mut cmd = Command::new("qemu-img");
    cmd.arg("create")
        .arg("-b")
        .arg(backing_file)
        .args(["-F", "qcow2"])
        .args(["-f", "qcow2"])
        .arg(new_file);

    let mut runner = CommandRunner::new(cmd);
    runner.capture_stdout();
    runner.capture_stderr();
    let result = runner.execute();
    match &result {
        Ok(_) => (),
        Err(CommandError::CommandFailed { output, .. }) => {
            eprintln!("{}", String::from_utf8_lossy(&output.stderr));
            result.map_err(|err| QemuUtilError::cow(new_file, backing_file, err))?;
        }
        _ => panic!("{result:#?}"),
    }
    Ok(())
}

/// Possible errors from `qemu_utils` module.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
pub enum QemuUtilError {
    /// Can't convert image with `qemu-img`.
    #[error("failed to run qemu-img convert {0} to {1}")]
    Convert(PathBuf, PathBuf, #[source] Box<CommandError>),

    /// Can't create a copy-on-write image file with `qemu-img`.
    #[error("failed to run qemu-img to create a copy-on-write file {0}, backing on {1}")]
    COW(PathBuf, PathBuf, #[source] Box<CommandError>),
}

impl QemuUtilError {
    fn convert(original: &Path, new: &Path, err: CommandError) -> Self {
        Self::Convert(original.to_path_buf(), new.to_path_buf(), Box::new(err))
    }

    fn cow(original: &Path, new: &Path, err: CommandError) -> Self {
        Self::COW(original.to_path_buf(), new.to_path_buf(), Box::new(err))
    }
}
