use std::io::{self, Result};
use std::path::{Path, PathBuf};
use rand::{Rng, distributions::Alphanumeric};
use tokio::fs;

/// Create a temporary file named `temp.[a-zA-Z0-9]{length}` in
/// the given location and immediately open it. Returns the
/// filename and the corresponding file handle. It is the caller's
/// responsibility to clean up the temporary file when it is no
/// longer needed.
///
/// Uses [`OpenOptions::create_new`][fs::OpenOptions::create_new]
/// to detect filename collisions, in which case it will
/// automatically retry until the operation succeeds.
///
/// # Errors
///
/// Returns the underlying [`io::Error`] if the operation fails
/// due to reasons other than filename collision.
pub async fn mktemp<T, B>(dir: T, basename: B, length: usize) -> Result<(PathBuf, fs::File)>
where
    T: AsRef<Path>,
    B: Into<Option<&'static str>>
{
    let dir = dir.as_ref();
    let basename = basename.into().unwrap_or("");
    fs::create_dir_all(dir).await?;

    loop {
        let filename = dir.join(format!(
            "{}{}{}",
            basename,
            if basename.is_empty() { "" } else { "." },
            {
                let string = rand::thread_rng()
                .sample_iter(&Alphanumeric)
                .take(length)
                .collect::<Vec<u8>>();
                String::from_utf8(string).unwrap()
            }
        ));

        match fs::OpenOptions::new()
            .create_new(true)
            .write(true)
            .open(&filename)
            .await
        {
            Ok(file) => return Ok((filename, file)),
            Err(err) => match err.kind() {
                io::ErrorKind::AlreadyExists => continue,
                _ => return Err(err)
            }
        }
    }
}