use std::fmt;
use std::future::Future;
use std::time::Duration;

pub use web_time::Instant;

pub async fn sleep(duration: Duration) {
    futures_timer::Delay::new(duration).await;
}

#[derive(Debug)]
pub struct Elapsed;

impl fmt::Display for Elapsed {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("operation timed out")
    }
}

impl std::error::Error for Elapsed {}

pub async fn timeout<F: Future>(duration: Duration, future: F) -> Result<F::Output, Elapsed> {
    use futures::future::{select, Either};
    let sleep = sleep(duration);
    futures::pin_mut!(future);
    futures::pin_mut!(sleep);
    match select(future, sleep).await {
        Either::Left((output, _)) => Ok(output),
        Either::Right(_) => Err(Elapsed),
    }
}

#[derive(Debug)]
pub struct TaskHandle<T> {
    receiver: futures::channel::oneshot::Receiver<T>,
}

pub enum TaskPoll<T> {
    Pending,
    Finished(T),
    Gone,
}

impl<T> TaskHandle<T> {
    pub fn poll_result(&mut self) -> TaskPoll<T> {
        match self.receiver.try_recv() {
            Ok(Some(value)) => TaskPoll::Finished(value),
            Ok(None) => TaskPoll::Pending,
            Err(_) => TaskPoll::Gone,
        }
    }
}

#[cfg(not(target_arch = "wasm32"))]
pub fn spawn<T, F>(future: F) -> TaskHandle<T>
where
    T: Send + 'static,
    F: Future<Output = T> + Send + 'static,
{
    let (sender, receiver) = futures::channel::oneshot::channel();
    tokio::spawn(async move {
        let _ = sender.send(future.await);
    });
    TaskHandle { receiver }
}

#[cfg(target_arch = "wasm32")]
pub fn spawn<T, F>(future: F) -> TaskHandle<T>
where
    T: 'static,
    F: Future<Output = T> + 'static,
{
    let (sender, receiver) = futures::channel::oneshot::channel();
    wasm_bindgen_futures::spawn_local(async move {
        let _ = sender.send(future.await);
    });
    TaskHandle { receiver }
}
