use either::Either;

use endpoint::{ApplyContext, ApplyResult, Endpoint};
use error::Error;

#[allow(missing_docs)]
#[derive(Debug, Copy, Clone)]
pub struct OrStrict<E1, E2> {
    pub(super) e1: E1,
    pub(super) e2: E2,
}

impl<'a, E1, E2> Endpoint<'a> for OrStrict<E1, E2>
where
    E1: Endpoint<'a>,
    E2: Endpoint<'a, Output = E1::Output>,
{
    type Output = E1::Output;
    type Future = OrStrictFuture<E1::Future, E2::Future>;

    fn apply(&'a self, ecx: &mut ApplyContext<'_>) -> ApplyResult<Self::Future> {
        let orig_cursor = ecx.cursor().clone();
        match self.e1.apply(ecx) {
            Ok(future1) => {
                *ecx.cursor() = orig_cursor;
                Ok(OrStrictFuture::left(future1))
            }
            Err(err1) => match self.e2.apply(ecx) {
                Ok(future) => Ok(OrStrictFuture::right(future)),
                Err(err2) => Err(err1.merge(err2)),
            },
        }
    }
}

#[allow(missing_docs)]
#[derive(Debug)]
pub struct OrStrictFuture<L, R> {
    inner: Either<L, R>,
}

impl<L, R> OrStrictFuture<L, R> {
    fn left(l: L) -> Self {
        OrStrictFuture {
            inner: Either::Left(l),
        }
    }

    fn right(r: R) -> Self {
        OrStrictFuture {
            inner: Either::Right(r),
        }
    }
}

impl<L, R> ::futures::Future for OrStrictFuture<L, R>
where
    L: ::futures::Future<Error = Error>,
    R: ::futures::Future<Item = L::Item, Error = Error>,
{
    type Item = L::Item;
    type Error = Error;

    #[inline(always)]
    fn poll(&mut self) -> ::futures::Poll<Self::Item, Self::Error> {
        match self.inner {
            Either::Left(ref mut t) => t.poll(),
            Either::Right(ref mut t) => t.poll(),
        }
    }
}
