use std::marker::PhantomData;

use crate::prelude::*;
use crate::{Limit, Prng};

/// The value generated by [`dice::fn_builder`].
///
/// [`dice::fn_builder`]: dice::fn_builder()
pub struct FnBuilder<I, O, IC, OD> {
    input_codie: IC,
    output_die: OD,
    prng: Prng,
    limit: Limit,
    _i: PhantomData<I>,
    _o: PhantomData<O>,
}

impl<I, O, IC, OD> FnBuilder<I, O, IC, OD>
where
    IC: Codie<I>,
    OD: DieOnce<O>,
{
    pub fn build_fn_once(mut self) -> impl FnOnce(I) -> O {
        move |input| {
            let output_die = self.output_die;
            let randomness = self.input_codie.coroll(input);
            let prng = &mut self.prng;
            prng.reseed(randomness);
            let limit = self.limit;
            let fate = Fate::new(prng, limit);
            output_die.roll_once(fate)
        }
    }
}

impl<I, O, IC, OD> FnBuilder<I, O, IC, OD>
where
    IC: Codie<I>,
    OD: Die<O>,
{
    pub fn build_fn(self) -> impl Fn(I) -> O {
        move |input| {
            let output_die = &self.output_die;
            let randomness = self.input_codie.coroll(input);
            let prng = &mut self.prng.clone();
            prng.reseed(randomness);
            let limit = self.limit;
            let fate = Fate::new(prng, limit);
            output_die.roll(fate)
        }
    }

    pub fn build_fn_mut(mut self) -> impl FnMut(I) -> O {
        move |input| {
            let output_die = &self.output_die;
            let randomness = self.input_codie.coroll(input);
            let prng = &mut self.prng;
            prng.reseed(randomness);
            let limit = self.limit;
            let fate = Fate::new(prng, limit);
            output_die.roll(fate)
        }
    }
}

/// Generates a function builder.
///
/// The builder can be converted into an implementation of [`FnOnce`], [`FnMut`] or [`Fn`].
///
/// # Examples
///
/// This example generates a [`FnOnce`]:
/// ```
/// use dicetest::prelude::*;
/// use dicetest::{Prng, Limit};
///
/// let mut prng = Prng::from_seed(0x5EED.into());
/// let limit = Limit::default();
/// let mut fate = Fate::new(&mut prng, limit);
///
/// let f = fate.roll(dice::fn_builder(
///     codice::from_default_hasher(),
///     dice::u8(..),
/// )).build_fn_once();
///
/// let x = f(42);
/// ```
///
/// This example generates a [`FnMut`]:
/// ```
/// use dicetest::prelude::*;
/// use dicetest::{Prng, Limit};
///
/// let mut prng = Prng::from_seed(0x5EED.into());
/// let limit = Limit::default();
/// let mut fate = Fate::new(&mut prng, limit);
///
/// let mut f = fate.roll(dice::fn_builder(
///     codice::from_default_hasher(),
///     dice::u8(..),
/// )).build_fn_mut();
///
/// let x = f(42);
/// let y = f(42);
/// ```
///
/// This example generates a [`Fn`]:
/// ```
/// use dicetest::prelude::*;
/// use dicetest::{Prng, Limit};
///
/// let mut prng = Prng::from_seed(0x5EED.into());
/// let limit = Limit::default();
/// let mut fate = Fate::new(&mut prng, limit);
///
/// let f = fate.roll(dice::fn_builder(
///     codice::from_default_hasher(),
///     dice::u8(..),
/// )).build_fn();
///
/// let x = f(42);
/// let y = f(42);
/// assert_eq!(x, y);
/// ```
pub fn fn_builder<I, O, IC, OD>(
    input_codie: IC,
    output_die: OD,
) -> impl DieOnce<FnBuilder<I, O, IC, OD>>
where
    IC: Codie<I>,
    OD: DieOnce<O>,
{
    dice::from_fn_once(|mut fate| FnBuilder {
        input_codie,
        output_die,
        prng: fate.fork_prng(),
        limit: fate.limit(),
        _i: PhantomData,
        _o: PhantomData,
    })
}
