//! Adapter from smol's `futures-lite` AsyncRead/AsyncWrite to hyper 1's
//! `hyper::rt::Read` / `hyper::rt::Write`.
//!
//! We avoid pulling tokio in (it would conflict with our smol commitment),
//! and `hyper-util` only ships a tokio bridge, so the bridge lives here.
//! The unsafe in `poll_read` is the standard layout transmute used by
//! every smol↔hyper adapter — `[u8]` and `[MaybeUninit<u8>]` have the same
//! layout, and `futures` `AsyncRead` writes its bytes into the destination
//! before returning, so no read of uninit memory occurs.

use std::io;
use std::mem::MaybeUninit;
use std::pin::Pin;
use std::task::{ Context, Poll };

use hyper::rt::{ Read as HyperRead, ReadBufCursor, Write as HyperWrite };
use pin_project_lite::pin_project;
use smol::io::{ AsyncRead, AsyncWrite };

pin_project!
{
	pub struct SmolIo<S>
	{
		#[ pin ]
		inner: S,
	}
}

impl<S> SmolIo<S>
{
	pub fn new( inner: S ) -> Self
	{
		Self { inner }
	}
}

impl<S: AsyncRead> HyperRead for SmolIo<S>
{
	fn poll_read(
		self: Pin<&mut Self>,
		cx: &mut Context<'_>,
		mut buf: ReadBufCursor<'_>,
	) -> Poll<io::Result<()>>
	{
		let this = self.project();

		// SAFETY: see module docs. The slice is layout-equivalent and the
		// futures AsyncRead contract states the implementation writes into
		// the buffer; we never read it before advancing the cursor.
		let dst: &mut [ u8 ] = unsafe
		{
			let unfilled: &mut [ MaybeUninit<u8> ] = buf.as_mut();
			std::mem::transmute::<&mut [ MaybeUninit<u8> ], &mut [ u8 ]>( unfilled )
		};

		match this.inner.poll_read( cx, dst )
		{
			Poll::Ready( Ok( n ) ) =>
			{
				// SAFETY: `n` bytes were just written by `poll_read`.
				unsafe { buf.advance( n ); }
				Poll::Ready( Ok( () ) )
			}
			Poll::Ready( Err( e ) ) => Poll::Ready( Err( e ) ),
			Poll::Pending           => Poll::Pending,
		}
	}
}

impl<S: AsyncWrite> HyperWrite for SmolIo<S>
{
	fn poll_write(
		self: Pin<&mut Self>,
		cx: &mut Context<'_>,
		buf: &[ u8 ],
	) -> Poll<io::Result<usize>>
	{
		self.project().inner.poll_write( cx, buf )
	}

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

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