use http_body_util::BodyExt;
use hyper::{rt, Uri};
use smol::{io::AsyncRead, io::AsyncWrite, net};
use std::{io, pin::Pin, task};
use super::frontend::Frontend;

/// While the hyper library is async run-time agnostic, there are currently no bindings
/// to async-std. Implement them here. Note that hyper still drags tokio into dependencies,
/// even though it is not used :-/
struct Stream(net::TcpStream);

impl rt::Read for Stream {
    fn poll_read(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>,
            mut buf: rt::ReadBufCursor<'_>) -> task::Poll<Result<(), io::Error>> {
        // It's currently impossible to do zero-copy read from the contained stream to
        // ReadBufCursor, even in unsafe mode. We can get &mut [MaybeUninit<u8>] from
        // ReadBufCursor but we can't pass it to io::Read::poll_read, since it expects
        // &mut [u8]. There's currently no way to go from the former to the latter without
        // triggering undefined behavior. Even initializing it is not possible;
        // MaybeUninit::fill is night-only. So we have to allocate *and* zero-fill a
        // temporary buffer, only to copy it to the ReadBufCursor, as stupid as it is.
        let mut dest = vec![0u8; buf.remaining()];
        match Pin::new(&mut self.0).poll_read(cx, dest.as_mut_slice()) {
            task::Poll::Ready(ready) => {
                match ready {
                    Ok(size) => {
                        buf.put_slice(&dest[..size]);
                        task::Poll::Ready(Ok(()))
                    }
                    Err(err) => task::Poll::Ready(Err(err))
                }
            }
            task::Poll::Pending => task::Poll::Pending
        }
    }
}

impl rt::Write for Stream {
    fn poll_write(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8])
            -> task::Poll<Result<usize, io::Error>> {
        Pin::new(&mut self.0).poll_write(cx, buf)
    }

    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>)
            -> task::Poll<Result<(), io::Error>> {
        Pin::new(&mut self.0).poll_flush(cx)
    }

    fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>)
            -> task::Poll<Result<(), io::Error>> {
        Pin::new(&mut self.0).poll_close(cx)
    }
}

pub(crate) async fn post(url: &str, data: String, frontend: &impl Frontend)
    -> Result<Vec<u8>, String>
{
    let url = url.parse::<Uri>().map_err(|e| e.to_string())?;
    let address = (
        url.host().ok_or_else(|| "No host specified".to_owned())?,
        url.port_u16().unwrap_or(80)
    );
    let path = url.path_and_query().map_or_else(|| "/", |pq| pq.as_str());
    let stream = net::TcpStream::connect(address).await.map_err(|e| e.to_string())?;
    let (mut sender, conn) = hyper::client::conn::http1::handshake(Stream(stream)).await
        .map_err(|e| e.to_string())?;
    frontend.spawn_future(async move {
        let _ = conn.await;
    });
    let req = hyper::Request::post(path)
        .header(hyper::header::CONTENT_TYPE, "application/json")
        .body(data)
        .map_err(|e| e.to_string())?;
    let mut res = sender.send_request(req).await.map_err(|e| e.to_string())?;
    let mut buf = vec![];
    while let Some(next) = res.frame().await {
        let frame = next.map_err(|e| e.to_string())?;
        if let Some(chunk) = frame.data_ref() {
            buf.extend_from_slice(chunk);
        }
    }
    Ok(buf)
}
