#[derive(Debug)]
pub enum Either<L, R> {
    Left(L),
    Right(R),
}

impl<L, R> Either<L, R> {
    pub fn is_left(&self) -> bool {
        matches!(self, Either::Left(_))
    }

    pub fn is_right(&self) -> bool {
        matches!(self, Either::Right(_))
    }
}

pub trait EitherExt<L, R>: Sized {
    fn either(self) -> Either<L, R>;
    fn or(self) -> Either<R, L>;
}

impl<L, R> EitherExt<L, R> for L {
    fn either(self) -> Either<L, R> {
        Either::Left(self)
    }
    fn or(self) -> Either<R, L> {
        Either::Right(self)
    }
}

#[cfg(feature = "futures")]
impl<L, R> futures::io::AsyncWrite for Either<L, R>
where
    L: futures::io::AsyncWrite + Unpin,
    R: futures::io::AsyncWrite + Unpin,
{
    fn poll_write(
        mut self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
        buf: &[u8],
    ) -> std::task::Poll<std::io::Result<usize>> {
        match &mut *self {
            Either::Left(l) => std::pin::Pin::new(l).poll_write(cx, buf),
            Either::Right(r) => std::pin::Pin::new(r).poll_write(cx, buf),
        }
    }

    fn poll_flush(
        mut self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<std::io::Result<()>> {
        match &mut *self {
            Either::Left(l) => std::pin::Pin::new(l).poll_flush(cx),
            Either::Right(r) => std::pin::Pin::new(r).poll_flush(cx),
        }
    }

    fn poll_close(
        mut self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<std::io::Result<()>> {
        match &mut *self {
            Either::Left(l) => std::pin::Pin::new(l).poll_close(cx),
            Either::Right(r) => std::pin::Pin::new(r).poll_close(cx),
        }
    }
}

#[cfg(feature = "tokio")]
impl<L, R> tokio::io::AsyncWrite for Either<L, R>
where
    L: tokio::io::AsyncWrite + Unpin,
    R: tokio::io::AsyncWrite + Unpin,
{
    fn poll_write(
        mut self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
        buf: &[u8],
    ) -> std::task::Poll<std::io::Result<usize>> {
        match &mut *self {
            Either::Left(l) => std::pin::Pin::new(l).poll_write(cx, buf),
            Either::Right(r) => std::pin::Pin::new(r).poll_write(cx, buf),
        }
    }

    fn poll_flush(
        mut self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<std::io::Result<()>> {
        match &mut *self {
            Either::Left(l) => std::pin::Pin::new(l).poll_flush(cx),
            Either::Right(r) => std::pin::Pin::new(r).poll_flush(cx),
        }
    }

    fn poll_shutdown(
        mut self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<std::io::Result<()>> {
        match &mut *self {
            Either::Left(l) => std::pin::Pin::new(l).poll_shutdown(cx),
            Either::Right(r) => std::pin::Pin::new(r).poll_shutdown(cx),
        }
    }
}

#[test]
fn test_either_is_left_right() {
    let left: Either<i32, &str> = Either::Left(42);
    let right: Either<i32, &str> = Either::Right("hello");

    assert!(left.is_left());
    assert!(!left.is_right());

    assert!(right.is_right());
    assert!(!right.is_left());
}
