//! Virtual machine image handling.

use log::debug;
use std::fs::copy;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::result::Result;

/// Errors from this module.
#[derive(Debug, thiserror::Error)]
pub enum ImageError {
    /// The image file for a new VM already exists.
    #[error("image file already exists: {0}")]
    ImageExists(PathBuf),

    /// Error resizing image.
    #[error("qemu-img resize failed: {0}")]
    ResizeError(String),

    /// Base image copy error.
    #[error("could not copy base image {0} to {1}")]
    BaseImageCopy(PathBuf, PathBuf, #[source] std::io::Error),

    /// Could not execute command.
    #[error("couldn't execute {0}: {1}")]
    CommandError(String, #[source] std::io::Error),

    /// Error parsing a string as UTF8.
    #[error(transparent)]
    StringError(#[from] std::string::FromUtf8Error),
}

/// A virtual machine image.
#[derive(Debug, Clone)]
pub struct VirtualMachineImage {
    filename: PathBuf,
}

impl VirtualMachineImage {
    /// Create new image from a base image.
    pub fn new_from_base(base_image: &Path, image: &Path) -> Result<Self, ImageError> {
        debug!(
            "creating new image {} from base image {}",
            image.display(),
            base_image.display()
        );
        debug!("does {} exixt? {}", image.display(), image.exists());
        if image.exists() {
            debug!("image already exists: {}", image.display());
            return Err(ImageError::ImageExists(image.to_path_buf()));
        }
        debug!("copying base image to new image");
        copy(base_image, image).map_err(|err| {
            ImageError::BaseImageCopy(base_image.to_path_buf(), image.to_path_buf(), err)
        })?;
        debug!("all good");
        Ok(Self {
            filename: image.to_path_buf(),
        })
    }

    /// Filename of the image.
    pub fn filename(&self) -> &Path {
        &self.filename
    }

    /// Change size of image.
    pub fn resize(&self, new_size: u64) -> Result<(), ImageError> {
        let r = Command::new("qemu-img")
            .arg("resize")
            .arg(self.filename())
            .arg(format!("{new_size}"))
            .output()
            .map_err(|err| ImageError::CommandError("qemu-img resize".to_string(), err))?;
        if !r.status.success() {
            let stderr = String::from_utf8(r.stderr)?;
            return Err(ImageError::ResizeError(stderr));
        }
        Ok(())
    }
}
