//! Helper functions to make common tasks easier.
//!
//! These are not specific to implementing backup software, and
//! ideally would come from a dependency, but finding and managing a
//! dependency would have been more work than implementing these.

use std::{
    ffi::OsStr,
    io::{Read, Write},
    path::{Path, PathBuf},
};

/// Lossily create a `String` from an `OsStr`.
pub fn lossy_string<S: AsRef<OsStr>>(s: S) -> String {
    String::from_utf8_lossy(s.as_ref().as_encoded_bytes()).to_string()
}

/// Read stdin or named file.
#[mutants::skip]
pub fn read(filename: &Option<PathBuf>) -> Result<Vec<u8>, UtilError> {
    if let Some(filename) = filename {
        read_file(filename)
    } else {
        read_stdin()
    }
}

/// Read the contents of a named file into memory.
///
/// This is [`std::fs::read`] but with a more helpful error message.
#[mutants::skip]
pub fn read_file(filename: &Path) -> Result<Vec<u8>, UtilError> {
    std::fs::read(filename).map_err(|err| UtilError::ReadFile(filename.into(), err))
}

/// Read the contents of stdin into memory.
#[mutants::skip]
pub fn read_stdin() -> Result<Vec<u8>, UtilError> {
    let mut data = vec![];
    std::io::stdin()
        .read_to_end(&mut data)
        .map_err(UtilError::ReadStdin)?;
    Ok(data)
}

/// Write data to named file or stdout.
#[mutants::skip]
pub fn write(filename: &Option<PathBuf>, data: &[u8]) -> Result<(), UtilError> {
    if let Some(filename) = filename {
        write_file(filename, data)
    } else {
        write_stdout(data)
    }
}

/// Write data to a named file.
///
/// This is [`std::fs::write`] but with a more helpful error message.
#[mutants::skip]
pub fn write_file(filename: &Path, data: &[u8]) -> Result<(), UtilError> {
    std::fs::write(filename, data).map_err(|err| UtilError::Write(filename.into(), err))
}

/// Write data to the standard output.
///
/// This is [`std::io::Stdout::write_all`] but with a more helpful
/// error message.
#[mutants::skip]
pub fn write_stdout(data: &[u8]) -> Result<(), UtilError> {
    std::io::stdout().write_all(data).map_err(UtilError::Stdout)
}

/// Errors from utility functions.
#[derive(Debug, thiserror::Error)]
pub enum UtilError {
    /// Can't read a named file.
    #[error("failed to read file {0}")]
    ReadFile(PathBuf, #[source] std::io::Error),

    /// Can't read stdin.
    #[error("failed standard input")]
    ReadStdin(#[source] std::io::Error),

    /// Can't write a named file.
    #[error("failed to write file {0}")]
    Write(PathBuf, #[source] std::io::Error),

    /// Can't write to stdout.
    #[error("failed to write data to standard output")]
    Stdout(#[source] std::io::Error),
}
