//! Utilities.

use home_dir::HomeDirExt;

use lazy_static::lazy_static;
use log::debug;
use regex::Regex;
use std::net::TcpStream;
use std::path::{Path, PathBuf};

const SSH_PORT: i32 = 22;

// Wait for a virtual machine to have opened its SSH port.
pub fn wait_for_ssh(name: &str) {
    debug!("waiting for {} to respond to SSH", name);
    let addr = format!("{name}:{SSH_PORT}");
    loop {
        if TcpStream::connect(&addr).is_ok() {
            return;
        }
    }
}

/// Expand a ~/ at the beginning of a Path to refer to the home directory.
pub fn expand_tilde(path: &Path) -> Result<PathBuf, home_dir::Error> {
    path.expand_home()
}

pub fn expand_optional_pathbuf(maybe_path: &mut Option<PathBuf>) -> Result<(), home_dir::Error> {
    if let Some(path) = maybe_path {
        *maybe_path = Some(expand_tilde(path)?);
    }
    Ok(())
}

pub fn expand_optional_pathbufs(
    maybe_paths: &mut Option<Vec<PathBuf>>,
) -> Result<(), home_dir::Error> {
    if let Some(paths) = maybe_paths {
        let mut expanded = vec![];
        for path in paths {
            expanded.push(expand_tilde(path)?);
        }
        *maybe_paths = Some(expanded);
    }
    Ok(())
}

pub fn check_network_names(networks: &[String]) -> Result<(), NetworkNameError> {
    lazy_static! {
        static ref RE: Regex = Regex::new("^(network|bridge)=[a-z0-9-]*$").unwrap();
    }
    for name in networks.iter() {
        if !RE.is_match(name) {
            return Err(NetworkNameError::BadNetworkName(name.to_string()));
        }
    }
    Ok(())
}

#[derive(Debug, thiserror::Error)]
pub enum NetworkNameError {
    /// Bad network name.
    #[error("Network name should be network=foo or bridge=bar, not: {0}")]
    BadNetworkName(String),
}

#[cfg(test)]
mod test {
    use super::check_network_names;

    fn networks(names: &[&str]) -> Vec<String> {
        names.iter().map(|s| s.to_string()).collect()
    }

    #[test]
    fn accepts_network_name() {
        let names = networks(&["network=foo"]);
        assert!(check_network_names(&names).is_ok());
    }

    #[test]
    fn accepts_bridge_name() {
        let names = networks(&["bridge=foo"]);
        assert!(check_network_names(&names).is_ok());
    }

    #[test]
    fn rejects_plain_name() {
        let names = networks(&["foo"]);
        assert!(check_network_names(&names).is_err());
    }
}
