//! Generic functional concepts implemented in Rust.
//! Some choices are intentionally less generic due to specifics of the domain.
//! That, for example, includes the almost exclusive focus on [`FnOnce`] category.
//!
//! Sources:
//! * <https://hackage.haskell.org/package/base-4.18.0.0/docs/Data-Functor.html>
//! * <https://hackage.haskell.org/package/base-4.18.0.0/docs/Control-Applicative.html>
//! * <https://hackage.haskell.org/package/base-4.18.0.0/docs/Control-Monad.html>

pub mod classes;
pub mod clone_func;
pub mod copy_func;
pub mod derivations;
#[cfg(test)]
pub mod test_suite;
#[cfg(test)]
pub mod tests;

/// Part of Haskell's `Functor f` responsible to use `f a`.
///
/// <https://hackage.haskell.org/package/base-4.18.0.0/docs/Data-Functor.html>
pub trait WeakFunctor {
    type F<'a, A: 'a>: 'a
    where
        Self: 'a;
}

/// Rust-specific implementation of [`Functor`], respecting `move` semantics.
///
/// Cannot insantiate for e.g. multi-element collections:
/// ```compile_fail
/// use radn_rs::func::*;
///
/// struct VecClass;
///
/// impl WeakFunctor for VecClass {
///     type F<'a, A> = Vec<A>;
/// }
///
/// impl Functor for VecClass {
///     fn fmap<'a, A: 'a, B: 'a>(f: impl 'a + FnOnce(A) -> B, fa: Self::F<'a, A>) -> Self::F<'a, B> {
///         fa.into_iter().map(f).collect()
///     }
/// }
/// ```
/// Why does it fail to compile? `.map` expects `FnMut` (or we can think of it being `Fn`, doesn't matter here). But what we provide it (`f`) is
///
/// For Haskell-style Functors, use [`clone_func::CloneFunctor`] instead.
/// ```
/// use radn_rs::func::clone_func::*;
///
/// struct VecClass;
///
/// impl CloneWeakFunctor for VecClass {
///     type ClF<'a, A: Clone> = Vec<A>;
/// }
///
/// impl CloneFunctor for VecClass {
///     fn clone_fmap<'a, A: 'a + Clone, B: 'a + Clone>(
///         f: impl 'a + Fn(A) -> B,
///         fa: Self::ClF<'a, A>,
///     ) -> Self::ClF<'a, B> {
///         fa.into_iter().map(f).collect()
///     }
/// }
/// ```
///
/// <https://hackage.haskell.org/package/base-4.18.0.0/docs/Data-Functor.html>
pub trait Functor: WeakFunctor {
    /// Equivalent or Haskell's `fmap`.
    /// Due to Rust limitations, it's not a `function->function` conversion.
    /// For that see [`derivations::fmap`].
    fn fmap<'a, A: 'a, B: 'a>(f: impl 'a + FnOnce(A) -> B, fa: Self::F<'a, A>) -> Self::F<'a, B>
    where
        Self: 'a;

    /// Equivalent of Haskell's `$>`/`<$`.
    fn replace<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, b: B) -> Self::F<'a, B>
    where
        Self: 'a,
    {
        Self::fmap(|_| b, fa)
    }

    /// Equivalent of Haskell's `void`.
    fn void<'a, A: 'a>(fa: Self::F<'a, A>) -> Self::F<'a, ()>
    where
        Self: 'a,
    {
        Self::replace(fa, ())
    }
}

/// Part of [`Applicative`] responsible for Haskell's value lifting, `pure`.
pub trait Pure: Functor {
    /// Equivalent of Haskell's `pure`/`return`.
    fn pure<'a, A: 'a>(a: A) -> Self::F<'a, A>
    where
        Self: 'a;
}

/// Part of [`Applicative`] responsible for Haskell's sequential application `<*>`.
pub trait ApplicativeSeq: Functor {
    /// Equivalent of Haskell's `<*>`.
    fn seq<'a, A: 'a, B: 'a>(
        ff: Self::F<'a, impl 'a + FnOnce(A) -> B>,
        fa: Self::F<'a, A>,
    ) -> Self::F<'a, B>
    where
        Self: 'a;
}

/// Part of [`Applicative`] responsible for Haskell's result combination `listA2`.
pub trait ApplicativeLA2: Functor {
    /// Equivalent of Haskell's `listA2`.
    fn la2<'a, A: 'a, B: 'a, C: 'a>(
        f: impl 'a + FnOnce(A, B) -> C,
        fa: Self::F<'a, A>,
        fb: Self::F<'a, B>,
    ) -> Self::F<'a, C>
    where
        Self: 'a;
}

/// Part of [`Applicative`] responsible for Rust-style result combination, specifically for tuples.
pub trait ApplicativeTuple: Functor {
    /// Similar to Haskell's `listA2` but with [Iterator::collect]-ish semantics.
    fn tuple<'a, A: 'a, B: 'a>(fab: (Self::F<'a, A>, Self::F<'a, B>)) -> Self::F<'a, (A, B)>
    where
        Self: 'a;
}

/// Equivalent of Haskell's `Applicative`.
/// Split into [`Pure`], [`ApplicativeSeq`], [`ApplicativeLA2`] and [`ApplicativeTuple`] due to Rust limitations.
///
/// <https://hackage.haskell.org/package/base-4.18.0.0/docs/Control-Applicative.html>
pub trait Applicative: Pure + ApplicativeSeq + ApplicativeLA2 + ApplicativeTuple {
    /// Equivalent of Haskell's `*>`/`>>`.
    fn discard_first<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, fb: Self::F<'a, B>) -> Self::F<'a, B>
    where
        Self: 'a,
    {
        Self::seq(Self::replace(fa, |b| b), fb)
    }

    /// Equivalent of Haskell's `<*`.
    fn discard_second<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, fb: Self::F<'a, B>) -> Self::F<'a, A>
    where
        Self: 'a,
    {
        Self::la2(|a, _| a, fa, fb)
    }
}

/// Represents iteration state.
pub enum IState<A, B> {
    /// Loop running.
    Pending(A),
    /// Loop finished.
    Done(B),
}

/// Equivalent of Haskell's `Monad`.
///
/// <https://hackage.haskell.org/package/base-4.18.0.0/docs/Control-Monad.html>
pub trait Monad: Applicative {
    /// Equivalent of Haskell's `>==`.
    fn bind<'a, A: 'a, B: 'a>(
        fa: Self::F<'a, A>,
        f: impl 'a + FnOnce(A) -> Self::F<'a, B>,
    ) -> Self::F<'a, B>
    where
        Self: 'a;

    /// Kleisli category composition special case used to represent loops.
    /// Included for optimisation and clarity.
    /// Generally, [`Monad::bind`] should be enough implement it.
    /// See [`classes::stackless::StacklessClass::ibind`] for a generic, though less-than ideal, blanket implementation.
    /// On practice, you shouldn't be using [`Monad::bind`]/[`Pure::pure`]/[`Functor::fmap`] here.
    fn ibind<'a, A: 'a, B: 'a>(
        a: A,
        f: impl 'a + FnMut(A) -> Self::F<'a, IState<A, B>>,
    ) -> Self::F<'a, B>
    where
        Self: 'a;

    /// Equivalent of Haskell's `join`.
    fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A>
    where
        Self::F<'a, A>: 'a,
        Self: 'a,
    {
        Self::bind(ffa, |fa| fa)
    }
}

/// Equivalent of Haskell's `MonadFail`.
///
/// <https://hackage.haskell.org/package/base-4.18.0.0/docs/Control-Monad.html>
pub trait MonadFail<E>: Monad {
    /// Equivalent of Haskell's `fail`.
    fn fail<'a, A: 'a>(e: E) -> Self::F<'a, A>
    where
        Self: 'a;
}

/// Equivalent of Haskell's `Alternative`.
/// Lacks `some`/`many` definitions due to [`FnOnce`] category semantics.
///
/// <https://hackage.haskell.org/package/base-4.18.0.0/docs/Control-Applicative.html>
pub trait Alternative: Applicative {
    /// Equivalent of Haskell's `empty`.
    fn empty<'a, A: 'a>() -> Self::F<'a, A>
    where
        Self: 'a;

    /// Equivalent of Haskell's `<|>`.
    fn add<'a, A: 'a>(fa: Self::F<'a, A>, fb: Self::F<'a, A>) -> Self::F<'a, A>
    where
        Self: 'a;
}

/// Represents wrapped results which are instantly available.
pub trait LocalFunctor: WeakFunctor {
    /// Extract iteration state, if successful.
    fn unstuff<'a, A: 'a, B: 'a>(state: Self::F<'a, IState<A, B>>) -> IState<A, Self::F<'a, B>>
    where
        Self: 'a;

    /// Stuff wrapped result into another functor.
    fn stuff<'a, A: 'a, T: 'a + Pure>(fa: Self::F<'a, T::F<'a, A>>) -> T::F<'a, Self::F<'a, A>>
    where
        Self: 'a;
}