use anyhow::Result;
use futures::stream::AbortHandle;
use futures::stream::Abortable;

use std::future::Future;
use tokio::task::JoinHandle;
use tokio::time::Duration;

pub fn spawn<F>(name: &'static str, f: F) -> std::thread::JoinHandle<()>
where
    F: 'static + Send + FnOnce() -> Result<()>,
{
    std::thread::Builder::new()
        .name(name.to_owned())
        .spawn(move || {
            if let Err(e) = f() {
                warn!("{} thread failed: {}", name, e);
            }
        })
        .expect("failed to spawn a thread")
}

pub fn spawn_task<T, F>(name: &'static str, future: F) -> JoinHandle<()>
where
    T: Send + 'static,
    F: Future<Output = Result<T>> + Send + 'static,
{
    tokio::task::spawn(async move {
        if let Err(err) = future.await {
            warn!("task '{}' failed: {}", name, err)
        }
    })
}

pub async fn run_with_timeout<F, T>(duration: Duration, future: F) -> Result<T>
where
    F: std::future::Future<Output = T>,
{
    let (abort_handle, abort_registration) = AbortHandle::new_pair();
    let abortable_future = Abortable::new(future, abort_registration);
    tokio::pin!(abortable_future);

    tokio::select! {
        result = &mut abortable_future => {
            match result {
                Ok(val) => Ok(val),
                Err(e) => bail!(e.to_string())
            }
        }
        _ = tokio::time::sleep(duration) => {
            abort_handle.abort(); // Soft-cancel the future
            bail!("Timeout occurred after {} seconds", duration.as_secs())
        }
    }
}
