use std::fmt;

pub enum Either<A, B> {
    Left(A),
    Right(B),
}

pub struct TryFilter<I, E1, E2, T, F>
where
    I: Iterator<Item = Result<T, E1>>,
    F: FnMut(&T) -> Result<bool, E2>,
    E2: From<E1>,
{
    iterator: I,
    filter: F,
}

pub trait IteratorExt<I, E1, T> {
    fn try_filter<E2, F>(self, filter: F) -> impl Iterator<Item = Result<T, E2>>
    where
        F: FnMut(&T) -> Result<bool, E2>,
        E2: From<E1>;
}

#[macro_export]
macro_rules! primitive_enum {
    (
        $(#[$meta:meta])*
        $vis:vis enum $ident:ident: $repr:ty [$conversion_err:ident] {
            $($variant:ident = $discriminant:literal),+ $(,)?
        }
    ) => {
        $(#[$meta])*
        #[repr($repr)]
        $vis enum $ident {
            $($variant = $discriminant),+
        }

        impl From<$ident> for $repr {
            fn from(value: $ident) -> $repr {
                value as $repr
            }
        }

        impl TryFrom<$repr> for $ident {
            type Error = $conversion_err;

            fn try_from(value: $repr) -> Result<$ident, $conversion_err> {
                match value {
                    $($discriminant => Ok($ident::$variant),)+
                    _ => Err($conversion_err(value))
                }
            }
        }
    };
}

impl<A: fmt::Debug, B: fmt::Debug> fmt::Debug for Either<A, B> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Left(a) => fmt::Debug::fmt(a, f),
            Self::Right(b) => fmt::Debug::fmt(b, f),
        }
    }
}

impl<I, E1, T> IteratorExt<I, E1, T> for I
where
    I: Iterator<Item = Result<T, E1>>,
{
    fn try_filter<E2, F>(self, filter: F) -> impl Iterator<Item = Result<T, E2>>
    where
        F: FnMut(&T) -> Result<bool, E2>,
        E2: From<E1>,
    {
        TryFilter {
            iterator: self,
            filter,
        }
    }
}

impl<I, E1, E2, T, F> Iterator for TryFilter<I, E1, E2, T, F>
where
    I: Iterator<Item = Result<T, E1>>,
    F: FnMut(&T) -> Result<bool, E2>,
    E2: From<E1>,
{
    type Item = Result<T, E2>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            let value = self.iterator.next()?;
            let value = match value {
                Ok(value) => value,
                Err(error) => return Some(Err(error.into())),
            };
            let cond = match (self.filter)(&value) {
                Ok(value) => value,
                Err(error) => return Some(Err(error)),
            };

            match cond {
                true => return Some(Ok(value)),
                false => continue,
            }
        }
    }
}
